Kendryte K230 · hardware note
k230.dtsi describes the silicon and switches almost all of it off. A board .dts reaches back into it by label and turns things on. That seam is the whole of a board port, so it is worth knowing exactly what it lets you do.
composition
Four source files, one flattened tree, and a second device tree entirely — U-Boot’s — that runs first and leaves the pin multiplexer in a state the kernel simply inherits. Our board file is a third sibling of the two reference boards, not a modification of either.
cpp and dtc at the pinned commit and compile clean. The two flattened trees differ by sixteen bytes. Structurally they are the same device tree; what differs is a model string, two compatible strings, a panel and a touch controller. That is what a board port of this kind actually is. Neither v3 file is listed in the canaan Makefile, so all three are built by name rather than by the dtb-y list.census
Fifty-six hand-written nodes: forty-seven at the top level plus the nine
children of sysctl and security. The interesting
split is by status — nineteen sit disabled waiting for a
board to claim them, twenty-five are switched on unconditionally, and twelve
carry no status at all, which in device-tree semantics means
enabled.
After a board file has had its say the flattened tree is
234 nodes: 184 okay, 11 still disabled. Those eleven are
uart1, uart2, uart4,
i2c0–i2c2, all three SPI controllers and
mipi1/mipi2: everything nobody claimed. Anything
this board needs from that list is a node we would have to enable ourselves,
and the charger, the fuel gauge and the LoRa radio are all in it.
mechanism
A board file does not copy a node. It writes &label { … },
which the compiler resolves to whichever node carries that label anywhere in
the included tree, then merges the properties in: later assignment wins,
everything unmentioned survives.
&mipi0 by re-assigning reg, interrupts and resets — a node that was already okay in the SoC file quietly moves to a different address and a different interrupt. Nothing flags it, and our board file inherits that edit unchanged because the reference does it.taxonomy
Read against the two reference boards and our own, every edit falls into one of these seven. There is no eighth kind, which is a useful thing to know before writing a board file.
Kind 1 — flip a switch
The commonest edit and the least interesting. uart0,
uart3, usb0, usb1 and
i2c4 get exactly this on our board.
&uart0 {
status = "okay";
}; Kind 2 — flip and tune
Signal-integrity and voltage facts only the board knows.
rx_delay_line and tx_delay_line are
Canaan-specific delay-chain trims; no-1-8-v says the level
shifters cannot do 1.8 V signalling.
&mmc_sd0 {
status = "okay";
no-1-8-v;
rx_delay_line = <0x0d>;
tx_delay_line = <0xc0>;
}; Kind 3 — attach a device
The child node’s unit address is the I²C address. GPIOs are referenced through the bank controller’s label and a per-bank index. This is the shape of our GT9895 node, shown as we actually wrote it.
&i2c3 {
status = "okay";
touchscreen@5d {
compatible = "goodix,gt9895";
reg = <0x5d>;
reset-gpios = <&gpio0_ports 24 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio0_ports>;
interrupts = <23 IRQ_TYPE_EDGE_FALLING>;
};
}; Kind 4 — re-point
The board tells mipi0 it is a CSI-2 receiver and hands it a
different register window and interrupt. Note there is no
status here — the node was already okay.
An unconditional rewrite of a hardware address from a board file is a strong smell. It is also what upstream does, and we carry it over rather than diverge on something we have not tested.
&mipi0 {
// set to csi2
id = <2>;
reg = <0x0 0x9000a800 0x0 0x800>;
interrupts = <121 IRQ_TYPE_LEVEL_HIGH>;
resets = <&sysctl_reset K230_RESET_CSI2_…>,
<&sysctl_reset K230_RESET_M2_…>;
}; Kind 5 — graft an endpoint
The SoC file leaves &dsi with only port@0
filled. A board adds port@1 and the endpoint naming its
downstream sink — a bridge chip on one board, a panel on ours.
Endpoints are declared in pairs and both halves must name each other or
the bridge chain never forms.
&dsi {
ports {
port@1 {
reg = <1>;
dsi_out_rm69a10: endpoint {
remote-endpoint = <&rm69a10_in>;
};
};
};
}; Kind 6 — new root nodes
How much DRAM, which UART is the console, what the board is called, the
machine-level sound card. These are written into / directly
because there is nothing upstream to reference.
aliases matters more than it looks: it fixes the
ttySn and mmcblkn numbering the rest of the
system will see.
/ {
model = "LILYGO T-Display-K230";
compatible = "lilygo,t-display-k230",
"canaan,canmv-k230",
"canaan,kendryte-k230";
aliases { serial3 = &uart3; mmc0 = &mmc_sd0;
mmc1 = &mmc_sd1; i2c0 = &i2c4; i2c1 = &i2c3; };
chosen { stdout-path = "serial0:115200n8"; };
ddr: memory@0 {
device_type = "memory";
reg = <0x0 0x0 0x0 0x40000000>; // 1 GiB
};
sound { compatible = "canaan,k230-audio-inno"; … };
}; Kind 7 — include a fragment
One #include is the entire difference between the plain
CanMV v3 board and the LCD variant. The fragment is not a node — it
is a bundle of &-overrides that happens to live in its own
file, which is why it can be reused across boards.
This is the mechanism our port uses: write the panel fragment, include it instead, and attach the pins by label afterwards.
#include "k230.dtsi"
#include "display-rm69a10-568x1232.dtsi"
// …and later, supply the pins the fragment
// declared but deliberately left off the panel node:
&lcd {
dsi_reset-gpios = <&gpio0_ports 22 GPIO_ACTIVE_HIGH>;
backlight_gpio-gpios = <&gpio0_ports 25 GPIO_ACTIVE_HIGH>;
}; the real thing
An earlier version of this note carried a sketch of what a T-Display board
file might look like, with a Wi-Fi power sequence and a
bus-width override in it, and a footnote saying it had never been
compiled. The file below is the one in the repository. It compiles against the
real k230.dtsi, every node was verified present in the output
rather than assumed, and the two guessed-at Wi-Fi edits are not in it.
// nix/dts/k230-tdisplay.dts — compiles, and booted Linux on
// 2026-09-20. The panel it describes has still never lit.
/dts-v1/;
#include "k230.dtsi"
#include "display-rm69a10-568x1232.dtsi" // ours
/ {
model = "LILYGO T-Display-K230";
compatible = "lilygo,t-display-k230", "canaan,canmv-k230",
"canaan,kendryte-k230";
aliases {
serial3 = &uart3;
mmc0 = &mmc_sd0; // the RTL8189FTV, over SDIO
mmc1 = &mmc_sd1; // the TF card, and the boot device
i2c0 = &i2c4; i2c1 = &i2c3;
};
chosen { stdout-path = "serial0:115200n8"; }; // ttyS0, not ttyS1
ddr: memory@0 { device_type = "memory"; reg = <0x0 0x0 0x0 0x40000000>; };
sound { compatible = "canaan,k230-audio-inno"; … };
};
&uart0 { status = "okay"; }; // CH342 ch0 — console
&uart3 { status = "okay"; }; // CH342 ch1
&usb0 { status = "okay"; };
&usb1 { status = "okay"; };
&mmc_sd1 { status = "okay"; no-1-8-v; cap-sd-highspeed; mshc_ctrl_r = <0x00>; };
&mmc_sd0 { // carried over verbatim
status = "okay"; no-1-8-v;
rx_delay_line = <0x0d>; tx_delay_line = <0xc0>;
};
// NOT WRITTEN — no mmc-pwrseq and no regulator, so nothing asserts IO45
// and the radio stays unpowered under Linux exactly as it does under
// RT-Smart. A known gap, left visible rather than guessed at.
&i2c3 {
status = "okay";
touchscreen@5d { // the reference has ft5306@38
compatible = "goodix,gt9895"; // no driver matches this, anywhere
reg = <0x5d>;
reset-gpios = <&gpio0_ports 24 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio0_ports>;
interrupts = <23 IRQ_TYPE_EDGE_FALLING>;
};
};
&i2c4 { status = "okay"; };
&mipi0 { /* re-pointed to CSI-2, exactly as the reference does */ };
// NOT WRITTEN — no LT9611 node. The non-LCD reference gives it
// gpio0_ports 24 and 23, which on this board are the touch controller's.
// NOT WRITTEN — BQ25896, BQ27220, SX1262/LR2021: bus and chip-select unread.
&lcd { // the label on our panel node
dsi_reset-gpios = <&gpio0_ports 22 GPIO_ACTIVE_HIGH>;
backlight_gpio-gpios = <&gpio0_ports 25 GPIO_ACTIVE_HIGH>;
}; Census taken by walking arch/riscv/boot/dts/canaan/k230.dtsi at ruyisdk/linux-xuantie-kernel 7d4e1f444f461dbe3833bd99a4640e7b6c2cd529. Byte counts and node counts are from running cpp and dtc over each board file at that commit and re-reading the output.
Board evidence: the vendored U-Boot device tree k230_canmv_v3p0.dts, the RT-Smart k230_canmv_v3p0_defconfig, and the V1.0_NEW schematic.