Touch driver example
The sheng device repository contains a complete touch driver for the Novatek NT36532E controller. It shows the structure a new touch driver can follow.
The source lives in UniXNU-sheng:
| Path | Contents |
|---|---|
drivers/NativeNovatekTouch/NvtTouchCore.c | Pure touch processing |
drivers/NativeNovatekTouch/NvtProtocol.c | Controller protocol |
drivers/NativeNovatekTouch/NvtProtocol.h | Protocol constants and bus interface |
drivers/NativeGeniSpi/GeniSpi.c | GENI SPI engine |
package/TouchBoard.h | Board constants and clock helpers |
package/TouchProbe.cpp | Kernel probe and frame loop |
package/TouchBoardTests.c | Host tests for the board helpers |
drivers/NativeHidInput/NativeHidInput.cpp | HID digitizer source |
Two layers
The driver separates pure logic from hardware access.
NvtTouchCore.c matrix to contacts, no allocation, no I/O
NvtProtocol.c chip identification, firmware, frames, through nvt_bus
TouchProbe.cpp MMIO, clocks, pins, DMA, SMMU, threads
NativeHidInput.cpp contacts to HID reportsThe pure core and the protocol take a struct nvt_bus:
struct nvt_bus {
void *ctx;
int (*xfer)(void *ctx, const uint8_t *tx, uint8_t *rx, size_t len);
void (*delay)(void *ctx, unsigned ms);
};The host tests supply a fake bus. The kernel probe supplies the GENI SPI transfer.
Touch processing
NvtTouchCore.c processes a 40 by 60 matrix of 16-bit values:
- Collect 72 startup frames.
- Form a per-node reference from the median of the startup frames.
- Subtract the reference to get a delta matrix.
- Label nodes above the noise floor by steepest ascent.
- Find local maxima and form blobs.
- Reject blobs above the palm node count.
- Compute a weighted centroid per blob.
- Apply edge compensation.
- Track contacts across frames with a radius and a down/up threshold.
- Report contacts from the second frame of a slot.
The core has no allocation. Its scratch arrays live in struct nvt_touch_core, which the probe allocates once. The comments record why: kernel thread stacks are 16 KiB and the scratch is larger.
Controller protocol
NvtProtocol.c provides:
| Function | Purpose |
|---|---|
nvt_identify | Read the chip id and select the map |
nvt_fw_parse | Parse the firmware image into ILM and DLM parts |
nvt_download_firmware | Reset the bootloader, write the parts, verify the SRAM |
nvt_start_scanning | Read the firmware version and matrix geometry |
nvt_peek_frame_header | Read the 32-byte THP header |
nvt_read_frame | Read a complete THP frame |
nvt_frame_header_valid | Reject a torn frame |
nvt_frame_matrix | Decode the matrix |
The transfer length, read maximum and frame length are constants from the vendor protocol.
Board constants
TouchBoard.h holds every address and bit with a source comment:
| Group | Examples |
|---|---|
| QUP wrapper and serial engine | Base addresses and sizes |
| SMMU | Stream ID and apps SMMU base |
| GCC | Vote registers, halt registers, DFS levels |
| TLMM | GPIO numbers, function, drive strength |
| SPI | Mode and maximum frequency |
The clock helpers are pure functions. tb_choose_clock selects the DFS level and divider that give the highest rate not above the target. The host tests cover the divider arithmetic.
Probe stages
The probe is off by default. The boot argument sheng-touch=<n> selects a stage:
| Stage | Work |
|---|---|
| 1 | Read GCC branches, DFS levels, pins and the serial engine identity |
| 2 | Vote and wait for the QUP clocks, configure the pins |
| 3 | Initialize GENI SPI, select the clock, identify the chip |
| 4 | Download the firmware, start scanning, process frames |
Each stage reports on the diagnostic page. The first failure stops the sequence.
Hardware path
- Vote for GPLL0 and the QUP branches in GCC.
- Wait for the branch clocks to leave the off state.
- Configure the SPI pins in TLMM.
- Map the QUP wrapper and the serial engine.
- Initialize GENI SPI.
- Select the DFS level and divider.
- Allocate a physically contiguous DMA buffer.
- Map the buffer through the apps SMMU with a bypass stream.
- Maintain the cache around each transfer.
- Reset the touch bootloader and download the firmware.
- Poll the THP header, read complete frames and publish contacts.
The SMMU mapping follows the same bypass model as the UFS driver. The DMA buffer is cached, and the driver performs explicit clean and invalidate operations around each transfer.
HID publication
The probe publishes contacts through globals:
volatile uint32_t native_touch_seq;
volatile uint32_t native_touch_count;
volatile int native_touch_xs[NVT_SLOTS];
volatile int native_touch_ys[NVT_SLOTS];
volatile int native_touch_ids[NVT_SLOTS];The writer increments the sequence before and after the update, so the reader detects a torn snapshot. NativeHidInput reads the snapshot and emits digitizer reports.
Adapting the example
- Keep the pure processing layer separate from the bus.
- Put every board constant in a header with its source.
- Write host tests for the pure helpers before the hardware probe.
- Add a staged boot argument so each hardware step is observable.
- Publish contacts through a sequence counter and a fixed size snapshot.
- Keep the driver off by default until a boot log shows each stage.