@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
@@ -19,7 +19,7 @@
19
19
  // collapse to a one-liner.
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
- import type { ZephyrChipDescriptor } from './types.js';
22
+ import type { ZephyrChipDescriptor, ZephyrGpioController } from './types.js';
23
23
 
24
24
  /**
25
25
  * Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
@@ -30,23 +30,53 @@ import type { ZephyrChipDescriptor } from './types.js';
30
30
  * for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
31
31
  */
32
32
  export function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string {
33
+ const hit = controllerRangeForPin(chip, pin);
34
+ return hit ? hit.nodelabel : chip.gpioController;
35
+ }
36
+
37
+ /**
38
+ * The owning controller range for a HAL pin, if the chip declares a split.
39
+ */
40
+ export function controllerRangeForPin(
41
+ chip: ZephyrChipDescriptor,
42
+ pin: number,
43
+ ): ZephyrGpioController | undefined {
33
44
  const ranges = chip.gpioControllers;
34
45
  if (ranges && ranges.length > 0) {
35
- const hit = ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
36
- if (hit) return hit.nodelabel;
46
+ return ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
37
47
  }
38
- return chip.gpioController;
48
+ return undefined;
39
49
  }
40
50
 
41
51
  /**
42
- * Emit the C++ source for a runtime pin GPIO-device dispatcher.
52
+ * The PORT-RELATIVE pin index for the raw gpio_pin_*_raw() API the Zephyr
53
+ * raw calls address the index WITHIN the controller, not the global HAL pin
54
+ * number. For split SoCs each controller's `minPin` is its base (STM32:
55
+ * gpiob minPin 16, so PB12 = pin 28 → raw 12); single-controller SoCs have
56
+ * no offset. Emitting the global number against a port driver would address
57
+ * a nonexistent port bit (STM32 gpiob is 0–15) and fail at runtime.
58
+ */
59
+ export function controllerRawPinForPin(chip: ZephyrChipDescriptor, pin: number): number {
60
+ const hit = controllerRangeForPin(chip, pin);
61
+ return pin - (hit?.minPin ?? 0);
62
+ }
63
+
64
+ /**
65
+ * Emit the C++ source for runtime pin → GPIO-device / port-relative-index
66
+ * dispatchers.
67
+ *
68
+ * Returns lines defining:
69
+ * - `static inline const struct device* __tc_gpio_dev(uint32_t pin)` — the
70
+ * owning controller's device, resolved via DEVICE_DT_GET(DT_NODELABEL(...))
71
+ * at compile time (the macro is evaluated per branch, so it is always
72
+ * statically valid); only `pin` is runtime.
73
+ * - `static inline gpio_pin_t __tc_gpio_pin(uint32_t pin)` — the
74
+ * port-relative index for gpio_pin_*_raw() (see controllerRawPinForPin).
75
+ * The runtime shim paths (UI pin-watch, safety voters) take a runtime pin,
76
+ * so they cannot bake the offset in at emit time.
43
77
  *
44
- * Returns lines defining `static inline const struct device* __tc_gpio_dev(uint32_t pin)`.
45
- * Each branch resolves its controller via `DEVICE_DT_GET(DT_NODELABEL(...))` at
46
- * compile time (the macro is evaluated per branch, so it is always statically
47
- * valid); only `pin` is runtime. For a single-controller SoC this collapses to
48
- * a one-liner returning that controller, so the existing XIAO nRF52840 behavior
49
- * is byte-for-byte unchanged.
78
+ * For a single-controller SoC both collapse to one-liners, so the existing
79
+ * XIAO nRF52840 behavior is byte-for-byte unchanged.
50
80
  */
51
81
  export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
52
82
  const ranges = chip.gpioControllers;
@@ -56,19 +86,38 @@ export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
56
86
  ` (void)pin;`,
57
87
  ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
58
88
  '}',
89
+ 'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
90
+ ` return (gpio_pin_t)pin;`,
91
+ '}',
59
92
  ];
60
93
  }
61
94
  const lines: string[] = [
62
95
  'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
63
96
  ];
97
+ const pinLines: string[] = [
98
+ 'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
99
+ ];
64
100
  for (const r of ranges) {
65
101
  lines.push(
66
102
  ` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`,
67
103
  );
104
+ if (r.minPin === 0) {
105
+ pinLines.push(
106
+ ` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)pin; }`,
107
+ );
108
+ } else {
109
+ pinLines.push(
110
+ ` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)(pin - ${r.minPin}); }`,
111
+ );
112
+ }
68
113
  }
