feat: opt-in GT911 INT wake support (FREEINK_GT911_INT_WAKE) - #41
Open
hooligan333 wants to merge 2 commits into
Open
feat: opt-in GT911 INT wake support (FREEINK_GT911_INT_WAKE)#41hooligan333 wants to merge 2 commits into
hooligan333 wants to merge 2 commits into
Conversation
The GT911 driver never touched the INT line after the reset/address-select dance: beginGt911() left it as a plain INPUT and every contact was found by polling 0x814E over I2C. What the line does between frames is decided by the controller's own self-loaded config (this panel has no host-uploaded config table), whose Module_Switch1 (0x804D) bits[1:0] select the INT trigger: 0 rising edge, 1 falling edge, 2 low level, 3 high level. Only the two level modes hold the line from the report frame until the host clears 0x814E; the edge modes emit one pulse per frame, and light sleep clock-gates the SoC's edge detector, so a host can wake on a level and never on a pulse. Under FREEINK_GT911_INT_WAKE (default off), beginGt911() now reads the config block (0x8047..0x80FE plus its checksum at 0x80FF), verifies the stored 8-bit two's-complement checksum, and — only if the INT trigger is not already low level — patches Module_Switch1, recomputes the checksum and applies it with Config_Fresh. Config_Version is written as 0x00 so the table is applied but not burned into the controller's NVM: the factory config returns on the next reset and the write can never permanently alter the hardware, at the cost of re-applying it on every boot. The result is verified by reading 0x804D back, and the INT pin is pulled up so an open-drain module still idles HIGH. touchWakeIrqPin() reports the INT GPIO only when that verification passed, so a caller can never arm a level wake on a line that merely pulses; it returns -1 without the flag, without touch, or when the controller refuses the write. Flag off = no config read, no write, no pin-mode change, accessor always -1. Residual risks: a panel that rejects a version-0x00 config silently keeps its factory INT mode (reported as unusable, TOUCH_PROBE_DEBUG logs it) — making it stick would need a persisted write, which is deliberately not done here; and pollGt911()'s early return on an I2C read failure leaves 0x814E uncleared, so a bus fault would hold INT asserted and suppress sleep until it recovers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUgQffv7GGVCWQ9gijsVbp
…ter fresh Adversarial review of the INT-wake path found the success test too weak. The read-back of Module_Switch1 (0x804D) proves only that the I2C write landed: the register holds whatever was written to it whether or not the controller ever re-read the table, so a panel that ignores a version-0x00 config would still pass and the app would arm a level wake on a line that only pulses. The controller's own acknowledgement is Config_Fresh (0x8100), which it clears once it has actually consumed the block — now polled for up to 100 ms in 10 ms steps, with a still-set flag treated as a refusal (the existing clean fallback: touchWakeIrqUsable stays false, the accessor keeps reporting -1, the app keeps polling). The 0x804D check is kept as a second guard. Re-reading the config makes the controller latch a report, and in low-level mode a pending report holds INT down until the status register is cleared, so the last step before declaring the line usable is a 0x814E clear. Without it the app arms its first level wake on an already-asserted pin. TouchConfig::irqActiveLow had no readers and disagreed with the hardcoded `return true` in touchWakeIrqActiveLow(): the X4 Pro profile said false, while the GT911 INT in low-level mode asserts LOW. The profile value was simply wrong for this use, so it is corrected to true (with a comment) and the accessor now consults it, which also makes the field live. Since the driver only ever programs a low-level hold, gt911ConfigureIntWake() refuses to configure a profile that claims active-high rather than let a host arm a high-level wake on a line that idles high — a permanent wake trigger. Also drops the stale "GPIO2 ... role unknown; not modeled here" note in the X4 Pro profile: GPIO2 is the touch rail, already modeled as powerEnable=2 / powerEnableActiveHigh=false a few lines above. Two read-only accessors the host's idle-wait gate needs, both in service of the same wake path: isHomeKeyDown(), because a motionless home-key hold emits no frames and its long-press threshold is timed by update() against the wall clock (a host that stops polling stretches it); and hasButtonHook(), so a host can tell that some of its buttons sit behind an I2C expander and are invisible to a GPIO interrupt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UUgQffv7GGVCWQ9gijsVbp
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The GT911's INT trigger mode is panel-self-loaded and typically edge-mode, which is unusable for ESP32-S3 light-sleep GPIO wake — the SoC's edge detector is clock-gated while asleep, so only a level can wake it. Under
FREEINK_GT911_INT_WAKE(off by default),beginGt911()now:0x8047..0x80FE+ its stored checksum at0x80FF) and verifies the 8-bit two's-complement checksum before touching anything;Module_Switch1(0x804D) bits[1:0] to low level, recomputes the checksum and applies it withConfig_Fresh, writingConfig_Version = 0x00so the table is applied but never burned to the panel's NVM (the factory config returns on the next reset; the cost is re-applying it on every boot);Config_Fresh(0x8100) for up to 100 ms in 10 ms steps, because the0x804Dread-back alone only proves the I2C write landed, not that the table was re-read.0x804Dis kept as a second guard;0x814E) last, since re-reading the config makes the controller latch a report and a pending report holds INT down in low-level mode.Every step fails closed to "unusable":
touchWakeIrqUsablestays false,touchWakeIrqPin()keeps returning-1, and the host keeps polling. A host can therefore never arm a level wake on a line that only pulses, or on a pin that is already asserted.New accessors, all read-only:
touchWakeIrqPin()/touchWakeIrqActiveLow()— the wake pin and its asserted level. Polarity now comes from the board profile'sTouchConfig::irqActiveLow(previously a dead field); the X4 Pro profile'sfalsewas simply wrong for this use and is corrected totrue, andgt911ConfigureIntWake()refuses to configure a profile that claims active-high rather than let a host arm a high-level wake on a line that idles high (a permanent wake trigger).isHomeKeyDown()— a motionless home-key hold emits no frames and its long-press threshold is timed byupdate()against the wall clock, so a host that stops polling stretches it.hasButtonHook()— lets a host tell that some of its buttons sit behind an I2C expander and are invisible to a GPIO interrupt.Off by default; zero behavior change without the define — no config read, no write, no pin-mode change, and the accessor always reports
-1.Residual risks are documented in the commit messages: a panel that silently rejects a version-0x00 config keeps its factory INT mode (reported as unusable, logged under
TOUCH_PROBE_DEBUG), andpollGt911()'s early return on an I2C read failure leaves0x814Euncleared, so a bus fault holds INT asserted and suppresses sleep until it recovers.Testing
Built against the X4 Pro firmware with
FREEINK_GT911_INT_WAKE+ the consuming app flag: SUCCESS. Default builds are unaffected (the code is entirely inside#ifdef).On-device measurements
On-device PM-profiling comparison by the submitter (X4 Pro hardware; both runs
CONFIG_PM_PROFILINGdumps under an esp_pm/tickless test firmware — the mechanism being upstreamed here is the wake-source plumbing those runs exercised):The residual ~4.5 wakes/s in the measured build traces to an unrelated background task's fallback timer, not this mechanism.
AI Usage: YES — Claude Code implementation and adversarial review (held-level busy-loop, notification hygiene, board gating, and GT911 config-consumption verification were all review-hardened); hardware testing and measurements by the submitter.
🤖 Generated with Claude Code
https://claude.ai/code/session_01UUgQffv7GGVCWQ9gijsVbp