@typecad/framework-zephyr 1.0.0-alpha.18 → 1.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/audit.d.ts +111 -0
  2. package/dist/audit.js +416 -0
  3. package/dist/boardgen.js +63 -5
  4. package/dist/chips/resolve.js +20 -0
  5. package/dist/chips/types.d.ts +21 -0
  6. package/dist/display/bindings.d.ts +55 -0
  7. package/dist/display/bindings.js +316 -0
  8. package/dist/display/gfx.d.ts +2 -3
  9. package/dist/display/gfx.js +166 -154
  10. package/dist/display/index.js +20 -1
  11. package/dist/display/mipi-dbi-host.d.ts +9 -0
  12. package/dist/display/mipi-dbi-host.js +174 -0
  13. package/dist/display/profiles.d.ts +109 -4
  14. package/dist/display/profiles.js +270 -7
  15. package/dist/display/touch-adapter.js +118 -48
  16. package/dist/display/ui-adapter-eink.d.ts +2 -0
  17. package/dist/display/ui-adapter-eink.js +4 -0
  18. package/dist/display/ui-adapter-gray.d.ts +8 -0
  19. package/dist/display/ui-adapter-gray.js +170 -0
  20. package/dist/display/ui-adapter-mono.d.ts +13 -0
  21. package/dist/display/ui-adapter-mono.js +230 -0
  22. package/dist/display/ui-adapter-native.d.ts +10 -0
  23. package/dist/display/ui-adapter-native.js +295 -0
  24. package/dist/display/ui-adapter-shared.d.ts +11 -0
  25. package/dist/display/ui-adapter-shared.js +122 -0
  26. package/dist/display/ui-adapter.js +53 -101
  27. package/dist/dt-config/kconfig.d.ts +62 -1
  28. package/dist/dt-config/kconfig.js +123 -26
  29. package/dist/dt-config/overlay.d.ts +13 -0
  30. package/dist/dt-config/overlay.js +431 -16
  31. package/dist/framework.manifest.d.ts +9 -3
  32. package/dist/framework.manifest.js +123 -10
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +9 -1
  35. package/dist/licenses.js +5 -84
  36. package/dist/lowering/can.d.ts +25 -0
  37. package/dist/lowering/can.js +97 -0
  38. package/dist/lowering/clock.d.ts +17 -0
  39. package/dist/lowering/clock.js +58 -0
  40. package/dist/lowering/hid.d.ts +27 -0
  41. package/dist/lowering/hid.js +244 -0
  42. package/dist/lowering/i2c.d.ts +8 -0
  43. package/dist/lowering/i2c.js +140 -0
  44. package/dist/lowering/i2s.d.ts +27 -0
  45. package/dist/lowering/i2s.js +98 -0
  46. package/dist/lowering/index.d.ts +9 -1
  47. package/dist/lowering/index.js +25 -1
  48. package/dist/lowering/matrix.d.ts +15 -0
  49. package/dist/lowering/matrix.js +63 -0
  50. package/dist/lowering/power.d.ts +10 -0
  51. package/dist/lowering/power.js +35 -0
  52. package/dist/lowering/pwm.js +25 -0
  53. package/dist/lowering/sensor.d.ts +2 -2
  54. package/dist/lowering/sensor.js +8 -4
  55. package/dist/lowering/strip.d.ts +16 -0
  56. package/dist/lowering/strip.js +70 -0
  57. package/dist/lowering/thread.js +5 -1
  58. package/dist/lowering/trace.d.ts +44 -0
  59. package/dist/lowering/trace.js +239 -0
  60. package/dist/lowering/uart.js +6 -1
  61. package/dist/lowering/usb.d.ts +3 -1
  62. package/dist/lowering/usb.js +6 -3
  63. package/dist/sbom.d.ts +181 -0
  64. package/dist/sbom.js +901 -0
  65. package/dist/strategy.d.ts +60 -2
  66. package/dist/strategy.js +485 -25
  67. package/dist/toolchain/index.d.ts +12 -1
  68. package/dist/toolchain/index.js +115 -27
  69. package/dist/toolchain/scaffold.d.ts +4 -1
  70. package/dist/toolchain/scaffold.js +67 -3
  71. package/dist/west-inventory.d.ts +25 -0
  72. package/dist/west-inventory.js +97 -0
  73. package/package.json +6 -6
  74. package/src/audit.ts +529 -0
  75. package/src/boardgen.ts +54 -5
  76. package/src/chips/resolve.ts +21 -0
  77. package/src/chips/types.ts +576 -567
  78. package/src/display/bindings.ts +347 -0
  79. package/src/display/gfx.ts +318 -306
  80. package/src/display/index.ts +87 -70
  81. package/src/display/mipi-dbi-host.ts +183 -0
  82. package/src/display/profiles.ts +458 -139
  83. package/src/display/touch-adapter.ts +274 -204
  84. package/src/display/ui-adapter-eink.ts +13 -0
  85. package/src/display/ui-adapter-gray.ts +178 -0
  86. package/src/display/ui-adapter-mono.ts +238 -0
  87. package/src/display/ui-adapter-native.ts +304 -0
  88. package/src/display/ui-adapter-shared.ts +125 -0
  89. package/src/display/ui-adapter.ts +51 -100
  90. package/src/dt-config/kconfig.ts +647 -511
  91. package/src/dt-config/overlay.ts +433 -16
  92. package/src/framework.manifest.ts +131 -10
  93. package/src/index.ts +11 -1
  94. package/src/licenses.ts +5 -84
  95. package/src/lowering/can.ts +140 -0
  96. package/src/lowering/clock.ts +91 -0
  97. package/src/lowering/hid.ts +261 -0
  98. package/src/lowering/i2c.ts +146 -0
  99. package/src/lowering/i2s.ts +143 -0
  100. package/src/lowering/index.ts +18 -1
  101. package/src/lowering/matrix.ts +70 -0
  102. package/src/lowering/power.ts +41 -0
  103. package/src/lowering/pwm.ts +192 -167
  104. package/src/lowering/sensor.ts +159 -155
  105. package/src/lowering/strip.ts +81 -0
  106. package/src/lowering/thread.ts +5 -1
  107. package/src/lowering/trace.ts +270 -0
  108. package/src/lowering/uart.ts +6 -1
  109. package/src/lowering/usb.ts +224 -221
  110. package/src/sbom.ts +1117 -0
  111. package/src/strategy.ts +440 -25
  112. package/src/toolchain/index.ts +117 -28
  113. package/src/toolchain/scaffold.ts +62 -3
  114. package/src/west-inventory.ts +102 -0
