Skip to content

Add LilyGO T-Deck Max (e-paper) base board support#552

Merged
KenVanHoeylandt merged 4 commits into
TactilityProject:mainfrom
Crazypedia:tdeckmax-baseboard
Jul 8, 2026
Merged

Add LilyGO T-Deck Max (e-paper) base board support#552
KenVanHoeylandt merged 4 commits into
TactilityProject:mainfrom
Crazypedia:tdeckmax-baseboard

Conversation

@Crazypedia

@Crazypedia Crazypedia commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #539. This is PR 1 of the incremental plan discussed there: base board (display + touch + keyboard) first, with power, LoRa radio, GPS, and haptics to follow as separate PRs once this lands.

What this adds

Board: Devices/lilygo-tdeck-max — LilyGO T-Deck Max (ESP32-S3, 16 MB flash, 8 MB PSRAM), the e-paper T-Deck variant (vendor repo Xinyuan-LilyGO/T-Deck-MAX). incubating=true per your suggestion in #539, pending fuller SD-card verification (see below).

New reusable drivers:

  • Drivers/GDEQ031T10 — generic UC8253-based e-paper panel driver (240x320, 1bpp), with windowed partial refresh and a full-refresh ghost-clear gate (ported from the vendor's GxEPD2 waveforms). Refresh runs on a background task so the ~0.5-3s panel update never blocks LVGL/input.
  • Drivers/xl9555-module — XL9555 I2C IO expander as a kernel module with gpio_controller bindings. This supersedes Add XL9555 I/O expander kernel module #537, which I'd closed before it was hardware-tested; it's now verified as part of this board (it gates the touch/keyboard reset lines).

Board-local:

  • CST66xx touch (Hynitron 4-byte register protocol). Vendor docs mislabel this part as "CST328" — the vendor's own HynTouch driver probes CST66xx first and that's what actually answers on this board.
  • TCA8418 keyboard matrix wiring (existing upstream driver).
  • SD card as an espressif,esp32-sdspi devicetree node on the shared SPI2 bus (per your develop-branch SD rework). Unused chip-selects (LoRa, SD) are deasserted before the EPD touches the bus so a floating CS can't latch stray command bytes.

Radio (SX1262), power (SY6970/BQ27220), GPS, and haptics (DRV2605) are all deliberately left out of this PR — they're built and working on my fork already, just being split into separate PRs per the plan in #539.

Verification

  • Simulator + all 4 test suites (TactilityFreeRtosTests, TactilityKernelTests, TactilityTests, CryptModuleTests) pass.
  • ESP32 build clean, 34% of the app partition free.
  • Flashed to real hardware: clean boot to launcher, no panics/watchdog resets. Serial log shows display, touch, keyboard, and (incidentally) the SD card all initializing and mounting successfully.
  • Live-traced the e-paper refresh pipeline on hardware (temporary diagnostic logging, since reverted) through real navigation, app switching, and text entry: the diff/refresh-mode selection logic (full vs. windowed-partial vs. ghost-clear) behaved correctly in every case observed.

Known caveat (not from this PR)

During hardware testing I hit a case where touch stopped responding to new taps a few minutes into a session (confirmed via the live trace: the touch controller returned the exact same I2C frame on every subsequent poll — i.e. latched/frozen). This matches a quirk from earlier bring-up: a warm reset or reflash does not fully power-cycle the CST66xx, so it can occasionally need a full power-off to clear. Not something this PR's code can fix from the host side; documenting here for visibility. Happy to discuss defensive options (e.g. a periodic touch-health check with auto re-init) as a follow-up if useful.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added the LilyGO T-Deck Max target with its 3.1" SPI e-paper display, CST66xx touch input, and 4x10 keypad keyboard (including backlight impulse behavior).
    • Added board hardware definitions (device tree + device properties), plus an XL9555 I/O expander driver/module integration and wiring.
    • Added e-paper refresh modes with differential refresh and ghosting/ghost-scrub handling.
  • Bug Fixes
    • Improved boot-time peripheral initialization by resetting touch/keyboard hardware via the I/O expander when available and ensuring SPI bus chip-select state is cleared.
    • Improved LVGL keyboard/touch event handling for more reliable consecutive inputs.
  • Documentation
    • Added documentation for the GDEQ031T10 e-paper panel and the XL9555 module.

Base board bring-up for the LilyGO T-Deck Max (ESP32-S3, 16 MB flash,
8 MB PSRAM), per the plan discussed in TactilityProject#539: display, touch, and
keyboard only — power, LoRa radio, GPS, and haptics follow as separate
PRs.

New reusable drivers:
- Drivers/GDEQ031T10: UC8253-based 240x320 1bpp e-paper panel with
  windowed partial refresh + full-refresh ghost gate, refresh runs on a
  background task.
- Drivers/xl9555-module: XL9555 I2C IO expander as a kernel module with
  gpio_controller bindings (the board routes touch/keyboard resets and
  several power rails through it).

Board specifics:
- CST66xx touch (Hynitron 4-byte register protocol) implemented
  device-locally; vendor docs mislabel the part as CST328.
- TCA8418 keyboard matrix (existing upstream driver) with backlight.
- SD card as an espressif,esp32-sdspi devicetree node on the shared
  SPI2 bus; unused chip-selects (LoRa, SD) are deasserted before the
  EPD touches the bus.
- incubating=true until SD storage is verified on this board.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 11f538cc-2b24-44c2-95ad-af5ae44f2ec4

📥 Commits

Reviewing files that changed from the base of the PR and between d3ab8b4 and 9fcaa2d.

📒 Files selected for processing (2)
  • .github/workflows/build.yml
  • Devices/lilygo-tdeck-max/Source/Configuration.cpp
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/build.yml

📝 Walkthrough

Walkthrough

This PR adds an xl9555-module I2C GPIO expander driver with GPIO controller API and devicetree bindings; a GDEQ031T10 SPI e-paper display driver with full/fast/slow/partial refresh modes, refresh-task offloading, and LVGL integration; and a lilygo-tdeck-max device package wiring board boot configuration, CST66xx touch, TCA8418 keyboard with PWM backlight control, display factories, devicetree, build, and documentation files.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding base board support for the LilyGO T-Deck Max e-paper variant.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f99c293d-2954-42e0-b773-bd1e5c7308dc

📥 Commits

Reviewing files that changed from the base of the PR and between 66a4eb6 and eaae21a.

📒 Files selected for processing (26)
  • Devices/lilygo-tdeck-max/CMakeLists.txt
  • Devices/lilygo-tdeck-max/Source/Configuration.cpp
  • Devices/lilygo-tdeck-max/Source/devices/Cst66xxTouch.cpp
  • Devices/lilygo-tdeck-max/Source/devices/Cst66xxTouch.h
  • Devices/lilygo-tdeck-max/Source/devices/Display.cpp
  • Devices/lilygo-tdeck-max/Source/devices/Display.h
  • Devices/lilygo-tdeck-max/Source/devices/TdeckmaxKeyboard.cpp
  • Devices/lilygo-tdeck-max/Source/devices/TdeckmaxKeyboard.h
  • Devices/lilygo-tdeck-max/Source/module.cpp
  • Devices/lilygo-tdeck-max/device.properties
  • Devices/lilygo-tdeck-max/devicetree.yaml
  • Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts
  • Drivers/GDEQ031T10/CMakeLists.txt
  • Drivers/GDEQ031T10/README.md
  • Drivers/GDEQ031T10/Source/Gdeq031t10Display.cpp
  • Drivers/GDEQ031T10/Source/Gdeq031t10Display.h
  • Drivers/xl9555-module/CMakeLists.txt
  • Drivers/xl9555-module/LICENSE-Apache-2.0.md
  • Drivers/xl9555-module/README.md
  • Drivers/xl9555-module/bindings/xlsemi,xl9555.yaml
  • Drivers/xl9555-module/devicetree.yaml
  • Drivers/xl9555-module/include/bindings/xl9555.h
  • Drivers/xl9555-module/include/drivers/xl9555.h
  • Drivers/xl9555-module/include/xl9555_module.h
  • Drivers/xl9555-module/source/module.cpp
  • Drivers/xl9555-module/source/xl9555.cpp

Comment thread Devices/lilygo-tdeck-max/Source/devices/TdeckmaxKeyboard.cpp Outdated
Comment thread Devices/lilygo-tdeck-max/Source/devices/TdeckmaxKeyboard.cpp
Comment thread Drivers/GDEQ031T10/README.md Outdated
Comment thread Drivers/GDEQ031T10/Source/Gdeq031t10Display.cpp Outdated
Comment thread Drivers/GDEQ031T10/Source/Gdeq031t10Display.cpp Outdated
Comment thread Drivers/GDEQ031T10/Source/Gdeq031t10Display.cpp
Comment thread Drivers/GDEQ031T10/Source/Gdeq031t10Display.h Outdated
Comment thread Drivers/xl9555-module/source/xl9555.cpp
Comment thread Devices/lilygo-tdeck-max/Source/devices/Cst66xxTouch.cpp Outdated
Comment thread Devices/lilygo-tdeck-max/Source/devices/Cst66xxTouch.cpp Outdated
Comment thread Devices/lilygo-tdeck-max/Source/Configuration.cpp Outdated
Comment thread Devices/lilygo-tdeck-max/Source/Configuration.cpp Outdated
Comment thread Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts Outdated
Comment thread Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts Outdated
Comment thread Devices/lilygo-tdeck-max/lilygo,tdeck-max.dts Outdated
Comment thread Drivers/xl9555-module/source/xl9555.cpp Outdated
Comment thread Drivers/xl9555-module/source/xl9555.cpp Outdated
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Thanks, that looks good! I left some comments on a few things, but I didn't find anything major.
I did not check all the CodeRabbit comments, but the few that I checked seem to be accurate.

I am quite busy in the coming 24 hours, so my responses will be a bit slower.

…ject#552

GDEQ031T10:
- waitWhileBusy() now times out (5s) instead of spinning forever on a
  stuck BUSY pin, which previously could hang the refresh task and
  make stop() un-recoverable.
- writeCommand()/writeData() and all callers now check the SPI
  transfer result instead of advancing panel/shadow state as if it
  always succeeds.
- start() no longer marks the display powered/initialized before the
  refresh task is confirmed created; on task-creation failure it now
  unwinds the SPI device and allocated buffers instead of leaking them.
- `powered` and `currentRefreshMode` are now atomic (read on the
  refresh task, written from arbitrary caller threads).
- Corrected a stale doc comment on setRefreshMode() (not one-shot).
- README updated to describe the actual windowed partial-refresh
  pipeline instead of the old full-refresh-every-flush description.

TdeckmaxKeyboard:
- Modifier state (shift/sym/caps) moved from function-local statics to
  instance members.
- Keyboard queue send is now non-blocking (drops+logs on a full queue)
  instead of blocking inside the periodic input timer callback.
- initBacklight() now returns false when channel config fails, instead
  of reporting success.

xl9555:
- set_flags() rejects GPIO_FLAG_ACTIVE_LOW combined with
  GPIO_FLAG_DIRECTION_OUTPUT: the polarity register only affects input
  reads, so it silently did nothing for outputs.
- set_level()/set_flags() now serialize their read-modify-write
  register updates with device_lock()/device_unlock(), since the
  shared i2c_controller_register8_{set,reset}_bits() helpers do a
  separate read then write.
- start() uses check() instead of a soft parent-type check, and drops
  a log line the parent already emits.

Configuration.cpp: i2c0 is unconditionally declared in this board's
devicetree, so createDevices() now uses check(i2c) instead of an if,
matching the "trust internal guarantees" convention.

Cst66xxTouch: unbind the Home/Recents bezel keys. Both called
tt::app::start(), which pushes a new app-stack instance on every
press; repeated presses would leak Launcher/AppList instances
indefinitely. Left unbound until the app stack has a way to resume an
existing instance instead of pushing a new one.

device.properties / dts: corrected the product name to "T-Deck Max"
(was "T-Deck Pro Max") to match the rest of the PR, the vendor repo,
and LilyGO's actual naming.

Verified with a full idf.py build for lilygo-tdeck-max (IDF 5.5.2).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Crazypedia added a commit to Crazypedia/Tactility-lilygo-tdeck-max that referenced this pull request Jul 6, 2026
…-max

Follow-up to TactilityProject#552 (base board). Battery metrics come from the BQ27220
fuel gauge (I2C 0x55); charging status, charge control, and power-off
(ship mode via SY6970 REG09 BATFET_DIS) use the SY6970 charger (0x6A —
newer T-Deck Max revisions use this instead of the older BQ25896 at
0x6B, confirmed by I2C scan; the two are register-compatible for
everything used here). Enables the launcher's power-off button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Thanks for all the fixes we're nearly there! 👍

Regarding esp32_spi_deselect_all_cs(): if that suggested improvement doesn't work, then let me know in the thread please!

Don't forget to update .github/workflows/build.yml. This makes the device build automatically, so we can test it here, and so it can be deployed automatically to install.tactilityproject.org after merging.

Crazypedia and others added 2 commits July 7, 2026 21:13
…cs()

Implements the TactilityProject#552 review suggestion. The manual gpio_config/
gpio_set_level block re-drove, with raw IDF calls, pins spi0 already owns
via kernel GPIO descriptors (GPIO_OWNER_SPI): kernel_init() starts every
status-okay devicetree device before hal::init runs, so spi0's start()
has already initialized SPI2_HOST and driven all three cs-gpios (EPD, SD,
LoRa) high by the time initBoot executes. The helper call re-asserts that
state as a cheap guard before the EPD driver first transacts, mirroring
the esp32_sdspi mount path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Requested in TactilityProject#552 review so CI builds the board. The entry becomes
redundant once TactilityProject#556 (generated device matrix) lands — the generator picks
the board up from device.properties' hardware.target automatically.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@KenVanHoeylandt

Copy link
Copy Markdown
Contributor

Thank you for the contribution! This might help me with my pending T-Deck Pro implementation as I was struggling with the display driver.

@KenVanHoeylandt KenVanHoeylandt merged commit d6ac070 into TactilityProject:main Jul 8, 2026
56 checks passed
Crazypedia added a commit to Crazypedia/Tactility-lilygo-tdeck-max that referenced this pull request Jul 8, 2026
…-max

Follow-up to TactilityProject#552 (base board). Battery metrics come from the BQ27220
fuel gauge (I2C 0x55); charging status, charge control, and power-off
(ship mode via SY6970 REG09 BATFET_DIS) use the SY6970 charger (0x6A —
newer T-Deck Max revisions use this instead of the older BQ25896 at
0x6B, confirmed by I2C scan; the two are register-compatible for
everything used here). Enables the launcher's power-off button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants