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

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 +3 -3
  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
@@ -0,0 +1,304 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Zephyr UI display adapter — 'zephyr-display' transport.
3
+ //
4
+ // Emits the same display_ / display_target_ / display_canvas_ surface the UI
5
+ // runtime expects (see ui-adapter.ts), but reaches the panel through Zephyr's
6
+ // display API: display_write() with a buffer descriptor on the DT display
7
+ // device (the overlay's display0 node under the mipi-dbi-spi bridge), with
8
+ // display_get_capabilities() consulted for the panel's pixel format. An
9
+ // in-tree panel driver (e.g. ilitek,ili9341 with CONFIG_ILI9341) owns the
10
+ // init sequence, rotation, and wire format — this adapter carries no panel
11
+ // registers at all.
12
+ //
13
+ // Why this transport exists alongside 'direct-spi': the direct path is the
14
+ // escape hatch for panels the in-tree drivers cannot init (clone ST7796S —
15
+ // the generic mipi-dbi-spi bridge deasserts CS between the command byte and
16
+ // its parameters, scrambling those panels). Standard ILI9341-class SPI
17
+ // modules tolerate per-transaction CS (upstream Zephyr boards wire them this
18
+ // way), so they can ride the in-tree driver and drop the per-controller
19
+ // emitted init tables. Choose per profile via ZephyrDisplayProfile.transport;
20
+ // flip a profile here only after hardware verification on a rig.
21
+ //
22
+ // display_write() is a dirty-rect API — the descriptor's x/y/width/height
23
+ // become the controller address window — so the UI engine's
24
+ // setAddrWindow+writePixels pairs map onto it directly. Batching notes:
25
+ // display_write calls are atomic per call (the mipi-dbi layer serializes on a
26
+ // per-device mutex), so startWrite/endWrite are no-ops; solid fills tile a
27
+ // reused block buffer into multi-row display_write chunks, the same
28
+ // fewer/larger-transfers lesson the direct path learned (~40 instead of ~320
29
+ // writes on a full 480x320 clear).
30
+ //
31
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
32
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
33
+ //
34
+ // AGENTS.md rendering guardrails honored: scratch buffers are static and
35
+ // reused (no per-frame heap allocation); fills are block-buffered.
36
+ // ---------------------------------------------------------------------------
37
+
38
+ import type { DisplayAdapterCode } from "@typecad/cuttlefish/api/shared";
39
+ import type { ZephyrDisplayProfile } from "./profiles.js";
40
+ import { dbiHostFor, displayFactsLine } from "./profiles.js";
41
+ import { localHoldCsDbiHost } from "./mipi-dbi-host.js";
42
+ import { CANVAS_LIFECYCLE_SECTION, TARGET_FORWARDERS_SECTION, profileMarkerLine } from "./ui-adapter-shared.js";
43
+
44
+ /**
45
+ * Build the display-API UI adapter for a profile. The profile's dtLabel must
46
+ * name a display node an in-tree driver binds (the overlay generates it; the
47
+ * Kconfig layer enables the driver symbol for the profile's controller).
48
+ * Profiles whose dbiHost is 'local-hold-cs' additionally emit an app-local
49
+ * CS-holding mipi-dbi host the panel driver sits on (see mipi-dbi-host.ts).
50
+ */
51
+ export function zephyrDisplayApiAdapter(profile: ZephyrDisplayProfile): DisplayAdapterCode {
52
+ const w = profile.width;
53
+ const h = profile.height;
54
+ const maxDim = Math.max(w, h);
55
+ const dtLabel = profile.dtLabel;
56
+ const backlightAlias = profile.backlight;
57
+ const rbSwap = profile.channelSwapRb === true;
58
+ const nativeW = profile.nativeWidth ?? w;
59
+ const nativeH = profile.nativeHeight ?? h;
60
+ const host = dbiHostFor(profile) === 'local-hold-cs' ? localHoldCsDbiHost(profile) : undefined;
61
+
62
+ const includes = [
63
+ `// --- Zephyr UI display adapter (${profile.driver}, zephyr-display transport) ---`,
64
+ profileMarkerLine(profile.driver),
65
+ displayFactsLine(profile),
66
+ `// Native CuttlefishGFX path: do NOT #define CuttlefishCanvas16 so the`,
67
+ `// runtime header emits the in-tree CuttlefishGFX/CuttlefishCanvas16 class.`,
68
+ `#define CuttlefishDisplayTarget CuttlefishGFX`,
69
+ `#include <zephyr/kernel.h>`,
70
+ `#include <zephyr/drivers/display.h>`,
71
+ `#include <zephyr/drivers/gpio.h>`,
72
+ ...(host ? host.includes : []),
73
+ ].join("\n");
74
+
75
+ const declaration = [
76
+ `// CUTTLEFISH_DISPLAY_BEGIN`,
77
+ `// The panel device: the overlay's display0 node, bound by an in-tree panel`,
78
+ `// driver (CONFIG enabled for the profile's controller). Compile-time`,
79
+ `// constant — DEVICE_DT_GET requires the driver to exist at build time,`,
80
+ `// which the Kconfig layer guarantees for this transport.`,
81
+ `static const struct device* __tc_zd_dev = DEVICE_DT_GET(DT_NODELABEL(${dtLabel}));`,
82
+ `// Panel facts from display_get_capabilities, read once in display_init.`,
83
+ `static struct display_capabilities __tc_zd_caps;`,
84
+ `// Zephyr pixel formats carry byte-order semantics: PIXEL_FORMAT_RGB_565`,
85
+ `// is little-endian (Red in byte 1, Blue in byte 0) — exactly the memory`,
86
+ `// layout of the UI's uint16_t rgb565 words on little-endian targets — and`,
87
+ `// PIXEL_FORMAT_RGB_565X is the byte-swapped variant. Clone panels with`,
88
+ `// crossed 16-bit channel routing (verified ST7796S quirk) additionally`,
89
+ `// need an R/B channel swap. __tc_zd_fix_pixel applies the panel's fixup`,
90
+ `// chain before pixels reach display_write.`,
91
+ `static bool __tc_zd_swap = false;`,
92
+ `static const bool __tc_zd_rb = ${rbSwap};`,
93
+ `// Multi-row block buffer for solid fills AND the byte-swap scratch for`,
94
+ `// 565X pixel streams (8 rows of maxDim px, rgb565 — fillRect and`,
95
+ `// writePixels never interleave mid-call, so one buffer serves both).`,
96
+ `// fillRect tiles it once with the color, then delivers the rect in a`,
97
+ `// few large display_write calls instead of one per row — the same`,
98
+ `// per-call-overhead lesson as the direct transport. Reused, not per-frame`,
99
+ `// (AGENTS.md: no per-frame heap allocation).`,
100
+ `#define __TC_FILL_ROWS 8`,
101
+ `static uint16_t __tc_zd_block[${maxDim} * __TC_FILL_ROWS];`,
102
+ `// Stashed address window from the last setAddrWindow call. The runtime`,
103
+ `// calls setAddrWindow + writePixels as a matched pair, so we stash the rect`,
104
+ `// here and consume it in writePixels.`,
105
+ `static int16_t __tc_aw_x = 0;`,
106
+ `static int16_t __tc_aw_y = 0;`,
107
+ `static int16_t __tc_aw_w = ${w};`,
108
+ `static int16_t __tc_aw_h = ${h};`,
109
+ `// CUTTLEFISH_DISPLAY_END`,
110
+ ].join("\n");
111
+
112
+ // Backlight: same DT-alias drive as the direct transport (the overlay emits
113
+ // the gpio-leds node + alias only when a backlight GPIO is configured).
114
+ const blInit = backlightAlias
115
+ ? `#if DT_HAS_ALIAS(${backlightAlias})\n const struct gpio_dt_spec __bl = GPIO_DT_SPEC_GET(DT_ALIAS(${backlightAlias}), gpios);\n if (device_is_ready(__bl.port)) { gpio_pin_configure_dt(&__bl, GPIO_OUTPUT_ACTIVE); }\n#endif`
116
+ : '';
117
+
118
+ const functions = `${host ? host.functions : ''}
119
+ // ── display-API transport ───────────────────────────────────────────────
120
+ // Every panel write is one display_write() carrying its own address window
121
+ // (the descriptor's x/y/width/height) — no CASET/RASET bookkeeping here. The
122
+ // in-tree driver beneath the device owns init, rotation, and the wire format.
123
+
124
+ static void __tc_zd_write_rect(int16_t x, int16_t y, int16_t rw, int16_t rows,
125
+ const uint16_t* px) {
126
+ if (rw <= 0 || rows <= 0) return;
127
+ struct display_buffer_descriptor __desc = {};
128
+ __desc.width = static_cast<uint16_t>(rw);
129
+ __desc.height = static_cast<uint16_t>(rows);
130
+ __desc.pitch = static_cast<uint16_t>(rw);
131
+ __desc.buf_size = static_cast<size_t>(rw) * static_cast<size_t>(rows) * 2U;
132
+ (void)display_write(__tc_zd_dev, static_cast<uint16_t>(x), static_cast<uint16_t>(y),
133
+ &__desc, px);
134
+ }
135
+
136
+ // The panel's pack-time pixel fixup chain: R/B channel swap (clone panels
137
+ // whose 16-bit routing is crossed — lossless, verified equivalent to the
138
+ // direct transport's 18-bit mode), then byte swap when the panel reports
139
+ // RGB565X. No-op for well-behaved panels on little-endian targets.
140
+ static inline uint16_t __tc_zd_fix_pixel(uint16_t c) {
141
+ if (__tc_zd_rb) {
142
+ c = static_cast<uint16_t>(((c & 0x1Fu) << 11) | ((c >> 11) & 0x1Fu) | (c & 0x07E0u));
143
+ }
144
+ if (__tc_zd_swap) {
145
+ c = static_cast<uint16_t>((c >> 8) | ((c & 0xFFu) << 8));
146
+ }
147
+ return c;
148
+ }
149
+
150
+ // Fix a pixel run into the block scratch (passthrough when the panel needs
151
+ // no fixup — the common case). count ≤ maxDim*__TC_FILL_ROWS — the largest
152
+ // call is a full writePixels batch.
153
+ static const uint16_t* __tc_zd_fixup_run(const uint16_t* px, uint32_t count) {
154
+ if (!__tc_zd_rb && !__tc_zd_swap) return px;
155
+ for (uint32_t i = 0; i < count; i++) {
156
+ __tc_zd_block[i] = __tc_zd_fix_pixel(px[i]);
157
+ }
158
+ return __tc_zd_block;
159
+ }
160
+
161
+ // ── Panel-ops consumed by CuttlefishGFX ─────────────────────────────────
162
+ // startWrite/endWrite are no-ops: display_write is atomic per call (the
163
+ // mipi-dbi layer serializes on a per-device mutex), so there is no CS batch
164
+ // to bracket. The depth-counter concept from the direct transport does not
165
+ // apply.
166
+ static void __tc_op_startWrite(void* /*ctx*/) { }
167
+ static void __tc_op_endWrite(void* /*ctx*/) { }
168
+
169
+ // Stash the target rect. The runtime always follows this with writePixels
170
+ // delivering (w*h) pixels for this rect, OR fillRect/writePixel which ignore
171
+ // the stash.
172
+ static void __tc_op_setAddrWindow(void* /*ctx*/, int16_t x, int16_t y, int16_t winW, int16_t winH) {
173
+ __tc_aw_x = x; __tc_aw_y = y; __tc_aw_w = winW; __tc_aw_h = winH;
174
+ }
175
+
176
+ // Push the stashed rect's worth of rgb565 pixels, delivered in multi-row
177
+ // display_write chunks (≤ __TC_FILL_ROWS rows per call) so a canvas push
178
+ // stays a handful of calls instead of one per row. A trailing partial row
179
+ // (count not a multiple of the window width) goes out as its own 1-row rect.
180
+ static void __tc_op_writePixels(void* /*ctx*/, const uint16_t* px, uint32_t n) {
181
+ if (n == 0U || __tc_aw_w <= 0) return;
182
+ const uint32_t __full = static_cast<uint32_t>(__tc_aw_w);
183
+ uint32_t __rows = n / __full;
184
+ int16_t __row = 0;
185
+ const uint16_t* __p = px;
186
+ while (__rows > 0U) {
187
+ uint32_t __batch = (__rows > static_cast<uint32_t>(__TC_FILL_ROWS))
188
+ ? static_cast<uint32_t>(__TC_FILL_ROWS) : __rows;
189
+ __tc_zd_write_rect(__tc_aw_x, static_cast<int16_t>(__tc_aw_y + __row), __tc_aw_w,
190
+ static_cast<int16_t>(__batch),
191
+ __tc_zd_fixup_run(__p, __full * __batch));
192
+ __p += __full * __batch;
193
+ __rows -= __batch;
194
+ __row = static_cast<int16_t>(__row + __batch);
195
+ }
196
+ const uint32_t __rem = n % __full;
197
+ if (__rem > 0U) {
198
+ __tc_zd_write_rect(__tc_aw_x, static_cast<int16_t>(__tc_aw_y + __row),
199
+ static_cast<int16_t>(__rem), 1, __tc_zd_fixup_run(__p, __rem));
200
+ }
201
+ }
202
+
203
+ // Single pixel: one 1x1 write.
204
+ static void __tc_op_writePixel(void* /*ctx*/, int16_t x, int16_t y, uint16_t c) {
205
+ uint16_t __buf[1];
206
+ __buf[0] = __tc_zd_fix_pixel(c);
207
+ __tc_zd_write_rect(x, y, 1, 1, __buf);
208
+ }
209
+
210
+ // Fill a rect with a solid color: tile the block buffer with the color, then
211
+ // deliver the rect in multi-row display_write chunks.
212
+ static void __tc_op_fillRect(void* /*ctx*/, int16_t x, int16_t y, int16_t rw, int16_t rh, uint16_t c) {
213
+ if (rw <= 0 || rh <= 0) return;
214
+ const uint16_t __word = __tc_zd_fix_pixel(c);
215
+ for (int16_t r = 0; r < __TC_FILL_ROWS; r++) {
216
+ for (int16_t i = 0; i < rw; i++) {
217
+ __tc_zd_block[static_cast<uint32_t>(r) * static_cast<uint32_t>(rw) + static_cast<uint32_t>(i)] = __word;
218
+ }
219
+ }
220
+ int16_t remaining = rh;
221
+ int16_t yy = y;
222
+ while (remaining > 0) {
223
+ int16_t chunk = (remaining > __TC_FILL_ROWS) ? __TC_FILL_ROWS : remaining;
224
+ __tc_zd_write_rect(x, yy, rw, chunk, __tc_zd_block);
225
+ remaining -= chunk;
226
+ yy = static_cast<int16_t>(yy + chunk);
227
+ }
228
+ }
229
+
230
+ // Width/height are the profile's compile-time effective dimensions — the
231
+ // CuttlefishGFX global below is constructed before main() runs, so it cannot
232
+ // wait for display_get_capabilities. display_init cross-checks the caps
233
+ // against these and reports a mismatch (a wiring/binding problem worth
234
+ // surfacing, not silently rendering at the wrong resolution).
235
+ static int16_t __tc_op_width(void* /*ctx*/) { return ${w}; }
236
+ static int16_t __tc_op_height(void* /*ctx*/) { return ${h}; }
237
+
238
+ // flush is nullptr — immediate-mode panel (TFT); the dirty loop's writes are
239
+ // already on the glass.
240
+ struct CuttlefishPanelOps;
241
+ extern const CuttlefishPanelOps __tc_display_ops;
242
+
243
+ const CuttlefishPanelOps __tc_display_ops = {
244
+ __tc_op_startWrite,
245
+ __tc_op_endWrite,
246
+ __tc_op_setAddrWindow,
247
+ __tc_op_writePixels,
248
+ __tc_op_writePixel,
249
+ __tc_op_fillRect,
250
+ __tc_op_width,
251
+ __tc_op_height,
252
+ nullptr, // flush — immediate-mode panel
253
+ };
254
+
255
+ // The live display target: a CuttlefishGFX driven by the panel-ops vtable.
256
+ CuttlefishGFX __tc_display(&__tc_display_ops, nullptr);
257
+
258
+ // ── display_init (called from setup) ────────────────────────────────────
259
+
260
+ static inline void display_init() {
261
+ printk("TC_DISPLAY: display-API transport (${dtLabel})\\n");
262
+ ${blInit}
263
+ if (!device_is_ready(__tc_zd_dev)) {
264
+ printk("TC_DISPLAY: device not ready — is the panel driver enabled?\\n");
265
+ return;
266
+ }
267
+ display_get_capabilities(__tc_zd_dev, &__tc_zd_caps);
268
+ __tc_zd_swap = (__tc_zd_caps.current_pixel_format == PIXEL_FORMAT_RGB_565X);
269
+ if (__tc_zd_caps.current_pixel_format != PIXEL_FORMAT_RGB_565
270
+ && __tc_zd_caps.current_pixel_format != PIXEL_FORMAT_RGB_565X) {
271
+ printk("TC_DISPLAY: unexpected pixel format %u (expected RGB565) — rendering best-effort\\n",
272
+ static_cast<unsigned int>(__tc_zd_caps.current_pixel_format));
273
+ }
274
+ if (__tc_zd_caps.x_resolution != static_cast<uint16_t>(${nativeW})
275
+ || __tc_zd_caps.y_resolution != static_cast<uint16_t>(${nativeH})) {
276
+ printk("TC_DISPLAY: panel reports %ux%u, DT node carries ${nativeW}x${nativeH} — check the display node geometry\\n",
277
+ static_cast<unsigned int>(__tc_zd_caps.x_resolution),
278
+ static_cast<unsigned int>(__tc_zd_caps.y_resolution));
279
+ }
280
+ (void)display_blanking_off(__tc_zd_dev);
281
+ __tc_op_fillRect(nullptr, 0, 0, ${w}, ${h}, 0x0000);
282
+ printk("TC_DISPLAY: display-API init done (RGB565, black fill)\\n");
283
+ }
284
+
285
+ static inline void display_fillScreen(UI_COLOR_T color) {
286
+ __tc_op_fillRect(nullptr, 0, 0, ${w}, ${h}, static_cast<uint16_t>(color));
287
+ }
288
+ static inline CuttlefishDisplayTarget* display_defaultTarget() { return &__tc_display; }
289
+ static inline int16_t display_width() { return ${w}; }
290
+ static inline int16_t display_height() { return ${h}; }
291
+
292
+ static inline void display_startWrite() { }
293
+ static inline void display_endWrite() { }
294
+ static inline void display_setAddrWindow(int16_t x, int16_t y, int16_t winW, int16_t winH) {
295
+ __tc_op_setAddrWindow(nullptr, x, y, winW, winH);
296
+ }
297
+ static inline void display_writePixels(const UI_COLOR_T* pixels, uint32_t count) {
298
+ uint16_t* px = const_cast<uint16_t*>(static_cast<const uint16_t*>(pixels));
299
+ __tc_op_writePixels(nullptr, px, count);
300
+ }
301
+ ${CANVAS_LIFECYCLE_SECTION}${TARGET_FORWARDERS_SECTION}`;
302
+
303
+ return { includes, declaration, functions };
304
+ }
@@ -0,0 +1,125 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared C++ sections for the Zephyr UI display adapters.
3
+ //
4
+ // The canvas lifecycle + target-polymorphic sections of the emitted adapter
5
+ // are transport-independent (they operate on CuttlefishCanvas16 /
6
+ // CuttlefishDisplayTarget and never touch the panel), so both transports
7
+ // (direct-spi in ui-adapter.ts, zephyr-display in ui-adapter-native.ts)
8
+ // emit them verbatim from this module. Keeping one copy prevents the two
9
+ // transports from drifting apart.
10
+ //
11
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
12
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /** Offscreen canvas lifecycle + accessors (malloc + placement-new; PSRAM via
16
+ * Zephyr's shared multi-heap when BOARD_HAS_PSRAM). */
17
+ export const CANVAS_LIFECYCLE_SECTION = `
18
+ // ── Canvas lifecycle + accessors (offscreen rgb565 compositing) ──────────
19
+ // Allocate the canvas object via malloc + placement-new (not operator new).
20
+ // Under CONFIG_REQUIRES_FULL_LIBCPP without CONFIG_CPP_EXCEPTIONS, operator new
21
+ // throws std::bad_alloc on OOM and the nothrow wrapper's internal catch cannot
22
+ // unwind (no EH runtime) → std::terminate → abort. malloc returns NULL on
23
+ // failure with no exception path; placement-new then constructs the object in
24
+ // place (vtable included). display_deleteCanvas mirrors with an explicit dtor
25
+ // + free. The runtime's callers already null-check the return, so an OOM
26
+ // degrades gracefully instead of aborting.
27
+ static inline CuttlefishCanvas16* display_createCanvas(int16_t cw, int16_t ch) {
28
+ void* mem = malloc(sizeof(CuttlefishCanvas16));
29
+ if (!mem) return nullptr;
30
+ return new (mem) CuttlefishCanvas16(cw, ch);
31
+ }
32
+ // psramFound() shim: the Arduino ESP32 core provides this, but Zephyr does not.
33
+ // Under BOARD_HAS_PSRAM the ESP heap serves PSRAM, so report it as present. The
34
+ // runtime's ui_create_canvas_best calls this under #if defined(BOARD_HAS_PSRAM).
35
+ #if defined(BOARD_HAS_PSRAM) && !defined(psramFound)
36
+ #include <zephyr/multi_heap/shared_multi_heap.h>
37
+ static inline bool psramFound() {
38
+ // Compile-time truth: if BOARD_HAS_PSRAM is defined, the build targets a
39
+ // PSRAM board with CONFIG_ESP_SPIRAM enabled (the framework emits both).
40
+ return true;
41
+ }
42
+ // Allocate from PSRAM via Zephyr's shared multi-heap (the ESP32 SoC code
43
+ // registers PSRAM as an SMH_REG_ATTR_EXTERNAL region at boot).
44
+ static inline void* ui_psram_malloc(size_t bytes) {
45
+ return shared_multi_heap_alloc(SMH_REG_ATTR_EXTERNAL, bytes);
46
+ }
47
+ #endif
48
+ static inline CuttlefishCanvas16* display_createCanvasPsram(int16_t cw, int16_t ch) {
49
+ // Allocate the pixel buffer in PSRAM (large: w*h*2 bytes) and the small
50
+ // canvas object in SRAM. The canvas takes ownership of the PSRAM buffer and
51
+ // frees it via free() in its dtor (the SMH allocator's free is compatible
52
+ // with the standard k_free/free path). Returns nullptr if PSRAM isn't
53
+ // available or the allocation fails — ui_create_canvas_best falls back.
54
+ #if defined(BOARD_HAS_PSRAM)
55
+ if ((cw > 0) && (ch > 0)) {
56
+ size_t bytes = static_cast<size_t>(cw) * static_cast<size_t>(ch) * sizeof(uint16_t);
57
+ uint16_t* psramBuf = static_cast<uint16_t*>(ui_psram_malloc(bytes));
58
+ if (psramBuf) {
59
+ void* mem = malloc(sizeof(CuttlefishCanvas16));
60
+ if (mem) {
61
+ return new (mem) CuttlefishCanvas16(cw, ch, psramBuf, 1);
62
+ }
63
+ free(psramBuf);
64
+ }
65
+ }
66
+ #else
67
+ (void)cw; (void)ch;
68
+ #endif
69
+ return nullptr;
70
+ }
71
+ static inline void display_deleteCanvas(CuttlefishCanvas16* canvas) {
72
+ if (!canvas) return;
73
+ canvas->~CuttlefishCanvas16();
74
+ free(canvas);
75
+ }
76
+ static inline int16_t display_canvasWidth(CuttlefishCanvas16* c) { return c->width(); }
77
+ static inline int16_t display_canvasHeight(CuttlefishCanvas16* c) { return c->height(); }
78
+ // Canvas buffers are addressed in UI_COLOR_T units by every runtime caller;
79
+ // the underlying CuttlefishCanvas16 storage is 16-bit (the 565/mono world the
80
+ // canvas machinery was built for). Gray8/full-frame targets never allocate
81
+ // canvases, so the reinterpret never dereferences on those builds.
82
+ static inline UI_COLOR_T* display_canvasBuffer(CuttlefishCanvas16* c) {
83
+ return reinterpret_cast<UI_COLOR_T*>(c->getBuffer());
84
+ }
85
+ static inline uint16_t display_canvasGetPixel(CuttlefishCanvas16* c, int16_t x, int16_t y) { return c->getPixel(x, y); }
86
+ static inline void display_canvasFillScreen(CuttlefishCanvas16* c, UI_COLOR_T color) { c->fillScreen(static_cast<uint16_t>(color)); }
87
+ static inline void display_canvasFillRect(CuttlefishCanvas16* c, int16_t x, int16_t y, int16_t cw, int16_t ch, UI_COLOR_T color) {
88
+ c->fillRect(x, y, cw, ch, static_cast<uint16_t>(color));
89
+ }
90
+ `;
91
+
92
+ /** Target-polymorphic draw passthroughs (panel or canvas via
93
+ * CuttlefishDisplayTarget*). Pure virtual dispatch on the target object. */
94
+ export const TARGET_FORWARDERS_SECTION = `
95
+ // ── Target-polymorphic draw (panel or canvas via CuttlefishDisplayTarget*) ─
96
+ static inline void display_targetDrawPixel(CuttlefishDisplayTarget* t, int16_t x, int16_t y, UI_COLOR_T color) { t->drawPixel(x, y, static_cast<uint16_t>(color)); }
97
+ static inline int16_t display_targetWidth(CuttlefishDisplayTarget* t) { return t->width(); }
98
+ static inline int16_t display_targetHeight(CuttlefishDisplayTarget* t) { return t->height(); }
99
+ static inline void display_targetDrawRGBBitmap(CuttlefishDisplayTarget* t, int16_t x, int16_t y, const UI_COLOR_T* bitmap, int16_t bw, int16_t bh) {
100
+ t->drawRGBBitmap(x, y, reinterpret_cast<const uint16_t*>(bitmap), bw, bh);
101
+ }
102
+ static inline void display_targetFillRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, UI_COLOR_T color) { t->fillRect(x, y, bw, bh, static_cast<uint16_t>(color)); }
103
+ static inline void display_targetDrawFastHLine(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, UI_COLOR_T color) { t->drawFastHLine(x, y, bw, static_cast<uint16_t>(color)); }
104
+ static inline void display_targetDrawFastVLine(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bh, UI_COLOR_T color) { t->drawFastVLine(x, y, bh, static_cast<uint16_t>(color)); }
105
+ static inline void display_targetFillRoundRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, int16_t r, UI_COLOR_T color) { t->fillRoundRect(x, y, bw, bh, r, static_cast<uint16_t>(color)); }
106
+ static inline void display_targetDrawRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, UI_COLOR_T color) { t->drawRect(x, y, bw, bh, static_cast<uint16_t>(color)); }
107
+ static inline void display_targetDrawRoundRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, int16_t r, UI_COLOR_T color) { t->drawRoundRect(x, y, bw, bh, r, static_cast<uint16_t>(color)); }
108
+ static inline void display_targetDrawLine(CuttlefishDisplayTarget* t, int16_t x0, int16_t y0, int16_t x1, int16_t y1, UI_COLOR_T color) { t->drawLine(x0, y0, x1, y1, static_cast<uint16_t>(color)); }
109
+ static inline void display_targetFillCircle(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t r, UI_COLOR_T color) { t->fillCircle(x, y, r, static_cast<uint16_t>(color)); }
110
+ static inline void display_targetDrawCircle(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t r, UI_COLOR_T color) { t->drawCircle(x, y, r, static_cast<uint16_t>(color)); }
111
+ static inline void display_targetSetCursor(CuttlefishDisplayTarget* t, int16_t x, int16_t y) { t->setCursor(x, y); }
112
+ static inline void display_targetSetTextColor(CuttlefishDisplayTarget* t, UI_COLOR_T fg) { t->setTextColor(static_cast<uint16_t>(fg)); }
113
+ static inline void display_targetSetTextColorBg(CuttlefishDisplayTarget* t, UI_COLOR_T fg, UI_COLOR_T bg) { t->setTextColor(static_cast<uint16_t>(fg), static_cast<uint16_t>(bg)); }
114
+ static inline void display_targetSetTextSize(CuttlefishDisplayTarget* t, uint8_t s) { t->setTextSize(s); }
115
+ static inline void display_targetSetTextWrap(CuttlefishDisplayTarget* t, bool wp) { t->setTextWrap(wp); }
116
+ static inline void display_targetPrint(CuttlefishDisplayTarget* t, const char* s) { t->print(s); }
117
+ `;
118
+
119
+ /** The marker line every adapter stamps into its includes so the toolchain
120
+ * can recover the emitting profile. Pairs with displayFactsLine() from
121
+ * profiles.ts, which adapters emit alongside it (the facts line carries
122
+ * synthesized, compatible-driven profiles too). */
123
+ export function profileMarkerLine(driver: string): string {
124
+ return `// typecad-display-profile: ${driver}`;
125
+ }
@@ -33,8 +33,13 @@
33
33
  // ---------------------------------------------------------------------------
