evidence · 44.5 KiB

board-capability-inventory.md

Committed at docs/research/board-capability-inventory.md. This is what grounds the requirements that cite it.

Open raw source

Board capability inventory

Compiled 2026-09-25. Answers one question per hardware feature the LILYGO T-Display-K230 (main board, optional nRF52840 base, optional nRF9151/keyboard base) is documented to carry: what it is, where it lives, whether our pinned kernel and device tree can drive it, whether userspace can use it, and what full support would cost.

How to read this document

Grounding tiers, from .skills/k230-spec-change/SKILL.md, strongest first:

  1. An observation on the board — a boot log, console transcript, photograph committed under docs/evidence/.
  2. Vendor source that has been read — a driver, defconfig or device tree, cited by path (ours, or LILYGO’s/Canaan’s).
  3. Nothing else — marked UNVERIFIED inline.

A datasheet or marketing page is never grounding by itself.

Support-level column, one of:

Two source trees underpin the driver/config claims below. Our pinned kernel is ruyisdk/linux-xuantie-kernel @ 7d4e1f444f461dbe3833bd99a4640e7b6c2cd529 (nix/kernel-src.nix; unpacked locally at /nix/store/bjgv1xn5b66gbz2kxv2aqzr1szp7dk7c-linux-xuantie-k230-src at the time of writing, so every “our tree has/lacks X” claim below was checked by reading that exact checkout’s arch/riscv/configs/k230_defconfig, arch/riscv/boot/dts/canaan/k230.dtsi and drivers/, not inferred). LILYGO publishes a separate, genuinely detailed BSP, Xinyuan-LilyGO/T-Display-K230:k230_bsp/, as a patch set against kendryte/k230_linux_sdk (k230_bsp/metadata/upstream_sdk_url.txt = https://github.com/kendryte/k230_linux_sdk.git, upstream_sdk_commit.txt = 22d02c6b6783a57a3aca7eb3160e313e772cb710) — that SDK’s own buildroot config vendors the same ruyisdk/linux-xuantie-kernel tree ours is pinned to (docs/findings.md), and one spot-checked patch (0064-input-k230-pmu-pwrkey.patch) diffs cleanly against context that matches our tree’s k230.dtsi verbatim (the tsensor@91107000 / hardlock = <2> block immediately preceding security: security {), so these patches are very likely a near-direct fit, not merely a similar board. Treat every LILYGO-patch citation below as grounding tier 2 (vendor source read, cited by path/URL) and every “our tree already has this” claim as tier 2 from our own checkout — both stronger than a datasheet, per the SKILL.md ordering. Nothing in this document is tier-1 (board-observed) unless it cites a docs/evidence/ file.


Summary: what the board can do

FeaturePartLocation
AMOLED panelRM69A10, 568×1232, MIPI-DSI 2-laneMain board
TouchGT9895 (Goodix “Berlin” family)Main board
Wi-FiRTL8189FTV/RTL8189FS, SDIOMain board
BluetoothGeneric USB dongle only — no onboard BT on this board; see docs/research/bluetooth-onboard.md (bundled test unit enumerates as a CSR8510 clone, 0a12:0001)Accessory over USB host
CameraGC2093, MIPI-CSI2Main board
HDMI outLontium LT9611 bridge, I2CMain board (diagnostic; shares GPIO23/24 with touch — mutually exclusive)
USB Ethernet/modemGeneric USB-class (CDC-ECM/NCM/MBIM/RNDIS/QMI)Accessory over USB host
SD cardTF slot, 4-bit SDIOMain board
LoRaSX1262 (v1.1+) / LR2021 (v1.3), SPIMain board
I2S amplifierMAX98357AMain board (per feat/speaker; LILYGO’s split docs it on the nRF52840 base)
Temp/humidity sensorAHT20, I2CnRF52840 base board
ChargerBQ25896, I2CKeyboard/nRF9151 base board
Fuel gaugeBQ27220, I2CKeyboard/nRF9151 base board
GPIO expanderXL9555 (PCA9555-compatible)Keyboard/nRF9151 base board
Keyboard controllerTCA8418, I2C matrix scannerKeyboard/nRF9151 base board
Keyboard backlightPWM4 / GPIO52Keyboard/nRF9151 base board
Power keyPMU INT0 channel (not a plain GPIO)Main board
BLE/cellular co-processornRF52840 (BLE+audio+sensors) / nRF9151 (LTE/NTN/DECT)Optional base boards, each its own UART link
On-chip thermal sensorK230 tsensorSoC
On-chip RTCK230 rtcSoC
On-chip watchdogSynopsys DesignWare WDT ×2SoC
On-chip ADCK230 adcSoC
On-chip PWMK230 pwm0/pwm1 (+ pwm3_5 group for PWM4)SoC
Video decode/encodeArm/Canaan MVX (H.264, HEVC, JPEG)SoC
2D GPUVG-Lite (Vivante)SoC
NPU/KPUCanaan KPU + nncase runtimeSoC (closed userspace runtime; see docs/blob-inventory.md B1/B2)
Crypto engine + TRNGAES/hash/RSA/RNG/OTP blockSoC
Second RISC-V C908 core800 MHz hartSoC
USB OTG ×2DWC2 dual-roleSoC

Summary: what we support today

CapabilityLevelEvidence / spec
Panel (RM69A10)Working-and-evidenceddocs/evidence/panel-lit.md, openspec/specs/display/panel/spec.md
Touch (GT9895)Working-and-evidenceddocs/evidence/touch-reports.md, openspec/specs/display/touch/spec.md
Wi-Fi (RTL8189)Working-and-evidencedopenspec/specs/radio/wifi/spec.md
SD cardWorking-and-evidenceddocs/evidence/sd-image.txt, openspec/specs/image/sd-layout/spec.md
USB mass-storage host/gadget (UMS)Working-and-evidenceddocs/evidence/usb-host-validation.md
USB Ethernet (r8152)Enabled-but-unverifieddocs/evidence/ethernet-readiness.txt (interface exists, no cable tested)
Software video playbackWorking-and-evidencedopenspec/specs/runtime/video/spec.md
MVX hardware video decodeEnabled-but-unverified (experiment-gated)openspec/specs/runtime/video/spec.md, docs/evidence/mvx-v4l2-audit.md
VG-Lite 2D GPU (private buffer only)Enabled-but-unverifiedopenspec/specs/runtime/gpu-validation/spec.md
Second core (SMP release)Not attempted; readiness-onlyopenspec/specs/system/second-core-readiness/spec.md
Thermal sensorEnabled-but-unverified, uncalibrateddocs/thermal.md
GPIO, on-chip PWM/ADCEnabled-but-unverifieddriver default ARCH_CANAAN, DT okay, never read from userspace on this board
Audio (I2S)Present-but-not-enabled correctlyboard sound node describes the on-chip Inno codec, not this board’s MAX98357A — see Overlaps
Backlight brightness controlMissing (fixed value baked into panel init)see row below
BluetoothMissing (kernel config off)see row below
CameraMissing-driversee row below
Charger / fuel gauge / power key / keyboard baseMissing-DT-node (drivers mostly present in-tree)see rows below
LoRaMissing-DT-node (SPI disabled)see row below
RTCPresent-but-not-enabled (one Kconfig line)see row below
NPU/KPUNot-applicable under Linuxdocs/blob-inventory.md B1/B2

Summary: gaps ordered by value to a handheld user, vs. effort

RankGapValueEffortRisk
1Battery %, charge state (BQ25896 + BQ27220)Very high — no battery UI todayMCharger register writes are risky; read-only first
2Adjustable screen brightness (backlight class)Very high — brightness is currently fixedS/MLow — DSI command writes only
3Power key press/long-press → shutdown/wakeVery high — no clean shutdown todayMMedium — new driver, wakeup path
4Bluetooth (generic USB dongle)HighSLow — Kconfig only, no DT/patch
5RTC (wall-clock across reboots)Medium-highSLow — one Kconfig line, DT already enabled
6Keyboard base (TCA8418 + XL9555 + backlight)High, but only if that accessory is boughtMLow; watch the shared UART3 conflict (see row)
7LoRa (SX1262/LR2021 over spidev)Medium — niche but a headline featureMMedium — RF transmit correctness/regulatory
8Camera (GC2093 + ISP)MediumLLow physical risk, largest engineering lift
9Audio (MAX98357A route)High— owned by feat/speaker, not duplicated here—
10Ethernet-over-USB verified linkLow-mediumSLow — needs only a cable and a test
11Watchdog exercisedLowSLow
12Second C908 coreSpeculative/long-termLGated by its own OpenSpec change
13HDMILow (diagnostic only; conflicts with touch)MLow
14NPU/KPULow today (nothing needs on-device inference)L, blocked on vendorclosed runtime, see blob inventory

Detailed inventory

Display and input

RM69A10 AMOLED panel

GT9895 touch (Goodix “Berlin” family)

GC2093 camera

HDMI (Lontium LT9611)

Radio

Wi-Fi (RTL8189FTV / RTL8189FS)

One row only — this is already fully specified and evidenced; see openspec/specs/radio/wifi/spec.md and docs/evidence/wifi-driver-audit.md (driver selection, 8189fs.ko, firmware-blob boundary) and docs/findings.md/docs/dts-evidence.md (SDIO0 vs SDIO1 correction, &gpio1_ports 13 enable line). Working-and-evidenced.

Bluetooth (generic USB dongle)

LoRa (SX1262 v1.1+ / LR2021 v1.3)

MAX98357A I2S amplifier — overlap only

Owned by another agent on feat/speaker; not duplicated here beyond one row. Current state as of this inventory: nix/dts/k230-tdisplay.dts’s sound node describes compatible = "canaan,k230-audio-inno" driving the on-chip Inno codec (CONFIG_SND_SOC_CANAAN_K230_INNO=y, already in our defconfig) — inherited from the reference board, and not this board’s actual MAX98357A external I2S DAC/amp. The mainline sound/soc/codecs/max98357a.c codec driver already exists in our pinned tree (confirmed by direct file listing) with no DT node wired to it. LILYGO’s own BSP does not add a second ALSA card either; instead it adds an ALSA mixer switch ("External I2S Output Switch", sound/soc/canaan/canaan_k230_inno.c, patch 0059-asoc-canaan-add-external-i2s-output-switch.patch) that gates the same I2S stream to the external amp via GPIO34 from userspace, plus pinmux for BCLK GPIO32/LRCK GPIO33/DATA GPIO35. Present-but-wrong node today; effort and risk are feat/speaker’s to size.

Sensors, power, and the keyboard base

BQ25896 charger + BQ27220 fuel gauge — “the handheld reports its battery”

AHT20 temperature/humidity sensor

XL9555 GPIO expander

TCA8418 keyboard-matrix controller

Keyboard backlight (PWM4 / GPIO52)

Power key (PMU INT0 channel)

nRF52840 (optional BLE/audio/sensor base) and nRF9151 (optional cellular/GNSS/keyboard base)

SoC-internal blocks

On-chip RTC

On-chip watchdog

On-chip ADC

On-chip PWM

See “Keyboard backlight” above for the concrete consumer. The controller itself (pwm0/pwm1, compatible = "canaan,k230-pwm", Kconfig PWM_K230 default ARCH_CANAAN) is enabled by default and already built. Enabled-but-unverified at the controller level.

On-chip GPIO

gpio0/gpio1, compatible = "canaan,k230-apb-gpio", driver gpio-k230.c, Kconfig GPIO_K230 default ARCH_CANAAN. Working-and- evidenced — already the mechanism behind the panel reset/backlight, touch reset/IRQ, and Wi-Fi enable lines documented above and in docs/dts-evidence.md.

Crypto engine (AES/hash/RSA), TRNG, OTP

Video decode/encode (MVX: H.264, HEVC, JPEG)

One row only — see openspec/specs/runtime/video/spec.md and docs/evidence/mvx-v4l2-audit.md for the full picture, including the compiled-in microcode/blob boundary (docs/blob-inventory.md A11). Enabled-but-unverified as a general decoder; proven for one bounded 30-fps H.264 experiment.

2D GPU (VG-Lite)

One row only — see openspec/specs/runtime/gpu-validation/spec.md and docs/research/vglite-validation.md. /dev/vg_lite is registered by our pinned kernel; libvg_lite.so builds from the vendor SDK’s Vivante MIT-licensed source. Private-buffer RGB565/alpha probes have physically passed; DRM dma-buf import, a real wlroots renderer, and scanout remain open. Enabled-but-unverified as an integrated renderer, Working-and-evidenced for the narrow private-buffer probes cited above.

NPU/KPU

More nuanced than a flat “RTOS-only”: there is a genuine, thin Linux kernel driver layer, already enabled in our pinned defconfig — CONFIG_K230_GNNE_DRIVER=y, CONFIG_K230_AI2D_DRIVER=y, CONFIG_K230_MMZ=y (checked directly), matching DT nodes gnne@80400000 (compatible k230-gnne) and ai2d@80400c00 (compatible k230-ai2d). These are register/IRQ/reserved-memory shims (char devices), not a scheduler. The actual model-loading and inference intelligence needed to use them lives entirely in Canaan’s closed nncase userspace runtime, shipped only as a binary tarball/wheel with no public K230 module on any nncase branch (docs/blob-inventory.md B1/B2: “we do not use the NPU… it becomes a hard blocker the moment anything wants on-device inference”). So: Enabled-but-unverified for the kernel shim, Not-applicable in practice for on-device inference without adopting that closed runtime. Nothing in this project’s current goal needs it; flagged here only because the user asked for “everything the board can do.”

Second RISC-V C908 core

One row only — this already has its own OpenSpec change and capability, system/second-core-readiness (openspec/specs/system/second-core-readiness/spec.md, openspec/changes/the-system-runs-on-both-cores/). Current state: read-only readiness tooling exists (tools/second-core-readiness.sh); the live device tree and firmware handoff describe one hart; SMP release is explicitly gated behind further evidence, not attempted.

USB (DWC2 ×2, host/gadget/UVC)

Bus controllers and interrupt/pinmux infrastructure (not separate rows above)

A second research pass, against kendryte/k230_linux_sdk’s own copy of the same pinned commit, cross-checked the remaining bus-level blocks without turning up any that change this document’s conclusions; two details are worth recording:

Thermal sensor

One row only — see docs/thermal.md in full: the sensor reads, but the value is an uncalibrated raw ADC code, not millidegrees, there are no trip points in the driver or device tree, and there is no cpufreq driver to throttle against even if one existed. Enabled-but-unverified is generous; functionally there is no thermal protection at all today. LILYGO’s own BSP separately fixes exactly this (0063-thermal-canaan-convert-k230-tsensor-raw.patch: a degree-4 polynomial raw-to-millicelsius conversion matching RT-Thread’s own formula) — a concrete, adoptable reference for closing this gap. Effort S/M, risk low, value medium (safety-adjacent, not user-visible day to day).


Overlaps with other in-flight work


Proposed sequence of OpenSpec changes

Grouped by what becomes true for the person holding the handheld, in the same rough order as the value/effort table above. Names are sentences per .skills/k230-spec-change/SKILL.md; none of these directories exist yet.

  1. the-handheld-reports-its-battery — system/power (new capability, or display/system per taxonomy discussion): read-only BQ25896 + BQ27220 access over the existing &i2c4 bus once probe confirms wiring; expose charge percent, charging/discharging state, and a upower-compatible power_supply sysfs node. No register writes in the first pass.
  2. the-panel-brightness-is-adjustable — display/backlight (the taxonomy already names this capability; no spec exists yet): port LILYGO’s DSI-command backlight patch, wire a real /sys/class/backlight device, and fix canaan_panel_prepare()’s missing reset pulses as a prerequisite so brightness changes survive a modeset.
  3. the-power-key-suspends-and-wakes — system/power: port the PMU INT0 power-key driver, land KEY_POWER events first (read-only/observable), then wire orderly shutdown, and only then the long-press wake path. Each stage gets its own board proof before the next.
  4. the-handheld-talks-bluetooth — radio/bluetooth (new capability): flip the Kconfig symbols, add BlueZ, prove pairing with a real USB dongle. No device tree or kernel patch required.
  5. the-clock-survives-a-reboot — system/kernel or a new system/rtc capability: enable RTC_DRV_K230, confirm /dev/rtc0, confirm the SoC RTC itself has a backing supply that survives a full power-off (UNVERIFIED today).
  6. the-keyboard-base-types — runtime/shell + a new input/keyboard-base capability: TCA8418 keypad DT node, XL9555 expander DT node, keyboard backlight PWM, explicitly documenting the UART3/nRF9151 conflict so it is not silently traded away.
  7. the-handheld-transmits-lora — radio/lora: enable &spi0, add the spidev + GPIO nodes, bring up a userspace RadioLib path, and get a regulatory/frequency-plan review before first transmit.
  8. the-camera-captures-a-frame — new media/camera capability: port the GC2093 sensor driver and vvcam ISP path, land a single v4l2-ctl --stream capture as the proof before any libcamera/app integration.
  9. the-thermal-sensor-reads-real-degrees — system/kernel or a new system/thermal capability: port LILYGO’s raw-to-millidegree conversion, then add a critical trip point tied to orderly_poweroff even without a cpufreq cooling device.
  10. the-usb-ethernet-link-is-verified — system/console or radio/*: plug a cable into the existing enu1 r8152 interface and record the missing evidence class (DHCP, route, DNS, ping) that docs/evidence/ethernet-readiness.txt stopped short of.

tools/board-inventory-probe.sh

A strictly read-only inventory probe, in the same style as tools/second-core-readiness.sh: POSIX sh, parameterized by BOARD_INVENTORY_ROOT (default /) so the file-based parts can be tested against a synthetic tree on a host with no board attached (tools/test-board-inventory-probe.sh, mirroring tools/test-second-core-readiness.sh’s fixture pattern).

Run on the board (it reads local /proc, /sys, and runs local commands — it is not a serial-console wrapper like tools/probe-display.sh, though the coordinator can paste its one-liner into a tools/console.py --send invocation over /dev/ttyACM0 exactly the way tools/probe-display.sh already does for the display/touch checks):

sh tools/board-inventory-probe.sh

It collects, in order, and only ever reads:

  1. Every /proc/device-tree node it can walk, with each node’s status property (or (default okay) when the property is absent, per the devicetree spec).
  2. Every /dev/i2c-* bus found, named from /sys/class/i2c-dev/i2c-N/name, then i2cdetect -y -r on it — -r only, the SMBus “receive byte” read, never the default “quick write” probe; that choice (plus i2cdetect’s own reserved-range skip, 0x00–0x02/0x78–0x7f) is what makes every scan safe. BOARD_INVENTORY_I2C_SKIP (space-separated hex addresses) is recorded against each bus in the output as a reviewer note when a coordinator already knows a specific chip on this board is read-sensitive — it annotates the output, it does not change what gets scanned (i2cdetect has no such exclude option).
  3. ls on /sys/class/{power_supply,backlight,input,sound,video4linux, bluetooth,net,thermal,pwm,gpio}.
  4. lsusb.
  5. dmesg, filtered to driver-probe-shaped lines (probe, bound, ready, okay, error, fail, case-insensitive).
  6. lsmod.

It never loads/unloads a module, never writes a sysfs attribute, never opens /dev/ttyACM0 itself (it runs on the board, not over the console link), and never issues an I2C/SPI write. Any command that is not found on the running image is reported as <unavailable> rather than failing the whole probe.

tools/test-board-inventory-probe.sh builds a small synthetic /proc/device-tree and /sys/class/* tree under a temp directory (the same pattern as tools/test-second-core-readiness.sh), runs the probe against it with BOARD_INVENTORY_ROOT set, and asserts on specific output lines. The command-based sections (i2cdetect, lsusb, dmesg, lsmod) are exercised only for their “tool not present” fallback in the host fixture, since faking their real output would test the fixture, not the probe.