LILYGO T-Display-K230 · hardware note · observed

The touch controller was awake the whole time

The GT9895 bound, probed, and created /dev/input/event0 while reporting nothing at all. Every status check passed. The fault was one word in the device tree: the interrupt was declared edge‑triggered, and a Goodix Berlin part holds its interrupt line low until it is acknowledged.

Observed
2026-09-21
evtest and /proc/interrupts, on the board
Controller
Goodix GT9895 on i2c‑1, 0x5d
interrupt GPIO23, reset GPIO24
The fix
one device-tree word
IRQ_TYPE_LEVEL_LOW, no kernel change
Reported grid
1024×2400, the digitizer's own
against a 568×1232 panel
Not settled
whether the axes are swapped
or mirrored against the screen

the measurement

Zero, against two thousand one hundred and seventy-three

Everything that is easy to check was passing. The driver bound at i2c 1-005d, goodix_berlin probed, /dev/input/event0 appeared, and the part answered 32‑bit‑addressed i2c reads. None of that is the same as a controller that is scanning the glass, which is the whole lesson of this board and the reason this site exists.

interrupts = <23 …> what changed /proc/interrupts
IRQ_TYPE_EDGE_FALLING (0x02) nothing else — same kernel, same image 0
IRQ_TYPE_LEVEL_LOW (0x08) the device tree word, and only that 2173 over one 45 s capture
87:   2173   gpio-k230  23  Level   goodix-berlin

That line is read back off the running board, so the setting under test is the setting that was live — not the one the source says should have been pushed. The gap between the two is not hypothetical here: the clock patch described in the panel note was built, shipped and never looked at, and it had blanked the screen.

the cause

A trigger type cloned from a different part

Our touch node was cloned from the Canaan reference board's, which describes an Edtech FT5306 — a different controller that happens to sit on the same pins. The comment in our device tree said as much. The trigger type came along for the ride and was never derived from any Goodix source.

touchscreen@38 {
    compatible = "edt,edt-ft5306";        /* not what is on this board */
    reg = <0x38>;
    reset-gpios = <&gpio0_ports 24 GPIO_ACTIVE_LOW>;
    interrupt-parent = <&gpio0_ports>;
    interrupts = <23 IRQ_TYPE_EDGE_FALLING>;   /* inherited */
};

what it reports

Multitouch, with the tracking IDs to prove it

Event: time ..., type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 27
Event: time ..., type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 456
Event: time ..., type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 888
Event: time ..., type 3 (EV_ABS), code 48 (ABS_MT_TOUCH_MAJOR), value 3
Event: time ..., type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
Event: time ..., type 3 (EV_ABS), code 0 (ABS_X), value 456
Event: time ..., type 3 (EV_ABS), code 1 (ABS_Y), value 888
Event: time ..., -------------- SYN_REPORT ------------

Tracking IDs are allocated and retired, BTN_TOUCH goes 1 and 0, and ABS_MT_TOUCH_MAJOR ramps as the contact patch grows. A slot‑0 trajectory decoded from a 60 s capture, with ABS_MT_SLOT honoured, spans X 86..728 and Y 765..1765 continuously: both axes move, and the motion is smooth rather than quantised to a handful of values. That is the difference between a device node and a digitizer.

axisminmaxpanel
ABS_X, ABS_MT_POSITION_X 01023568 pixels wide
ABS_Y, ABS_MT_POSITION_Y 023991232 pixels tall

The two grids do not match, and the driver cannot make them match. goodix_berlin reports through touchscreen_report_pos(), which applies the swap and invert properties and does not scale; touchscreen-size-x / touchscreen-size-y only declare the advertised maximum, and the controller's raw values pass through unchanged. So the input device speaks the digitizer's native 1024×2400 and anything consuming it scales by 568/1024 and 1232/2400. That is the ordinary Linux arrangement, and it is worth knowing before something lands a touch a factor of two away from the finger.

Declaring those two properties was itself a fix. Without them the driver advertises 0 .. SZ_64K - 1 on both axes for a sensor that reports 0..1023 and 0..2399 — a client scaling by the advertised maximum would have collapsed every touch into the top-left corner.

what is not known

A drag proves movement, not direction

the cost of the pin

Mainline has never supported this part

The pinned Xuantie kernel is 6.6 and carries drivers/input/touchscreen/goodix.c, which serves the older GT9xx generation. goodix_berlin landed in v6.9, and even at v6.18 the i2c driver matches only goodix,gt9916 while the binding enumerates gt9897 and gt9916. Mainline has never supported the GT9895 at any version.

So the driver is carried here as an explicit backport under nix/patches/goodix-berlin/, and the Berlin programming model turned out to cover this part unchanged — the register layout was checked byte‑for‑byte against LilyGO's own RT‑Smart gt9895.c, which performs no initialisation the backport is missing. Nothing in the kernel needed fixing. The cost of the pin is one backport to carry forward, recorded so that it is visible the next time the kernel revision moves.

Evidence: docs/evidence/touch-reports.md, docs/evidence/gt9895-port.md, docs/evidence/touch-probe.txt, and docs/evidence/gt9895-touch.md.

Source read for this note: the backported goodix_berlin_core.c under nix/patches/, drivers/gpio/gpio-k230.c and arch/riscv/boot/dts/canaan/k230-canmv-v3-lcd.dts in the pinned tree, and LilyGO's RT‑Smart gt9895.c. The vendor trees are not committed here, so those paths are named and not linked.