@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
@@ -1,139 +1,458 @@
1
- // ---------------------------------------------------------------------------
2
- // Zephyr display profiles — DT-binding descriptors
3
- //
4
- // Analog of framework-arduino/src/displays/, but profiles describe a Zephyr
5
- // devicetree node (DT_NODELABEL) to resolve via DEVICE_DT_GET, not Adafruit
6
- // pin wiring. The GFX runtime (gfx.ts) reads width/height/colorFormat from the
7
- // active profile to size its line buffer.
8
- // ---------------------------------------------------------------------------
9
-
10
- export interface ZephyrDisplayProfile {
11
- /** Driver id, matched against ctx.frameworkData.display / manifest drivers. */
12
- readonly driver: string;
13
- /** Devicetree nodelabel, e.g. 'display0'. Emitted as DT_NODELABEL(<dtLabel>). */
14
- readonly dtLabel: string;
15
- /** Effective (screen-space) dimensions after rotation. */
16
- readonly width: number;
17
- readonly height: number;
18
- /** Native panel dimensions before rotation, when they differ from the
19
- * effective size (e.g. a 320x480 panel mounted landscape = 480x320). */
20
- readonly nativeWidth?: number;
21
- readonly nativeHeight?: number;
22
- readonly colorFormat: 'rgb565' | 'mono';
23
- /** Applied via display_set_orientation (0/90/180/270). */
24
- readonly rotation?: number;
25
- /** DT alias for the backlight GPIO (set high at init), if any. */
26
- readonly backlight?: string;
27
- /** Panel controller the direct-drive UI adapter targets. Selects the init
28
- * sequence + pixel wire format (ST7796S: 18-bit; ILI9341: 16-bit RGB565).
29
- * Required for rgb565 profiles; mono profiles use the direct-op GFX
30
- * runtime and ignore it. */
31
- readonly controller?: ZephyrPanelController;
32
- /** DT compatible string for the display@0 node. Defaults per controller
33
- * (see PANEL_CONTROLLER_DEFAULTS) — override only for a panel whose DT
34
- * binding differs from its controller family. */
35
- readonly dtCompatible?: string;
36
- /** SPI controller nodelabel the panel hangs off (and SPI touch, if any).
37
- * Default 'spi2' — the ESP32-S3 first general-purpose SPI controller. */
38
- readonly busLabel?: string;
39
- /** Nodelabel of the MIPI DBI bridge node carrying the dc/reset GPIOs.
40
- * Default 'mipi_dbi' (the overlay emits the bridge under that label). */
41
- readonly bridgeLabel?: string;
42
- }
43
-
44
- /** Panel controllers the direct-drive UI adapter knows how to init. */
45
- export type ZephyrPanelController = 'st7796s' | 'ili9341';
46
-
47
- /** Per-controller DT + transport defaults, shared by the overlay generator
48
- * (DT node props) and the UI adapter (init sequence + wire format). */
49
- export const PANEL_CONTROLLER_DEFAULTS: Record<
50
- ZephyrPanelController,
51
- { dtCompatible: string }
52
- > = {
53
- st7796s: { dtCompatible: 'sitronix,st7796s' },
54
- ili9341: { dtCompatible: 'ilitek,ili9341' },
55
- };
56
-
57
- /** Resolve a profile's panel controller, inferring it from the driver id when
58
- * the profile doesn't declare one (the '<controller>-zephyr' naming scheme). */
59
- export function panelControllerFor(
60
- profile: Pick<ZephyrDisplayProfile, 'driver' | 'controller'>,
61
- ): ZephyrPanelController {
62
- if (profile.controller) return profile.controller;
63
- if (profile.driver.startsWith('ili9341')) return 'ili9341';
64
- return 'st7796s';
65
- }
66
-
67
- /**
68
- * Built-in profile registry. Looked up by driver id. Add a profile here when a
69
- * new board's display node is wired into its devicetree.
70
- */
71
- export const ZEPHYR_DISPLAY_PROFILES: Record<string, ZephyrDisplayProfile> = {
72
- 'ili9341-zephyr': {
73
- driver: 'ili9341-zephyr',
74
- dtLabel: 'display0',
75
- width: 320,
76
- height: 240,
77
- colorFormat: 'rgb565',
78
- controller: 'ili9341',
79
- rotation: 90,
80
- backlight: 'backlight',
81
- },
82
- 'st7796-zephyr': {
83
- // ST7796S SPI TFT, 320x480 RGB565 mounted landscape (effective 480x320).
84
- // Same DT nodelabel convention as the ILI9341 — the board's devicetree
85
- // carries the `display0` node bound to the ST7796 driver; the overlay
86
- // enables it via status="okay". Effective dims match the display
87
- // st7796-spi profile (480x320 landscape, rotation 1).
88
- driver: 'st7796-zephyr',
89
- dtLabel: 'display0',
90
- width: 480,
91
- height: 320,
92
- nativeWidth: 320,
93
- nativeHeight: 480,
94
- colorFormat: 'rgb565',
95
- controller: 'st7796s',
96
- rotation: 1,
97
- backlight: 'backlight',
98
- },
99
- 'ssd1306-zephyr': {
100
- // Monochrome OLED (SSD1306-class, 128x64, 1bpp). Driven through Zephyr's
101
- // generic display API (the ssd1306 driver + a DT display node). The GFX
102
- // runtime (gfx.ts mono branch) keeps a full page-framebuffer and pushes it
103
- // on display_flush — the standard model for page-buffered OLEDs. Direct
104
- // display.* ops only (no @typecad/ui CuttlefishGFX rendering on mono).
105
- driver: 'ssd1306-zephyr',
106
- dtLabel: 'display0',
107
- width: 128,
108
- height: 64,
109
- colorFormat: 'mono',
110
- rotation: 0,
111
- },
112
- };
113
-
114
- /** The default profile used when resolveDisplayOp is probed without a display.init. */
115
- export const DEFAULT_ZEPHYR_DISPLAY_PROFILE: ZephyrDisplayProfile =
116
- ZEPHYR_DISPLAY_PROFILES['ili9341-zephyr'];
117
-
118
- /**
119
- * The Zephyr profiles mapped to the shared DisplayProfile shape — the single
120
- * mapping, so no consumer needs to know the DT-binding descriptor layout.
121
- * The strategy's getProfileRegistry() and the preview's profile-registry
122
- * loader both consume this (the same role `BUILT_IN_PROFILES` plays in
123
- * framework-arduino's displays modules).
124
- */
125
- export const BUILT_IN_PROFILES: Record<string, import('@typecad/cuttlefish/api/shared').DisplayProfile> =
126
- Object.fromEntries(
127
- Object.entries(ZEPHYR_DISPLAY_PROFILES).map(([name, p]) => [
128
- name,
129
- {
130
- driver: p.driver,
131
- width: p.width,
132
- height: p.height,
133
- nativeWidth: p.nativeWidth,
134
- nativeHeight: p.nativeHeight,
135
- colorFormat: p.colorFormat,
136
- rotation: p.rotation ?? 1,
137
- },
138
- ]),
139
- );
1
+ // ---------------------------------------------------------------------------
2
+ // Zephyr display profiles — DT-binding descriptors
3
+ //
4
+ // Analog of framework-arduino/src/displays/, but profiles describe a Zephyr
5
+ // devicetree node (DT_NODELABEL) to resolve via DEVICE_DT_GET, not Adafruit
6
+ // pin wiring. The GFX runtime (gfx.ts) reads width/height/colorFormat from the
7
+ // active profile to size its line buffer.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export interface ZephyrDisplayProfile {
11
+ /** Driver id, matched against ctx.frameworkData.display / manifest drivers. */
12
+ readonly driver: string;
13
+ /** Devicetree nodelabel, e.g. 'display0'. Emitted as DT_NODELABEL(<dtLabel>). */
14
+ readonly dtLabel: string;
15
+ /** Effective (screen-space) dimensions after rotation. */
16
+ readonly width: number;
17
+ readonly height: number;
18
+ /** Native panel dimensions before rotation, when they differ from the
19
+ * effective size (e.g. a 320x480 panel mounted landscape = 480x320). */
20
+ readonly nativeWidth?: number;
21
+ readonly nativeHeight?: number;
22
+ readonly colorFormat: 'rgb565' | 'mono' | 'gray8';
23
+ /** Panel class — drives the capability derivation (eink ⇒ deferred
24
+ * refresh, all dynamic features off). */
25
+ readonly displayClass?: 'tft' | 'eink' | 'oled';
26
+ /** Applied via display_set_orientation (0/90/180/270). */
27
+ readonly rotation?: number;
28
+ /** DT alias for the backlight GPIO (set high at init), if any. */
29
+ readonly backlight?: string;
30
+ /** Panel controller the direct-drive UI adapter targets. Selects the init
31
+ * sequence + pixel wire format (ST7796S: 18-bit; ILI9341: 16-bit RGB565).
32
+ * Required for rgb565 profiles; mono profiles use the direct-op GFX
33
+ * runtime and ignore it. */
34
+ readonly controller?: ZephyrPanelController;
35
+ /** How the UI adapter reaches the panel:
36
+ * - 'direct-spi' (default): the emitted adapter drives the panel directly
37
+ * over spi_write (Adafruit ST77xx protocol, CS held across command+data).
38
+ * Required for panels the in-tree drivers cannot init (clone ST7796S —
39
+ * the generic mipi-dbi-spi bridge deasserts CS between the command byte
40
+ * and its parameters, which scrambles those panels).
41
+ * - 'zephyr-display': the adapter calls Zephyr's display API
42
+ * (display_write/display_get_capabilities) on the DT display device; an
43
+ * in-tree panel driver (bound from the overlay's display node) owns the
44
+ * init sequence, rotation, and wire format. */
45
+ readonly transport?: ZephyrDisplayTransport;
46
+ /** Which mipi-dbi host device backs the panel driver under the
47
+ * 'zephyr-display' transport:
48
+ * - 'spi-bridge' (default): the stock zephyr,mipi-dbi-spi host
49
+ * (CONFIG_MIPI_DBI_SPI). Fine for panels that tolerate per-transaction
50
+ * CS (standard ILI9341 modules).
51
+ * - 'local-hold-cs': an app-local host device the adapter emits — the
52
+ * mipi-dbi-spi protocol with CS held across each command+data burst
53
+ * (GPIO-managed, mirroring the hardware-verified direct transport).
54
+ * Needed for clone ST77xx panels the stock bridge scrambles; implements
55
+ * the mipi-hold-cs behavior the binding documents but 4.4.2 does not. */
56
+ readonly dbiHost?: ZephyrDbiHost;
57
+ /** The panel's R and B channels are crossed in 16-bit mode (verified clone
58
+ * ST7796S quirk): the native adapter swaps R/B channels at pack time so
59
+ * the in-tree driver's RGB565 stream renders with true colors (lossless —
60
+ * the direct transport instead avoids it by driving 18-bit mode). */
61
+ readonly channelSwapRb?: boolean;
62
+ /** RGB565 wire byte-order inversion: the overlay emits the st7796s-family
63
+ * rgb-is-inverted DT property, flipping the format the in-tree driver
64
+ * reports (565 <-> 565X) so the adapter byte-swaps at pack time. Clone
65
+ * SPI panels whose white reads purple / dark reads green need it. */
66
+ readonly rgbInverted?: boolean;
67
+ /** DT compatible string for the display@0 node. Defaults per controller
68
+ * (see PANEL_CONTROLLER_DEFAULTS) — override only for a panel whose DT
69
+ * binding differs from its controller family. */
70
+ readonly dtCompatible?: string;
71
+ /** SPI controller nodelabel the panel hangs off (and SPI touch, if any).
72
+ * Default 'spi2' — the ESP32-S3 first general-purpose SPI controller. */
73
+ readonly busLabel?: string;
74
+ /** Nodelabel of the MIPI DBI bridge node carrying the dc/reset GPIOs.
75
+ * Default 'mipi_dbi' (the overlay emits the bridge under that label). */
76
+ readonly bridgeLabel?: string;
77
+ /** The board's own devicetree already wires this display (native_sim's
78
+ * built-in sdl_dc): the overlay emits NO display node — the dtLabel points
79
+ * at the board's node. */
80
+ readonly boardProvidesDisplay?: boolean;
81
+ /** Extra Kconfig fragments the profile requires (e.g. the SDL panel's mono
82
+ * pixel-format choice). Appended verbatim to the generated prj.conf. */
83
+ readonly kconfig?: readonly string[];
84
+ }
85
+
86
+ /** How the UI adapter talks to the panel (see ZephyrDisplayProfile.transport). */
87
+ export type ZephyrDisplayTransport = 'direct-spi' | 'zephyr-display';
88
+
89
+ /** Which mipi-dbi host backs the panel driver (see ZephyrDisplayProfile.dbiHost). */
90
+ export type ZephyrDbiHost = 'spi-bridge' | 'local-hold-cs';
91
+
92
+ /** Resolve a profile's transport with the historical default. */
93
+ export function transportFor(
94
+ profile: Pick<ZephyrDisplayProfile, 'transport'>,
95
+ ): ZephyrDisplayTransport {
96
+ return profile.transport ?? 'direct-spi';
97
+ }
98
+
99
+ /** Resolve a profile's mipi-dbi host. Clone-ST77xx panels need the local
100
+ * CS-holding host; everything else defaults to the stock SPI bridge. */
101
+ export function dbiHostFor(
102
+ profile: Pick<ZephyrDisplayProfile, 'dbiHost' | 'controller'>,
103
+ ): ZephyrDbiHost {
104
+ if (profile.dbiHost) return profile.dbiHost;
105
+ return profile.controller === 'st7796s' ? 'local-hold-cs' : 'spi-bridge';
106
+ }
107
+
108
+ /** Marker the display adapters stamp into the emitted source so the toolchain
109
+ * can recover the exact profile that produced it (one source of truth — the
110
+ * profile registry — instead of re-deriving geometry from emitted C). */
111
+ export const DISPLAY_PROFILE_MARKER = 'typecad-display-profile:';
112
+
113
+ /** Machine-readable facts line the adapters stamp beside the marker: JSON
114
+ * carrying everything the toolchain needs to regenerate the DT overlay —
115
+ * including synthesized (compatible-driven) profiles that have no registry
116
+ * entry. */
117
+ export function displayFactsLine(profile: ZephyrDisplayProfile): string {
118
+ const controller = panelControllerFor(profile);
119
+ const facts: Record<string, unknown> = {
120
+ driver: profile.driver,
121
+ transport: transportFor(profile),
122
+ width: profile.width,
123
+ height: profile.height,
124
+ };
125
+ if (profile.nativeWidth !== undefined) facts.nativeWidth = profile.nativeWidth;
126
+ if (profile.nativeHeight !== undefined) facts.nativeHeight = profile.nativeHeight;
127
+ if (controller !== undefined) facts.controller = controller;
128
+ if (profile.rotation !== undefined) facts.rotation = profile.rotation;
129
+ if (profile.channelSwapRb === true) facts.channelSwapRb = true;
130
+ if (profile.rgbInverted === true) facts.rgbInverted = true;
131
+ if (transportFor(profile) === 'zephyr-display') {
132
+ facts.dbiHost = dbiHostFor(profile);
133
+ }
134
+ return `// typecad-display-facts: ${JSON.stringify(facts)}`;
135
+ }
136
+
137
+ /** Recover the profile whose adapter emitted this source, or undefined when
138
+ * no display adapter marker is present (no display in the program). Prefers
139
+ * the JSON facts line (carries synthesized profiles); falls back to the
140
+ * plain marker + registry lookup. */
141
+ export function profileFromEmittedSource(src: string): ZephyrDisplayProfile | undefined {
142
+ const facts = src.match(/typecad-display-facts: (\{.*\})/);
143
+ if (facts) {
144
+ try {
145
+ const f = JSON.parse(facts[1]) as Record<string, unknown>;
146
+ const registered = ZEPHYR_DISPLAY_PROFILES[f.driver as string];
147
+ if (registered) return registered;
148
+ if (typeof f.driver === 'string' && isDtCompatible(f.driver)) {
149
+ const synth = synthesizeZephyrProfile({
150
+ driver: f.driver,
151
+ width: f.width as number,
152
+ height: f.height as number,
153
+ nativeWidth: f.nativeWidth as number | undefined,
154
+ nativeHeight: f.nativeHeight as number | undefined,
155
+ rotation: f.rotation as number | undefined,
156
+ channelSwapRb: f.channelSwapRb === true,
157
+ rgbInverted: f.rgbInverted === true,
158
+ csHold: f.dbiHost === 'local-hold-cs',
159
+ });
160
+ if (synth) return synth;
161
+ }
162
+ } catch {
163
+ // Malformed facts line — fall through to the plain marker.
164
+ }
165
+ }
166
+ const m = src.match(/typecad-display-profile: ([\w-]+)/);
167
+ if (!m) return undefined;
168
+ return ZEPHYR_DISPLAY_PROFILES[m[1]];
169
+ }
170
+
171
+ /** Panel controllers the direct-drive UI adapter knows how to init. */
172
+ export type ZephyrPanelController = 'st7796s' | 'ili9341' | 'ssd16xx' | 'uc81xx';
173
+
174
+ /** Per-controller DT + transport defaults, shared by the overlay generator
175
+ * (DT node props) and the UI adapter (init sequence + wire format). */
176
+ export const PANEL_CONTROLLER_DEFAULTS: Record<
177
+ ZephyrPanelController,
178
+ { dtCompatible: string }
179
+ > = {
180
+ st7796s: { dtCompatible: 'sitronix,st7796s' },
181
+ ili9341: { dtCompatible: 'ilitek,ili9341' },
182
+ // E-ink families: no single dtCompatible (the drop-in driver IS one of the
183
+ // many panel compatibles) — the overlay branch keys off the driver string.
184
+ ssd16xx: { dtCompatible: 'solomon,ssd16xx' },
185
+ uc81xx: { dtCompatible: 'ultrachip,uc81xx' },
186
+ };
187
+
188
+ /** Resolve a profile's panel controller, inferring it from the driver id when
189
+ * the profile doesn't declare one (the '<controller>-zephyr' naming scheme).
190
+ * Synthesized (compatible-driven) profiles carry no controller — undefined
191
+ * routes them to the binding-driven generic paths. */
192
+ export function panelControllerFor(
193
+ profile: Pick<ZephyrDisplayProfile, 'driver' | 'controller'>,
194
+ ): ZephyrPanelController | undefined {
195
+ if (profile.controller) return profile.controller;
196
+ if (profile.driver.startsWith('ili9341')) return 'ili9341';
197
+ if (profile.driver.startsWith('st7796')) return 'st7796s';
198
+ // E-ink families (drop-in compatibles): the driver symbol the Kconfig
199
+ // layer enables under the mipi-dbi SPI branch.
200
+ if (profile.driver.startsWith('solomon,ssd16')) return 'ssd16xx';
201
+ if (profile.driver.startsWith('ultrachip,uc81')) return 'uc81xx';
202
+ return undefined;
203
+ }
204
+
205
+ /** A DT compatible string (vendor,name) — the drop-in driver id shape. */
206
+ export function isDtCompatible(driver: string): boolean {
207
+ return /^[a-z0-9]+(-[a-z0-9]+)*,[a-z0-9-]+$/i.test(driver);
208
+ }
209
+
210
+ /** 1bpp mono panel compatibles (DATA, not code): the Zephyr drivers behind
211
+ * these report PIXEL_FORMAT_MONO01/10 — the Stage 2 full-frame lowering
212
+ * target. A drop-in config naming one of these synthesizes a mono profile
213
+ * without an explicit colorFormat. Grows as mono drivers are verified; an
214
+ * unlisted mono panel still works via display.init({ colorFormat: 'mono' }).
215
+ * (ssd1320/ssd1327-class are L8/grayscale — Stage 3, not here.) */
216
+ const MONO_PANEL_COMPATIBLES: ReadonlySet<string> = new Set([
217
+ 'solomon,ssd1306',
218
+ 'solomon,ssd1309',
219
+ 'sinowealth,sh1106',
220
+ ]);
221
+
222
+ /** 16-gray panels (Stage 3): Zephyr's solomon,ssd1327 driver accepts
223
+ * PIXEL_FORMAT_L_8 (8-bit luminance in, nibble-reduced to the panel's 16
224
+ * levels) — the Stage 3 gray8 lowering target. Same drop-in rule as mono. */
225
+ const GRAY_PANEL_COMPATIBLES: ReadonlySet<string> = new Set([
226
+ 'solomon,ssd1327',
227
+ ]);
228
+
229
+ /** E-Ink panels (Stage 4): the ssd16xx/uc81xx SPI families — same 1bpp
230
+ * format as mono, the new axis is the REFRESH MODEL (deferred: render on
231
+ * signal change, flush with the panel's flash cycle, panel sleeps). Grows
232
+ * as e-ink drivers are verified. */
233
+ const EINK_PANEL_COMPATIBLES: ReadonlySet<string> = new Set([
234
+ 'solomon,ssd1608',
235
+ 'solomon,ssd1673',
236
+ 'solomon,ssd1675a',
237
+ 'solomon,ssd1680',
238
+ 'solomon,ssd1681',
239
+ 'ultrachip,uc8151d',
240
+ 'ultrachip,uc8175',
241
+ 'ultrachip,uc8176',
242
+ 'ultrachip,uc8179',
243
+ ]);
244
+
245
+ /** True when a drop-in compatible (or explicit config) selects the e-ink
246
+ * lowering target — mono format + deferred refresh. */
247
+ export function isEinkDisplay(display: { driver: string; displayClass?: string }): boolean {
248
+ if (display.displayClass === 'eink') return true;
249
+ return EINK_PANEL_COMPATIBLES.has(display.driver);
250
+ }
251
+
252
+ /** True when a drop-in compatible (or explicit config) selects the gray8
253
+ * (8-bit luminance) lowering target. */
254
+ export function isGrayDisplay(display: { driver: string; colorFormat?: string }): boolean {
255
+ if (display.colorFormat === 'gray8') return true;
256
+ if (display.colorFormat && display.colorFormat !== 'gray8') return false;
257
+ return GRAY_PANEL_COMPATIBLES.has(display.driver);
258
+ }
259
+
260
+ /** True when a drop-in compatible (or explicit config) selects the mono
261
+ * (1bpp) lowering target. */
262
+ export function isMonoDisplay(display: { driver: string; colorFormat?: string }): boolean {
263
+ if (display.colorFormat === 'mono') return true;
264
+ if (display.colorFormat && display.colorFormat !== 'mono') return false;
265
+ return MONO_PANEL_COMPATIBLES.has(display.driver);
266
+ }
267
+
268
+ /** Synthesize a native-transport profile for a compatible-driven config
269
+ * (display.driver = DT compatible, no registry profile). The in-tree driver
270
+ * bound by the overlay's display node owns init/geometry/quirks; the
271
+ * config's panel-quirk flags (channelSwapRb, csHold) carry what the
272
+ * driver cannot know. Returns undefined unless the driver string is a
273
+ * compatible shape. */
274
+ export function synthesizeZephyrProfile(display: {
275
+ driver: string;
276
+ width: number;
277
+ height: number;
278
+ nativeWidth?: number;
279
+ nativeHeight?: number;
280
+ colorFormat?: string;
281
+ rotation?: number;
282
+ channelSwapRb?: boolean;
283
+ csHold?: boolean;
284
+ rgbInverted?: boolean;
285
+ }): ZephyrDisplayProfile | undefined {
286
+ if (!isDtCompatible(display.driver)) return undefined;
287
+ return {
288
+ driver: display.driver,
289
+ dtLabel: 'display0',
290
+ width: display.width,
291
+ height: display.height,
292
+ nativeWidth: display.nativeWidth,
293
+ nativeHeight: display.nativeHeight,
294
+ // E-ink compatibles are mono-format panels with a deferred refresh
295
+ // model — displayClass drives deriveCapabilities' eink branch (all
296
+ // features off, deferred-partial) and the e-ink adapter dispatch.
297
+ displayClass: isEinkDisplay(display) ? 'eink' : undefined,
298
+ colorFormat: isMonoDisplay(display) || isEinkDisplay(display) ? 'mono' : isGrayDisplay(display) ? 'gray8' : 'rgb565',
299
+ controller: undefined,
300
+ transport: 'zephyr-display',
301
+ dbiHost: display.csHold === true ? 'local-hold-cs' : undefined,
302
+ channelSwapRb: display.channelSwapRb === true ? true : undefined,
303
+ rgbInverted: display.rgbInverted === true ? true : undefined,
304
+ rotation: display.rotation ?? 0,
305
+ backlight: 'backlight',
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Built-in profile registry. Looked up by driver id. Add a profile here when a
311
+ * new board's display node is wired into its devicetree.
312
+ */
313
+ export const ZEPHYR_DISPLAY_PROFILES: Record<string, ZephyrDisplayProfile> = {
314
+ 'ili9341-zephyr': {
315
+ driver: 'ili9341-zephyr',
316
+ dtLabel: 'display0',
317
+ width: 320,
318
+ height: 240,
319
+ colorFormat: 'rgb565',
320
+ controller: 'ili9341',
321
+ rotation: 90,
322
+ backlight: 'backlight',
323
+ },
324
+ // Same ILI9341 panel as 'ili9341-zephyr', but driven through Zephyr's
325
+ // display API: the overlay's display0 node (ilitek,ili9341 under the
326
+ // mipi-dbi-spi bridge) binds the in-tree driver with CONFIG_MIPI_DBI_SPI +
327
+ // CONFIG_ILI9341, and the emitted adapter speaks display_write instead of
328
+ // spi_write. The panel init sequence, rotation, and pixel wire format move
329
+ // upstream (the driver owns them). Per-transaction CS (the mipi-dbi-spi
330
+ // bridge's behavior) is fine on standard ILI9341 SPI modules — the
331
+ // CS-held-across-command+data requirement that forces 'direct-spi' is a
332
+ // clone-ST7796S-class quirk. The sibling 'st7796-zephyr-display' profile
333
+ // is hardware-verified end-to-end; this one is compile/link-verified only
334
+ // (no ILI9341 module on the rig yet).
335
+ 'ili9341-zephyr-display': {
336
+ driver: 'ili9341-zephyr-display',
337
+ dtLabel: 'display0',
338
+ width: 320,
339
+ height: 240,
340
+ colorFormat: 'rgb565',
341
+ controller: 'ili9341',
342
+ transport: 'zephyr-display',
343
+ rotation: 90,
344
+ backlight: 'backlight',
345
+ },
346
+ 'st7796-zephyr': {
347
+ // ST7796S SPI TFT, 320x480 RGB565 mounted landscape (effective 480x320).
348
+ // Same DT nodelabel convention as the ILI9341 — the board's devicetree
349
+ // carries the `display0` node bound to the ST7796 driver; the overlay
350
+ // enables it via status="okay". Effective dims match the display
351
+ // st7796-spi profile (480x320 landscape, rotation 1).
352
+ driver: 'st7796-zephyr',
353
+ dtLabel: 'display0',
354
+ width: 480,
355
+ height: 320,
356
+ nativeWidth: 320,
357
+ nativeHeight: 480,
358
+ colorFormat: 'rgb565',
359
+ controller: 'st7796s',
360
+ rotation: 1,
361
+ backlight: 'backlight',
362
+ },
363
+ 'ssd1306-zephyr': {
364
+ // Monochrome OLED (SSD1306-class, 128x64, 1bpp) — Stage 2's mono
365
+ // lowering target. The full-frame adapter (ui-adapter-mono.ts) keeps a
366
+ // vtiled MONO01 backing store and pushes it whole each frame through
367
+ // display_write on the DT display node (the ssd1306 driver over I2C
368
+ // self-builds from the overlay's node). Raw display.* ops ride the same
369
+ // model through gfx.ts's mono branch.
370
+ driver: 'ssd1306-zephyr',
371
+ dtLabel: 'display0',
372
+ dtCompatible: 'solomon,ssd1306',
373
+ width: 128,
374
+ height: 64,
375
+ colorFormat: 'mono',
376
+ rotation: 0,
377
+ },
378
+ // The verified clone ST7796S on the demo rig, on the native display API:
379
+ // the in-tree sitronix,st7796s driver (CONFIG_ST7796S) owns init/gamma/
380
+ // geometry, backed by an app-local CS-holding mipi-dbi host the adapter
381
+ // emits (dbiHost 'local-hold-cs' — the stock bridge deasserts CS between
382
+ // command and data, which scrambles this clone; the local host implements
383
+ // the mipi-hold-cs behavior Zephyr documents but 4.4.2 does not ship).
384
+ // The clone's 16-bit color pipeline is handled at pack time (byte swap
385
+ // via the reported 565X format — see the color notes below); the direct
386
+ // transport avoids the question entirely by driving 18-bit mode.
387
+ // Color pipeline (hardware-verified on the rig, 16-bit mode, three knobs):
388
+ // - rgb-is-inverted: byte-swap at pack time (565 wire order).
389
+ // - NO R/B channel swap (the 18-bit BGR finding does not transfer).
390
+ // - CS-hold (this profile's default local host): REQUIRED on this clone —
391
+ // the stock mipi-dbi-spi bridge toggles CS per transaction and
392
+ // corrupts pixel bursts (confirmed twice: the original bring-up, and
393
+ // the drop-in path's first stock-bridge build showed the same
394
+ // corruption until csHold was set). With all three, colors are
395
+ // hardware-confirmed correct on the rig. Ladder testing observed a
396
+ // slight uniform dimness vs the direct path's 18-bit mode; the direct
397
+ // profile remains the max-fidelity option for this panel.
398
+ // Touch: FT6336U on the input subsystem — its power enable must be driven
399
+ // (resetPin 4 on the rig; see touch-adapter.ts for the rig-verified
400
+ // sequence). Touch + UI interaction hardware-verified on the rig.
401
+ 'st7796-zephyr-display': {
402
+ driver: 'st7796-zephyr-display',
403
+ dtLabel: 'display0',
404
+ width: 480,
405
+ height: 320,
406
+ nativeWidth: 320,
407
+ nativeHeight: 480,
408
+ colorFormat: 'rgb565',
409
+ controller: 'st7796s',
410
+ transport: 'zephyr-display',
411
+ rotation: 1,
412
+ backlight: 'backlight',
413
+ },
414
+ // Stage 2g — the no-hardware gate: the whole mono lowering on native_sim's
415
+ // built-in SDL panel (zephyr,sdl-dc, 320x240). The board's devicetree
416
+ // already wires sdl_dc, so the overlay emits no display node; the SDL
417
+ // panel's pixel-format choice flips to MONO01 so the 1bpp adapter's
418
+ // display_write flows render as black/white in the emulator window.
419
+ // Compile+link only in CI (Linux — the POSIX arch does not build on
420
+ // Windows); the SDL window needs libsdl2-dev on the runner to link.
421
+ 'native-sim-mono': {
422
+ driver: 'native-sim-mono',
423
+ dtLabel: 'sdl_dc',
424
+ width: 320,
425
+ height: 240,
426
+ colorFormat: 'mono',
427
+ transport: 'zephyr-display',
428
+ boardProvidesDisplay: true,
429
+ kconfig: ['CONFIG_SDL_DISPLAY_DEFAULT_PIXEL_FORMAT_MONO01=y'],
430
+ },
431
+ };
432
+
433
+ /** The default profile used when resolveDisplayOp is probed without a display.init. */
434
+ export const DEFAULT_ZEPHYR_DISPLAY_PROFILE: ZephyrDisplayProfile =
435
+ ZEPHYR_DISPLAY_PROFILES['ili9341-zephyr'];
436
+
437
+ /**
438
+ * The Zephyr profiles mapped to the shared DisplayProfile shape — the single
439
+ * mapping, so no consumer needs to know the DT-binding descriptor layout.
440
+ * The strategy's getProfileRegistry() and the preview's profile-registry
441
+ * loader both consume this (the same role `BUILT_IN_PROFILES` plays in
442
+ * framework-arduino's displays modules).
443
+ */
444
+ export const BUILT_IN_PROFILES: Record<string, import('@typecad/cuttlefish/api/shared').DisplayProfile> =
445
+ Object.fromEntries(
446
+ Object.entries(ZEPHYR_DISPLAY_PROFILES).map(([name, p]) => [
447
+ name,
448
+ {
449
+ driver: p.driver,
450
+ width: p.width,
451
+ height: p.height,
452
+ nativeWidth: p.nativeWidth,
453
+ nativeHeight: p.nativeHeight,
454
+ colorFormat: p.colorFormat,
455
+ rotation: p.rotation ?? 1,
456
+ },
457
+ ]),
458
+ );