-
Notifications
You must be signed in to change notification settings - Fork 0
Multi touch Experimental
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.
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.
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 2 →5 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).
heatmap_process_frame() runs on every raw frame once raw_mode=1 is active:
- 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).
-
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. - Morphological dilation — touched cells are expanded by one neighbor in each direction to merge nearby fragments into one blob.
- Connected-component labeling (8-way, two-pass with union-find) — groups touched cells into distinct blobs.
-
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 asABS_MT_POSITION_X/Yevents on a dedicated multitouchinput_dev(only created whenraw_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.
-
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
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.
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.
-
SPI clock speed for the
SET_FEATUREwrite specifically (setfeat_speed_hzmodule 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_doublemodule param, sending the real 14-byte wire frame directly) — this made things measurably worse (the device starts repeatingGET_FEAT_RESPin 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.
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.