LILYGO T-Display-K230 · hardware note

What the device tree actually turns on

Every subsystem on this board, traced to the exact device-tree node that enables it — and marked where the upstream CanMV v3 reference describes a board that is not ours.

SoC file
canaan/k230.dtsi
857 lines, 56 nodes
Reference board
canaan/k230-canmv-v3-lcd.dts
96 lines
Our board file
nix/dts/k230-tdisplay.dts
compiles to 55,156 bytes
Kernel
ruyisdk/linux-xuantie-kernel
pinned 7d4e1f4
Observed so far
U-Boot, then Linux to a root shell
2026-09-20; no peripheral but the card

the board

Four things a product render can prove

A 4.1″ AMOLED slab on a K230D. The picture below is LilyGO’s own product shot and the markers are placed by eye. The authoritative map is the fan-out that follows: the device tree cares which pad a thing hangs off, not where it sits on the board.

Two views of the LILYGO T-Display-K230: the AMOLED front, and the component side. RM69A10 568×1232 AMOLED camera · MIPI CSI RF module K230D, approximately
Only four markers, because only four things are identifiable in a render. The connectors along the bottom edge — USB-C, HDMI and the card slot are all present per LilyGO’s pin table — have not been told apart here, and guessing at them would be the wrong kind of confident. Component identities come from the schematic and the vendor BSP; positions are eyeballed.

fan-out

Nine paths off the pads

Each path carries the bus or pin group it uses and the device-tree node that has to exist before Linux sees it. Colour says which file the node lives in, not how well it is known — only one row on this diagram has been exercised on hardware.

Kendryte K230D canaan,kendryte-k230 cpu@0 · thead,c908 plic@f00000000 clint@f04000000 gpio0_ports · IO0–31 gpio1_ports · IO32–63 vo@90840000 dsi@90850000 gnne · ai2d · gpu · vpu PADS IO0–IO63 via FPIOA MMC0 — dedicated DSI D0/D1/CLK CSI lanes NO PINCTRL NODE FPIOA muxing is set by U-Boot and inherited. Nothing in Linux sets it. IO38/39 · UART0_TXD/RXD CH342 ch0 — the console &uart0 { status = "okay"; } · stdout-path = serial0 IO50/51 · UART3_TXD/RXD CH342 ch1 — second port &uart3 { status = "okay"; } · aliases { serial3 } DSI 2 lane · RST IO22 high · BL IO25 RM69A10 AMOLED 568×1232 &dsi { lcd: panel@0 } — ours; the reference fits an ST7701 IO36/37 · IIC3 · RST IO24 · INT IO23 GT9895 touch @ 0x5d no mainline driver matches this part, at any version IIC3 · would want RST IO24 · INT IO23 LT9611 HDMI bridge @ 0x3b only in the non-LCD k230-canmv-v3.dts — not our base MMC0 pads · WIFI_3V3 gated by IO45 RTL8189FTV Wi-Fi — SDIO &mmc_sd0 enabled — but nothing drives IO45; no pwrseq yet IO54–59 · MMC1_CMD/CLK/D0–D3 micro-SD — the boot device U-Boot on our board: mmc1@91581000: 1 (SD), partitions read CSI lanes · IIC1 on IO40/41 Camera — MIPI CSI-2 &mipi0 re-pointed: reg, interrupts and resets all overridden an I²C bus and an SPI — which, is unread BQ25896 charger · BQ27220 gauge · LoRa no node in any device tree; bus and chip-select unread ink = carried from the reference unchanged · violet = written in this repo · green = observed on hardware · dashed = not established blue = declared by the SoC .dtsi
Pad assignments read out of this board’s own U-Boot pinmux (u-boot/arch/riscv/dts/k230_canmv_v3p0.dts, whose model string is the one U-Boot prints on our hardware) and cross-checked against the FPIOA alternate-function table in machine_fpioa.c. Node names and status come from the Xuantie tree at 7d4e1f4; the violet nodes come from nix/dts/ in this repository and compile, which is not the same as working.

subsystems

Which file carries which node

Left: the hardware and its pins. Right: where the node lives — blue for the SoC k230.dtsi, plain ink for the reference board .dts, violet for what this repository had to write.

ConsoleCH342 ch0
IO38 TX · IO39 RX
115200 8N1