@@ -10,8 +10,74 @@
10
10
  // that brings external capabilities into a build. (Arduino does it by parsing
11
11
  // library headers into .d.ts; Zephyr does it by enabling DT nodes + Kconfig.)
12
12
  // ---------------------------------------------------------------------------
13
- import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor } from '../display/profiles.js';
13
+ import { controllerNodelabelForPin, controllerRawPinForPin } from '../chips/controllers.js';
14
+ import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor, transportFor, isDtCompatible } from '../display/profiles.js';
15
+ import { readDisplayBinding } from '../display/bindings.js';
16
+ import { isEinkDisplay } from '../display/profiles.js';
17
+ /** Standard values for the mono-OLED family's default-free required props
18
+ * (segment/page/display offsets, multiplex ratio, precharge period) — the
19
+ * values upstream board overlays ship; tune per panel from here. */
20
+ function monoPropFallback(prop, info, nativeHeight) {
21
+ if (prop === 'width' || prop === 'height' || prop === 'reg' || prop === 'compatible')
22
+ return undefined;
23
+ const def = info?.default;
24
+ if (def?.kind === 'int')
25
+ return `${prop} = <${def.value}>`;
26
+ if (prop === 'multiplex-ratio')
27
+ return `multiplex-ratio = <${nativeHeight - 1}>`;
28
+ if (prop === 'prechargep')
29
+ return 'prechargep = <0x22>';
30
+ if (prop === 'segment-offset' || prop === 'page-offset' || prop === 'display-offset')
31
+ return `${prop} = <0>`;
32
+ // SSD1327-class (Stage 3 gray): required by solomon,ssd1327_5-common.yaml
33
+ // with no binding default. Panel-tunable — these are the datasheet/common-
34
+ // module starting points (scanline artifacts: raise oscillator-freq toward
35
+ // 0x70; striping: add 0x40 to remap-value per the binding's bit table).
36
+ if (prop === 'oscillator-freq')
37
+ return 'oscillator-freq = <0x00>';
38
+ if (prop === 'start-line')
39
+ return 'start-line = <0>';
40
+ if (prop === 'remap-value')
41
+ return 'remap-value = <0x51>';
42
+ if (prop === 'phase-length')
43
+ return 'phase-length = <0x1f>';
44
+ return undefined;
45
+ }
46
+ /** Format one binding-required property with its harvested default. Binding
47
+ * props that are deliberately default-free ("panel specific" — e.g. the
48
+ * sitronix gamma arrays) fall back to the rig-validated tuning the curated
49
+ * profiles ship, so the generated node is always DT-valid. */
50
+ function emitBindingProp(lines, prop, info) {
51
+ const def = info?.default;
52
+ if (def?.kind === 'bytes' && def.value.length > 0) {
53
+ lines.push(` ${prop} = [${def.value.map((b) => b.toString(16).padStart(2, '0')).join(' ')}];`);
54
+ return;
55
+ }
56
+ if (def?.kind === 'bool' && def.value) {
57
+ lines.push(` ${prop};`);
58
+ return;
59
+ }
60
+ if (def?.kind === 'string') {
61
+ lines.push(` ${prop} = "${def.value}";`);
62
+ return;
63
+ }
64
+ if (info?.type === 'uint8-array') {
65
+ // No binding default (panel-specific): emit the rig-validated gamma
66
+ // tuning the curated st7796 profile carries. Tune per panel from here.
67
+ lines.push(` ${prop} = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];`);
68
+ return;
69
+ }
70
+ const v = def?.kind === 'int' ? def.value : 0;
71
+ lines.push(` ${prop} = <${v}>;`);
72
+ }
14
73
  import { SENSOR_PART_INFO } from '@typecad/hal';