34
34
 
35
35
  import type { DisplayAdapterCode, DisplayAdapterGenerator } from "@typecad/cuttlefish/api/shared";
36
- import { ZEPHYR_DISPLAY_PROFILES, panelControllerFor } from "./profiles.js";
36
+ import { ZEPHYR_DISPLAY_PROFILES, panelControllerFor, transportFor, synthesizeZephyrProfile, displayFactsLine } from "./profiles.js";
37
37
  import type { ZephyrDisplayProfile } from "./profiles.js";
38
+ import { CANVAS_LIFECYCLE_SECTION, TARGET_FORWARDERS_SECTION, profileMarkerLine } from "./ui-adapter-shared.js";
39
+ import { zephyrDisplayApiAdapter } from "./ui-adapter-native.js";
40
+ import { zephyrMonoDisplayAdapter } from "./ui-adapter-mono.js";
41
+ import { zephyrGrayDisplayAdapter } from "./ui-adapter-gray.js";
42
+ import { zephyrEinkDisplayAdapter } from "./ui-adapter-eink.js";
38
43
 
39
44
  /**
40
45
  * Build the Zephyr UI display adapter for a profile. Emits the full
@@ -84,6 +89,8 @@ export function zephyrUiDisplayAdapter(
84
89
 
85
90
  const includes = [
86
91
  `// --- Zephyr UI display adapter (${profile.driver}) ---`,
92
+ profileMarkerLine(profile.driver),
93
+ displayFactsLine(profile),
87
94
  `// Native CuttlefishGFX path: do NOT #define CuttlefishCanvas16 so the`,
88
95
  `// runtime header emits the in-tree CuttlefishGFX/CuttlefishCanvas16 class.`,
89
96
  `#define CuttlefishDisplayTarget CuttlefishGFX`,
@@ -661,100 +668,11 @@ static inline void display_endWrite() { __tc_op_endWrite(nullptr); }
661
668
  static inline void display_setAddrWindow(int16_t x, int16_t y, int16_t winW, int16_t winH) {
662
669
  __tc_op_setAddrWindow(nullptr, x, y, winW, winH);
663
670
  }
664
- static inline void display_writePixels(uint16_t* pixels, uint32_t count) {
665
- __tc_op_writePixels(nullptr, pixels, count);
671
+ static inline void display_writePixels(const UI_COLOR_T* pixels, uint32_t count) {
672
+ uint16_t* px = const_cast<uint16_t*>(static_cast<const uint16_t*>(pixels));
673
+ __tc_op_writePixels(nullptr, px, count);
666
674
  }
667
-
668
- // ── Canvas lifecycle + accessors (offscreen rgb565 compositing) ──────────
669
- // Allocate the canvas object via malloc + placement-new (not operator new).
670
- // Under CONFIG_REQUIRES_FULL_LIBCPP without CONFIG_CPP_EXCEPTIONS, operator new
671
- // throws std::bad_alloc on OOM and the nothrow wrapper's internal catch cannot
672
- // unwind (no EH runtime) → std::terminate → abort. malloc returns NULL on
673
- // failure with no exception path; placement-new then constructs the object in
674
- // place (vtable included). display_deleteCanvas mirrors with an explicit dtor
675
- // + free. The runtime's callers already null-check the return, so an OOM
676
- // degrades gracefully instead of aborting.
677
- static inline CuttlefishCanvas16* display_createCanvas(int16_t cw, int16_t ch) {
678
- void* mem = malloc(sizeof(CuttlefishCanvas16));
679
- if (!mem) return nullptr;
680
- return new (mem) CuttlefishCanvas16(cw, ch);
681
- }
682
- // psramFound() shim: the Arduino ESP32 core provides this, but Zephyr does not.
683
- // Under BOARD_HAS_PSRAM the ESP heap serves PSRAM, so report it as present. The
684
- // runtime's ui_create_canvas_best calls this under #if defined(BOARD_HAS_PSRAM).
685
- #if defined(BOARD_HAS_PSRAM) && !defined(psramFound)
686
- #include <zephyr/multi_heap/shared_multi_heap.h>
687
- static inline bool psramFound() {
688
- // Compile-time truth: if BOARD_HAS_PSRAM is defined, the build targets a
689
- // PSRAM board with CONFIG_ESP_SPIRAM enabled (the framework emits both).
690
- return true;
691
- }
692
- // Allocate from PSRAM via Zephyr's shared multi-heap (the ESP32 SoC code
693
- // registers PSRAM as an SMH_REG_ATTR_EXTERNAL region at boot).
694
- static inline void* ui_psram_malloc(size_t bytes) {
695
- return shared_multi_heap_alloc(SMH_REG_ATTR_EXTERNAL, bytes);
696
- }
697
- #endif
698
- static inline CuttlefishCanvas16* display_createCanvasPsram(int16_t cw, int16_t ch) {
699
- // Allocate the pixel buffer in PSRAM (large: w*h*2 bytes) and the small
700
- // canvas object in SRAM. The canvas takes ownership of the PSRAM buffer and
701
- // frees it via free() in its dtor (the SMH allocator's free is compatible
702
- // with the standard k_free/free path). Returns nullptr if PSRAM isn't
703
- // available or the allocation fails — ui_create_canvas_best falls back.
704
- #if defined(BOARD_HAS_PSRAM)
705
- if ((cw > 0) && (ch > 0)) {
706
- size_t bytes = static_cast<size_t>(cw) * static_cast<size_t>(ch) * sizeof(uint16_t);
707
- uint16_t* psramBuf = static_cast<uint16_t*>(ui_psram_malloc(bytes));
708
- if (psramBuf) {
709
- void* mem = malloc(sizeof(CuttlefishCanvas16));
710
- if (mem) {
711
- return new (mem) CuttlefishCanvas16(cw, ch, psramBuf, 1);
712
- }
713
- free(psramBuf);
714
- }
715
- }
716
- #else
717
- (void)cw; (void)ch;
718
- #endif
719
- return nullptr;
720
- }
721
- static inline void display_deleteCanvas(CuttlefishCanvas16* canvas) {
722
- if (!canvas) return;
723
- canvas->~CuttlefishCanvas16();
724
- free(canvas);
725
- }
726
- static inline int16_t display_canvasWidth(CuttlefishCanvas16* c) { return c->width(); }
727
- static inline int16_t display_canvasHeight(CuttlefishCanvas16* c) { return c->height(); }
728
- static inline uint16_t* display_canvasBuffer(CuttlefishCanvas16* c) { return c->getBuffer(); }
729
- static inline uint16_t display_canvasGetPixel(CuttlefishCanvas16* c, int16_t x, int16_t y) { return c->getPixel(x, y); }
730
- static inline void display_canvasFillScreen(CuttlefishCanvas16* c, UI_COLOR_T color) { c->fillScreen(static_cast<uint16_t>(color)); }
731
- static inline void display_canvasFillRect(CuttlefishCanvas16* c, int16_t x, int16_t y, int16_t cw, int16_t ch, UI_COLOR_T color) {
732
- c->fillRect(x, y, cw, ch, static_cast<uint16_t>(color));
733
- }
734
-
735
- // ── Target-polymorphic draw (panel or canvas via CuttlefishDisplayTarget*) ─
736
- static inline void display_targetDrawPixel(CuttlefishDisplayTarget* t, int16_t x, int16_t y, UI_COLOR_T color) { t->drawPixel(x, y, static_cast<uint16_t>(color)); }
737
- static inline int16_t display_targetWidth(CuttlefishDisplayTarget* t) { return t->width(); }
738
- static inline int16_t display_targetHeight(CuttlefishDisplayTarget* t) { return t->height(); }
739
- static inline void display_targetDrawRGBBitmap(CuttlefishDisplayTarget* t, int16_t x, int16_t y, const uint16_t* bitmap, int16_t bw, int16_t bh) {
740
- t->drawRGBBitmap(x, y, bitmap, bw, bh);
741
- }
742
- static inline void display_targetFillRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, UI_COLOR_T color) { t->fillRect(x, y, bw, bh, static_cast<uint16_t>(color)); }
743
- static inline void display_targetDrawFastHLine(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, UI_COLOR_T color) { t->drawFastHLine(x, y, bw, static_cast<uint16_t>(color)); }
744
- static inline void display_targetDrawFastVLine(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bh, UI_COLOR_T color) { t->drawFastVLine(x, y, bh, static_cast<uint16_t>(color)); }
745
- static inline void display_targetFillRoundRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, int16_t r, UI_COLOR_T color) { t->fillRoundRect(x, y, bw, bh, r, static_cast<uint16_t>(color)); }
746
- static inline void display_targetDrawRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, UI_COLOR_T color) { t->drawRect(x, y, bw, bh, static_cast<uint16_t>(color)); }
747
- static inline void display_targetDrawRoundRect(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t bw, int16_t bh, int16_t r, UI_COLOR_T color) { t->drawRoundRect(x, y, bw, bh, r, static_cast<uint16_t>(color)); }
748
- static inline void display_targetDrawLine(CuttlefishDisplayTarget* t, int16_t x0, int16_t y0, int16_t x1, int16_t y1, UI_COLOR_T color) { t->drawLine(x0, y0, x1, y1, static_cast<uint16_t>(color)); }
749
- static inline void display_targetFillCircle(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t r, UI_COLOR_T color) { t->fillCircle(x, y, r, static_cast<uint16_t>(color)); }
750
- static inline void display_targetDrawCircle(CuttlefishDisplayTarget* t, int16_t x, int16_t y, int16_t r, UI_COLOR_T color) { t->drawCircle(x, y, r, static_cast<uint16_t>(color)); }
751
- static inline void display_targetSetCursor(CuttlefishDisplayTarget* t, int16_t x, int16_t y) { t->setCursor(x, y); }
752
- static inline void display_targetSetTextColor(CuttlefishDisplayTarget* t, UI_COLOR_T fg) { t->setTextColor(static_cast<uint16_t>(fg)); }
753
- static inline void display_targetSetTextColorBg(CuttlefishDisplayTarget* t, UI_COLOR_T fg, UI_COLOR_T bg) { t->setTextColor(static_cast<uint16_t>(fg), static_cast<uint16_t>(bg)); }
754
- static inline void display_targetSetTextSize(CuttlefishDisplayTarget* t, uint8_t s) { t->setTextSize(s); }
755
- static inline void display_targetSetTextWrap(CuttlefishDisplayTarget* t, bool wp) { t->setTextWrap(wp); }
756
- static inline void display_targetPrint(CuttlefishDisplayTarget* t, const char* s) { t->print(s); }
757
- `;
675
+ ${CANVAS_LIFECYCLE_SECTION}${TARGET_FORWARDERS_SECTION}`;
758
676
 
759
677
  return { includes, declaration, functions };
760
678
  }
@@ -767,12 +685,45 @@ static inline void display_targetPrint(CuttlefishDisplayTarget* t, const char* s
767
685
  */