The SoC file declares five identical snps,dw-apb-uart nodes, all disabled, and aliases serial0 to uart0. The board file flips uart0 on and points stdout-path at it.

Only uart0 and uart3 are ever enabled, so the kernel will register ttyS0 and ttyS3 and there is no ttyS1 — which the SDK’s default environment nevertheless asks for.

k230.dtsiuart0: serial@91400000
status = "disabled"
carried over&uart0 { status = "okay"; }
DisplayRM69A10
568×1232 AMOLED
DSI 2 lane
RST IO22 · BL IO25

The whole display chain — display-subsystem, vo@90840000 and dsi@90850000 — is already status = "okay" in the SoC file, wired VO → DSI by an endpoint pair. A board file’s only job is to hang a panel off &dsi port 1.

The reference -lcd variant does that by including display-st7701-480x800.dtsi. Same reset pin, same enable pin, entirely different panel.

k230.dtsi — already okayvo@90840000
dsi@90850000
display-subsystem
ours, written and compilednix/dts/display-rm69a10-568x1232.dtsi
TouchGT9895 @ 0x5d
I²C3 on IO36/37
RST IO24 · INT IO23

Our pins are identical to the reference LCD board’s touch wiring — reset-gpios = <&gpio0_ports 24>, interrupts = <23 IRQ_TYPE_EDGE_FALLING>, confirmed against the schematic. Only the chip and its address change.

The node is written and inert. Mainline has never carried a GT9895: at v6.18 goodix_berlin matches only goodix,gt9916, and the binding enumerates gt9897 and gt9916 and nothing else.

k230.dtsii2c3: i2c@91408000
status = "disabled"
reference — different chiptouchscreen@38
compatible = "edt,edt-ft5306"
ourstouchscreen@5d
compatible = "goodix,gt9895"
Wi-FiRTL8189FTV
SDIO on MMC0
EN IO45

The schematic ties WIFI_CLK/CMD/D0–D3 to the SoC’s MMC0 balls — the dedicated pads, not FPIOA pins. The RT-Smart board defconfig agrees: CONFIG_REALTEK_SDIO_DEV0=y.

Power is a rail, not a reset line: IO45 enables an RT9080-33 LDO that makes WIFI_3V3. Our board file carries the reference’s &mmc_sd0 block over unchanged and adds nothing, so nothing asserts IO45 under Linux either. That is a known gap, not an oversight.

k230.dtsimmc_sd0: sdhci0@91580000
bus-width = <8>; disabled
carried over verbatimokay; no-1-8-v;
rx_delay_line = <0x0d>;
tx_delay_line = <0xc0>;
not writtenno mmc-pwrseq, no regulator,
no cap-sdio-irq — unresolved
Storagemicro-SD
MMC1 on IO54–59
4-bit · 50 MHz

The card is the boot device, and the one path on this page with an observation behind it: U-Boot enumerated it, listed its partitions and read our three files off the ext4.

The reference settings carry over unchanged. This is the one storage path where our board and the CanMV v3 agree exactly.

k230.dtsimmc_sd1: sdhci1@91581000
bus-width = <4>; disabled
carried over verbatimokay; no-1-8-v;
cap-sd-highspeed;
mshc_ctrl_r = <0x00>;
USB2 × OTG
USB-C + host

Two canaan,k230-otg controllers, both disabled in the SoC file, both switched on by the board file with no further properties. Nothing board-specific to do.

k230.dtsiusb0: usb@91500000
usb1: usb@91540000
carried over&usb0 { okay; } &usb1 { okay; }
AudioInno codec
I²S

Unusually, the codec and the I²S controller are already okay in the SoC file. The board file supplies only the machine-level glue node that binds them into a sound card.

k230.dtsi — already okayi2s@9140f000
inno_codec@9140e000
audio@9140f400
carried oversound { compatible =
"canaan,k230-audio-inno"; }
AcceleratorsKPU · GPU
VPU · crypto

The NPU (gnne plus ai2d), the GC8000UL GPU, the video codec and the whole security subtree are enabled in the SoC file and never mentioned by any board file. They cost a new board port nothing — and, in the GPU’s case, give it nothing either: no Mesa or Vulkan driver exists for that part.

k230.dtsi — all okaygnne@80400000 · ai2d@80400c00
gpu@90800000 · vpu@90400000
aes · hash · rsa · trng · otp
Power & radioBQ25896
BQ27220
SX1262 / LR2021

