Skip to content

Multi touch Experimental

Syax89 edited this page Jul 8, 2026 · 1 revision

Multi-touch (Experimental — raw_mode=1)

Not recommended for daily use. Standard mode (raw_mode=0, the default) is what's stable. This page documents the experimental multi-touch path and its known, currently-unsolved reliability problem.

⚠️ Once raw_mode=1 has been active, the device stays in raw mode even after unloading the module — SET_FEATURE changes persistent device state that an ACPI _PS3_PS0 power-cycle does not clear. A full reboot is needed to get back to standard mode.

What raw mode does

Setting the raw_mode=1 module parameter makes the driver send GET_FEATURE(id=4) followed by SET_FEATURE(id=4, val=1) right after the report descriptor exchange (state 25 in the state machine), switching the device into raw heatmap streaming: every ~10ms it sends a content_id=0x0C-tagged frame of ~4302 bytes containing raw capacitive/DFT sensor data, instead of the pre-computed Report ID 0x40 coordinates.

The payload is not a simple capacitance grid — it's dual-frequency DFT antenna data (9 "Short" + 9 "Long" antennas per axis, real + imaginary components per Windows's own TouchPenProcessor0C19.dll). 4297 bytes of it is prime, so it can't be a clean rectangular grid; the driver's current blob detector treats it as an approximate 86×50 node grid (GRID_COLS/GRID_ROWS in driver/spi-hid-core.c), which is a working approximation, not a confirmed-correct mapping (see the open coordinate-mapping problem below).

The blob detection pipeline

heatmap_process_frame() runs on every raw frame once raw_mode=1 is active:

  1. Baseline tracking — the maximum value seen per cell over the first 30 frames becomes that cell's "resting" (untouched) baseline; updated continuously afterward (touches only ever reduce capacitance, so a rising max is always safe to adopt as the new baseline).
  2. Touch marking — a cell is "touched" if it drops more than HEATMAP_TOUCH_THRESHOLD (15) below its baseline, restricted to cells with an established baseline ≥ 0x20.
  3. Morphological dilation — touched cells are expanded by one neighbor in each direction to merge nearby fragments into one blob.
  4. Connected-component labeling (8-way, two-pass with union-find) — groups touched cells into distinct blobs.
  5. Weighted centroid per blob (weighted by deviation from baseline), then the HEATMAP_MAX_SLOTS (2) strongest blobs are matched against the previous frame's slot positions (nearest-neighbor) and smoothed with an EMA (HEATMAP_EMA_ALPHA), then emitted as ABS_MT_POSITION_X/Y events on a dedicated multitouch input_dev (only created when raw_mode=1, to avoid exposing a second, permanently-dead touch device in the default configuration).

This mechanical pipeline is confirmed working — verified live with evtest on the real input device: two simultaneous touches are tracked as two independent, stable slots with no spurious deactivation.

Calibration/debug tuning

  • debug_coords=1 — logs each frame's blob grid position, weight, and computed screen coordinate (CALIB: blob[...] grid=(...) weight=... screen=(...))
  • invert_x=1, invert_y=1, swap_xy=1 — axis calibration knobs for whatever the eventual correct grid mapping turns out to need

The real, unsolved problem: the handshake is unreliable

SET_FEATURE only succeeds in switching the device into raw streaming intermittently — observed success rates have varied a lot session to session (roughly 10-25%), never a stable number. When it fails, the device goes completely silent — zero further GPIO interrupts, not even a RESET_RSP — so the driver has no event to react to.

The watchdog (mitigation, not a fix)

spi_hid_raw_handshake_watchdog() is a delayed_work armed right after sending GET_FEATURE. If no real heatmap frame confirms success within RAW_HANDSHAKE_TIMEOUT_MS (2000ms), it resends DESCREQ (plus an ACPI _PS3_PS0 power-cycle, since a plain resend alone isn't always enough) and retries, up to RAW_HANDSHAKE_MAX_RETRIES (3) times before giving up.

These exact parameters (2000ms timeout, 3 retries) were reverse-engineered from Windows's own HidSpiCx.sys — decompiling it shows it uses a real state machine (SmFx) with the identical timeout/retry-count pattern (CompleteTransferIfDoneOrStartResponseTimer, CheckingResetRetryCountEntry). Windows is not more reliable than this driver at the protocol level — it just retries automatically and invisibly. GET_FEATURE and SET_FEATURE are dispatched through literally the same generic code path in HidSpiCx.sys, so there's no hidden Windows-side step being missed.

What's been tried and ruled out

  • SPI clock speed for the SET_FEATURE write specifically (setfeat_speed_hz module param, tested at 800kHz and 100kHz vs. the 33.33MHz default) — no difference.
  • The opcode-doubling quirk (see Wire Protocol) applied to SET_FEATURE (setfeat_no_double module param, sending the real 14-byte wire frame directly) — this made things measurably worse (the device starts repeating GET_FEAT_RESP in a tight loop instead of going silent), confirming the quirk is required here too, not optional for shorter writes only.
  • Byte content of GET_FEATURE/GET_FEAT_RESP/SET_FEATURE — verified byte-identical to a real Windows ETW capture in every case.

What's left unexplained is either a genuine electrical/signal-integrity issue (would need a logic analyzer to observe directly) or a reaction inside the touch chip's own firmware to the semantic SET_FEATURE(id=4, val=1) command — invisible to any Windows driver decompilation since it happens on a physically separate chip.

Coordinate mapping (unsolved)

Even once connected, the grid position → physical screen position mapping is not confirmed correct. The blob detector's 86×50 grid assumption is a reasonable approximation (matches the touchscreen's physical aspect ratio closely) but not verified against ground truth. Properly solving this needs either reverse-engineering TouchPenProcessor0C19.dll's real DFT-to-position math, or a careful empirical calibration pass (touch known reference points, record what the blob detector reports) — both of which are hard to do efficiently while the handshake itself is this unreliable.