74
+ /** 1-Wire family codes (the high byte of the 64-bit ROM ID) for the parts
75
+ * the catalog carries on the w1 bus — vendor silicon data, like the ESP32
76
+ * DAC pin table. Unknown compatibles default to the DS18B20's 0x28. */
77
+ const W1_FAMILY_CODES = {
78
+ 'maxim,ds18b20': 0x28,
79
+ 'maxim,ds18s20': 0x10,
80
+ };
15
81
  /** Display panel CS default — shared by the display block and the SPI-sensor
16
82
  * cs-gpios merge so both sites write the same entry. */
17
83
  const DEFAULT_DISPLAY_CS = 5;
@@ -128,6 +194,36 @@ export function generateOverlay(chip, usage, display, wiring, touch, diagnostics
128
194
  lines.push('};');
129
195
  lines.push('');
130
196
  }
197
+ // One w1-gpio master + sensor child per constructed 1-Wire sensor. The
198
+ // master bit-bangs the data pin (open-drain, internal pull-up enabled —
199
+ // the binding recommends an external 4.7 kΩ, the internal one is weak);
200
+ // the child's family-code comes from the vendor table below (part of the
201
+ // 64-bit ROM ID), resolution from the construction opts. The nodelabels
202
+ // match lowering/sensor.ts sensorNames — the tc-pwm discipline.
203
+ for (const sp of sensorParts.filter((s) => s.busKind === 'w1')) {
204
+ const info = SENSOR_PART_INFO[sp.part.replace(/^SENSOR\./, '')];
205
+ const family = W1_FAMILY_CODES[info?.compatible ?? ''] ?? 0x28;
206
+ if (!info)
207
+ continue; // the lowering already threw on unknown parts
208
+ const pinCtrl = controllerNodelabelForPin(chip, sp.port);
209
+ const pinBit = controllerRawPinForPin(chip, sp.port);
210
+ const nodeName = info.compatible.split(',')[1] ?? sp.part;
211
+ const stem = `${sp.part.replace(/^SENSOR\./, '')}_w1_p${sp.port}`;
212
+ lines.push('/ {');
213
+ lines.push(` tc_w1_p${sp.port}: tc-w1-p${sp.port} {`);
214
+ lines.push(' compatible = "zephyr,w1-gpio";');
215
+ lines.push(` gpios = <&${pinCtrl} ${pinBit} (GPIO_OPEN_DRAIN | GPIO_PULL_UP)>;`);
216
+ lines.push(' status = "okay";');
217
+ lines.push(` tc_${stem}: ${nodeName} {`);
218
+ lines.push(` compatible = "${info.compatible}";`);
219
+ lines.push(` family-code = <0x${family.toString(16)}>;`);
220
+ lines.push(` resolution = <${sp.resolution}>;`);
221
+ lines.push(' status = "okay";');
222
+ lines.push(' };');
223
+ lines.push(' };');
224
+ lines.push('};');
225
+ lines.push('');
226
+ }
131
227
  if (usage.usesSpi && chip.spi) {
132
228
  for (const [i, c] of chip.spi.controllers.entries()) {
133
229
  if (usage.spiUsedInstances && !usage.spiUsedInstances.includes(i))
@@ -226,6 +322,178 @@ export function generateOverlay(chip, usage, display, wiring, touch, diagnostics
226
322
  }
227
323
  }
228
324
  }