69
114
  lines.push(
70
115
  ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
71
116
  '}',
72
117
  );
73
- return lines;
118
+ pinLines.push(
119
+ ` return (gpio_pin_t)pin;`,
120
+ '}',
121
+ );
122
+ return [...lines, ...pinLines];
74
123
  }
@@ -1,54 +1,35 @@
1
- // ---------------------------------------------------------------------------
2
- // Chip descriptor registrymaps Zephyr board target ZephyrChipDescriptor
3
- //
4
- // Mirrors framework-esp32/src/chips/index.ts: a module-level activeChip,
5
- // setActiveChip/getActiveChip, and chipForTarget resolving from
6
- // frameworkData.buildTarget. The strategy calls setActiveChip during profile
7
- // resolution so the lowering reads a single cached descriptor.
8
- // ---------------------------------------------------------------------------
9
-
10
- import type { ZephyrChipDescriptor } from './types.js';
11
- import { XIAO_BLE } from './xiao-ble.js';
12
- import { ESP32S3_DEVKITC } from './esp32s3.js';
13
- import { ESP32_DEVKITC } from './esp32.js';
14
-
15
- export { XIAO_BLE, ESP32S3_DEVKITC, ESP32_DEVKITC };
16
- export type { ZephyrChipDescriptor, ZephyrGpioDtSpec } from './types.js';
17
-
18
- /**
19
- * Default chip used when no buildTarget is supplied. The XIAO nRF52840 is the
20
- * canonical MVP target; subsequent board additions extend the switch below.
21
- */
22
- const DEFAULT_CHIP: ZephyrChipDescriptor = XIAO_BLE;
23
-
24
- let activeChip: ZephyrChipDescriptor = DEFAULT_CHIP;
25
-
26
- export function setActiveChip(d: ZephyrChipDescriptor): void {
27
- activeChip = d;
28
- }
29
-
30
- export function getActiveChip(): ZephyrChipDescriptor {
31
- return activeChip;
32
- }
33
-
34
- /**
35
- * Resolve a chip descriptor from the Zephyr board target string
36
- * (frameworkData.buildTarget / frameworkData.target). Accepts the bare board
37
- * id ('xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc') or a board/qualifier
38
- * path ('esp32s3_devkitc/esp32s3/procpu', 'esp32_devkitc/esp32/procpu').
39
- */
40
- export function chipForTarget(target?: string): ZephyrChipDescriptor {
41
- const t = (target ?? '').trim().toLowerCase();
42
- const boardId = t.split('/')[0];
43
- switch (boardId) {
44
- case 'xiao_ble':
45
- return XIAO_BLE;
46
- case 'esp32s3_devkitc':
47
- return ESP32S3_DEVKITC;
48
- case 'esp32_devkitc':
49
- return ESP32_DEVKITC;
50
- case '':
51
- default:
52
- return DEFAULT_CHIP;
53
- }
54
- }
1
+ // ---------------------------------------------------------------------------
2
+ // Chip descriptor cachethe mechanism the strategy's lowering reads.
3
+ //
4
+ // There is no curated descriptor registry: every board's chip view is
5
+ // reconstructed from its generated board manifest via resolveChipFromBoard
6
+ // (src/chips/resolve.ts). This module only caches that resolved descriptor
7
+ // for the process lifetime (setActiveChip during profile resolution, the
8
+ // lowering reads getActiveChip) and provides the empty fallback for
9
+ // contexts with no board at all.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ import type { ZephyrChipDescriptor } from './types.js';
13
+
14
+ /**
15
+ * The no-board fallback: no controllers, no buses, no silicon facts. The
16
+ * lowering treats it as "unsupported operation" per subsystem, which is the
17
+ * honest answer when no board resolved.
18
+ */
19
+ export const NO_BOARD_CHIP: ZephyrChipDescriptor = {
20
+ id: 'no-board',
21
+ soc: '',
22
+ gpioController: 'gpio0',
23
+ gpio: { dtSpecs: [] },
24
+ probeMethods: [],
25
+ };
26
+
27
+ let activeChip: ZephyrChipDescriptor = NO_BOARD_CHIP;
28
+
29
+ export function setActiveChip(d: ZephyrChipDescriptor): void {
30
+ activeChip = d;
31
+ }
32
+
33
+ export function getActiveChip(): ZephyrChipDescriptor {
34
+ return activeChip;
35
+ }
@@ -7,20 +7,22 @@
7
7
  // (from the MCU package's zephyr field) with board-level overrides (from the
