@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (191) hide show
  1. package/README.md +22 -35
  2. package/dist/as-built.d.ts +33 -0
  3. package/dist/as-built.js +188 -0
  4. package/dist/async/timer-polyfill.js +1 -1
  5. package/dist/boardgen.d.ts +128 -0
  6. package/dist/boardgen.js +1366 -0
  7. package/dist/chips/board-overrides.d.ts +7 -0
  8. package/dist/chips/board-overrides.js +11 -0
  9. package/dist/chips/controllers.d.ts +28 -8
  10. package/dist/chips/controllers.js +49 -12
  11. package/dist/chips/esp32s3.js +49 -0
  12. package/dist/chips/index.d.ts +6 -12
  13. package/dist/chips/index.js +18 -34
  14. package/dist/chips/resolve.d.ts +16 -2
  15. package/dist/chips/resolve.js +298 -15
  16. package/dist/chips/soc/.d.ts +2 -0
  17. package/dist/chips/soc/.js +129 -0
  18. package/dist/chips/soc/esp32.d.ts +2 -0
  19. package/dist/chips/soc/esp32.js +120 -0
  20. package/dist/chips/soc/esp32c3.d.ts +2 -0
  21. package/dist/chips/soc/esp32c3.js +90 -0
  22. package/dist/chips/soc/esp32c6.d.ts +2 -0
  23. package/dist/chips/soc/esp32c6.js +109 -0
  24. package/dist/chips/soc/esp32s3.d.ts +2 -0
  25. package/dist/chips/soc/esp32s3.js +189 -0
  26. package/dist/chips/soc/index.d.ts +2 -0
  27. package/dist/chips/soc/index.js +23 -0
  28. package/dist/chips/soc/nrf52840.d.ts +2 -0
  29. package/dist/chips/soc/nrf52840.js +130 -0
  30. package/dist/chips/soc/rp2040.d.ts +2 -0
  31. package/dist/chips/soc/rp2040.js +141 -0
  32. package/dist/chips/soc/rp2350a.d.ts +2 -0
  33. package/dist/chips/soc/rp2350a.js +145 -0
  34. package/dist/chips/soc/samd21g18a.d.ts +2 -0
  35. package/dist/chips/soc/samd21g18a.js +143 -0
  36. package/dist/chips/soc/stm32f411xe.d.ts +2 -0
  37. package/dist/chips/soc/stm32f411xe.js +251 -0
  38. package/dist/chips/types.d.ts +382 -12
  39. package/dist/chips/xiao-ble.js +33 -0
  40. package/dist/display/profiles.js +1 -1
  41. package/dist/doctor.js +38 -4
  42. package/dist/dt-config/custom-board.d.ts +23 -0
  43. package/dist/dt-config/custom-board.js +227 -0
  44. package/dist/dt-config/kconfig.d.ts +83 -1
  45. package/dist/dt-config/kconfig.js +116 -27
  46. package/dist/dt-config/overlay.js +623 -18
  47. package/dist/framework.manifest.d.ts +20 -30
  48. package/dist/framework.manifest.js +124 -178
  49. package/dist/index.d.ts +4 -1
  50. package/dist/index.js +8 -2
  51. package/dist/lowering/adc.d.ts +26 -5
  52. package/dist/lowering/adc.js +137 -31
  53. package/dist/lowering/ble.js +31 -28
  54. package/dist/lowering/dac.js +9 -9
  55. package/dist/lowering/fs.js +18 -10
  56. package/dist/lowering/gpio.d.ts +2 -0
  57. package/dist/lowering/gpio.js +90 -10
  58. package/dist/lowering/http.js +39 -14
  59. package/dist/lowering/hwtimer.d.ts +4 -0
  60. package/dist/lowering/hwtimer.js +35 -10
  61. package/dist/lowering/i2c.js +46 -54
  62. package/dist/lowering/index.d.ts +5 -5
  63. package/dist/lowering/index.js +15 -16
  64. package/dist/lowering/interrupts.d.ts +22 -5
  65. package/dist/lowering/interrupts.js +104 -22
  66. package/dist/lowering/mqtt.js +12 -6
  67. package/dist/lowering/power.js +0 -4
  68. package/dist/lowering/preferences.js +49 -77
  69. package/dist/lowering/pulse.js +5 -36
  70. package/dist/lowering/pwm.d.ts +32 -3
  71. package/dist/lowering/pwm.js +105 -17
  72. package/dist/lowering/random.js +3 -3
  73. package/dist/lowering/sensor.d.ts +37 -0
  74. package/dist/lowering/sensor.js +119 -0
  75. package/dist/lowering/spi.d.ts +12 -0
  76. package/dist/lowering/spi.js +65 -40
  77. package/dist/lowering/thread.d.ts +14 -0
  78. package/dist/lowering/thread.js +55 -0
  79. package/dist/lowering/timing.js +20 -46
  80. package/dist/lowering/tone.js +18 -5
  81. package/dist/lowering/uart.d.ts +13 -0
  82. package/dist/lowering/uart.js +72 -51
  83. package/dist/lowering/usb.d.ts +34 -0
  84. package/dist/lowering/usb.js +206 -0
  85. package/dist/lowering/wdt.d.ts +1 -1
  86. package/dist/lowering/wdt.js +17 -7
  87. package/dist/lowering/wifi.d.ts +3 -2
  88. package/dist/lowering/wifi.js +120 -111
  89. package/dist/sdk/board-catalog-sync.d.ts +16 -0
  90. package/dist/sdk/board-catalog-sync.js +41 -0
  91. package/dist/sdk/board-data.generated.d.ts +2 -0
  92. package/dist/sdk/board-data.generated.js +4 -0
  93. package/dist/sdk/catalog-walker.d.ts +90 -0
  94. package/dist/sdk/catalog-walker.js +682 -0
  95. package/dist/sdk/dts-reader.d.ts +83 -0
  96. package/dist/sdk/dts-reader.js +596 -0
  97. package/dist/strategy.d.ts +136 -17
  98. package/dist/strategy.js +881 -212
  99. package/dist/toolchain/bossac-touch.d.ts +18 -0
  100. package/dist/toolchain/bossac-touch.js +88 -0
  101. package/dist/toolchain/compat.d.ts +2 -2
  102. package/dist/toolchain/compat.js +8 -3
  103. package/dist/toolchain/debug-config.d.ts +21 -26
  104. package/dist/toolchain/debug-config.js +212 -72
  105. package/dist/toolchain/env-check.d.ts +4 -4
  106. package/dist/toolchain/env-check.js +5 -5
  107. package/dist/toolchain/index.d.ts +66 -1
  108. package/dist/toolchain/index.js +713 -58
  109. package/dist/toolchain/scaffold.d.ts +9 -0
  110. package/dist/toolchain/scaffold.js +84 -7
  111. package/dist/toolchain/west-discover.d.ts +8 -1
  112. package/dist/toolchain/west-discover.js +19 -6
  113. package/dist/toolchain/west-spawn.js +17 -5
  114. package/installer/CHANGELOG.md +473 -0
  115. package/installer/README.md +328 -0
  116. package/installer/environment.yml +46 -0
  117. package/installer/etc/conda/activate.d/zephyr.bat +7 -0
  118. package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
  119. package/installer/etc/conda/activate.d/zephyr.sh +20 -0
  120. package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
  121. package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
  122. package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
  123. package/installer/install.mjs +580 -0
  124. package/installer/install.ps1 +619 -0
  125. package/installer/install.sh +310 -0
  126. package/installer/lib/detect-platform.sh +62 -0
  127. package/installer/lib/fetch-sdk.sh +304 -0
  128. package/installer/lib/init-workspace.sh +153 -0
  129. package/installer/lib/write-activation.sh +76 -0
  130. package/installer/patches/tfm-c-include-separators.patch +19 -0
  131. package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
  132. package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
  133. package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
  134. package/installer/templates/project/.vscode/settings.json +30 -0
  135. package/installer/templates/project/README.md +58 -0
  136. package/installer/versions.env +110 -0
  137. package/package.json +16 -16
  138. package/src/as-built.ts +206 -0
  139. package/src/boardgen.ts +1428 -0
  140. package/src/chips/controllers.ts +61 -12
  141. package/src/chips/index.ts +35 -54
  142. package/src/chips/resolve.ts +316 -14
  143. package/src/chips/types.ts +388 -13
  144. package/src/display/profiles.ts +1 -1
  145. package/src/doctor.ts +110 -77
  146. package/src/dt-config/custom-board.ts +325 -0
  147. package/src/dt-config/kconfig.ts +177 -19
  148. package/src/dt-config/overlay.ts +1058 -450
  149. package/src/framework.manifest.ts +125 -178
  150. package/src/index.ts +57 -41
  151. package/src/lowering/adc.ts +222 -102
  152. package/src/lowering/ble.ts +32 -28
  153. package/src/lowering/dac.ts +82 -82
  154. package/src/lowering/fs.ts +18 -10
  155. package/src/lowering/gpio.ts +242 -152
  156. package/src/lowering/http.ts +512 -488
  157. package/src/lowering/hwtimer.ts +139 -101
  158. package/src/lowering/i2c.ts +116 -126
  159. package/src/lowering/index.ts +91 -93
  160. package/src/lowering/interrupts.ts +113 -21
  161. package/src/lowering/mqtt.ts +12 -6
  162. package/src/lowering/preferences.ts +327 -354
  163. package/src/lowering/pwm.ts +167 -74
  164. package/src/lowering/random.ts +3 -3
  165. package/src/lowering/sensor.ts +155 -0
  166. package/src/lowering/spi.ts +164 -124
  167. package/src/lowering/thread.ts +63 -0
  168. package/src/lowering/timing.ts +46 -72
  169. package/src/lowering/uart.ts +73 -51
  170. package/src/lowering/usb.ts +221 -0
  171. package/src/lowering/wdt.ts +95 -84
  172. package/src/lowering/wifi.ts +120 -111
  173. package/src/sdk/board-catalog-sync.ts +62 -0
  174. package/src/strategy.ts +888 -229
  175. package/src/toolchain/bossac-touch.ts +112 -0
  176. package/src/toolchain/compat.ts +8 -3
  177. package/src/toolchain/debug-config.ts +666 -522
  178. package/src/toolchain/env-check.ts +9 -9
  179. package/src/toolchain/index.ts +765 -44
  180. package/src/toolchain/scaffold.ts +81 -7
  181. package/src/toolchain/west-discover.ts +334 -316
  182. package/src/toolchain/west-spawn.ts +17 -5
  183. package/src/async/timer-polyfill.ts +0 -107
  184. package/src/chips/esp32.ts +0 -74
  185. package/src/chips/esp32s3.ts +0 -57
  186. package/src/chips/xiao-ble.ts +0 -70
  187. package/src/lowering/power.ts +0 -70
  188. package/src/lowering/pulse.ts +0 -93
  189. package/src/lowering/tone.ts +0 -60
  190. package/src/lowering/worker-backing.ts +0 -94
  191. package/src/lowering/worker.ts +0 -17
