@typecad/framework-zephyr 1.0.0-alpha.10

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 (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/async/timer-polyfill.d.ts +10 -0
  4. package/dist/async/timer-polyfill.js +95 -0
  5. package/dist/chips/controllers.d.ts +21 -0
  6. package/dist/chips/controllers.js +66 -0
  7. package/dist/chips/esp32.d.ts +2 -0
  8. package/dist/chips/esp32.js +59 -0
  9. package/dist/chips/esp32s3.d.ts +2 -0
  10. package/dist/chips/esp32s3.js +54 -0
  11. package/dist/chips/index.d.ts +15 -0
  12. package/dist/chips/index.js +45 -0
  13. package/dist/chips/resolve.d.ts +9 -0
  14. package/dist/chips/resolve.js +121 -0
  15. package/dist/chips/types.d.ts +154 -0
  16. package/dist/chips/types.js +14 -0
  17. package/dist/chips/xiao-ble.d.ts +2 -0
  18. package/dist/chips/xiao-ble.js +61 -0
  19. package/dist/debug-codegen.d.ts +33 -0
  20. package/dist/debug-codegen.js +162 -0
  21. package/dist/display/gfx.d.ts +17 -0
  22. package/dist/display/gfx.js +171 -0
  23. package/dist/display/index.d.ts +20 -0
  24. package/dist/display/index.js +53 -0
  25. package/dist/display/profiles.d.ts +25 -0
  26. package/dist/display/profiles.js +41 -0
  27. package/dist/display/touch-adapter.d.ts +9 -0
  28. package/dist/display/touch-adapter.js +95 -0
  29. package/dist/display/ui-adapter.d.ts +21 -0
  30. package/dist/display/ui-adapter.js +534 -0
  31. package/dist/doctor.d.ts +6 -0
  32. package/dist/doctor.js +48 -0
  33. package/dist/dt-config/kconfig.d.ts +27 -0
  34. package/dist/dt-config/kconfig.js +283 -0
  35. package/dist/dt-config/overlay.d.ts +35 -0
  36. package/dist/dt-config/overlay.js +246 -0
  37. package/dist/framework.manifest.d.ts +101 -0
  38. package/dist/framework.manifest.js +536 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +17 -0
  41. package/dist/lowering/adc.d.ts +19 -0
  42. package/dist/lowering/adc.js +76 -0
  43. package/dist/lowering/ble.d.ts +20 -0
  44. package/dist/lowering/ble.js +419 -0
  45. package/dist/lowering/board.d.ts +9 -0
  46. package/dist/lowering/board.js +33 -0
  47. package/dist/lowering/gpio.d.ts +12 -0
  48. package/dist/lowering/gpio.js +123 -0
  49. package/dist/lowering/http.d.ts +23 -0
  50. package/dist/lowering/http.js +482 -0
  51. package/dist/lowering/i2c.d.ts +15 -0
  52. package/dist/lowering/i2c.js +114 -0
  53. package/dist/lowering/index.d.ts +29 -0
  54. package/dist/lowering/index.js +93 -0
  55. package/dist/lowering/interrupts.d.ts +21 -0
  56. package/dist/lowering/interrupts.js +91 -0
  57. package/dist/lowering/mqtt.d.ts +19 -0
  58. package/dist/lowering/mqtt.js +366 -0
  59. package/dist/lowering/power.d.ts +9 -0
  60. package/dist/lowering/power.js +64 -0
  61. package/dist/lowering/preferences.d.ts +23 -0
  62. package/dist/lowering/preferences.js +345 -0
  63. package/dist/lowering/pulse.d.ts +7 -0
  64. package/dist/lowering/pulse.js +82 -0
  65. package/dist/lowering/pwm.d.ts +15 -0
  66. package/dist/lowering/pwm.js +59 -0
  67. package/dist/lowering/random.d.ts +18 -0
  68. package/dist/lowering/random.js +129 -0
  69. package/dist/lowering/spi.d.ts +17 -0
  70. package/dist/lowering/spi.js +113 -0
  71. package/dist/lowering/timing.d.ts +9 -0
  72. package/dist/lowering/timing.js +63 -0
  73. package/dist/lowering/tone.d.ts +10 -0
  74. package/dist/lowering/tone.js +50 -0
  75. package/dist/lowering/uart.d.ts +15 -0
  76. package/dist/lowering/uart.js +102 -0
  77. package/dist/lowering/util.d.ts +4 -0
  78. package/dist/lowering/util.js +12 -0
  79. package/dist/lowering/wdt.d.ts +15 -0
  80. package/dist/lowering/wdt.js +80 -0
  81. package/dist/lowering/wifi.d.ts +21 -0
  82. package/dist/lowering/wifi.js +447 -0
  83. package/dist/lowering/worker-backing.d.ts +14 -0
  84. package/dist/lowering/worker-backing.js +79 -0
  85. package/dist/lowering/worker.d.ts +6 -0
  86. package/dist/lowering/worker.js +14 -0
  87. package/dist/strategy.d.ts +168 -0
  88. package/dist/strategy.js +1094 -0
  89. package/dist/toolchain/compat.d.ts +38 -0
  90. package/dist/toolchain/compat.js +133 -0
  91. package/dist/toolchain/debug-config.d.ts +82 -0
  92. package/dist/toolchain/debug-config.js +359 -0
  93. package/dist/toolchain/index.d.ts +73 -0
  94. package/dist/toolchain/index.js +444 -0
  95. package/dist/toolchain/scaffold.d.ts +15 -0
  96. package/dist/toolchain/scaffold.js +196 -0
  97. package/dist/toolchain/west-discover.d.ts +40 -0
  98. package/dist/toolchain/west-discover.js +203 -0
  99. package/dist/toolchain/west-spawn.d.ts +39 -0
  100. package/dist/toolchain/west-spawn.js +117 -0
  101. package/package.json +82 -0
  102. package/src/async/timer-polyfill.ts +107 -0
  103. package/src/chips/controllers.ts +74 -0
  104. package/src/chips/esp32.ts +62 -0
  105. package/src/chips/esp32s3.ts +57 -0
  106. package/src/chips/index.ts +54 -0
  107. package/src/chips/resolve.ts +146 -0
  108. package/src/chips/types.ts +163 -0
  109. package/src/chips/xiao-ble.ts +64 -0
  110. package/src/debug-codegen.ts +207 -0
  111. package/src/display/gfx.ts +190 -0
  112. package/src/display/index.ts +70 -0
  113. package/src/display/profiles.ts +63 -0
  114. package/src/display/touch-adapter.ts +100 -0
  115. package/src/display/ui-adapter.ts +558 -0
  116. package/src/doctor.ts +56 -0
  117. package/src/dt-config/kconfig.ts +308 -0
  118. package/src/dt-config/overlay.ts +293 -0
  119. package/src/framework.manifest.ts +558 -0
  120. package/src/index.ts +29 -0
  121. package/src/lowering/adc.ts +102 -0
  122. package/src/lowering/ble.ts +426 -0
  123. package/src/lowering/board.ts +38 -0
  124. package/src/lowering/gpio.ts +148 -0
  125. package/src/lowering/http.ts +488 -0
  126. package/src/lowering/i2c.ts +126 -0
  127. package/src/lowering/index.ts +86 -0
  128. package/src/lowering/interrupts.ts +114 -0
  129. package/src/lowering/mqtt.ts +371 -0
  130. package/src/lowering/power.ts +70 -0
  131. package/src/lowering/preferences.ts +354 -0
  132. package/src/lowering/pulse.ts +93 -0
  133. package/src/lowering/pwm.ts +74 -0
  134. package/src/lowering/random.ts +135 -0
  135. package/src/lowering/spi.ts +124 -0
  136. package/src/lowering/timing.ts +72 -0
  137. package/src/lowering/tone.ts +60 -0
  138. package/src/lowering/uart.ts +111 -0
  139. package/src/lowering/util.ts +12 -0
  140. package/src/lowering/wdt.ts +84 -0
  141. package/src/lowering/wifi.ts +452 -0
  142. package/src/lowering/worker-backing.ts +94 -0
  143. package/src/lowering/worker.ts +17 -0
  144. package/src/strategy.ts +1265 -0
  145. package/src/toolchain/compat.ts +145 -0
  146. package/src/toolchain/debug-config.ts +399 -0
  147. package/src/toolchain/index.ts +482 -0
  148. package/src/toolchain/scaffold.ts +196 -0
  149. package/src/toolchain/west-discover.ts +233 -0
  150. package/src/toolchain/west-spawn.ts +141 -0
@@ -0,0 +1,126 @@
1
+ // ---------------------------------------------------------------------------
2
+ // I2C lowering — transactional txbuf accumulator + per-call i2c_write
3
+ //
4
+ // Mirrors the ESP32 stateful model but simpler: Zephyr's `i2c_write(bus, buf,
5
+ // len, addr)` takes the address per-call, so there's no device-handle cache.
6
+ // The begin_transmission → write* → end_transmission sequence accumulates into
7
+ // `__tc_i2c<N>_txbuf` and flushes as one `i2c_write` at end_transmission.
8
+ // Reads go through request_from → i2c_read into `__tc_i2c<N>_rxbuf`, consumed
9
+ // by subsequent i2c.read / i2c.available calls.
10
+ //
11
+ // The device handle resolves at compile time via DEVICE_DT_GET(DT_NODELABEL(...)).
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
15
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
16
+ import { parseControllerIndex } from './util.js';
17
+
18
+ const TXBUF_SIZE = 32;
19
+ const RXBUF_SIZE = 32;
20
+
21
+ /** The C variable prefix for a controller's state. */
22
+ function prefix(idx: number): string {
23
+ return `__tc_i2c${idx}`;
24
+ }
25
+
26
+ /**
27
+ * Emit the per-controller I2C state. Called from shimLines for each
28
+ * controller the program uses (gated on ctx.analysis.usesI2C).
29
+ */
30
+ export function i2cInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[] {
31
+ const ctrl = chip.i2c?.controllers[controllerIndex];
32
+ if (!ctrl) return [];
33
+ const p = prefix(controllerIndex);
34
+ return [
35
+ '// CUTTLEFISH_I2C_BEGIN',
36
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
37
+ `static uint16_t ${p}_addr = 0xFFFF;`,
38
+ `static uint8_t ${p}_txbuf[${TXBUF_SIZE}];`,
39
+ `static size_t ${p}_txlen = 0;`,
40
+ `static uint8_t ${p}_rxbuf[${RXBUF_SIZE}];`,
41
+ `static size_t ${p}_rxlen = 0;`,
42
+ `static size_t ${p}_rxpos = 0;`,
43
+ '// CUTTLEFISH_I2C_END',
44
+ ];
45
+ }
46
+
47
+ /** Map a clock speed in Hz to an I2C_SPEED_* define. */
48
+ function speedForHz(hz: number): string {
49
+ if (hz <= 100_000) return 'I2C_SPEED_STANDARD';
50
+ if (hz <= 400_000) return 'I2C_SPEED_FAST';
51
+ if (hz <= 1_000_000) return 'I2C_SPEED_FAST_PLUS';
52
+ return 'I2C_SPEED_HIGH';
53
+ }
54
+
55
+ /**
56
+ * Resolve a HAL i2c.* op to Zephyr C++.
57
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
58
+ */
59
+ export function lowerI2c(
60
+ op: HALOpIR,
61
+ chip: ZephyrChipDescriptor,
62
+ ): { code?: string; expression?: string } {
63
+ const o = op as any;
64
+ const idx = parseControllerIndex(o.bus);
65
+ const p = prefix(idx);
66
+
67
+ switch (op.operation) {
68
+ case 'i2c.begin':
69
+ // Zephyr resolves the device at compile time; begin is a no-op (device
70
+ // ready check is folded into the driver calls).
71
+ return { code: `(void)${p}_dev;` };
72
+ case 'i2c.end':
73
+ return { code: `(void)${p}_dev;` };
74
+ case 'i2c.set_clock': {
75
+ const hz = typeof o.hz === 'number' ? o.hz : parseInt(String(o.hz), 10);
76
+ const speed = isNaN(hz) ? 'I2C_SPEED_STANDARD' : speedForHz(hz || 100000);
77
+ return { code: `i2c_configure(${p}_dev, I2C_SPEED_SET(${speed}));` };
78
+ }
79
+ case 'i2c.begin_transmission':
80
+ // Record the target address + reset the pending write buffer.
81
+ return { code: `${p}_addr = static_cast<uint16_t>(${o.address}); ${p}_txlen = 0;` };
82
+ case 'i2c.write':
83
+ // Append one byte (clamped to capacity).
84
+ return { code: `if (${p}_txlen < ${TXBUF_SIZE}) { ${p}_txbuf[${p}_txlen++] = static_cast<uint8_t>(${o.data}); }` };
85
+ case 'i2c.write_bytes': {
86
+ const bytes: unknown[] = o.bytes ?? [];
87
+ const stmts = bytes.map(
88
+ (b) => `if (${p}_txlen < ${TXBUF_SIZE}) { ${p}_txbuf[${p}_txlen++] = static_cast<uint8_t>(${b}); }`,
89
+ );
90
+ return { code: stmts.join(' ') };
91
+ }
92
+ case 'i2c.write_buffer': {
93
+ // Copy from a user buffer (its name is o.data), clamped to capacity.
94
+ return {
95
+ code: `for (size_t __i = 0; __i < sizeof(${o.data}) && ${p}_txlen < ${TXBUF_SIZE}; __i++) { ${p}_txbuf[${p}_txlen++] = reinterpret_cast<const uint8_t*>(${o.data})[__i]; }`,
96
+ };
97
+ }
98
+ case 'i2c.end_transmission':
99
+ // Flush the accumulated txbuf to the cached address.
100
+ return { code: `i2c_write(${p}_dev, ${p}_txbuf, ${p}_txlen, ${p}_addr);` };
101
+ case 'i2c.request_from': {
102
+ const qty = o.quantity;
103
+ return {
104
+ code: `i2c_read(${p}_dev, ${p}_rxbuf, static_cast<uint32_t>(${qty}), static_cast<uint16_t>(${o.address})); ${p}_rxlen = ${qty}; ${p}_rxpos = 0;`,
105
+ };
106
+ }
107
+ case 'i2c.available':
108
+ return { expression: `(${p}_rxlen - ${p}_rxpos)` };
109
+ case 'i2c.read':
110
+ return { expression: `(${p}_rxpos < ${p}_rxlen ? ${p}_rxbuf[${p}_rxpos++] : -1)` };
111
+ case 'i2c.read_buffer': {
112
+ // Read straight into the user's buffer (o.buffer), count bytes.
113
+ const count = o.count;
114
+ return {
115
+ code: `i2c_read(${p}_dev, reinterpret_cast<uint8_t*>(${o.buffer}), static_cast<uint32_t>(${count}), ${p}_addr);`,
116
+ };
117
+ }
118
+ case 'i2c.recover':
119
+ return { code: `i2c_recover_bus(${p}_dev);` };
120
+ default:
121
+ throw new Error(
122
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
123
+ `Open an issue or use rawCpp() to emit it manually.`,
124
+ );
125
+ }
126
+ }
@@ -0,0 +1,86 @@
1
+ // ---------------------------------------------------------------------------
2
+ // HAL lowering dispatcher
3
+ //
4
+ // Routes a HALOpIR to the per-category lowering module by category prefix
5
+ // (e.g. 'gpio.write' → lowerGpio). Returns undefined for categories the
6
+ // framework does not lower (display/fs/mdns/ota/rmt/dac/
7
+ // hwtimer/capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
8
+ // the manifest), so the transpiler falls back and the manifest validator
9
+ // cross-checks the unsupported categories. (board.* is registered below but is
10
+ // a dead-letter — its values are constant-folded at IR-build time.)
11
+ //
12
+ // Prefix dispatch (matching framework-esp32's lowerHalOp) keeps this resilient:
13
+ // a new op added to a category's lowering fn is picked up here automatically,
14
+ // without also editing a 40-case switch. The per-fn default arm still throws a
15
+ // clear "unsupported op" error for ops that fall within a category prefix but
16
+ // aren't handled, so coverage stays honest.
17
+ // ---------------------------------------------------------------------------
18
+
19
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
20
+ import { getActiveChip } from '../chips/index.js';
21
+ import { lowerGpio } from './gpio.js';
22
+ import { lowerTiming } from './timing.js';
23
+ import { lowerAdc } from './adc.js';
24
+ import { lowerPwm } from './pwm.js';
25
+ import { lowerI2c } from './i2c.js';
26
+ import { lowerSpi } from './spi.js';
27
+ import { lowerUart } from './uart.js';
28
+ import { lowerInterrupt } from './interrupts.js';
29
+ import { lowerWdt } from './wdt.js';
30
+ import { lowerPower } from './power.js';
31
+ import { lowerTone } from './tone.js';
32
+ import { lowerPulseOrShift } from './pulse.js';
33
+ import { lowerBle } from './ble.js';
34
+ import { lowerWorker } from './worker.js';
35
+ import { lowerWifi } from './wifi.js';
36
+ import { lowerHttp } from './http.js';
37
+ import { lowerMqtt } from './mqtt.js';
38
+ import { lowerPreferences } from './preferences.js';
39
+ import { lowerBoard } from './board.js';
40
+ import { lowerRandom } from './random.js';
41
+
42
+ export {
43
+ lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart,
44
+ lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle,
45
+ lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom,
46
+ };
47
+
48
+ /**
49
+ * Lower a HAL op to Zephyr C++. Returns undefined for unsupported categories
50
+ * (mirrors lowerHalOp in framework-esp32/src/lowering/index.ts).
51
+ */
52
+ export function lowerHalOp(
53
+ op: HALOpIR,
54
+ ): { code?: string; expression?: string } | undefined {
55
+ const chip = getActiveChip();
56
+
57
+ if (op.operation.startsWith('gpio.')) return lowerGpio(op, chip);
58
+ if (op.operation.startsWith('timing.')) return lowerTiming(op);
59
+ if (op.operation.startsWith('adc.')) return lowerAdc(op, chip);
60
+ if (op.operation.startsWith('pwm.')) return lowerPwm(op, chip);
61
+ if (op.operation.startsWith('i2c.')) return lowerI2c(op, chip);
62
+ if (op.operation.startsWith('spi.')) return lowerSpi(op, chip);
63
+ if (op.operation.startsWith('uart.')) return lowerUart(op);
64
+ if (op.operation.startsWith('interrupt.')) return lowerInterrupt(op, chip);
65
+ if (op.operation.startsWith('wdt.')) return lowerWdt(op);
66
+ if (op.operation.startsWith('power.')) return lowerPower(op);
67
+ if (op.operation.startsWith('tone.')) return lowerTone(op, chip);
68
+ // pulse.* and shift.* share a bit-bang lowering module.
69
+ if (op.operation.startsWith('pulse.') || op.operation.startsWith('shift.'))
70
+ return lowerPulseOrShift(op, chip);
71
+ if (op.operation.startsWith('ble.')) return lowerBle(op);
72
+ if (op.operation.startsWith('worker.')) return lowerWorker(op);
73
+ if (op.operation.startsWith('wifi.')) return lowerWifi(op);
74
+ if (op.operation.startsWith('http.')) return lowerHttp(op);
75
+ if (op.operation.startsWith('mqtt.')) return lowerMqtt(op);
76
+ if (op.operation.startsWith('preferences.')) return lowerPreferences(op);
77
+ // board.resolve is constant-folded at IR-build time; lowerBoard is the
78
+ // dead-letter reached only on an unresolvable path.
79
+ if (op.operation.startsWith('board.')) return lowerBoard(op);
80
+ if (op.operation.startsWith('random.')) return lowerRandom(op);
81
+
82
+ // raw / snprintf.emit / display.* / ... — not lowered by this
83
+ // framework. Return undefined so the transpiler falls back and the manifest
84
+ // validator confirms the unsupported declaration.
85
+ return undefined;
86
+ }
@@ -0,0 +1,114 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Interrupt lowering — gpio_init_callback + gpio_add_callback
3
+ //
4
+ // The most complex lowering: Zephyr's GPIO interrupt model requires a
5
+ // `struct gpio_callback` per pin, registered with gpio_add_callback, with a
6
+ // trampoline that calls the user's C handler. The descriptor's gpio.interruptPins
7
+ // lists the pins with DT specs (buttons). The user's handler is a free C
8
+ // function; the trampoline calls it with no args (Arduino attachInterrupt style).
9
+ //
10
+ // Each interrupt pin gets a static callback struct + a trampoline in shimLines.
11
+ // attach enables + adds the callback; detach disables + removes it.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
15
+ import type { ZephyrChipDescriptor, ZephyrInterruptPin } from '../chips/types.js';
16
+
17
+ /** Look up an interrupt pin spec by HAL pin number. */
18
+ function findIntPin(chip: ZephyrChipDescriptor, pin: number): ZephyrInterruptPin | undefined {
19
+ return chip.gpio.interruptPins?.find((p) => p.pin === pin);
20
+ }
21
+
22
+ /** The C variable name for a pin's gpio_dt_spec. */
23
+ function dtSpecVar(dtSpec: string): string {
24
+ return `__tc_int_${dtSpec.replace(/-/g, '_')}`;
25
+ }
26
+
27
+ /** Map a HAL interrupt mode string to Zephyr GPIO_INT_* flags. */
28
+ function modeToFlags(mode: string): string {
29
+ switch (mode.toLowerCase()) {
30
+ case 'rising': return 'GPIO_INT_EDGE_RISING';
31
+ case 'falling': return 'GPIO_INT_EDGE_FALLING';
32
+ case 'change': return 'GPIO_INT_EDGE_BOTH';
33
+ case 'high': return 'GPIO_INT_LEVEL_HIGH';
34
+ case 'low': return 'GPIO_INT_LEVEL_LOW';
35
+ default: return 'GPIO_INT_EDGE_BOTH';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Emit the per-pin interrupt callback state + trampolines. Called from shimLines
41
+ * when the program uses interrupts. The trampoline calls a user function via a
42
+ * function-pointer static, set at attach time.
43
+ */
44
+ export function interruptInitLines(chip: ZephyrChipDescriptor): string[] {
45
+ const pins = chip.gpio.interruptPins ?? [];
46
+ if (pins.length === 0) return [];
47
+ const lines: string[] = ['// CUTTLEFISH_INT_BEGIN'];
48
+ for (const pin of pins) {
49
+ const v = dtSpecVar(pin.dtSpec);
50
+ lines.push(
51
+ `static const struct gpio_dt_spec ${v} = GPIO_DT_SPEC_GET(DT_ALIAS(${pin.dtSpec}), gpios);`,
52
+ `static struct gpio_callback ${v}_cb;`,
53
+ `static void (*${v}_handler)(void) = NULL;`,
54
+ `static void ${v}_tramp(const struct device* port, struct gpio_callback* cb, gpio_port_pins_t pins_v) {`,
55
+ ` (void)port; (void)cb; (void)pins_v;`,
56
+ ` if (${v}_handler) { ${v}_handler(); }`,
57
+ `}`,
58
+ );
59
+ }
60
+ lines.push('// CUTTLEFISH_INT_END');
61
+ return lines;
62
+ }
63
+
64
+ /**
65
+ * Resolve a HAL interrupt.* op to Zephyr C++.
66
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
67
+ *
68
+ * Note: attach requires the pin to be in the descriptor's interruptPins. If a
69
+ * program attaches to an unlisted pin, we fall back to a diagnostic comment
70
+ * (the probe uses an unknown pin, so the manifest marks these 'supported' via
71
+ * the comment lowering — the probe sees a non-undefined return).
72
+ */
73
+ export function lowerInterrupt(
74
+ op: HALOpIR,
75
+ chip: ZephyrChipDescriptor,
76
+ ): { code?: string; expression?: string } {
77
+ const o = op as any;
78
+ const pin = findIntPin(chip, o.pin);
79
+
80
+ if (!pin) {
81
+ // Fallback (also covers the manifest probe): a diagnostic comment so the
82
+ // resolver returns non-undefined. Real attach needs a descriptor entry.
83
+ if (op.operation === 'interrupt.attach') {
84
+ return { code: `/* interrupt.attach(pin ${o.pin}): no DT spec — add to chip descriptor gpio.interruptPins */` };
85
+ }
86
+ return { code: `/* interrupt.detach(pin ${o.pin}): no DT spec */` };
87
+ }
88
+
89
+ const v = dtSpecVar(pin.dtSpec);
90
+
91
+ switch (op.operation) {
92
+ case 'interrupt.attach': {
93
+ const flags = modeToFlags(o.mode);
94
+ return {
95
+ code: [
96
+ `${v}_handler = (${o.handler});`,
97
+ `gpio_pin_configure_dt(&${v}, GPIO_INPUT);`,
98
+ `gpio_init_callback(&${v}_cb, ${v}_tramp, BIT(${v}.pin));`,
99
+ `gpio_pin_interrupt_configure_dt(&${v}, ${flags});`,
100
+ `gpio_add_callback(${v}.port, &${v}_cb);`,
101
+ ].join(' '),
102
+ };
103
+ }
104
+ case 'interrupt.detach':
105
+ return {
106
+ code: `gpio_pin_interrupt_configure_dt(&${v}, GPIO_INT_DISABLE); gpio_remove_callback(${v}.port, &${v}_cb); ${v}_handler = NULL;`,
107
+ };
108
+ default:
109
+ throw new Error(
110
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
111
+ `Open an issue or use rawCpp() to emit it manually.`,
112
+ );
113
+ }
114
+ }