8
8
  // board package's zephyr field).
9
9
  //
10
- // When a board package carries Zephyr config, this path replaces the
11
- // hardcoded chip descriptor registry. When it doesn't (legacy), chipForTarget
12
- // still works as the fallback.
10
+ // This is the ONE resolution path: every board's chip view reconstructs
11
+ // from its generated manifest there is no curated registry to fall back
12
+ // to.
13
13
  // ---------------------------------------------------------------------------
14
14
 
15
15
  import type { BoardConstants } from '@typecad/cuttlefish/api/shared';
16
16
  import type {
17
17
  ZephyrChipDescriptor,
18
+ ZephyrProbeMethod,
18
19
  ZephyrGpioDtSpec,
19
20
  ZephyrGpioController,
20
21
  ZephyrBusController,
21
22
  ZephyrPwmSpec,
22
23
  ZephyrInterruptPin,
23
24
  ZephyrAdcChannel,
25
+ ZephyrDacChannel,
24
26
  } from './types.js';
25
27
 
26
28
  /** Collect an indexed array of objects reconstructed from flat dot-path keys. */
@@ -49,7 +51,34 @@ function collectBusControllers(
49
51
  ): ZephyrBusController[] {
50
52
  return collectIndexed<ZephyrBusController>(bc, prefix, (m, i) => {
51
53
  const nodeLabel = m.get(`${prefix}.${i}.nodeLabel`) as string;
52
- return nodeLabel ? { nodeLabel } : null;
54
+ if (!nodeLabel) return null;
55
+ // Optional pinctrl synthesis data (nested object; string arrays arrive
56
+ // comma-joined from the board-constants flattener).
57
+ const splitCsv = (v: unknown): string[] | undefined =>
58
+ typeof v === 'string' && v.length > 0
59
+ ? v.split(',').map((s) => s.trim()).filter(Boolean)
60
+ : undefined;
61
+ const include = m.get(`${prefix}.${i}.pinctrl.include`) as string | undefined;
62
+ const pinmux = splitCsv(m.get(`${prefix}.${i}.pinctrl.pinmux`));
63
+ const inputPinmux = splitCsv(m.get(`${prefix}.${i}.pinctrl.inputPinmux`));
64
+ const defines = splitCsv(m.get(`${prefix}.${i}.pinctrl.defines`));
65
+ const pinctrlRef = m.get(`${prefix}.${i}.pinctrlRef`) as string | undefined;
66
+ const props = splitCsv(m.get(`${prefix}.${i}.props`));
67
+ return {
68
+ nodeLabel,
69
+ ...(include && pinmux
70
+ ? {
71
+ pinctrl: {
72
+ include,
73
+ pinmux,
74
+ ...(inputPinmux ? { inputPinmux } : {}),
75
+ ...(defines ? { defines } : {}),
76
+ },
77
+ }
78
+ : {}),
79
+ ...(pinctrlRef ? { pinctrlRef } : {}),
80
+ ...(props ? { props } : {}),
81
+ };
53
82
  });
54
83
  }
55
84
 
