@typecad/framework-zephyr 1.0.0-alpha.16 → 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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -10,6 +10,7 @@
10
10
  // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
11
11
  // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
12
12
  // ---------------------------------------------------------------------------
13
+ import { displayFactsLine } from './profiles.js';
13
14
  // 5x7 bitmap glyphs for 0-9 and A-Z. Each glyph is 5 bytes (columns), MSB=top.
14
15
  // Unknown characters fall back to the space (all-zero columns).
15
16
  // (Compact hand-authored bitmaps; the test asserts presence of '0'..'9','A'..'Z'.)
@@ -80,9 +81,8 @@ function fontTableCpp() {
80
81
  * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
81
82
  * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
82
83
  * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
83
- * display_flush pushes the whole framebuffer. The MONO01 packing is
84
- * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
85
- * bit = 0x80>>(x&7).
84
+ * display_flush pushes the whole framebuffer as VTILED MONO01 (the layout
85
+ * the driver consumes; see monoHelpers).
86
86
  */
87
87
  export function buildDisplayRuntime(profile) {
88
88
  const w = profile.width;
@@ -99,8 +99,12 @@ export function buildDisplayRuntime(profile) {
99
99
  ? [
100
100
  '// CUTTLEFISH_DISPLAY_BEGIN',
101
101
  `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
102
- `// Mono framebuffer (Zephyr MONO01: horizontal, MSB-first). ${(w + 7) >> 3} bytes/row x ${h} rows.`,
103
- `static uint8_t __tc_display_fb[((${w} * ${h}) + 7) / 8];`,
102
+ `// Mono framebuffer, vtiled MONO01 — the layout Zephyr's ssd1306-class`,
103
+ `// drivers expect from display_write (SCREEN_INFO_MONO_VTILED, matching`,
104
+ `// cfb's draw_point): byte = fb[(y>>3)*${w} + x], bit = 1<<(y&7).`,
105
+ `// pitch must equal width and (y, height) must be multiples of 8 — the`,
106
+ `// full-frame flush satisfies both by construction.`,
107
+ `static uint8_t __tc_display_fb[(${w} * ${h} + 7) / 8];`,
104
108
  '// CUTTLEFISH_DISPLAY_END',
105
109
  ]
106
110
  : [
@@ -115,7 +119,10 @@ export function buildDisplayRuntime(profile) {
115
119
  const fontTable = fontTableCpp();
116
120
  return {
117
121
  includes: ['<zephyr/drivers/display.h>'],
118
- stateLines,
122
+ // Profile marker: lets the toolchain recover which profile emitted this
123
+ // runtime (profileFromEmittedSource in profiles.ts) instead of re-deriving
124
+ // geometry from the emitted C.
125
+ stateLines: [`// typecad-display-profile: ${profile.driver}`, displayFactsLine(profile), ...stateLines],
119
126
  helpers,
120
127
  fontTable,
121
128
  };
@@ -123,86 +130,91 @@ export function buildDisplayRuntime(profile) {
123
130
  /**
124
131
  * Mono (OLED) helpers: a full framebuffer + bit-packing. SSD1306-class panels
125
132
  * are page-buffered, so draw ops set bits in the framebuffer and display_flush
126
- * pushes the whole buffer. color != 0 ⇒ lit.
133
+ * pushes the whole buffer. color != 0 ⇒ lit. The packing is VTILED MONO01
134
+ * (byte = 8 vertical pixels, fb[(y>>3)*W + x], bit = 1<<(y&7)) because that
135
+ * is what the driver's display_write consumes — a horizontal-packed buffer
136
+ * with pitch = rowBytes fails the driver's pitch==width check (-EINVAL) and
137
+ * nothing reaches the panel.
127
138
  */
128
139
  function monoHelpers(w, h, blInit) {
129
- const rowBytes = (w + 7) >> 3; // bytes per row (w is byte-aligned for 128-wide panels)
130
- return `
131
- // MONO01 pixel packing: byte = (y * ${rowBytes}) + (x >> 3), bit = 0x80 >> (x & 7).
132
- static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
133
- if ((x >= ${w}U) || (y >= ${h}U)) { return; }
134
- uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) * ${rowBytes}U) + (x >> 3));
135
- uint8_t mask = static_cast<uint8_t>(0x80U >> (x & 7U));
136
- if (on != 0U) {
137
- __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
138
- } else {
139
- __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
140
- }
141
- }
142
-
143
- static inline void display_init(void) {
144
- if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
145
- ${blInit}
146
- for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
147
- display_blanking_off(__tc_display);
148
- }
149
-
150
- static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
151
- uint8_t on = (color != 0U) ? 1U : 0U;
152
- for (uint16_t row = 0; row < rh; row++) {
153
- for (uint16_t i = 0; i < rw; i++) {
154
- __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
155
- }
156
- }
157
- }
158
-
159
- static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
160
- uint8_t on = (color != 0U) ? 1U : 0U;
161
- for (uint16_t i = 0; i < rw; i++) {
162
- __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
163
- __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
164
- }
165
- for (uint16_t row = 1U; row < rh - 1U; row++) {
166
- __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
167
- __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
168
- }
169
- }
170
-
171
- static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
172
- uint8_t on = (color != 0U) ? 1U : 0U;
173
- uint16_t cx = x;
174
- for (const char* p = text; *p != 0; p++) {
175
- uint8_t uc = static_cast<uint8_t>(*p);
176
- if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
177
- const uint8_t* glyph = &__tc_font5x7[uc][0];
178
- if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
179
- glyph = &__tc_font5x7[uc - 32U][0];
180
- } else if (uc < static_cast<uint8_t>('0')
181
- || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
182
- || uc > static_cast<uint8_t>('Z')) {
183
- glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
184
- }
185
- for (uint16_t col = 0; col < 5U; col++) {
186
- uint8_t bits = glyph[col];
187
- for (uint16_t row = 0; row < 7U; row++) {
188
- if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
189
- __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
190
- }
191
- }
192
- }
193
- cx = static_cast<uint16_t>(cx + 6U);
194
- }
195
- }
196
-
197
- static inline void display_flush(void) {
198
- struct display_buffer_descriptor __desc;
199
- __desc.buf_size = sizeof(__tc_display_fb); // ${rowBytes} * ${h} bytes
200
- __desc.width = ${w}U;
201
- __desc.height = ${h}U;
202
- __desc.pitch = ${rowBytes}U; // bytes per row
203
- __desc.frame_incomplete = false;
204
- (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
205
- }
140
+ return `
141
+ // VTILED MONO01 pixel packing: byte = fb[(y>>3)*${w}] + x, bit = 1<<(y&7).
142
+ static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
143
+ if ((x >= ${w}U) || (y >= ${h}U)) { return; }
144
+ uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) >> 3) * ${w}U + x);
145
+ uint8_t mask = static_cast<uint8_t>(1U << (y & 7U));
146
+ if (on != 0U) {
147
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
148
+ } else {
149
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
150
+ }
151
+ }
152
+
153
+ static inline void display_init(void) {
154
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
155
+ ${blInit}
156
+ for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
157
+ display_blanking_off(__tc_display);
158
+ }
159
+
160
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
161
+ uint8_t on = (color != 0U) ? 1U : 0U;
162
+ for (uint16_t row = 0; row < rh; row++) {
163
+ for (uint16_t i = 0; i < rw; i++) {
164
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
165
+ }
166
+ }
167
+ }
168
+
169
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
170
+ uint8_t on = (color != 0U) ? 1U : 0U;
171
+ for (uint16_t i = 0; i < rw; i++) {
172
+ __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
173
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
174
+ }
175
+ for (uint16_t row = 1U; row < rh - 1U; row++) {
176
+ __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
177
+ __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
178
+ }
179
+ }
180
+
181
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
182
+ uint8_t on = (color != 0U) ? 1U : 0U;
183
+ uint16_t cx = x;
184
+ for (const char* p = text; *p != 0; p++) {
185
+ uint8_t uc = static_cast<uint8_t>(*p);
186
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
187
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
188
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
189
+ glyph = &__tc_font5x7[uc - 32U][0];
190
+ } else if (uc < static_cast<uint8_t>('0')
191
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
192
+ || uc > static_cast<uint8_t>('Z')) {
193
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
194
+ }
195
+ for (uint16_t col = 0; col < 5U; col++) {
196
+ uint8_t bits = glyph[col];
197
+ for (uint16_t row = 0; row < 7U; row++) {
198
+ if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
199
+ __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
200
+ }
201
+ }
202
+ }
203
+ cx = static_cast<uint16_t>(cx + 6U);
204
+ }
205
+ }
206
+
207
+ static inline void display_flush(void) {
208
+ // Full-frame push. pitch = width and height a multiple of 8 are the
209
+ // ssd1306 driver's hard requirements — the full frame satisfies both.
210
+ struct display_buffer_descriptor __desc;
211
+ __desc.buf_size = sizeof(__tc_display_fb); // ${w} * ${h} / 8 bytes
212
+ __desc.width = ${w}U;
213
+ __desc.height = ${h}U;
214
+ __desc.pitch = ${w}U; // vtiled: bytes per 8-pixel page row == width
215
+ __desc.frame_incomplete = false;
216
+ (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
217
+ }
206
218
  `;
207
219
  }
208
220
  /**
@@ -210,75 +222,75 @@ static inline void display_flush(void) {
210
222
  * display_write. No full framebuffer (AGENTS.md rendering guardrails).
211
223
  */
212
224
  function rgb565Helpers(w, blInit) {
213
- return `
214
- // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
215
- // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
216
- // 2 bytes/pixel) and pushes the one-row line buffer.
217
- static inline void __tc_display_write_row(uint16_t x, uint16_t y, uint16_t rw) {
218
- struct display_buffer_descriptor __desc;
219
- __desc.buf_size = static_cast<uint32_t>(rw) * 2U; // 2 bytes per rgb565 pixel
220
- __desc.width = rw;
221
- __desc.height = 1U;
222
- __desc.pitch = rw;
223
- __desc.frame_incomplete = false;
224
- (void)display_write(__tc_display, x, y, &__desc, __tc_display_line);
225
- }
226
-
227
- static inline void display_init(void) {
228
- if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
229
- ${blInit}
230
- display_blanking_off(__tc_display);
231
- }
232
-
233
- static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
234
- for (uint16_t row = 0; row < h; row++) {
235
- for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
236
- __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
237
- }
238
- }
239
-
240
- static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
241
- // Top + bottom edges.
242
- for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
243
- __tc_display_write_row(x, y, rw);
244
- __tc_display_write_row(x, static_cast<uint16_t>(y + h - 1U), rw);
245
- // Left + right edges.
246
- for (uint16_t row = 1U; row < h - 1U; row++) {
247
- __tc_display_line[0] = color;
248
- if (rw > 1U) { __tc_display_line[rw - 1U] = color; }
249
- __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
250
- }
251
- }
252
-
253
- static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
254
- uint16_t cx = x;
255
- for (const char* p = text; *p != 0; p++) {
256
- // Read the byte UNSIGNED so bytes >= 0x80 don't index the 128-entry
257
- // font table negatively (signed char would make 0x80 == -128).
258
- uint8_t uc = static_cast<uint8_t>(*p);
259
- if (uc >= 128U) { uc = static_cast<uint8_t>(' '); } // unknown → space
260
- const uint8_t* glyph = &__tc_font5x7[uc][0];
261
- // Lowercase → uppercase; anything outside 0-9/A-Z → space glyph.
262
- if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
263
- glyph = &__tc_font5x7[uc - 32U][0];
264
- } else if (uc < static_cast<uint8_t>('0')
265
- || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
266
- || uc > static_cast<uint8_t>('Z')) {
267
- glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
268
- }
269
- for (uint16_t col = 0; col < 5U; col++) {
270
- uint8_t bits = glyph[col];
271
- for (uint16_t row = 0; row < 7U; row++) {
272
- __tc_display_line[0] = ((bits & static_cast<uint8_t>(1U << row)) != 0U) ? color : 0U;
273
- __tc_display_write_row(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), 1U);
274
- }
275
- }
276
- cx = static_cast<uint16_t>(cx + 6U); // 5 cols + 1 space
277
- }
278
- }
279
-
280
- static inline void display_flush(void) {
281
- // No-op: writes are immediate via display_write; there is no framebuffer to push.
282
- }
225
+ return `
226
+ // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
227
+ // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
228
+ // 2 bytes/pixel) and pushes the one-row line buffer.
229
+ static inline void __tc_display_write_row(uint16_t x, uint16_t y, uint16_t rw) {
230
+ struct display_buffer_descriptor __desc;
231
+ __desc.buf_size = static_cast<uint32_t>(rw) * 2U; // 2 bytes per rgb565 pixel
232
+ __desc.width = rw;
233
+ __desc.height = 1U;
234
+ __desc.pitch = rw;
235
+ __desc.frame_incomplete = false;
236
+ (void)display_write(__tc_display, x, y, &__desc, __tc_display_line);
237
+ }
238
+
239
+ static inline void display_init(void) {
240
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
241
+ ${blInit}
242
+ display_blanking_off(__tc_display);
243
+ }
244
+
245
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
246
+ for (uint16_t row = 0; row < h; row++) {
247
+ for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
248
+ __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
249
+ }
250
+ }
251
+
252
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
253
+ // Top + bottom edges.
254
+ for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
255
+ __tc_display_write_row(x, y, rw);
256
+ __tc_display_write_row(x, static_cast<uint16_t>(y + h - 1U), rw);
257
+ // Left + right edges.
258
+ for (uint16_t row = 1U; row < h - 1U; row++) {
259
+ __tc_display_line[0] = color;
260
+ if (rw > 1U) { __tc_display_line[rw - 1U] = color; }
261
+ __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
262
+ }
263
+ }
264
+
265
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
266
+ uint16_t cx = x;
267
+ for (const char* p = text; *p != 0; p++) {
268
+ // Read the byte UNSIGNED so bytes >= 0x80 don't index the 128-entry
269
+ // font table negatively (signed char would make 0x80 == -128).
270
+ uint8_t uc = static_cast<uint8_t>(*p);
271
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); } // unknown → space
272
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
273
+ // Lowercase → uppercase; anything outside 0-9/A-Z → space glyph.
274
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
275
+ glyph = &__tc_font5x7[uc - 32U][0];
276
+ } else if (uc < static_cast<uint8_t>('0')
277
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
278
+ || uc > static_cast<uint8_t>('Z')) {
279
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
280
+ }
281
+ for (uint16_t col = 0; col < 5U; col++) {
282
+ uint8_t bits = glyph[col];
283
+ for (uint16_t row = 0; row < 7U; row++) {
284
+ __tc_display_line[0] = ((bits & static_cast<uint8_t>(1U << row)) != 0U) ? color : 0U;
285
+ __tc_display_write_row(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), 1U);
286
+ }
287
+ }
288
+ cx = static_cast<uint16_t>(cx + 6U); // 5 cols + 1 space
289
+ }
290
+ }
291
+
292
+ static inline void display_flush(void) {
293
+ // No-op: writes are immediate via display_write; there is no framebuffer to push.
294
+ }
283
295
  `;