@@ -34,21 +34,29 @@ export function fsInitLines(): string[] {
34
34
  'FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(__tc_fs_data);',
35
35
  'static struct fs_mount_t __tc_fs_mp_init = {',
36
36
  ' .type = FS_LITTLEFS,',
37
+ ' .mnt_point = "/lfs",',
37
38
  ' .fs_data = &__tc_fs_data,',
38
39
  ' .storage_dev = (void *)FIXED_PARTITION_ID(storage_partition),',
39
- ' .mnt_point = "/lfs",',
40
40
  '};',
41
41
  '',
42
42
  'static void __tc_fs_ensure_mount(void) {',
43
43
  ' if (__tc_fs_mounted) return;',
44
44
  ' __tc_fs_mp = __tc_fs_mp_init;',
45
45
  ' int rc = fs_mount(&__tc_fs_mp);',
46
- ' if (rc == -ENODATA || rc == -EINVAL) {',
47
- ' // Unformatted partition format it for littlefs, then remount.',
46
+ ' if (rc != 0) {',
47
+ ' // Any mount failure with a valid config means the partition is',
48
+ ' // unformatted or corrupt (littlefs reports -EIO on erased flash,',
49
+ ' // not only -ENODATA/-EINVAL) — format it and remount once.',
48
50
  ' (void)fs_mkfs(FS_LITTLEFS, static_cast<uintptr_t>(FIXED_PARTITION_ID(storage_partition)), NULL, 0);',
49
- ' (void)fs_mount(&__tc_fs_mp);',
51
+ ' rc = fs_mount(&__tc_fs_mp);',
52
+ ' }',
53
+ ' if (rc == 0) {',
54
+ ' __tc_fs_mounted = true;',
55
+ ' } else {',
56
+ ' char __tc_fs_err[48];',
57
+ ' (void)snprintf(__tc_fs_err, sizeof(__tc_fs_err), "cuttlefish-fs: mount failed rc=%d", rc);',
58
+ ' __tc_println(__tc_fs_err);',
50
59
  ' }',
51
- ' __tc_fs_mounted = true;',
52
60
  '}',
53
61
  '',
54
62
  '// Join the mount point with the user path (paths are within the FS).',
@@ -69,8 +77,11 @@ export function fsInitLines(): string[] {
69
77
  ' struct fs_file_t f;',
70
78
  ' fs_file_t_init(&f);',
71
79
  ' if (fs_open(&f, __tc_fs_path(path), FS_O_READ) == 0) {',
72
- ' (void)fs_read(&f, buf, sizeof(buf) - 1);',
73
- ' buf[sizeof(buf) - 1] = 0;',
80
+ ' // Terminate at the READ length: the buffer is static and shared',
81
+ ' // across reads, so bytes from a longer previous file survive a',
82
+ ' // short read otherwise.',
83
+ ' ssize_t n = fs_read(&f, buf, sizeof(buf) - 1);',
84
+ ' buf[(n > 0) ? n : 0] = 0;',
74
85
  ' (void)fs_close(&f);',
75
86
  ' }',
76
87
  ' return buf;',
@@ -107,9 +118,6 @@ export function fsInitLines(): string[] {
107
118
  export function lowerFs(op: HALOpIR): { code?: string; expression?: string } {
108
119
  const o = op as any;
109
120
  switch (op.operation) {
110
- case 'fs.begin':
111
- // No payload (the op carries only its discriminator).
112
- return { code: 'return __tc_fs_begin();' };
113
121
  case 'fs.read_text':
114
122
  return { code: `return __tc_fs_read_text(${o.path});` };
115
123
  case 'fs.write_text':
@@ -1,152 +1,242 @@
1
- // ---------------------------------------------------------------------------
2
- // GPIO lowering — devicetree-spec bridge
3
- //
4
- // Translates HAL gpio.* ops to Zephyr driver C++. Two addressing modes:
5
- //
6
- // 1. Devicetree spec (preferred): if the HAL pin matches a dtSpec in the
7
- // active chip descriptor, emit gpio_pin_*_dt() calls against a
8
- // `__tc_dt_<alias>` gpio_dt_spec. This honors the node's polarity flags
9
- // (GPIO_ACTIVE_LOW), so logical value 1 = LED on for an active-low LED.
10
- //
11
- // 2. Raw fallback: pins without a dtSpec are addressed via the SoC's gpio
12
- // controller node (DEVICE_DT_GET(DT_NODELABEL(gpio0))) and the
13
- // gpio_pin_*_raw() API. Polarity is physical (raw bypasses DT flags).
14
- // The manifest validator's probe (which sends {operation, pin:0} with no
15
- // port) hits this path, so it must return a lowered result, not undefined.
16
- // ---------------------------------------------------------------------------
17
-
18
- import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
19
- import type { ZephyrChipDescriptor } from '../chips/types.js';
20
- import { controllerNodelabelForPin } from '../chips/controllers.js';
21
-
22
- /** The C identifier emitted for a pin's gpio_dt_spec variable. */
23
- export function dtSpecVarName(dtSpec: string): string {
24
- return `__tc_dt_${dtSpec}`;
25
- }
26
-
27
- /** Look up a dtSpec by GPIO pin number; undefined if the pin has none. */
28
- function findDtSpec(chip: ZephyrChipDescriptor, pin: number) {
29
- return chip.gpio.dtSpecs.find((s) => s.pin === pin);
30
- }
31
-
32
- // HAL passes UPPERCASE modes ("OUTPUT") while docs say lowercase ("output").
33
- // The *_pullup / *_pulldown modes map to GPIO_INPUT (Zephyr does not have
34
- // separate input+pull mode flags) and OR in a GPIO_PULL_UP / GPIO_PULL_DOWN
35
- // bit via `dtFlagsForMode` without that bit the pin floats, so INPUT_PULLUP
36
- // was a silent no-op (bug B1). Mirrors the gpio_pullup_en/gpio_pulldown_en
37
- // extras framework-esp32 emits for the same modes.
38
- const MODE_MAP: Record<string, string> = {
39
- output: 'GPIO_OUTPUT',
40
- OUTPUT: 'GPIO_OUTPUT',
41
- input: 'GPIO_INPUT',
42
- INPUT: 'GPIO_INPUT',
43
- input_pullup: 'GPIO_INPUT',
44
- INPUT_PULLUP: 'GPIO_INPUT',
45
- input_pulldown: 'GPIO_INPUT',
46
- INPUT_PULLDOWN: 'GPIO_INPUT',
47
- };
48
-
49
- /** Additional DT flag bits for a HAL mode, OR'd into the configure flags.
50
- * Returns '' for modes with no extra bits so the join leaves the mode alone. */
51
- function dtFlagsForMode(mode: string): string {
52
- const m = (mode ?? '').toLowerCase();
53
- if (m === 'input_pullup') return ' | GPIO_PULL_UP';
54
- if (m === 'input_pulldown') return ' | GPIO_PULL_DOWN';
55
- return '';
56
- }
57
-
58
- /** Combined mode + pull flags for a HAL mode string, e.g.
59
- * 'INPUT_PULLUP' 'GPIO_INPUT | GPIO_PULL_UP'. */
60
- function flagsForMode(mode: string): string {
61
- const base = MODE_MAP[mode] ?? 'GPIO_INPUT';
62
- return base + dtFlagsForMode(mode);
63
- }
64
-
65
- /**
66
- * Resolve a HAL gpio.* op to Zephyr C++.
67
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
68
- */
69
- export function lowerGpio(
70
- op: HALOpIR,
71
- chip: ZephyrChipDescriptor,
72
- ): { code?: string; expression?: string } {
73
- const o = op as any;
74
- const pin: number = o.pin;
75
- const spec = findDtSpec(chip, pin);
76
-
77
- if (spec) {
78
- return lowerGpioDtSpec(op, spec.dtSpec);
79
- }
80
- return lowerGpioRaw(op, chip);
81
- }
82
-
83
- /** Devicetree-spec path polarity-correct via gpio_pin_*_dt(). */
84
- function lowerGpioDtSpec(
85
- op: HALOpIR,
86
- dtSpec: string,
87
- ): { code?: string; expression?: string } {
88
- const o = op as any;
89
- const varName = dtSpecVarName(dtSpec);
90
-
91
- switch (op.operation) {
92
- case 'gpio.set_mode': {
93
- return { code: `gpio_pin_configure_dt(&${varName}, ${flagsForMode(o.mode)});` };
94
- }
95
- case 'gpio.write': {
96
- // Literal 0/1 stays as-is; runtime expression coerced to int via ternary.
97
- const v = o.value;
98
- const rhs = typeof v === 'string' ? `((${v}) ? 1 : 0)` : v ? 1 : 0;
99
- return { code: `gpio_pin_set_dt(&${varName}, ${rhs});` };
100
- }
101
- case 'gpio.read':
102
- return { expression: `gpio_pin_get_dt(&${varName})` };
103
- case 'gpio.toggle':
104
- return { code: `gpio_pin_toggle_dt(&${varName});` };
105
- default:
106
- throw new Error(
107
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
108
- `Open an issue or use rawCpp() to emit it manually.`,
109
- );
110
- }
111
- }
112
-
113
- /**
114
- * Raw-controller path for pins without a DT spec (and the manifest probe).
115
- * Uses the SoC's primary GPIO controller node. Polarity is physical.
116
- */
117
- function lowerGpioRaw(
118
- op: HALOpIR,
119
- chip: ZephyrChipDescriptor,
120
- ): { code?: string; expression?: string } {
121
- const o = op as any;
122
- const pin: number = o.pin;
123
- // Resolve the owning controller by pin range (ESP32-S3 splits GPIO across
124
- // gpio0/gpio1). For single-controller SoCs this is just chip.gpioController.
125
- const controller = `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, pin)}))`;
126
-
127
- switch (op.operation) {
128
- case 'gpio.set_mode': {
129
- return { code: `gpio_pin_configure(${controller}, ${pin}, ${flagsForMode(o.mode)});` };
130
- }
131
- case 'gpio.write': {
132
- const v = o.value;
133
- const rhs = typeof v === 'string' ? `((${v}) ? 1 : 0)` : v ? 1 : 0;
134
- return { code: `gpio_pin_set_raw(${controller}, ${pin}, ${rhs});` };
135
- }
136
- case 'gpio.read':
137
- return { expression: `gpio_pin_get_raw(${controller}, ${pin})` };
138
- case 'gpio.toggle':
139
- // Native atomic toggle — never read-modify-write. gpio_pin_get_raw on
140
- // a direction-only output reads the input latch, which is undefined on
141
- // SoCs that don't latch it. Zephyr's toggle API has no _raw variant —
142
- // gpio_pin_toggle is the driver-level atomic toggle, and for pins
143
- // configured without GPIO_ACTIVE_LOW the logical level equals the
144
- // physical one, so it matches the get_raw/set_raw used elsewhere.
145
- return { code: `gpio_pin_toggle(${controller}, ${pin});` };
146
- default:
147
- throw new Error(
148
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
149
- `Open an issue or use rawCpp() to emit it manually.`,
150
- );
151
- }
152
- }
1
+ // ---------------------------------------------------------------------------
2
+ // GPIO lowering — devicetree-spec bridge
3
+ //
4
+ // Translates HAL gpio.* ops to Zephyr driver C++. Two addressing modes:
5
+ //
6
+ // 1. Devicetree spec (preferred): if the HAL pin matches a dtSpec in the
7
+ // active chip descriptor, emit gpio_pin_*_dt() calls against a
8
+ // `__tc_dt_<alias>` gpio_dt_spec. This honors the node's polarity flags
9
+ // (GPIO_ACTIVE_LOW), so logical value 1 = LED on for an active-low LED.
10
+ //
11
+ // 2. Raw fallback: pins without a dtSpec are addressed via the SoC's gpio
12
+ // controller node (DEVICE_DT_GET(DT_NODELABEL(gpio0))) and the
13
+ // gpio_pin_*_raw() API. Polarity is physical (raw bypasses DT flags).
14
+ // The manifest validator's probe (which sends {operation, pin:0} with no
15
+ // port) hits this path, so it must return a lowered result, not undefined.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
19
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
20
+ import { controllerNodelabelForPin, controllerRawPinForPin } from '../chips/controllers.js';
21
+ import { ZEPHYR_GPIO_FLAGS } from '@typecad/hal';
22
+
23
+ /** The C identifier emitted for a pin's gpio_dt_spec variable. */
24
+ export function dtSpecVarName(dtSpec: string): string {
25
+ return `__tc_dt_${dtSpec}`;
26
+ }
27
+
28
+ /** Look up a dtSpec by GPIO pin number; undefined if the pin has none. */
29
+ function findDtSpec(chip: ZephyrChipDescriptor, pin: number) {
30
+ return chip.gpio.dtSpecs.find((s) => s.pin === pin);
31
+ }
32
+
33
+ // HAL passes UPPERCASE modes ("OUTPUT") while docs say lowercase ("output").
34
+ // The *_pullup / *_pulldown modes map to GPIO_INPUT (Zephyr does not have
35
+ // separate input+pull mode flags) and OR in a GPIO_PULL_UP / GPIO_PULL_DOWN
36
+ // bit via `dtFlagsForMode` without that bit the pin floats, so INPUT_PULLUP
37
+ // was a silent no-op (bug B1). Mirrors the gpio_pullup_en/gpio_pulldown_en
38
+ // extras framework-esp32 emits for the same modes.
39
+ const MODE_MAP: Record<string, string> = {
40
+ output: 'GPIO_OUTPUT',
41
+ OUTPUT: 'GPIO_OUTPUT',
42
+ input: 'GPIO_INPUT',
43
+ INPUT: 'GPIO_INPUT',
44
+ input_pullup: 'GPIO_INPUT',
45
+ INPUT_PULLUP: 'GPIO_INPUT',
46
+ input_pulldown: 'GPIO_INPUT',
47
+ INPUT_PULLDOWN: 'GPIO_INPUT',
48
+ };
49
+
50
+ /** Additional DT flag bits for a HAL mode, OR'd into the configure flags.
51
+ * Returns '' for modes with no extra bits so the join leaves the mode alone. */
52
+ function dtFlagsForMode(mode: string): string {
53
+ const m = (mode ?? '').toLowerCase();
54
+ if (m === 'input_pullup') return ' | GPIO_PULL_UP';
55
+ if (m === 'input_pulldown') return ' | GPIO_PULL_DOWN';
56
+ return '';
57
+ }
58
+
59
+ /** Combined mode + pull flags for a HAL mode string, e.g.
60
+ * 'INPUT_PULLUP' 'GPIO_INPUT | GPIO_PULL_UP'. */
61
+ function flagsForMode(mode: string): string {
62
+ const base = MODE_MAP[mode] ?? 'GPIO_INPUT';
63
+ return base + dtFlagsForMode(mode);
64
+ }
65
+
66
+ // ── Thin GPIO (hal/gpio-pin.ts) flag tokens ─────────────────────────────
67
+ //
68
+ // "GPIO.OUTPUT | GPIO.PULL_UP" token text maps name-for-name onto the GPIO_*
69
+ // macros. The name set comes from the GENERATED Zephyr token table (parsed
70
+ // from the pinned tree's headers); unknown tokens are build errors listing
71
+ // the valid spellings — the sensor-catalog discipline.
72
+
73
+ const GPIO_FLAG_TOKENS: Record<string, string> = Object.fromEntries(
74
+ ZEPHYR_GPIO_FLAGS.map((f) => [`GPIO.${f}`, `GPIO_${f}`]),
75
+ );
76
+
77
+ /** Map thin-GPIO flag token text to the GPIO_* macro expression. */
78
+ export function gpioFlagsToMacros(flags: string): string {
79
+ const tokens = String(flags ?? '')
80
+ .split('|')
81
+ .map((t) => t.trim())
82
+ .filter(Boolean);
83
+ if (tokens.length === 0) return 'GPIO_INPUT';
84
+ const macros = tokens.map((t) => {
85
+ const m = GPIO_FLAG_TOKENS[t];
86
+ if (!m) {
87
+ throw new Error(
88
+ `GPIO flag '${t}' is not a known GPIO.* token — valid: ${Object.keys(GPIO_FLAG_TOKENS).join(', ')}.`,
89
+ );
90
+ }
91
+ return m;
92
+ });
93
+ return macros.join(' | ');
94
+ }
95
+
96
+ /** Guarded per-pin configure statement for the dt-spec path: construction
97
+ * flags apply exactly once, ahead of the first use. */
98
+ function dtSpecConfigure(varName: string, dtSpec: string, flags: string): string {
99
+ return `{ ${dtSpecGuardConfigure(varName, dtSpec, flags)} }`;
100
+ }
101
+
102
+ /** The guard body only (no wrapping braces) — for fusing into
103
+ * statement-expressions. */
104
+ function dtSpecGuardConfigure(varName: string, dtSpec: string, flags: string): string {
105
+ const done = `__tc_gpio_cfg_${dtSpec.replace(/-/g, '_')}_done`;
106
+ return `static bool ${done} = false; if (!${done}) { gpio_pin_configure_dt(&${varName}, ${gpioFlagsToMacros(flags)}); ${done} = true; }`;
107
+ }
108
+
109
+ /** Guarded per-pin configure statement for the raw-controller path. */
110
+ function rawConfigure(controller: string, rawPin: number, pin: number, flags: string): string {
111
+ return `{ ${rawGuardConfigure(controller, rawPin, pin, flags)} }`;
112
+ }
113
+
114
+ /** The raw-path guard body only for fusing into statement-expressions. */
115
+ function rawGuardConfigure(controller: string, rawPin: number, pin: number, flags: string): string {
116
+ const done = `__tc_gpio_cfg_raw${pin}_done`;
117
+ return `static bool ${done} = false; if (!${done}) { gpio_pin_configure(${controller}, ${rawPin}, ${gpioFlagsToMacros(flags)}); ${done} = true; }`;
118
+ }
119
+
120
+ /**
121
+ * Resolve a HAL gpio.* op to Zephyr C++.
122
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
123
+ */
124
+ export function lowerGpio(
125
+ op: HALOpIR,
126
+ chip: ZephyrChipDescriptor,
127
+ ): { code?: string; expression?: string } {
128
+ const o = op as any;
129
+ const pin: number = o.pin;
130
+ const spec = findDtSpec(chip, pin);
131
+
132
+ if (spec) {
133
+ return lowerGpioDtSpec(op, spec.dtSpec);
134
+ }
135
+ return lowerGpioRaw(op, chip);
136
+ }
137
+
138
+ /** Devicetree-spec path — polarity-correct via gpio_pin_*_dt(). */
139
+ function lowerGpioDtSpec(
140
+ op: HALOpIR,
141
+ dtSpec: string,
142
+ ): { code?: string; expression?: string } {
143
+ const o = op as any;
144
+ const varName = dtSpecVarName(dtSpec);
145
+
146
+ switch (op.operation) {
147
+ case 'gpio.configure':
148
+ // Thin GPIO: construction flags, applied once per pin (guard).
149
+ return { code: dtSpecConfigure(varName, dtSpec, o.flags) };
150
+ case 'gpio.read_cfg':
151
+ // Thin GPIO get(): the guarded configure FUSED into the read — one
152
+ // statement-expression, correct in any expression position (a method's
153
+ // leading side-effect ops are dropped when the call sits in a pure
154
+ // expression, e.g. an if-condition).
155
+ return {
156
+ expression: `({ ${dtSpecGuardConfigure(varName, dtSpec, o.flags)} gpio_pin_get_dt(&${varName}); })`,
157
+ };
158
+ case 'gpio.write': {
159
+ // Literal 0/1 stays as-is; runtime expression coerced to int via ternary.
160
+ const v = o.value;
161
+ const rhs = typeof v === 'string' ? `((${v}) ? 1 : 0)` : v ? 1 : 0;
162
+ return { code: `gpio_pin_set_dt(&${varName}, ${rhs});` };
163
+ }
164
+ case 'gpio.read':
165
+ return { expression: `gpio_pin_get_dt(&${varName})` };
166
+ case 'gpio.toggle':
167
+ return { code: `gpio_pin_toggle_dt(&${varName});` };
168
+ default:
169
+ throw new Error(
170
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
171
+ `Open an issue or use rawCpp() to emit it manually.`,
172
+ );
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Raw-controller path — for pins without a DT spec (and the manifest probe).
178
+ * Uses the SoC's primary GPIO controller node. Polarity is physical.
179
+ */
180
+ function lowerGpioRaw(
181
+ op: HALOpIR,
182
+ chip: ZephyrChipDescriptor,
183
+ ): { code?: string; expression?: string } {
184
+ const o = op as any;
185
+ const pin: number = o.pin;
186
+ // Resolve the owning controller by pin range (ESP32-S3 splits GPIO across
187
+ // gpio0/gpio1; STM32 across gpioa/gpiob/gpioc). For single-controller SoCs
188
+ // this is just chip.gpioController. The raw API takes the PORT-RELATIVE
189
+ // index (STM32 gpiob is 0-15), not the global HAL pin number.
190
+ const controller = `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, pin)}))`;
191
+ const rawPin = controllerRawPinForPin(chip, pin);
192
+
193
+ switch (op.operation) {
194
+ case 'gpio.configure':
195
+ // Thin GPIO: construction flags, applied once per pin (guard).
196
+ return { code: rawConfigure(controller, rawPin, pin, o.flags) };
197
+ case 'gpio.read_cfg':
198
+ // Fused guarded configure + raw read (see the dt-spec path above).
199
+ return {
200
+ expression: `({ ${rawGuardConfigure(controller, rawPin, pin, o.flags)} gpio_pin_get_raw(${controller}, ${rawPin}); })`,
201
+ };
202
+ case 'gpio.shift_out':
203
+ case 'gpio.shift_in': {
204
+ // Bit-bang over the raw-controller path (shift pairs are arbitrary
205
+ // pins): configure both once (guarded), then clock the bits. Zephyr
206
+ // verbs only — gpio_pin_set_raw / gpio_pin_get_raw / k_busy_wait.
207
+ const dataCtrl = `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, o.dataPin)}))`;
208
+ const dataRaw = controllerRawPinForPin(chip, o.dataPin);
209
+ const clkCtrl = `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, o.clockPin)}))`;
210
+ const clkRaw = controllerRawPinForPin(chip, o.clockPin);
211
+ const cfg = `{ static bool __tc_shf${o.dataPin}_${o.clockPin}_done = false; if (!__tc_shf${o.dataPin}_${o.clockPin}_done) { gpio_pin_configure(${dataCtrl}, ${dataRaw}, GPIO_INPUT); gpio_pin_configure(${clkCtrl}, ${clkRaw}, GPIO_OUTPUT); __tc_shf${o.dataPin}_${o.clockPin}_done = true; } }`;
212
+ if (op.operation === 'gpio.shift_out') {
213
+ return {
214
+ code: `${cfg} for (int __i = ${(o.msbFirst ?? true) ? '7' : '0'}; ${(o.msbFirst ?? true) ? '__i >= 0' : '__i < 8'}; ${(o.msbFirst ?? true) ? '__i--' : '__i++'}) { gpio_pin_set_raw(${dataCtrl}, ${dataRaw}, ((static_cast<uint8_t>(${o.value})) >> __i) & 1); gpio_pin_set_raw(${clkCtrl}, ${clkRaw}, 1); k_busy_wait(1); gpio_pin_set_raw(${clkCtrl}, ${clkRaw}, 0); }`,
215
+ };
216
+ }
217
+ return {
218
+ expression: `({ ${cfg} uint8_t __b = 0; for (int __i = ${(o.msbFirst ?? true) ? '7' : '0'}; ${(o.msbFirst ?? true) ? '__i >= 0' : '__i < 8'}; ${(o.msbFirst ?? true) ? '__i--' : '__i++'}) { gpio_pin_set_raw(${clkCtrl}, ${clkRaw}, 1); k_busy_wait(1); __b = static_cast<uint8_t>((__b << 1) | (gpio_pin_get_raw(${dataCtrl}, ${dataRaw}) & 1)); gpio_pin_set_raw(${clkCtrl}, ${clkRaw}, 0); } __b; })`,
219
+ };
220
+ }
221
+ case 'gpio.write': {
222
+ const v = o.value;
223
+ const rhs = typeof v === 'string' ? `((${v}) ? 1 : 0)` : v ? 1 : 0;
224
+ return { code: `gpio_pin_set_raw(${controller}, ${rawPin}, ${rhs});` };
225
+ }
226
+ case 'gpio.read':
227
+ return { expression: `gpio_pin_get_raw(${controller}, ${rawPin})` };
228
+ case 'gpio.toggle':
229
+ // Native atomic toggle — never read-modify-write. gpio_pin_get_raw on
230
+ // a direction-only output reads the input latch, which is undefined on
231
+ // SoCs that don't latch it. Zephyr's toggle API has no _raw variant —
232
+ // gpio_pin_toggle is the driver-level atomic toggle, and for pins
233
+ // configured without GPIO_ACTIVE_LOW the logical level equals the
234
+ // physical one, so it matches the get_raw/set_raw used elsewhere.
235
+ return { code: `gpio_pin_toggle(${controller}, ${rawPin});` };
236
+ default:
237
+ throw new Error(
238
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
239
+ `Open an issue or use rawCpp() to emit it manually.`,
240
+ );
241
+ }
242
+ }