@@ -57,7 +86,7 @@ function collectBusControllers(
57
86
  * Try to derive a ZephyrChipDescriptor from board/MCU package constants.
58
87
  *
59
88
  * Returns null when no zephyr info is available in the board constants
60
- * (the caller should fall back to the hardcoded chipForTarget registry).
89
+ * (the caller should treat the board as unresolved NO_BOARD_CHIP).
61
90
  */
62
91
  export function resolveChipFromBoard(
63
92
  bc: BoardConstants | undefined,
@@ -65,10 +94,25 @@ export function resolveChipFromBoard(
65
94
  if (!bc) return null;
66
95
 
67
96
  const boardTarget = bc.get('build.frameworks.zephyr') as string | undefined;
68
- if (!boardTarget) return null;
97
+ // MCU-silicon entry: an MCU-only config (no board package) carries the
98
+ // chip facts under zephyr.* without any build.frameworks target (the
99
+ // build target comes from frameworkData instead — a custom board name).
100
+ const socsRaw = bc.get('zephyr.socs') as string | undefined;
101
+ if (!boardTarget && !socsRaw) return null;
102
+
103
+ const socs = typeof socsRaw === 'string' && socsRaw.length > 0
104
+ ? socsRaw.split(',').map((s) => s.trim()).filter(Boolean)
105
+ : [];
106
+ // Chip identity: the board target when a board resolved it, else the SoC
107
+ // (an MCU-only chip — its "board" is the generated custom board).
108
+ const id = boardTarget ?? socs[0] ?? 'custom';
69
109
 
70
110
  const zGpioController = bc.get('zephyr.gpioController') as string | undefined;
71
- const soc = (bc.get('mcu.id') as string) ?? '';
111
+ // The soc name: MCU packages key it under mcu.id; generated board
112
+ // manifests carry it as the second segment of the qualified target.
113
+ const soc = (bc.get('mcu.id') as string)
114
+ ?? (socs.length > 0 ? socs[0] : undefined)
115
+ ?? (boardTarget && boardTarget.includes('/') ? boardTarget.split('/')[1] : '');
72
116
 
73
117
  // ── Build mutable sub-objects, then construct the final descriptor ──────
74
118
 
@@ -102,24 +146,184 @@ export function resolveChipFromBoard(
102
146
 
103
147
  const pwmSpecs = collectIndexed<ZephyrPwmSpec>(bc, 'zephyr.pwm.specs', (m, i) => {
104
148
  const pin = m.get(`zephyr.pwm.specs.${i}.pin`) as number;
105
- const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`) as string;
106
- if (pin != null && dtSpec) return { pin, dtSpec };
107
- return null;
149
+ const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`) as string | undefined;
150
+ const controller = m.get(`zephyr.pwm.specs.${i}.controller`) as string | undefined;
151
+ const channel = m.get(`zephyr.pwm.specs.${i}.channel`) as number | undefined;
152
+ const periodNs = m.get(`zephyr.pwm.specs.${i}.periodNs`) as number | undefined;
153
+ const polarity = m.get(`zephyr.pwm.specs.${i}.polarity`) as string | undefined;
154
+ const pinctrl = m.get(`zephyr.pwm.specs.${i}.pinctrl`) as string | undefined;
155
+ // Board-shipped alias form (dtSpec) or synthesized form (controller +
156
+ // channel → overlay-generated alias) — at least one, else drop the entry.
157
+ if (pin == null || !(dtSpec || (controller && channel != null))) return null;
158
+ return {
159
+ pin,
160
+ ...(dtSpec ? { dtSpec } : {}),
161
+ ...(controller ? { controller } : {}),
162
+ ...(channel != null ? { channel } : {}),
163
+ ...(periodNs != null ? { periodNs } : {}),
164
+ ...(polarity ? { polarity } : {}),
165
+ ...(pinctrl ? { pinctrl } : {}),
166
+ };
108
167
  });
109
168
 
110
169
  const adcNodeLabel = bc.get('zephyr.adc.nodeLabel') as string | undefined;
170
+ // PWM timer input clock — feeds the overlay's 16-bit prescaler derivation.
171
+ const pwmClockHz = bc.get('zephyr.pwm.clockHz') as number | undefined;
172
+
173
+ // PWM matrix (ESP32 LEDC): any listed pin can carry any channel; channels
174
+ // are assigned to the driven pins at build time (dt-config/overlay.ts).
175
+ const matrixController = bc.get('zephyr.pwm.matrix.controller') as string | undefined;
176
+ const matrixChannelCount = bc.get('zephyr.pwm.matrix.channelCount') as number | undefined;
177
+ const matrixPins = collectIndexed<number>(
178
+ bc, 'zephyr.pwm.matrix.pins',
179
+ (m, i) => (m.get(`zephyr.pwm.matrix.pins.${i}`) as number | undefined) ?? null,
180
+ );
181
+ const pwmMatrix = matrixController && matrixChannelCount
182
+ ? { controller: matrixController, channelCount: matrixChannelCount, pins: matrixPins }
183
+ : undefined;
184
+
111
185
  const adcResolution = bc.get('zephyr.adc.resolution') as number | undefined;
112
186
  const adcVref = bc.get('zephyr.adc.vrefMv') as number | undefined;
187
+ const adcGain = bc.get('zephyr.adc.gain') as string | undefined;
188
+ const adcReference = bc.get('zephyr.adc.reference') as string | undefined;
113
189
  const adcChannels = collectIndexed<ZephyrAdcChannel>(bc, 'zephyr.adc.channels', (m, i) => {
114
190
  const pin = m.get(`zephyr.adc.channels.${i}.pin`) as number;
115
191
  const channel = m.get(`zephyr.adc.channels.${i}.channel`) as number;
116
- if (pin != null && channel != null) return { pin, channel };
192
+ const pinctrl = m.get(`zephyr.adc.channels.${i}.pinctrl`) as string | undefined;
193
+ const controller = m.get(`zephyr.adc.channels.${i}.controller`) as string | undefined;
194
+ if (pin != null && channel != null) {
195
+ return { pin, channel, ...(pinctrl ? { pinctrl } : {}), ...(controller ? { controller } : {}) };
196
+ }
197
+ return null;
198
+ });
199
+
200
+ const dacDevice = bc.get('zephyr.dac.device') as string | undefined;
201
+ const dacChannels = collectIndexed<ZephyrDacChannel>(bc, 'zephyr.dac.channels', (m, i) => {
202
+ const pin = m.get(`zephyr.dac.channels.${i}.pin`) as number;
203
+ const channel = m.get(`zephyr.dac.channels.${i}.channel`) as number;
204
+ const resolution = m.get(`zephyr.dac.channels.${i}.resolution`) as number | undefined;
205
+ if (pin != null && channel != null) {
206
+ return { pin, channel, resolution: resolution ?? 12 };
207
+ }
117
208
  return null;
118
209
  });
119
210
 
120
211
  const wdtNodeLabel = bc.get('zephyr.wdt.nodeLabel') as string | undefined;
212
+
213
+ // Hardware counters (Zephyr counter devices): nodeLabel always; the child
214
+ // form carries the timer parent the overlay attaches the (label-less)
215
+ // counter child to.
216
+ const hwtimerControllers = collectIndexed<ZephyrBusController>(bc, 'zephyr.hwtimer.controllers', (m, i) => {
217
+ const nodeLabel = m.get(`zephyr.hwtimer.controllers.${i}.nodeLabel`) as string;
218
+ const counterParent = m.get(`zephyr.hwtimer.controllers.${i}.counterParent`) as string | undefined;
219
+ if (!nodeLabel) return null;
220
+ return { nodeLabel, ...(counterParent ? { counterParent } : {}) };
221
+ });
222
+
223
+ // Storage partition synthesis (boards whose DTS ships no storage_partition
224
+ // — see ZephyrChipDescriptor.storage).
225
+ const storageOffset = bc.get('zephyr.storage.offset') as number | undefined;
226
+ const storageSize = bc.get('zephyr.storage.size') as number | undefined;
227
+ const storagePreexisting = bc.get('zephyr.storage.preexisting') as boolean | undefined;
228
+
229
+ // USB device (CDC-ACM): zephyr.usb.controller + zephyr.usb.cdcInstances
230
+ // (+ optional vid/pid for the device descriptor).
231
+ const usbController = bc.get('zephyr.usb.controller') as string | undefined;
232
+ const usbCdcInstances = bc.get('zephyr.usb.cdcInstances') as number | undefined;
233
+ const usbVid = bc.get('zephyr.usb.vid') as string | undefined;
234
+ const usbPid = bc.get('zephyr.usb.pid') as string | undefined;
235
+ // Optional 1200-baud touch-to-reset data (BOSSA-bootloader boards).
236
+ const trFlag = bc.get('zephyr.usb.touchReset.flagAddress') as number | undefined;
237
+ const trMagic = bc.get('zephyr.usb.touchReset.magic') as number | undefined;
238
+ const trVid = bc.get('zephyr.usb.touchReset.bootloaderVid') as string | undefined;
239
+ const trPid = bc.get('zephyr.usb.touchReset.bootloaderPid') as string | undefined;
240
+
241
+ const probeMethods = collectIndexed<ZephyrProbeMethod>(bc, 'zephyr.probeMethods', (m, i) => { const id = m.get(`zephyr.probeMethods.${i}.id`) as string;
242
+ const runner = m.get(`zephyr.probeMethods.${i}.runner`) as string;
243
+ if (!id || !runner) return null;
244
+ const argsRaw = m.get(`zephyr.probeMethods.${i}.args`) as string | undefined;
245
+ const description = m.get(`zephyr.probeMethods.${i}.description`) as string | undefined;
246
+ const debugRaw = m.get(`zephyr.probeMethods.${i}.debug`) as boolean | undefined;
247
+ const debugInterface = m.get(`zephyr.probeMethods.${i}.debugInterface`) as string | undefined;
248
+ const debugDevice = m.get(`zephyr.probeMethods.${i}.debugDevice`) as string | undefined;
249
+ const debugCfgRaw = m.get(`zephyr.probeMethods.${i}.debugCfg`) as string | undefined;
250
+ const debugCfgSourceRaw = m.get(`zephyr.probeMethods.${i}.debugCfgSource`) as string | undefined;
251
+ // Args are comma-joined (west flags never carry commas); cfg lines are
252
+ // newline-joined (tcl legally contains commas).
253
+ const split = (v: string | undefined): string[] | undefined =>
254
+ typeof v === 'string' && v.length > 0 ? v.split(',') : undefined;
255
+ const splitLines = (v: string | undefined): string[] | undefined =>
256
+ typeof v === 'string' && v.length > 0 ? v.split('\n') : undefined;
257
+ const args = split(argsRaw);
258
+ return {
259
+ id,
260
+ runner,
261
+ ...(args ? { args } : {}),
262
+ ...(description ? { description } : {}),
263
+ ...(debugRaw !== undefined ? { debug: debugRaw } : {}),
264
+ ...(debugInterface ? { debugInterface: debugInterface as 'swd' | 'jtag' } : {}),
265
+ ...(debugDevice ? { debugDevice } : {}),
266
+ ...(split(debugCfgRaw) ? { debugCfg: splitLines(debugCfgRaw) } : {}),
267
+ ...(split(debugCfgSourceRaw) ? { debugCfgSource: splitLines(debugCfgSourceRaw) } : {}),
268
+ };
269
+ });
121
270
  const wifiSupported = bc.get('zephyr.wifi.supported') as boolean | undefined;
122
271
 
272
+ // ── Custom-board generator inputs (MCU silicon) ─────────────────────────
273
+ // Only complete when the MCU package carried a default console + clock
274
+ // plan — without those there is nothing to generate a board from.
275
+ const consoleNodeLabel = bc.get('zephyr.console.nodeLabel') as string | undefined;
276
+ const consoleTx = bc.get('zephyr.console.tx') as string | undefined;
277
+ const consoleRx = bc.get('zephyr.console.rx') as string | undefined;
278
+ const consoleSpeed = bc.get('zephyr.console.speed') as number | undefined;
279
+ const hseMHz = bc.get('zephyr.clocks.hseMHz') as number | undefined;
280
+ const pllDivM = bc.get('zephyr.clocks.pll.divM') as number | undefined;
281
+ const pllMulN = bc.get('zephyr.clocks.pll.mulN') as number | undefined;
282
+ const pllDivP = bc.get('zephyr.clocks.pll.divP') as number | undefined;
283
+ const pllDivQ = bc.get('zephyr.clocks.pll.divQ') as number | undefined;
284
+ const sysMHz = bc.get('zephyr.clocks.sysMHz') as number | undefined;
285
+ const dtsIncludesRaw = bc.get('zephyr.dtsIncludes') as string | undefined;
286
+ const usbNode = bc.get('zephyr.usb.usbNode') as string | undefined;
287
+ const usbPinctrlRaw = bc.get('zephyr.usb.pinctrl') as string | undefined;
288
+ const usbPinctrl = typeof usbPinctrlRaw === 'string' && usbPinctrlRaw.length > 0
289
+ ? usbPinctrlRaw.split(',').map((s) => s.trim()).filter(Boolean)
290
+ : undefined;
291
+ const cbAdcNodeLabel = bc.get('zephyr.adcNode.nodeLabel') as string | undefined;
292
+ const cbAdcClockSource = bc.get('zephyr.adcNode.clockSource') as string | undefined;
293
+ const cbAdcPrescaler = bc.get('zephyr.adcNode.prescaler') as number | undefined;
294
+ const cbAdcPinctrl = bc.get('zephyr.adcNode.pinctrl') as string | undefined;
295
+ const customBoard =
296
+ socs.length > 0 &&
297
+ consoleNodeLabel && consoleTx && consoleRx &&
298
+ hseMHz != null && pllDivM != null && pllMulN != null && pllDivP != null &&
299
+ pllDivQ != null && sysMHz != null
300
+ ? {
301
+ socs,
302
+ dtsIncludes: typeof dtsIncludesRaw === 'string' && dtsIncludesRaw.length > 0
303
+ ? dtsIncludesRaw.split(',').map((s) => s.trim()).filter(Boolean)
304
+ : [],
305
+ console: {
306
+ nodeLabel: consoleNodeLabel,
307
+ tx: consoleTx,
308
+ rx: consoleRx,
309
+ speed: consoleSpeed ?? 115200,
310
+ },
311
+ clocks: {
312
+ hseMHz,
313
+ pll: { divM: pllDivM, mulN: pllMulN, divP: pllDivP, divQ: pllDivQ },
314
+ sysMHz,
315
+ ahbPrescaler: (bc.get('zephyr.clocks.ahbPrescaler') as number) ?? 1,
316
+ apb1Prescaler: (bc.get('zephyr.clocks.apb1Prescaler') as number) ?? 1,
317
+ apb2Prescaler: (bc.get('zephyr.clocks.apb2Prescaler') as number) ?? 1,
318
+ },
319
+ ...(usbNode ? { usbNode } : {}),
320
+ ...(usbPinctrl ? { usbPinctrl } : {}),
321
+ ...(cbAdcNodeLabel && cbAdcClockSource && cbAdcPrescaler != null && cbAdcPinctrl
322
+ ? { adcNode: { nodeLabel: cbAdcNodeLabel, clockSource: cbAdcClockSource, prescaler: cbAdcPrescaler, pinctrl: cbAdcPinctrl } }
323
+ : {}),
324
+ }
325
+ : undefined;
326
+
123
327
  // ── Construct the final readonly descriptor ─────────────────────────────
124
328
 
125
329
  const gpio: ZephyrChipDescriptor['gpio'] = {
@@ -128,7 +332,7 @@ export function resolveChipFromBoard(
128
332
  };
129
333
 
130
334
  return {
131
- id: boardTarget,
335
+ id,
132
336
  soc,
133
337
  gpioController: zGpioController ?? 'gpio0',
134
338
  ...(gc.length > 0 ? { gpioControllers: gc } : {}),
@@ -136,11 +340,109 @@ export function resolveChipFromBoard(
136
340
  ...(i2cControllers.length > 0 ? { i2c: { controllers: i2cControllers } } : {}),
137
341
  ...(spiControllers.length > 0 ? { spi: { controllers: spiControllers } } : {}),
138
342
  ...(uartControllers.length > 0 ? { uart: { controllers: uartControllers } } : {}),
139
- ...(pwmSpecs.length > 0 ? { pwm: { specs: pwmSpecs } } : {}),
343
+ ...(pwmSpecs.length > 0 || pwmMatrix
344
+ ? {
345
+ pwm: {
346
+ specs: pwmSpecs,
347
+ ...(pwmMatrix ? { matrix: pwmMatrix } : {}),
348
+ ...(pwmClockHz ? { clockHz: pwmClockHz } : {}),
349
+ },
350
+ }
351
+ : {}),
140
352
  ...(adcNodeLabel || adcResolution != null || adcVref != null || adcChannels.length > 0
141
- ? { adc: { nodeLabel: adcNodeLabel ?? 'adc', resolution: adcResolution ?? 12, vrefMv: adcVref ?? 3000, channels: adcChannels } }
353
+ ? {
354
+ adc: {
355
+ nodeLabel: adcNodeLabel ?? 'adc',
356
+ resolution: adcResolution ?? 12,
357
+ vrefMv: adcVref ?? 3000,
358
+ channels: adcChannels,
359
+ ...(adcGain ? { gain: adcGain } : {}),
360
+ ...(adcReference ? { reference: adcReference } : {}),
361
+ },
362
+ }
142
363
  : {}),
364
+ ...(dacDevice && dacChannels.length > 0 ? { dac: { device: dacDevice, channels: dacChannels } } : {}),
143
365
  ...(wdtNodeLabel ? { wdt: { nodeLabel: wdtNodeLabel } } : {}),
366
+ ...(hwtimerControllers.length > 0 ? { hwtimer: { controllers: hwtimerControllers } } : {}),
367
+ ...(storageOffset != null && storageSize != null
368
+ ? { storage: { offset: storageOffset, size: storageSize, ...(storagePreexisting ? { preexisting: true } : {}) } }
369
+ : {}),
370
+ ...(usbController && usbCdcInstances && usbCdcInstances > 0
371
+ ? {
372
+ usb: {
373
+ controller: usbController,
374
+ cdcInstances: usbCdcInstances,
375
+ ...(usbVid ? { vid: usbVid } : {}),
376
+ ...(usbPid ? { pid: usbPid } : {}),
377
+ ...(trFlag != null && trMagic != null
378
+ ? {
379
+ touchReset: {
380
+ flagAddress: trFlag,
381
+ magic: trMagic,
382
+ ...(trVid ? { bootloaderVid: trVid } : {}),
383
+ ...(trPid ? { bootloaderPid: trPid } : {}),
384
+ },
385
+ }
386
+ : {}),
387
+ },
388
+ }
389
+ : {}),
144
390
  ...(wifiSupported ? { wifi: { supported: true as const } } : {}),
391
+ ...(probeMethods.length > 0 ? { probeMethods } : {}),
392
+ ...(customBoard ? { customBoard } : {}),
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Board-level PWM specs from the generated board manifest (zephyr.pwm.specs.*
398
+ * — the pwm-leds facts boardgen emits on virtual pins 8192+). The active
399
+ * chip's spec table is silicon-curated; these JOIN it (see mergeBoardPwmSpecs)
400
+ * so a board's own devicetree-aliased PWM LED is addressable without curation.
401
+ */
402
+ export function boardPwmSpecsFromConstants(
403
+ bc: BoardConstants | undefined,
404
+ ): ZephyrPwmSpec[] {
405
+ if (!bc) return [];
406
+ const specs: ZephyrPwmSpec[] = [];
407
+ for (let i = 0; i < 256; i++) {
408
+ const pin = bc.get(`zephyr.pwm.specs.${i}.pin`) as number | undefined;
409
+ if (pin == null) break;
410
+ const dtSpec = bc.get(`zephyr.pwm.specs.${i}.dtSpec`) as string | undefined;
411
+ const controller = bc.get(`zephyr.pwm.specs.${i}.controller`) as string | undefined;
412
+ const channel = bc.get(`zephyr.pwm.specs.${i}.channel`) as number | undefined;
413
+ if (!(dtSpec || (controller && channel != null))) continue;
414
+ specs.push({
415
+ pin,
416
+ ...(dtSpec ? { dtSpec } : {}),
417
+ ...(controller ? { controller } : {}),
418
+ ...(channel != null ? { channel } : {}),
419
+ });
420
+ }
421
+ return specs;
422
+ }
423
+
424
+ /**
425
+ * Merge board-level PWM specs into a chip descriptor. Collision rule: a
426
+ * board spec whose pin OR dtSpec already exists in the chip's table is
427
+ * dropped (the curated entry wins — e.g. the XIAO's hand-tuned pwm-led0
428
+ * spec). Returns the chip unchanged when nothing joins.
429
+ */
430
+ export function mergeBoardPwmSpecs(
431
+ chip: ZephyrChipDescriptor,
432
+ boardSpecs: readonly ZephyrPwmSpec[],
433
+ ): ZephyrChipDescriptor {
434
+ if (boardSpecs.length === 0) return chip;
435
+ const existing = chip.pwm?.specs ?? [];
436
+ const joining = boardSpecs.filter(
437
+ (s) => !existing.some((e) => e.pin === s.pin || (s.dtSpec != null && e.dtSpec === s.dtSpec)),
438
+ );
439
+ if (joining.length === 0) return chip;
440
+ return {
441
+ ...chip,
442
+ pwm: {
443
+ specs: [...existing, ...joining],
444
+ ...(chip.pwm?.matrix ? { matrix: chip.pwm.matrix } : {}),
445
+ ...(chip.pwm?.clockHz ? { clockHz: chip.pwm.clockHz } : {}),
446
+ },
145
447
  };
146
448
  }