284
296
  }
@@ -10,7 +10,7 @@
10
10
  // init, so when state is uninitialized we seed the DEFAULT profile and lower
11
11
  // anyway (the probe only checks code/expression is non-undefined).
12
12
  // ---------------------------------------------------------------------------
13
- import { DEFAULT_ZEPHYR_DISPLAY_PROFILE } from './profiles.js';
13
+ import { DEFAULT_ZEPHYR_DISPLAY_PROFILE, ZEPHYR_DISPLAY_PROFILES, synthesizeZephyrProfile } from './profiles.js';
14
14
  // Re-export the UI display/touch adapters + profile registry so the strategy
15
15
  // and consumers can reach them from the package barrel.
16
16
  export { zephyrUiDisplayAdapter, zephyrDisplayAdapterGenerator } from './ui-adapter.js';
@@ -30,6 +30,25 @@ export function resolveZephyrDisplayOp(op, state) {
30
30
  // fields, so use the default profile.
31
31
  if (op.operation === 'display.init') {
32
32
  state.initialized = true;
33
+ // Drop-in panels (driver = DT compatible): seed the synthesized profile
34
+ // so the direct-op GFX runtime sizes itself for THIS panel (the mono
35
+ // framebuffer branch keys off the profile's colorFormat/geometry). The
36
+ // op carries the resolved profile's colorFormat — explicit mono wins;
37
+ // otherwise the compatible table decides (ssd1306-class → mono).
38
+ const registered = ZEPHYR_DISPLAY_PROFILES[o.driver];
39
+ if (registered) {
40
+ state.profile = registered;
41
+ }
42
+ else {
43
+ const synth = synthesizeZephyrProfile({
44
+ driver: o.driver,
45
+ width: o.width,
46
+ height: o.height,
47
+ colorFormat: o.colorFormat,
48
+ });
49
+ if (synth)
50
+ state.profile = synth;
51
+ }
33
52
  return { code: 'display_init();' };
34
53
  }
35
54
  // For non-init ops: if not initialized (validator probe path), seed the
@@ -0,0 +1,9 @@
1
+ import type { ZephyrDisplayProfile } from "./profiles.js";
2
+ /** Build the local CS-holding mipi-dbi host for a profile. Emits a device at
3
+ * the overlay's bridge node (bridgeLabel) implementing
4
+ * struct mipi_dbi_driver_api, plus the includes it needs. The panel driver
5
+ * reaches it via DEVICE_DT_GET(DT_INST_PARENT(display0)). */
6
+ export declare function localHoldCsDbiHost(profile: ZephyrDisplayProfile): {
7
+ includes: string[];
8
+ functions: string;
9
+ };
@@ -0,0 +1,174 @@
1
+ // ---------------------------------------------------------------------------
2
+ // App-local CS-holding MIPI DBI host — the 'local-hold-cs' dbiHost.
3
+ //
4
+ // Implements the mipi-dbi host API for the overlay's zephyr,mipi-dbi-spi node
5
+ // with CS held across each command+data burst (GPIO-managed, DC toggled
6
+ // mid-burst — the Adafruit ST77xx protocol the direct transport verified on
7
+ // the clone-ST7796S rig). The stock mipi-dbi-spi driver issues the command
8
+ // byte and its parameters as two CS-deasserted SPI transactions, which
9
+ // scrambles clone ST77xx panels; the mipi-hold-cs DT knob that would fix it
10
+ // is documented by the binding but unimplemented in Zephyr 4.4.2. This host
11
+ // is that behavior, emitted app-locally — the stock bridge stays compiled
12
+ // out (CONFIG_MIPI_DBI_SPI=n) and the in-tree panel driver (e.g.
13
+ // sitronix,st7796s, CONFIG_ST7796S=y) binds on top unchanged.
14
+ //
15
+ // A bus host is a normal Zephyr extension point (11 ship in-tree); once
16
+ // upstream implements mipi-hold-cs, this section retires and the profile's
17
+ // dbiHost flips back to 'spi-bridge'.
18
+ //
19
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
20
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
21
+ // ---------------------------------------------------------------------------
22
+ /** Build the local CS-holding mipi-dbi host for a profile. Emits a device at
23
+ * the overlay's bridge node (bridgeLabel) implementing
24
+ * struct mipi_dbi_driver_api, plus the includes it needs. The panel driver
25
+ * reaches it via DEVICE_DT_GET(DT_INST_PARENT(display0)). */
26
+ export function localHoldCsDbiHost(profile) {
27
+ const bus = profile.busLabel ?? 'spi2';
28
+ const bridge = profile.bridgeLabel ?? 'mipi_dbi';
29
+ const includes = [
30
+ `#include <zephyr/drivers/mipi_dbi.h>`,
31
+ `#include <zephyr/drivers/spi.h>`,
32
+ ];
33
+ const functions = `
34
+ // ── App-local MIPI DBI host (CS held across command+data) ───────────────
35
+ // The mipi-hold-cs behavior the zephyr,mipi-dbi-spi child binding documents:
36
+ // CS asserted by GPIO around each complete command+data burst (or whole
37
+ // display write), DC toggled mid-burst. The spi_config carried in the
38
+ // panel's mipi_dbi_config is used verbatim EXCEPT .cs, which this host
39
+ // nulls so the SPI driver never touches the CS line mid-burst.
40
+ static const struct gpio_dt_spec __tc_dbi_dc = GPIO_DT_SPEC_GET(DT_NODELABEL(${bridge}), dc_gpios);
41
+ static const struct gpio_dt_spec __tc_dbi_rst = GPIO_DT_SPEC_GET(DT_NODELABEL(${bridge}), reset_gpios);
42
+ // CS comes from the SPI bus node's cs-gpios, selected by the panel's
43
+ // dbi config .slave (= reg). The overlay puts the panel at index 0 and an
44
+ // SPI touch controller, if any, at index 1 — each owner drives its own CS.
45
+ static const struct gpio_dt_spec __tc_dbi_cs[] = {
46
+ #if DT_PROP_LEN(DT_NODELABEL(${bus}), cs_gpios) > 0
47
+ GPIO_DT_SPEC_GET_BY_IDX(DT_NODELABEL(${bus}), cs_gpios, 0),
48
+ #endif
49
+ #if DT_PROP_LEN(DT_NODELABEL(${bus}), cs_gpios) > 1
50
+ GPIO_DT_SPEC_GET_BY_IDX(DT_NODELABEL(${bus}), cs_gpios, 1),
51
+ #endif
52
+ };
53
+ #define __TC_DBI_CS_COUNT (sizeof(__tc_dbi_cs) / sizeof(__tc_dbi_cs[0]))
54
+ // Staging buffer for display writes: display_write buffers may live in PSRAM
55
+ // (not DMA-capable on ESP32); chunked SRAM staging is the pattern the direct
56
+ // transport proved on this rig. Reused, never per-frame (AGENTS.md).
57
+ static uint8_t __tc_dbi_bounce[4096];
58
+
59
+ static const struct gpio_dt_spec* __tc_dbi_cs_for(const struct mipi_dbi_config* cfg) {
60
+ if (cfg->config.slave >= __TC_DBI_CS_COUNT) return nullptr;
61
+ return &__tc_dbi_cs[cfg->config.slave];
62
+ }
63
+
64
+ // One command byte (DC low) + its parameters (DC high) under one CS burst.
65
+ static int __tc_dbi_command_write(const struct device* /*dev*/,
66
+ const struct mipi_dbi_config* cfg,
67
+ uint8_t cmd, const uint8_t* data, size_t len) {
68
+ const struct gpio_dt_spec* cs = __tc_dbi_cs_for(cfg);
69
+ if (cs == nullptr) return -EIO;
70
+ struct spi_config sc = cfg->config;
71
+ // This host owns CS — keep the SPI core off it. Both fields matter in
72
+ // Zephyr 4.4: the core gates on cs.cs_is_gpio (NOT the port pointer), and
73
+ // leaving it set makes spi_context_cs_control deref the port-less spec
74
+ // (EXCCAUSE 28 on the first panel init command — verified on hardware).
75
+ sc.cs.cs_is_gpio = false;
76
+ sc.cs.gpio.port = nullptr;
77
+ struct spi_buf __cb = { &cmd, 1 };
78
+ struct spi_buf_set __csb = { &__cb, 1 };
79
+ gpio_pin_set_dt(cs, 1);
80
+ gpio_pin_set_dt(&__tc_dbi_dc, 0);
81
+ int ret = spi_write(DEVICE_DT_GET(DT_NODELABEL(${bus})), &sc, &__csb);
82
+ if ((ret == 0) && (len > 0)) {
83
+ struct spi_buf __db = { const_cast<uint8_t*>(data), len };
84
+ struct spi_buf_set __dsb = { &__db, 1 };
85
+ gpio_pin_set_dt(&__tc_dbi_dc, 1);
86
+ ret = spi_write(DEVICE_DT_GET(DT_NODELABEL(${bus})), &sc, &__dsb);
87
+ }
88
+ gpio_pin_set_dt(cs, 0);
89
+ return ret;
90
+ }
91
+
92
+ // Write-only host: the bridge node carries 'write-only' (no MISO readback
93
+ // path is wired for these panels).
94
+ static int __tc_dbi_command_read(const struct device* /*dev*/,
95
+ const struct mipi_dbi_config* /*cfg*/,
96
+ uint8_t* /*cmds*/, size_t /*num_cmds*/,
97
+ uint8_t* /*response*/, size_t /*len*/) {
98
+ return -ENOTSUP;
99
+ }
100
+
101
+ // Stream desc->buf_size bytes to the panel (DC high) under one CS assertion,
102
+ // chunked through the SRAM bounce buffer.
103
+ static int __tc_dbi_write_display(const struct device* /*dev*/,
104
+ const struct mipi_dbi_config* cfg,
105
+ const uint8_t* framebuf,
106
+ struct display_buffer_descriptor* desc,
107
+ enum display_pixel_format /*pixfmt*/) {
108
+ const struct gpio_dt_spec* cs = __tc_dbi_cs_for(cfg);
109
+ if (cs == nullptr) return -EIO;
110
+ struct spi_config sc = cfg->config;
111
+ sc.cs.cs_is_gpio = false;
112
+ sc.cs.gpio.port = nullptr;
113
+ gpio_pin_set_dt(cs, 1);
114
+ gpio_pin_set_dt(&__tc_dbi_dc, 1);
115
+ const uint8_t* p = framebuf;
116
+ size_t remaining = desc->buf_size;
117
+ int ret = 0;
118
+ while ((remaining > 0) && (ret == 0)) {
119
+ size_t chunk = (remaining > sizeof(__tc_dbi_bounce)) ? sizeof(__tc_dbi_bounce) : remaining;
120
+ memcpy(__tc_dbi_bounce, p, chunk);
121
+ struct spi_buf __b = { __tc_dbi_bounce, chunk };
122
+ struct spi_buf_set __s = { &__b, 1 };
123
+ ret = spi_write(DEVICE_DT_GET(DT_NODELABEL(${bus})), &sc, &__s);
124
+ p += chunk;
125
+ remaining -= chunk;
126
+ }
127
+ gpio_pin_set_dt(cs, 0);
128
+ return ret;
129
+ }
130
+
131
+ // Assert reset for the requested delay, then release (the stock host's
132
+ // semantics — st7796s calls mipi_dbi_reset(host, 100) at init).
133
+ static int __tc_dbi_reset(const struct device* /*dev*/, k_timeout_t delay) {
134
+ gpio_pin_set_dt(&__tc_dbi_rst, 1);
135
+ k_sleep(delay);
136
+ return gpio_pin_set_dt(&__tc_dbi_rst, 0);
137
+ }
138
+
139
+ // Nothing is locked between calls (no per-panel spi reconfig here).
140
+ static int __tc_dbi_release(const struct device* /*dev*/,
141
+ const struct mipi_dbi_config* /*cfg*/) {
142
+ return 0;
143
+ }
144
+
145
+ static DEVICE_API(mipi_dbi, __tc_dbi_api) = {
146
+ __tc_dbi_command_write,
147
+ __tc_dbi_command_read,
148
+ __tc_dbi_write_display,
149
+ __tc_dbi_reset,
150
+ __tc_dbi_release,
151
+ };
152
+
153
+ static int __tc_dbi_host_init(const struct device* /*dev*/) {
154
+ if (!device_is_ready(__tc_dbi_dc.port)) return -ENODEV;
155
+ gpio_pin_configure_dt(&__tc_dbi_dc, GPIO_OUTPUT);
156
+ if (device_is_ready(__tc_dbi_rst.port)) {
157
+ gpio_pin_configure_dt(&__tc_dbi_rst, GPIO_OUTPUT_INACTIVE);
158
+ }
159
+ for (size_t i = 0; i < __TC_DBI_CS_COUNT; i++) {
160
+ if (device_is_ready(__tc_dbi_cs[i].port)) {
161
+ gpio_pin_configure_dt(&__tc_dbi_cs[i], GPIO_OUTPUT_INACTIVE);
162
+ }
163
+ }
164
+ return 0;
165
+ }
166
+
167
+ // Same init level/priority slot the stock bridge uses — panel drivers init
168
+ // later (CONFIG_DISPLAY_INIT_PRIORITY=85 > MIPI_DBI_INIT_PRIORITY=80), so
169
+ // the host's GPIOs are live before the panel's init commands run.
170
+ DEVICE_DT_DEFINE(DT_NODELABEL(${bridge}), __tc_dbi_host_init, NULL, NULL, NULL,
171
+ POST_KERNEL, CONFIG_MIPI_DBI_INIT_PRIORITY, &__tc_dbi_api);
172
+ `;
173
+ return { includes, functions };
174
+ }