325
+ // LED strips: one ws2812-spi child per driven SPI controller. The frame
326
+ // tuning (6.4 MHz, one=0xf0 / zero=0xc0 — 625 ns bit halves) is the
327
+ // Zephyr led_strip sample's canonical SPI encoding; the color-mapping
328
+ // matches the WS2812's native GRB order (the driver reorders). The
329
+ // controller is enabled here too — a strip-only program emits no spi_*
330
+ // calls, so the bus enable cannot ride the generic SPI usage path.
331
+ // `line-idle-low` keeps MOSI low between frames, the WS2812 reset level.
332
+ if (usage.usesStrip && usage.strips && usage.strips.length > 0) {
333
+ lines.splice(2, 0, '#include <zephyr/dt-bindings/led/led.h>', '');
334
+ for (const s of usage.strips) {
335
+ const ctrl = chip.spi?.controllers[s.index];
336
+ if (!ctrl)
337
+ continue;
338
+ lines.push(`&${ctrl.nodeLabel} {`);
339
+ lines.push(' status = "okay";');
340
+ lines.push(' line-idle-low;');
341
+ lines.push(` tc_strip${s.index}: ws2812@0 {`);
342
+ lines.push(' compatible = "worldsemi,ws2812-spi";');
343
+ lines.push(' reg = <0>;');
344
+ lines.push(' spi-max-frequency = <6400000>;');
345
+ lines.push(` chain-length = <${s.count}>;`);
346
+ lines.push(' spi-cpha;');
347
+ lines.push(' spi-one-frame = <0xf0>;');
348
+ lines.push(' spi-zero-frame = <0xc0>;');
349
+ lines.push(' color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;');
350
+ lines.push(' };');
351
+ lines.push('};');
352
+ lines.push('');
353
+ }
354
+ }
355
+ // I2S: enable the board's own i2s@ controllers — their pinctrl groups
356
+ // are board-authored (the devkitC wires i2s0_default/i2s1_default), so
357
+ // status is the only synthesis needed. The GDMA the ESP32 I2S driver
358
+ // DMA-runs through also ships disabled — enable it alongside.
359
+ // Full-duplex: the RX side's I_BCK/I_WS inputs must be routed in the
360
+ // GPIO matrix to the same pads the TX outputs drive (input-enable on
361
+ // the TX clock pads — the same-pad GPIO matrix trick the CAN loopback
362
+ // uses; the devkitC's i2s0 clock pads are WS=36, BCK=37).
363
+ if (usage.usesI2s && chip.i2s) {
364
+ lines.push('&dma {');
365
+ lines.push(' status = "okay";');
366
+ lines.push('};');
367
+ lines.push('');
368
+ for (const c of chip.i2s.controllers) {
369
+ const n = c.nodeLabel.replace(/[^0-9]/g, '') || '0';
370
+ lines.push(`&${c.nodeLabel} {`);
371
+ lines.push(' status = "okay";');
372
+ lines.push('};');
373
+ lines.push('');
374
+ lines.push('&pinctrl {');
375
+ lines.push(' tc_i2s_clock_loop: tc-i2s-clock-loop {');
376
+ lines.push(' group1 {');
377
+ lines.push(` pinmux = <I2S${n}_I_BCK_GPIO37>,`);
378
+ lines.push(` <I2S${n}_I_WS_GPIO36>;`);
379
+ lines.push(' };');
380
+ lines.push(' };');
381
+ lines.push('};');
382
+ lines.push(`&${c.nodeLabel} {`);
383
+ lines.push(` pinctrl-0 = <&${c.nodeLabel}_default>, <&tc_i2s_clock_loop>;`);
384
+ lines.push(' pinctrl-names = "default";');
385
+ lines.push('};');
386
+ lines.push('');
387
+ }
388
+ }
389
+ // CAN: enable the board's own can@ controller (the harvested node —
390
+ // boards that ship pinctrl groups keep them; the bitrate is the begin()
391
+ // default, overridden at runtime by can_set_bitrate). Classic CAN only.
392
+ // LOOPBACK: the SJA1000 self-test transmits on the TX pad and receives
393
+ // on the RX pad — with no transceiver the overlay routes BOTH TWAI
394
+ // functions onto the TX pad (the Zephyr test suite's own
395
+ // twai-enable.overlay shape: output-enable on the RX mux, input-enable
396
+ // on the TX mux, one shared pad).
397
+ if (usage.usesCan && chip.can) {
398
+ for (const c of chip.can.controllers) {
399
+ lines.push(`&${c.nodeLabel} {`);
400
+ lines.push(' status = "okay";');
401
+ lines.push(' bitrate = <500000>;');
402
+ lines.push('};');
403
+ lines.push('');
404
+ if (usage.canLoopback && c.txPad !== undefined) {
405
+ lines.push('&pinctrl {');
406
+ lines.push(' tc_can_loopback: tc-can-loopback {');
407
+ lines.push(' group1 {');
408
+ lines.push(` pinmux = <TWAI_RX_GPIO${c.txPad}>;`);
409
+ lines.push(' output-enable;');
410
+ lines.push(' };');
411
+ lines.push(' group2 {');
412
+ lines.push(` pinmux = <TWAI_TX_GPIO${c.txPad}>;`);
413
+ lines.push(' input-enable;');
414
+ lines.push(' };');
415
+ lines.push(' };');
416
+ lines.push('};');
417
+ lines.push(`&${c.nodeLabel} { pinctrl-0 = <&tc_can_loopback>; pinctrl-names = "default"; };`);
418
+ lines.push('');
419
+ }
420
+ }
421
+ }
422
+ // Wall-clock shim: a zephyr,rtc-counter child on the board's first free
423
+ // counter node + the `rtc` alias (Zephyr's own discovery convention —
424
+ // the api tests filter on dt_alias_exists("rtc")). The shim is a CHILD:
425
+ // the counter driver keeps the parent node, so Counter and Clock
426
+ // coexist. V1 always synthesizes the shim (uniform semantics, no driver
427
+ // conflicts); preferring a board's own hardware-calendar alias — and the
428
+ // backup-domain persistence it buys — is the follow-up.
429
+ if (usage.usesClock && usage.clockShimCounter) {
430
+ const c = usage.clockShimCounter;
431
+ if (c.parent) {
432
+ // Child-form counters (ESP32 timerN children): the shim's parent
433
+ // must be the counter DEVICE, so it nests INSIDE the counter child —
434
+ // DT_INST_PARENT in the shim driver resolves to the counter, not the
435
+ // timer (which carries no device of its own).
436
+ lines.push(`&${c.parent} {`);
437
+ lines.push(' status = "okay";');
438
+ lines.push(` ${c.label}: counter {`);
439
+ lines.push(' status = "okay";');
440
+ lines.push(' tc_rtc: tc-rtc {');
441
+ lines.push(' compatible = "zephyr,rtc-counter";');
442
+ lines.push(' };');
443
+ lines.push(' };');
444
+ lines.push('};');
445
+ }
446
+ else {
447
+ lines.push(`&${c.label} {`);
448
+ lines.push(' tc_rtc: tc-rtc {');
449
+ lines.push(' compatible = "zephyr,rtc-counter";');
450
+ lines.push(' };');
451
+ lines.push('};');
452
+ }
453
+ lines.push('/ {');
454
+ lines.push(' aliases {');
455
+ lines.push(' rtc = &tc_rtc;');
456
+ lines.push(' };');
457
+ lines.push('};');
458
+ lines.push('');
459
+ }
460
+ // USB HID: one zephyr,hid-device interface per program (the v1 ceiling —
461
+ // Keyboard OR Mouse, not both). protocol-code stays "none": the boot
462
+ // subclass/protocol codes (keyboard/mouse) made Windows bind the
463
+ // collections but silently discard every input report on this stack,
464
+ // while "none" (pure report-protocol device, like Zephyr's own samples)
465
+ // delivers input normally. The report descriptor carries the real
466
+ // keyboard/mouse semantics either way.
467
+ if (usage.usesHid) {
468
+ lines.push('/ {');
469
+ lines.push(' tc_hid: tc-hid {');
470
+ lines.push(' compatible = "zephyr,hid-device";');
471
+ lines.push(' protocol-code = "none";');
472
+ lines.push(' in-report-size = <8>;');
473
+ lines.push(' in-polling-period-us = <1000>;');
474
+ lines.push(' };');
475
+ lines.push('};');
476
+ lines.push('');
477
+ }
478
+ // Key matrix: the gpio-kbd-matrix node from the construction pad lists.
479
+ // Rows are pull-up inputs (active-low); columns are driven one at a time.
480
+ // Pin → controller/bit via the same mapping the gpio lowering uses.
481
+ if (usage.usesMatrix && usage.matrix && usage.matrix.rows.length > 0) {
482
+ // Same mapping the gpio lowering uses — the canonical controller
483
+ // resolution (single-controller fallback included). The port-relative
484
+ // bit comes from controllerRawPinForPin (split-SoC offsets).
485
+ const gpioRef = (pin) => {
486
+ return `&${controllerNodelabelForPin(chip, pin)} ${controllerRawPinForPin(chip, pin)} (GPIO_PULL_UP | GPIO_ACTIVE_LOW)`;
487
+ };
488
+ lines.push('/ {');
489
+ lines.push(' tc_matrix: matrix {');
490
+ lines.push(' compatible = "gpio-kbd-matrix";');
491
+ lines.push(` row-gpios = ${usage.matrix.rows.map((pin) => `<${gpioRef(pin)}>`).join(', ')};`);
492
+ lines.push(` col-gpios = ${usage.matrix.cols.map((pin) => `<${gpioRef(pin)}>`).join(', ')};`);
493
+ lines.push(' };');
494
+ lines.push('};');
495
+ lines.push('');
496
+ }
229
497
  if (display) {
230
498
  // Emit a full display DT node definition. Boards like the ESP32 devkit
231
499
  // have no display node in their base DT, so a bare `&display0 { status }`
@@ -726,11 +994,87 @@ function emitAlert(lines, chip, alertPin, supported, indent) {
726
994
  function emitDisplayNode(lines, display, wiring, spiTouchCs) {
727
995
  const bus = display.busLabel ?? 'spi2';
728
996
  const controller = panelControllerFor(display);
729
- const compatible = display.dtCompatible ?? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible;
997
+ // Board-provided displays (native_sim's built-in sdl_dc): the board's own
998
+ // devicetree wires the panel — nothing to emit here.
999
+ if (display.boardProvidesDisplay) {
1000
+ return;
1001
+ }
1002
+ // I2C-family panels (mono OLEDs, ssd1306-class): a plain &i2cN child node —
1003
+ // no mipi-dbi bridge, no SPI bus block, no DMA. Bus pins come from the
1004
+ // touch wiring's remux (shared bus) or the board's default I2C pins.
1005
+ if (readDisplayBinding(display.dtCompatible ?? (isDtCompatible(display.driver) ? display.driver : ''))?.busFamily === 'i2c') {
1006
+ const addr = wiring?.address ?? 0x3c;
1007
+ // Optional I2C pin remux — mirrors the touch path's remux (the board's
1008
+ // default I2C pins rarely match a breakout's wiring). Named macros from
1009
+ // the SoC pinctrl header; the group lives under &pinctrl and overrides
1010
+ // i2c0's default group via pinctrl-0.
1011
+ const sda = wiring?.sda;
1012
+ const scl = wiring?.scl;
1013
+ if (sda !== undefined && scl !== undefined) {
1014
+ lines.push('&pinctrl {');
1015
+ lines.push(' i2c0_display: i2c0_display {');
1016
+ lines.push(' group1 {');
1017
+ lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
1018
+ lines.push(' bias-pull-up;');
1019
+ lines.push(' drive-open-drain;');
1020
+ lines.push(' };');
1021
+ lines.push(' };');
1022
+ lines.push('};');
1023
+ lines.push('');
1024
+ }
1025
+ // Node name from the compatible's panel segment (ssd1306/ssd1309/sh1106);
1026
+ // DT node names are labels, not compatibles — but matching the panel
1027
+ // keeps the built zephyr.dts readable.
1028
+ const panel = (display.dtCompatible ?? display.driver).split(',')[1] ?? 'ssd1306';
1029
+ lines.push('&i2c0 {');
1030
+ lines.push(' status = "okay";');
1031
+ // Fast mode: the full-frame mono push is bandwidth-bound (a 128x64 frame
1032
+ // is 1KB — ~25ms at the 100kHz default, ~7ms at 400kHz), so animations
1033
+ // step without it. OLED modules and the esp32s3/nRF i2c controllers all
1034
+ // handle 400kHz; declare it whenever we own the bus node generation.
1035
+ lines.push(' clock-frequency = <400000>;');
1036
+ if (sda !== undefined && scl !== undefined) {
1037
+ lines.push(' pinctrl-0 = <&i2c0_display>;');
1038
+ lines.push(' pinctrl-names = "default";');
1039
+ }
1040
+ lines.push(` ${display.dtLabel}: ${panel}@${addr.toString(16)} {`);
1041
+ lines.push(` compatible = "${display.dtCompatible ?? display.driver}";`);
1042
+ lines.push(` reg = <0x${addr.toString(16)}>;`);
1043
+ lines.push(` width = <${display.nativeWidth ?? display.width}>;`);
1044
+ lines.push(` height = <${display.nativeHeight ?? display.height}>;`);
1045
+ const binding = readDisplayBinding(display.dtCompatible ?? display.driver);
1046
+ if (binding) {
1047
+ for (const [prop, info] of binding.required) {
1048
+ const v = monoPropFallback(prop, info, display.nativeHeight ?? display.height);
1049
+ if (v !== undefined)
1050
+ lines.push(` ${v};`);
1051
+ }
1052
+ }
1053
+ lines.push(' };');
1054
+ lines.push('};');
1055
+ lines.push('');
1056
+ return;
1057
+ }
1058
+ // Compatible resolution: explicit override → the registry controllers →
1059
+ // the drop-in path (driver IS a compatible string) → a safe default.
1060
+ // Drop-in drivers (DT-compatible shape) ARE the compatible — they win over
1061
+ // the controller-default table (which only serves registry driver ids whose
1062
+ // id isn't itself a compatible; the e-ink family entries exist for the
1063
+ // Kconfig layer, not for node naming).
1064
+ const compatible = display.dtCompatible
1065
+ ?? (isDtCompatible(display.driver)
1066
+ ? display.driver
1067
+ : (controller !== undefined
1068
+ ? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible
1069
+ : PANEL_CONTROLLER_DEFAULTS.ili9341.dtCompatible));
730
1070
  const dc = wiring?.dc ?? 17;
731
1071
  const rst = wiring?.rst ?? 16;
732
1072
  const cs = wiring?.cs ?? DEFAULT_DISPLAY_CS;
733
- const freq = wiring?.spiFrequency ?? 80000000;
1073
+ // E-ink SPI tops out far below TFTs (the reel board runs ssd1673 at 4MHz;
1074
+ // the controllers' shift registers clock 4-10MHz). Cap the default for the
1075
+ // e-ink families; explicit spiFrequency always wins.
1076
+ const einkFamily = isEinkDisplay({ driver: display.driver, displayClass: display.displayClass });
1077
+ const freq = wiring?.spiFrequency ?? (einkFamily ? 4000000 : 80000000);
734
1078
  // DT node describes the NATIVE panel geometry; the effective (rotated)
735
1079
  // dimensions live in the display profile.
736
1080
  const nativeW = display.nativeWidth ?? display.width;
@@ -793,19 +1137,25 @@ function emitDisplayNode(lines, display, wiring, spiTouchCs) {
793
1137
  lines.push(` ${display.dtLabel}: display@0 {`);
794
1138
  lines.push(` compatible = "${compatible}";`);
795
1139
  lines.push(' reg = <0>;');
796
- if (wiring?.tearingEffectPin !== undefined) {
1140
+ if (wiring?.tearingEffectPin !== undefined && transportFor(display) === 'direct-spi') {
797
1141
  const tePin = wiring.tearingEffectPin;
798
1142
  // Tearing-effect input on the display node: GPIO_DT_SPEC_GET(
799
- // DT_NODELABEL(display0), te_gpios) in the adapter. Opt-in —
800
- // most modules don't break the TE pad out.
1143
+ // DT_NODELABEL(display0), te_gpios) in the direct-spi adapter.
1144
+ // Opt-in — most modules don't break the TE pad out. NOT emitted
1145
+ // on the zephyr-display transport: te-gpios makes the bound
1146
+ // in-tree driver allocate the TE GPIO interrupt (the ESP32
1147
+ // VECDESC_FL_SHARED conflict path) — the driver's TE handling
1148
+ // needs re-verification before it can be wired there.
801
1149
  lines.push(` te-gpios = <&${gpioController(tePin)} ${tePin} GPIO_ACTIVE_HIGH>;`);
802
1150
  }
803
1151
  lines.push(` mipi-max-frequency = <${freq}>;`);
804
1152
  lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
805
- // Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
806
- // matching upstream ILI9341 boards (esp_wrover_kit) and the C++
807
- // runtime, which drives these SPI TFTs as RGB565.
808
- lines.push(' pixel-format = <0>;');
1153
+ if (wiring?.busyPin !== undefined) {
1154
+ // E-ink BUSY line — required by the ssd16xx/uc81xx bindings
1155
+ // (the drivers block on it through the flash cycle). Active
1156
+ // level fixed per family: ssd16xx BUSY is active-high.
1157
+ lines.push(` busy-gpios = <&${gpioController(wiring.busyPin)} ${wiring.busyPin} GPIO_ACTIVE_HIGH>;`);
1158
+ }
809
1159
  lines.push(` width = <${nativeW}>;`);
810
1160
  lines.push(` height = <${nativeH}>;`);
811
1161
  if (controller === 'st7796s') {
@@ -815,12 +1165,72 @@ function emitDisplayNode(lines, display, wiring, spiTouchCs) {
815
1165
  lines.push(' madctl = <0x28>;');
816
1166
  lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
817
1167
  lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
1168
+ // rgb-is-inverted flips the format the driver REPORTS (565 <-> 565X).
1169
+ // Zephyr RGB_565 is little-endian storage, but an 8-bit SPI panel clocks
1170
+ // the LOW byte first — each pixel arrives byte-swapped, which mixes the
1171
+ // green channel into red/blue (white text reads purple, dark backgrounds
1172
+ // read green — verified on the rig). With the flag, the driver reports
1173
+ // 565X and the adapter byte-swaps at pack time, restoring wire order.
1174
+ // (The binding documents the prop for "buggy modules that display RGB as
1175
+ // BGR" — same mechanism, byte order.)
1176
+ lines.push(' rgb-is-inverted;');
818
1177
  }
819
- else {
1178
+ else if (controller === 'ili9341') {
820
1179
  // ILI9341: the ilitek,ili9341 binding carries defaults for every register
821
1180
  // (gamma, power, porch) and expresses orientation via `rotation` (degrees)
822
1181
  // instead of a raw MADCTL — no panel-specific props are required.
823
1182
  lines.push(` rotation = <${display.rotation ?? 0}>;`);
1183
+ // pixel-format is REQUIRED by the lcd-controller binding the ilitek
1184
+ // ili9xxx family includes: 0 = RGB565 (dt-bindings/display/panel.h),
1185
+ // matching upstream ILI9341 boards (esp_wrover_kit) and the C++ runtime.
1186
+ // The sitronix,st7796s binding does NOT declare pixel-format (its branch
1187
+ // omits the property — emitting it there is a DTC error, caught the hard
1188
+ // way once the overlay stopped carrying the wrong ilitek compatible).
1189
+ lines.push(' pixel-format = <0>;');
1190
+ }
1191
+ else {
1192
+ // Generic (drop-in) path: required properties come from the panel's own
1193
+ // binding in the user's Zephyr tree — the harvest is the single source
1194
+ // of truth, so the node is valid for ANY bound panel by construction.
1195
+ // Geometry/frequency props above are already emitted; pixel-format is
1196
+ // special (required by the lcd-controller family, value fixed to RGB565).
1197
+ const binding = readDisplayBinding(compatible);
1198
+ if (binding?.requiresPixelFormat) {
1199
+ lines.push(' pixel-format = <0>;');
1200
+ }
1201
+ if (binding) {
1202
+ for (const [prop, def] of binding.required) {
1203
+ // Skip structural properties the generator emits itself.
1204
+ if (prop === 'width' || prop === 'height' || prop === 'mipi-max-frequency'
1205
+ || prop === 'pixel-format' || prop === 'reg' || prop === 'compatible'
1206
+ || prop === 'status' || prop === 'mipi-mode'
1207
+ // phandle-array props are structural (GPIO routing) — the
1208
+ // generator emits them from config wiring (busy-gpios above),
1209
+ // never from a numeric fallback.
1210
+ || prop === 'busy-gpios' || prop === 'reset-gpios' || prop === 'dc-gpios') {
1211
+ continue;
1212
+ }
1213
+ emitBindingProp(lines, prop, def);
1214
+ }
1215
+ // Rotation on madctl-style panels (st7796s family): the binding ships
1216
+ // a neutral portrait default and the driver writes MADCTL verbatim, so
1217
+ // the config's rotation MUST reach the node or the UI's landscape
1218
+ // writes fall outside the portrait address window — half the screen
1219
+ // scrambled, half fine (verified on the rig). Table = ST77xx rotation
1220
+ // bits with BGR set (the verified clone convention; an R/B-swapped
1221
+ // panel flips to the config's channelSwapRb flag instead).
1222
+ // Byte-order quirk flag (clone panels): flips the format the driver
1223
+ // reports so the adapter byte-swaps at pack time. Only bindings that
1224
+ // declare the prop accept it — the DTC rejects it elsewhere, honestly.
1225
+ if (display.rgbInverted === true) {
1226
+ lines.push(' rgb-is-inverted;');
1227
+ }
1228
+ if (binding.props.has('madctl') && display.rotation !== undefined) {
1229
+ const MADCTL_ROTATION = [0x08, 0x28, 0x48, 0xE8];
1230
+ const r = ((display.rotation % 4) + 4) % 4;
1231
+ lines.push(` madctl = <0x${MADCTL_ROTATION[r].toString(16)}>;`);
1232
+ }
1233
+ }
824
1234
  }
825
1235
  lines.push(' };');
826
1236
  lines.push(' };');
@@ -909,11 +1319,16 @@ function emitFt6336uNode(lines, touch) {
909
1319
  lines.push(' ft6336u: ft6336u@38 {');
910
1320
  lines.push(' compatible = "focaltech,ft5336";');
911
1321
  lines.push(' reg = <0x38>;');
912
- // NOTE: int-gpios is intentionally omitted. The ft5336 Zephyr driver
913
- // registers a GPIO interrupt on int-gpios, which triggers an assertion
914
- // failure in the ESP32 interrupt controller (VECDESC_FL_SHARED conflict).
915
- // The cuttlefish touch adapter polls touch_isTouched() via I2C every frame
916
- // — it never uses the IRQ pin, so the interrupt registration is unnecessary.
1322
+ // NOTE: int-gpios is intentionally omitted. The driver's interrupt mode
1323
+ // registers a GPIO IRQ (the ESP32 VECDESC_FL_SHARED crash); with no
1324
+ // int-gpios the in-tree driver falls back to polling mode
1325
+ // (CONFIG_INPUT_FT5336_PERIOD, default 10ms) — no IRQ is registered and
1326
+ // the touch adapter consumes the driver's input events.
1327
+ // swapped-x-y: the driver reports pos(col, row) — the Y-register value
1328
+ // first. The swap makes its ABS_X/ABS_Y events carry the controller's
1329
+ // X/Y registers verbatim, which is the raw coordinate space the runtime's
1330
+ // calibration + rotation math (ui_poll_touch) expects from touch_readRaw.
1331
+ lines.push(' swapped-x-y;');
917
1332
  if (resetPin !== undefined) {
918
1333
  lines.push(` reset-gpios = <&${gpioController(resetPin)} ${resetPin} GPIO_ACTIVE_LOW>;`);
919
1334
  }
@@ -9,7 +9,7 @@ declare const _default: {
9
9
  entrypoint: {
10
10
  entrypointFunctionName: string;
11
11
  requiresLoopFunction: boolean;
12
- sourceExtension: "cpp" | "ino" | "cc" | "h";
12
+ sourceExtension: "h" | "cpp" | "ino" | "cc";
13
13
  generateHeaderFile: boolean;
14
14
  overrideBaseName?: string | undefined;
15
15
  outputSubdirectory?: string | undefined;
@@ -22,7 +22,7 @@ declare const _default: {
22
22
  };
23
23
  hal: Record<string, {
24
24
  supported: boolean;
25
- ops: Record<string, "stub" | "polyfill" | "unsupported" | "supported" | "probe-inconclusive">;
25
+ ops: Record<string, "supported" | "unsupported" | "stub" | "polyfill" | "probe-inconclusive">;
26
26
  partialCoverage: boolean;
27
27
  unsupportedReason?: string | undefined;
28
28
  }>;
@@ -62,7 +62,7 @@ declare const _default: {
62
62
  hasExceptions: boolean;
63
63
  hasRTTI: boolean;
64
64
  recommendedArrayImpl: "static_array" | "std_vector";
65
- recommendedStringImpl: "static_string" | "std_string";
65
+ recommendedStringImpl: "std_string" | "static_string";
66
66
  };
67
67
  };
68
68
  ambientTypes: string[];
@@ -85,6 +85,12 @@ declare const _default: {
85
85
  licenses?: {
86
86
  available: boolean;
87
87
  } | undefined;
88
+ sbom?: {
89
+ available: boolean;
90
+ } | undefined;
91
+ audit?: {
92
+ available: boolean;
93
+ } | undefined;
88
94
  compat?: {
89
95
  zephyr?: string | undefined;
90
96
  } | undefined;