768
686
  export const zephyrDisplayAdapterGenerator: DisplayAdapterGenerator = (display) => {
769
687
  const profile = ZEPHYR_DISPLAY_PROFILES[display.driver];
770
- if (!profile) return undefined as unknown as DisplayAdapterCode;
771
- // The UI adapter is RGB565/SPI (TFT) only. Monochrome panels (OLED) use the
772
- // direct display.* GFX runtime (gfx.ts mono branch) — there is no
773
- // CuttlefishGFX UI rendering path for mono. Decline so cuttlefish does not
774
- // emit an incompatible RGB565 adapter for a mono profile.
775
- if (profile.colorFormat === 'mono') return undefined as unknown as DisplayAdapterCode;
688
+ // Drop-in path: a DT compatible as the driver id (no registry profile)
689
+ // synthesizes a native-transport profile — the in-tree driver bound by the
690
+ // overlay's node owns the panel, the config's quirk flags carry the rest.
691
+ // Mono compatibles (ssd1306/1309-class via the compatible table or an
692
+ // explicit colorFormat) take the full-frame 1bpp adapter, NOT the rgb565
693
+ // display-API one.
694
+ if (!profile) {
695
+ const synth = synthesizeZephyrProfile(display);
696
+ if (synth) {
697
+ if (synth.colorFormat === 'mono') {
698
+ if (synth.displayClass === 'eink') return zephyrEinkDisplayAdapter(synth);
699
+ return zephyrMonoDisplayAdapter(synth);
700
+ }
701
+ if (synth.colorFormat === 'gray8') return zephyrGrayDisplayAdapter(synth);
702
+ return zephyrDisplayApiAdapter(synth);
703
+ }
704
+ return undefined as unknown as DisplayAdapterCode;
705
+ }
706
+ // Mono panels (Stage 2): the full-frame 1bpp adapter — a vtiled MONO01
707
+ // backing store pushed whole each frame through display_write. No transport
708
+ // split: mono profiles ride the in-tree driver (zephyr-display); the
709
+ // ssd1306-class I2C node owns init/geometry.
710
+ if (profile.colorFormat === 'mono') {
711
+ // E-ink (Stage 4): same 1bpp adapter retuned for the deferred refresh
712
+ // model — MONO10-always + flash-cycle-throttled flushes.
713
+ if (profile.displayClass === 'eink') return zephyrEinkDisplayAdapter(profile);
714
+ return zephyrMonoDisplayAdapter(profile);
715
+ }
716
+ // Gray panels (Stage 3): the full-frame L_8 adapter — same full-frame
717
+ // semantic as mono at 8-bit luminance fidelity.
718
+ if (profile.colorFormat === 'gray8') {
719
+ return zephyrGrayDisplayAdapter(profile);
720
+ }
721
+ // Transport switch: 'zephyr-display' profiles get the display-API adapter
722
+ // (display_write on the DT device, in-tree panel driver); everything else
723
+ // keeps the hardware-verified direct-SPI path.
724
+ if (transportFor(profile) === 'zephyr-display') {
725
+ return zephyrDisplayApiAdapter(profile);
726
+ }
776
727
  return zephyrUiDisplayAdapter(profile, {
777
728
  scanlineSync: display.scanlineSync,
778
729
  miso: display.spiPins?.miso,