Present on the board, absent from every device tree in play. The SoC file declares i2c0–i2c4 and spi0–spi2, all disabled; the U-Boot pinmux muxes IIC0 onto IO48/49 and IIC4 onto IO7/8. Which of those the PMIC hangs off, and which SPI and chip-select the radio uses, is not something this survey established.

k230.dtsii2c0@91405000 … i2c4@91409000
spi0 · spi1 · spi2 — all disabled
not establishedread the schematic nets before
writing any of these nodes

a trap

The pinmux is not in this device tree

Worth saying plainly, because it inverts the usual expectation: k230.dtsi and every board .dts in this family contain no pinctrl node, no pinmux, no pinctrl-names, and no regulators of any kind.

A grep for all of those across the Linux device-tree files returns nothing. GPIO banks exist as gpio-controllers, so a driver can ask for pin 22 — but nothing in the Linux device tree decides that pin 22 is a GPIO rather than OSPI_D6. That decision is made one stage earlier, in U-Boot’s own device tree, which drives the FPIOA block at 0x91105000 through pinctrl-single:

u-boot/arch/riscv/dts/k230_canmv_v3p0.dts

&iomux {
    pinctrl-names = "default";
    pinctrl-0 = <&pins>;
    pins: iomux_pins {
        pinctrl-single,pins = <
          // lcd_rst
          (IO22) ( 0<<SEL | … | 1<<IE | 1<<OE | 15<<DS | 1<<ST )
          // hdmi_int,gpio      ← on this board it is the touch INT
          (IO23) ( 0<<SEL | … )
          // hdmi_rset ,gpio    ← on this board it is the touch reset
          (IO24) ( 0<<SEL | … )
          // LCD_EN
          (IO25) ( 0<<SEL | … | 7<<DS | 1<<ST )
          // i2c3
          (IO36) ( 1<<SEL | … )    → IIC3_SCL
          // BT_UART_TXD         ← also wrong; SEL=1 on IO37 is IIC3_SDA
          (IO37) ( 1<<SEL | … )
          (IO45) ( 0<<SEL | … )    → plain GPIO; the schematic calls it IO45_WIFI_EN
          // MMC1 -> TFCARD
          (IO54) ( 2<<SEL | … )    → MMC1_CMD
        >;
    };
};

numbering

Two GPIO banks, and the off-by-32

The SoC file splits 64 pins into two controllers of 32, and each numbers its own pins from zero. Every GPIO our board file references lands in bank 0, so the trap never fires — the Wi-Fi enable would be the first thing to cross into bank 1, and nothing references it yet.

gpio0_ports gpio@9140b000 · snps,nr-gpios = 32 SoC pin DT index IO0 … IO31 0 … 31 (same number) gpio1_ports gpio@9140c000 · snps,nr-gpios = 32 SoC pin DT index IO32 … IO63 0 … 31 (pin − 32) WHAT OUR BOARD FILE REFERENCES BY NUMBER IO22 panel reset, active high <&gpio0_ports 22> IO23 touch INT <&gpio0_ports 23> IO24 touch reset <&gpio0_ports 24> IO25 backlight, active high <&gpio0_ports 25> IO45 Wi-Fi 3V3 enable not referenced by any node we ship <&gpio1_ports 13> not 45
Each canaan,k230-apb-gpio-port numbers its own pins from zero. Writing <&gpio1_ports 45> for the Wi-Fi enable would be out of range on a 32-pin controller; writing <&gpio0_ports 45> would be silently wrong. The index is 13.

divergence

Where the reference board is not our board

Kernel sources: ruyisdk/linux-xuantie-kernel @ 7d4e1f444f461dbe3833bd99a4640e7b6c2cd529 — arch/riscv/boot/dts/canaan/{k230.dtsi, k230-canmv-v3.dts, k230-canmv-v3-lcd.dts, display-st7701-480x800.dtsi}.

Board evidence: the V1.0_NEW schematic, the vendored U-Boot device tree k230_canmv_v3p0.dts, the RT-Smart k230_canmv_v3p0_defconfig, and machine_fpioa.c. The hardware observation is the first-boot console log committed under docs/evidence.

House rule in force: observations outrank vendor source, and a datasheet grounds nothing. Anything not read out of a file, a schematic net or a console is marked unestablished above.