@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.
- package/README.md +22 -35
- package/dist/as-built.d.ts +33 -0
- package/dist/as-built.js +188 -0
- package/dist/async/timer-polyfill.js +1 -1
- package/dist/boardgen.d.ts +128 -0
- package/dist/boardgen.js +1366 -0
- package/dist/chips/board-overrides.d.ts +7 -0
- package/dist/chips/board-overrides.js +11 -0
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/esp32s3.js +49 -0
- package/dist/chips/index.d.ts +6 -12
- package/dist/chips/index.js +18 -34
- package/dist/chips/resolve.d.ts +16 -2
- package/dist/chips/resolve.js +298 -15
- package/dist/chips/soc/.d.ts +2 -0
- package/dist/chips/soc/.js +129 -0
- package/dist/chips/soc/esp32.d.ts +2 -0
- package/dist/chips/soc/esp32.js +120 -0
- package/dist/chips/soc/esp32c3.d.ts +2 -0
- package/dist/chips/soc/esp32c3.js +90 -0
- package/dist/chips/soc/esp32c6.d.ts +2 -0
- package/dist/chips/soc/esp32c6.js +109 -0
- package/dist/chips/soc/esp32s3.d.ts +2 -0
- package/dist/chips/soc/esp32s3.js +189 -0
- package/dist/chips/soc/index.d.ts +2 -0
- package/dist/chips/soc/index.js +23 -0
- package/dist/chips/soc/nrf52840.d.ts +2 -0
- package/dist/chips/soc/nrf52840.js +130 -0
- package/dist/chips/soc/rp2040.d.ts +2 -0
- package/dist/chips/soc/rp2040.js +141 -0
- package/dist/chips/soc/rp2350a.d.ts +2 -0
- package/dist/chips/soc/rp2350a.js +145 -0
- package/dist/chips/soc/samd21g18a.d.ts +2 -0
- package/dist/chips/soc/samd21g18a.js +143 -0
- package/dist/chips/soc/stm32f411xe.d.ts +2 -0
- package/dist/chips/soc/stm32f411xe.js +251 -0
- package/dist/chips/types.d.ts +382 -12
- package/dist/chips/xiao-ble.js +33 -0
- package/dist/display/profiles.js +1 -1
- package/dist/doctor.js +38 -4
- package/dist/dt-config/custom-board.d.ts +23 -0
- package/dist/dt-config/custom-board.js +227 -0
- package/dist/dt-config/kconfig.d.ts +83 -1
- package/dist/dt-config/kconfig.js +116 -27
- package/dist/dt-config/overlay.js +623 -18
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +124 -178
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -2
- package/dist/lowering/adc.d.ts +26 -5
- package/dist/lowering/adc.js +137 -31
- package/dist/lowering/ble.js +31 -28
- package/dist/lowering/dac.js +9 -9
- package/dist/lowering/fs.js +18 -10
- package/dist/lowering/gpio.d.ts +2 -0
- package/dist/lowering/gpio.js +90 -10
- package/dist/lowering/http.js +39 -14
- package/dist/lowering/hwtimer.d.ts +4 -0
- package/dist/lowering/hwtimer.js +35 -10
- package/dist/lowering/i2c.js +46 -54
- package/dist/lowering/index.d.ts +5 -5
- package/dist/lowering/index.js +15 -16
- package/dist/lowering/interrupts.d.ts +22 -5
- package/dist/lowering/interrupts.js +104 -22
- package/dist/lowering/mqtt.js +12 -6
- package/dist/lowering/power.js +0 -4
- package/dist/lowering/preferences.js +49 -77
- package/dist/lowering/pulse.js +5 -36
- package/dist/lowering/pwm.d.ts +32 -3
- package/dist/lowering/pwm.js +105 -17
- package/dist/lowering/random.js +3 -3
- package/dist/lowering/sensor.d.ts +37 -0
- package/dist/lowering/sensor.js +119 -0
- package/dist/lowering/spi.d.ts +12 -0
- package/dist/lowering/spi.js +65 -40
- package/dist/lowering/thread.d.ts +14 -0
- package/dist/lowering/thread.js +55 -0
- package/dist/lowering/timing.js +20 -46
- package/dist/lowering/tone.js +18 -5
- package/dist/lowering/uart.d.ts +13 -0
- package/dist/lowering/uart.js +72 -51
- package/dist/lowering/usb.d.ts +34 -0
- package/dist/lowering/usb.js +206 -0
- package/dist/lowering/wdt.d.ts +1 -1
- package/dist/lowering/wdt.js +17 -7
- package/dist/lowering/wifi.d.ts +3 -2
- package/dist/lowering/wifi.js +120 -111
- package/dist/sdk/board-catalog-sync.d.ts +16 -0
- package/dist/sdk/board-catalog-sync.js +41 -0
- package/dist/sdk/board-data.generated.d.ts +2 -0
- package/dist/sdk/board-data.generated.js +4 -0
- package/dist/sdk/catalog-walker.d.ts +90 -0
- package/dist/sdk/catalog-walker.js +682 -0
- package/dist/sdk/dts-reader.d.ts +83 -0
- package/dist/sdk/dts-reader.js +596 -0
- package/dist/strategy.d.ts +136 -17
- package/dist/strategy.js +881 -212
- package/dist/toolchain/bossac-touch.d.ts +18 -0
- package/dist/toolchain/bossac-touch.js +88 -0
- package/dist/toolchain/compat.d.ts +2 -2
- package/dist/toolchain/compat.js +8 -3
- package/dist/toolchain/debug-config.d.ts +21 -26
- package/dist/toolchain/debug-config.js +212 -72
- package/dist/toolchain/env-check.d.ts +4 -4
- package/dist/toolchain/env-check.js +5 -5
- package/dist/toolchain/index.d.ts +66 -1
- package/dist/toolchain/index.js +713 -58
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +84 -7
- package/dist/toolchain/west-discover.d.ts +8 -1
- package/dist/toolchain/west-discover.js +19 -6
- package/dist/toolchain/west-spawn.js +17 -5
- package/installer/CHANGELOG.md +473 -0
- package/installer/README.md +328 -0
- package/installer/environment.yml +46 -0
- package/installer/etc/conda/activate.d/zephyr.bat +7 -0
- package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
- package/installer/etc/conda/activate.d/zephyr.sh +20 -0
- package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
- package/installer/install.mjs +580 -0
- package/installer/install.ps1 +619 -0
- package/installer/install.sh +310 -0
- package/installer/lib/detect-platform.sh +62 -0
- package/installer/lib/fetch-sdk.sh +304 -0
- package/installer/lib/init-workspace.sh +153 -0
- package/installer/lib/write-activation.sh +76 -0
- package/installer/patches/tfm-c-include-separators.patch +19 -0
- package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
- package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
- package/installer/templates/project/.vscode/settings.json +30 -0
- package/installer/templates/project/README.md +58 -0
- package/installer/versions.env +110 -0
- package/package.json +16 -16
- package/src/as-built.ts +206 -0
- package/src/boardgen.ts +1428 -0
- package/src/chips/controllers.ts +61 -12
- package/src/chips/index.ts +35 -54
- package/src/chips/resolve.ts +316 -14
- package/src/chips/types.ts +388 -13
- package/src/display/profiles.ts +1 -1
- package/src/doctor.ts +110 -77
- package/src/dt-config/custom-board.ts +325 -0
- package/src/dt-config/kconfig.ts +177 -19
- package/src/dt-config/overlay.ts +1058 -450
- package/src/framework.manifest.ts +125 -178
- package/src/index.ts +57 -41
- package/src/lowering/adc.ts +222 -102
- package/src/lowering/ble.ts +32 -28
- package/src/lowering/dac.ts +82 -82
- package/src/lowering/fs.ts +18 -10
- package/src/lowering/gpio.ts +242 -152
- package/src/lowering/http.ts +512 -488
- package/src/lowering/hwtimer.ts +139 -101
- package/src/lowering/i2c.ts +116 -126
- package/src/lowering/index.ts +91 -93
- package/src/lowering/interrupts.ts +113 -21
- package/src/lowering/mqtt.ts +12 -6
- package/src/lowering/preferences.ts +327 -354
- package/src/lowering/pwm.ts +167 -74
- package/src/lowering/random.ts +3 -3
- package/src/lowering/sensor.ts +155 -0
- package/src/lowering/spi.ts +164 -124
- package/src/lowering/thread.ts +63 -0
- package/src/lowering/timing.ts +46 -72
- package/src/lowering/uart.ts +73 -51
- package/src/lowering/usb.ts +221 -0
- package/src/lowering/wdt.ts +95 -84
- package/src/lowering/wifi.ts +120 -111
- package/src/sdk/board-catalog-sync.ts +62 -0
- package/src/strategy.ts +888 -229
- package/src/toolchain/bossac-touch.ts +112 -0
- package/src/toolchain/compat.ts +8 -3
- package/src/toolchain/debug-config.ts +666 -522
- package/src/toolchain/env-check.ts +9 -9
- package/src/toolchain/index.ts +765 -44
- package/src/toolchain/scaffold.ts +81 -7
- package/src/toolchain/west-discover.ts +334 -316
- package/src/toolchain/west-spawn.ts +17 -5
- package/src/async/timer-polyfill.ts +0 -107
- package/src/chips/esp32.ts +0 -74
- package/src/chips/esp32s3.ts +0 -57
- package/src/chips/xiao-ble.ts +0 -70
- package/src/lowering/power.ts +0 -70
- package/src/lowering/pulse.ts +0 -93
- package/src/lowering/tone.ts +0 -60
- package/src/lowering/worker-backing.ts +0 -94
- package/src/lowering/worker.ts +0 -17
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** A GPIO reference inside the devicetree: controller nodelabel + pin + flags. */
|
|
2
|
+
export interface DtsGpioRef {
|
|
3
|
+
/** Controller nodelabel, e.g. 'gpioa', 'gpio0'. */
|
|
4
|
+
readonly controller: string;
|
|
5
|
+
/** Pin number within the controller. */
|
|
6
|
+
readonly pin: number;
|
|
7
|
+
/** Raw flag tokens as written (macros stay opaque): ['GPIO_ACTIVE_LOW', 'GPIO_PULL_UP']. */
|
|
8
|
+
readonly flags: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/** A gpio-leds / gpio-keys child with its devicetree alias when aliased. */
|
|
11
|
+
export interface DtsGpioNode extends DtsGpioRef {
|
|
12
|
+
/** Node's own nodelabel, e.g. 'user_led', 'button0'. */
|
|
13
|
+
readonly nodelabel: string;
|
|
14
|
+
/** Devicetree alias pointing here, e.g. 'led0', 'sw0' (from the aliases node). */
|
|
15
|
+
readonly alias?: string;
|
|
16
|
+
}
|
|
17
|
+
/** A connector nexus (e.g. the XIAO's D0–D10 gpio-map). */
|
|
18
|
+
export interface DtsConnector {
|
|
19
|
+
/** Connector nodelabel, e.g. 'xiao_d', 'arduino_header_d'. */
|
|
20
|
+
readonly nodelabel: string;
|
|
21
|
+
/** Compatible string, e.g. 'seeed,xiao-gpio', 'arduino-header-r3'. */
|
|
22
|
+
readonly compatible?: string;
|
|
23
|
+
/** Silkscreen-ish label (from the map's trailing comments) → ref. */
|
|
24
|
+
readonly pins: Readonly<Record<string, DtsGpioRef>>;
|
|
25
|
+
}
|
|
26
|
+
/** Everything the reader could extract from one board DTS (post local includes). */
|
|
27
|
+
export interface DtsBoardFacts {
|
|
28
|
+
/** alias name → nodelabel ('led0' → 'user_led', 'sw0' → 'button0'). */
|
|
29
|
+
readonly aliases: Readonly<Record<string, string>>;
|
|
30
|
+
/** chosen property → nodelabel ('zephyr,console' → 'uart0'). */
|
|
31
|
+
readonly chosen: Readonly<Record<string, string>>;
|
|
32
|
+
/** gpio-leds children. */
|
|
33
|
+
readonly leds: readonly DtsGpioNode[];
|
|
34
|
+
/** gpio-keys children. */
|
|
35
|
+
readonly buttons: readonly DtsGpioNode[];
|
|
36
|
+
/** Connector nexus maps. */
|
|
37
|
+
readonly connectors: readonly DtsConnector[];
|
|
38
|
+
/** Addressable user LED (worldsemi,ws2812-*): the pad driving the pixel.
|
|
39
|
+
* NOT a gpio-leds node — consumers must treat it as a plain GPIO LED
|
|
40
|
+
* (no led0 devicetree spec), same as a curated board override. */
|
|
41
|
+
readonly stripLed?: DtsGpioRef;
|
|
42
|
+
/** PWM-driven LEDs (pwm-leds children), in board order. `alias` is the
|
|
43
|
+
* devicetree alias ('pwm-led0') — the addressable form; non-aliased
|
|
44
|
+
* channels carry their controller+channel only. */
|
|
45
|
+
readonly pwmLeds: readonly DtsPwmLed[];
|
|
46
|
+
/** The board's USB device wiring: 'enabled' when the DTS turns the device
|
|
47
|
+
* controller on (zephyr_udc0/&usbd status okay), 'disabled' when it
|
|
48
|
+
* explicitly turns it off, undefined when the DTS is silent (the app
|
|
49
|
+
* overlay may still compose a CDC device). */
|
|
50
|
+
readonly usbDevice?: 'enabled' | 'disabled';
|
|
51
|
+
/** The USB device controller nodelabel backing usbDevice ('zephyr_udc0',
|
|
52
|
+
* or the board's own label for &usbd). */
|
|
53
|
+
readonly usbController?: string;
|
|
54
|
+
/** Bus controller nodelabels the board DTS wires up (&i2c0/&spi2/&usart1
|
|
55
|
+
* status-okay overrides; &ref blocks). Board-level facts only — a bus
|
|
56
|
+
* enabled solely in the SoC dtsi is invisible here, by design. */
|
|
57
|
+
readonly buses: {
|
|
58
|
+
readonly i2c: readonly string[];
|
|
59
|
+
readonly spi: readonly string[];
|
|
60
|
+
readonly uart: readonly string[];
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** A pwm-leds child: one PWM-driven LED channel. */
|
|
64
|
+
export interface DtsPwmLed {
|
|
65
|
+
/** Devicetree alias ('pwm-led0') when aliased — the DT_ALIAS addressing form. */
|
|
66
|
+
readonly alias?: string;
|
|
67
|
+
/** PWM controller nodelabel ('pwm1'). */
|
|
68
|
+
readonly controller: string;
|
|
69
|
+
readonly channel: number;
|
|
70
|
+
/** Period cell from the pwms property, when present. */
|
|
71
|
+
readonly periodNs?: number;
|
|
72
|
+
/** Flag tokens (PWM_POLARITY_INVERTED, …). Macro expressions like
|
|
73
|
+
* PWM_MSEC(20) are period shorthand — never flags. */
|
|
74
|
+
readonly flags?: readonly string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read one board DTS file (following its local include chain) and extract
|
|
78
|
+
* the board-level facts. `zephyrBoardsRoot` (e.g. <zephyr>/boards) resolves
|
|
79
|
+
* `<../boards/common/...>` style includes.
|
|
80
|
+
*/
|
|
81
|
+
export declare function readBoardDts(dtsPath: string, opts?: {
|
|
82
|
+
zephyrBoardsRoot?: string;
|
|
83
|
+
}): DtsBoardFacts;
|
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// dts-reader.ts — tolerant Zephyr board-devicetree reader
|
|
3
|
+
//
|
|
4
|
+
// Extracts the BOARD-LEVEL facts the generated board module needs from a
|
|
5
|
+
// board's own DTS files: aliases, the chosen console, gpio-leds / gpio-keys
|
|
6
|
+
// children (LED/BUTTON devicetree specs), and connector nexus gpio-maps
|
|
7
|
+
// (arduino-header-r3, seeed,xiao-gpio). Silicon facts (ADC channel maps, PWM
|
|
8
|
+
// channel counts) are NOT here — they never live in devicetree; they are the
|
|
9
|
+
// curated soc descriptors in ../chips/soc/.
|
|
10
|
+
//
|
|
11
|
+
// DELIBERATELY NOT a devicetree compiler. The reader follows only the
|
|
12
|
+
// board's LOCAL include chain (relative *.dtsi and in-tree <...> includes it
|
|
13
|
+
// can resolve), and treats C macros it cannot expand as opaque flag tokens
|
|
14
|
+
// (`GPIO_ACTIVE_LOW` stays the literal text). It does not resolve phandles
|
|
15
|
+
// beyond &label capture, does not evaluate expressions, and ignores every
|
|
16
|
+
// property it has no consumer for. Boards whose interesting nodes live in
|
|
17
|
+
// deeper SoC includes simply yield fewer facts — the generator's tiering
|
|
18
|
+
// handles that honestly.
|
|
19
|
+
//
|
|
20
|
+
// Used by scripts/gen-zephyr-board-data.mjs at repo-build time against the
|
|
21
|
+
// pinned Zephyr tree; never runs in user builds.
|
|
22
|
+
// ----------------------------------------------------------------------------
|
|
23
|
+
import fs from 'node:fs';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
function resolveInclude(spec, fromFile, roots) {
|
|
26
|
+
// Strip the wrapper: <...> or "..."
|
|
27
|
+
const inner = spec.slice(1, -1);
|
|
28
|
+
// Quoted includes are relative to the including file.
|
|
29
|
+
if (!spec.startsWith('<')) {
|
|
30
|
+
const p = path.resolve(path.dirname(fromFile), inner);
|
|
31
|
+
return fs.existsSync(p) ? p : undefined;
|
|
32
|
+
}
|
|
33
|
+
// Board files use ../boards/common/... relative includes and bare filenames
|
|
34
|
+
// resolved against the boards tree; try the file's own dir first, then the
|
|
35
|
+
// configured angle roots.
|
|
36
|
+
const local = path.resolve(path.dirname(fromFile), inner);
|
|
37
|
+
if (fs.existsSync(local))
|
|
38
|
+
return local;
|
|
39
|
+
for (const root of roots.angle) {
|
|
40
|
+
const p = path.resolve(root, inner);
|
|
41
|
+
if (fs.existsSync(p))
|
|
42
|
+
return p;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Strip block/line comments, keeping one attachment channel for gpio-map labels. */
|
|
47
|
+
function stripCommentsKeepMarkers(src) {
|
|
48
|
+
// Replace comment bodies but KEEP a marker: /* D0 */ → ⟨D0⟩ inline, so the
|
|
49
|
+
// gpio-map parser can pick silkscreen labels out of the cell stream.
|
|
50
|
+
return src
|
|
51
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => '⟨' + m.slice(2, -2).trim() + '⟩')
|
|
52
|
+
.replace(/\/\/[^\n]*/g, '');
|
|
53
|
+
}
|
|
54
|
+
function emptyNode(name, parent) {
|
|
55
|
+
return { labels: [], name, props: new Map(), children: new Map(), parent };
|
|
56
|
+
}
|
|
57
|
+
/** Statement scanner: emits node headers (at every '{'), property
|
|
58
|
+
* statements ('a = <...>' at their owning depth), and node closes ('}')
|
|
59
|
+
* in source order, building the node tree with &label override merges. */
|
|
60
|
+
function parseStatements(src) {
|
|
61
|
+
const root = emptyNode('/');
|
|
62
|
+
const byLabel = new Map();
|
|
63
|
+
const stack = [root];
|
|
64
|
+
let depth = 0;
|
|
65
|
+
let start = 0;
|
|
66
|
+
const emit = (kind, text) => {
|
|
67
|
+
if (kind === 'end') {
|
|
68
|
+
if (stack.length > 1)
|
|
69
|
+
stack.pop();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const t = text.trim().replace(/^[\r\n\t ]+/, '');
|
|
73
|
+
if (!t)
|
|
74
|
+
return;
|
|
75
|
+
const top = stack[stack.length - 1];
|
|
76
|
+
if (kind === 'hdr') {
|
|
77
|
+
const refHeader = t.match(/^&([\w-]+)\s*\{?$/);
|
|
78
|
+
if (refHeader) {
|
|
79
|
+
let n = byLabel.get(refHeader[1]);
|
|
80
|
+
if (!n) {
|
|
81
|
+
n = emptyNode('&' + refHeader[1], top);
|
|
82
|
+
byLabel.set(refHeader[1], n);
|
|
83
|
+
// Attach to the synthetic tree so subtree walkers (connectors,
|
|
84
|
+
// gpio nodes, led strips, bus pinctrl) see &ref blocks — the SoC
|
|
85
|
+
// dtsi that parents them is not part of the local include chain.
|
|
86
|
+
top.children.set(n.name, n);
|
|
87
|
+
}
|
|
88
|
+
stack.push(n);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Header text may carry trailing debris from prior statements (the
|
|
92
|
+
// scanner slices from after the previous ';'/'}'), so isolate the LAST
|
|
93
|
+
// node-header line: [label:] name[@unit]
|
|
94
|
+
const headerLine = t.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean).pop() ?? t;
|
|
95
|
+
const parts = headerLine.split(':').map((s) => s.trim()).filter(Boolean);
|
|
96
|
+
const name = parts.length > 0 ? parts[parts.length - 1] : 'anon';
|
|
97
|
+
const labels = parts.slice(0, -1);
|
|
98
|
+
let node = top.children.get(name);
|
|
99
|
+
if (!node) {
|
|
100
|
+
node = emptyNode(name, top);
|
|
101
|
+
top.children.set(name, node);
|
|
102
|
+
}
|
|
103
|
+
for (const l of labels) {
|
|
104
|
+
if (!node.labels.includes(l))
|
|
105
|
+
node.labels.push(l);
|
|
106
|
+
byLabel.set(l, node);
|
|
107
|
+
}
|
|
108
|
+
stack.push(node);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// property
|
|
112
|
+
// Leading comment markers (a comment line above a property, inside the
|
|
113
|
+
// same node — 96b_nitrogen's `/* gpio flags need validation */` before
|
|
114
|
+
// button0's gpios) must not hide the property: the anchored match would
|
|
115
|
+
// fail and the node would silently lose its facts. Strip them first;
|
|
116
|
+
// markers INSIDE a value stay (gpio-map silkscreen labels).
|
|
117
|
+
const stripped = t.replace(/^(?:⟨[^⟩]*⟩\s*)+/, '');
|
|
118
|
+
const prop = stripped.match(/^([\w,#@-]+)\s*=\s*([\s\S]*?)\s*;?$/);
|
|
119
|
+
if (prop && top !== root) {
|
|
120
|
+
top.props.set(prop[1], prop[2].replace(/;$/, ''));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const bare = stripped.match(/^([\w,#@-]+)\s*;?$/);
|
|
124
|
+
if (bare && top !== root)
|
|
125
|
+
top.props.set(bare[1], '');
|
|
126
|
+
};
|
|
127
|
+
let i = 0;
|
|
128
|
+
while (i < src.length) {
|
|
129
|
+
const ch = src[i];
|
|
130
|
+
if (ch === '{') {
|
|
131
|
+
const stmt = src.slice(start, i);
|
|
132
|
+
emit('hdr', stmt);
|
|
133
|
+
depth++;
|
|
134
|
+
start = i + 1;
|
|
135
|
+
}
|
|
136
|
+
else if (ch === '}') {
|
|
137
|
+
const inner = src.slice(start, i).trim();
|
|
138
|
+
if (inner)
|
|
139
|
+
emit('prop', inner);
|
|
140
|
+
emit('end', '');
|
|
141
|
+
if (depth > 0)
|
|
142
|
+
depth--;
|
|
143
|
+
start = i + 1;
|
|
144
|
+
}
|
|
145
|
+
else if (ch === ';') {
|
|
146
|
+
const stmt = src.slice(start, i);
|
|
147
|
+
if (depth > 0)
|
|
148
|
+
emit('prop', stmt);
|
|
149
|
+
start = i + 1;
|
|
150
|
+
}
|
|
151
|
+
i++;
|
|
152
|
+
}
|
|
153
|
+
return { root, byLabel };
|
|
154
|
+
}
|
|
155
|
+
// ── cell parsing ───────────────────────────────────────────────────────────
|
|
156
|
+
/** Parse a property value like `<&gpioa 5 GPIO_ACTIVE_LOW>` into tokens. */
|
|
157
|
+
function parseCells(value) {
|
|
158
|
+
return value
|
|
159
|
+
// Angle-bracket cell wrappers are punctuation, not content.
|
|
160
|
+
.replace(/[<>]/g, ' ')
|
|
161
|
+
// Comment markers may carry multi-word labels ('D14 / A0', 'Pin 1,
|
|
162
|
+
// LEDK') — shield their internal whitespace AND commas from the split,
|
|
163
|
+
// restoring each to its own character afterwards (collapsing a comma
|
|
164
|
+
// to a space would hide it from the label splitter, and an unshielded
|
|
165
|
+
// comma tore the marker apart mid-token — '⟨Pin 1' + 'LEDK⟩' — whose
|
|
166
|
+
// truncated form minted garbage labels like 'Pin').
|
|
167
|
+
.replace(/⟨[^⟩]*⟩/g, (m) => ' ' + m.replace(/,/g, '\x1f').replace(/\s+/g, '\x1e') + ' ')
|
|
168
|
+
.split(/[\s,]+/)
|
|
169
|
+
.map((s) => s.trim().replace(/\x1f/g, ',').replace(/\x1e/g, ' '))
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
}
|
|
172
|
+
function parseGpioMap(value, parentCells) {
|
|
173
|
+
const toks = parseCells(value);
|
|
174
|
+
const entries = [];
|
|
175
|
+
let i = 0;
|
|
176
|
+
let pendingLabel;
|
|
177
|
+
const splitNames = (raw) =>
|
|
178
|
+
// '/' separates co-names of one pad ('D14 / A0'); ',' separates a pin
|
|
179
|
+
// number from its net name ('Pin 1, LEDK' → 'LEDK'). Whitespace is NOT
|
|
180
|
+
// a separator — 'Pin 1' must not collapse to 'Pin'.
|
|
181
|
+
raw.split(/[\/,]/).map((s) => s.trim()).filter((s) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(s));
|
|
182
|
+
while (i < toks.length) {
|
|
183
|
+
// Sweep up any ⟨label⟩ markers preceding the numeric entry.
|
|
184
|
+
while (i < toks.length && toks[i].startsWith('⟨')) {
|
|
185
|
+
const inner = toks[i].slice(1, -1).trim();
|
|
186
|
+
if (inner)
|
|
187
|
+
pendingLabel = inner;
|
|
188
|
+
i++;
|
|
189
|
+
}
|
|
190
|
+
const spec = toks[i];
|
|
191
|
+
if (spec === undefined)
|
|
192
|
+
break;
|
|
193
|
+
let childSpec = Number(spec);
|
|
194
|
+
// Macro child specifiers (the nano header's ARDUINO_NANO_HEADER_D14):
|
|
195
|
+
// the numeric suffix is the specifier, the alphabetic suffix carries the
|
|
196
|
+
// silkscreen name when no comment label exists.
|
|
197
|
+
let macroLabel;
|
|
198
|
+
if (!Number.isFinite(childSpec)) {
|
|
199
|
+
// Multi-word macros (ARDUINO_NANO_HEADER_D14): the LAST underscore
|
|
200
|
+
// splits the prefix from the <letters><digits> silkscreen suffix.
|
|
201
|
+
const m = spec.match(/^([A-Za-z][A-Za-z0-9_]*)_([A-Za-z]+)(\d+)$/);
|
|
202
|
+
if (m) {
|
|
203
|
+
childSpec = Number(m[3]);
|
|
204
|
+
macroLabel = `${m[2]}${m[3]}`;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
i++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// child-spec, pad, then &ctrl
|
|
212
|
+
const ctrl = toks[i + 2];
|
|
213
|
+
if (ctrl === undefined || !ctrl.startsWith('&')) {
|
|
214
|
+
i++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const pin = Number(toks[i + 3]);
|
|
218
|
+
// Macro/expression pins (RTIO routing etc.) cannot be placed on the HAL
|
|
219
|
+
// pin map — skip the entry, keep scanning.
|
|
220
|
+
if (!Number.isFinite(pin)) {
|
|
221
|
+
i++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const flags = [];
|
|
225
|
+
for (let f = i + 4; f < i + 3 + parentCells + 1 && f < toks.length; f++) {
|
|
226
|
+
const t = toks[f];
|
|
227
|
+
if (t.startsWith('&') || Number.isFinite(Number(t)))
|
|
228
|
+
break;
|
|
229
|
+
if (!t.startsWith('⟨'))
|
|
230
|
+
flags.push(t);
|
|
231
|
+
}
|
|
232
|
+
// A comment can name one pad twice ('D14 / A0' — the digital and analog
|
|
233
|
+
// silkscreen of the same pad): first name is the primary label, the rest
|
|
234
|
+
// ride as altLabels.
|
|
235
|
+
const names = pendingLabel ? splitNames(pendingLabel) : [];
|
|
236
|
+
const label = names[0] ?? macroLabel;
|
|
237
|
+
const altLabels = names.slice(1);
|
|
238
|
+
entries.push({
|
|
239
|
+
childSpecifier: childSpec,
|
|
240
|
+
...(label ? { label } : {}),
|
|
241
|
+
...(altLabels.length > 0 ? { altLabels } : {}),
|
|
242
|
+
ref: { controller: ctrl.slice(1), pin, flags },
|
|
243
|
+
});
|
|
244
|
+
pendingLabel = undefined;
|
|
245
|
+
// advance: spec + pad + &ctrl + parentCells numbers
|
|
246
|
+
i = i + 3 + parentCells;
|
|
247
|
+
// sweep trailing markers before the next entry
|
|
248
|
+
while (i < toks.length && toks[i].startsWith('⟨')) {
|
|
249
|
+
const inner = toks[i].slice(1, -1).trim();
|
|
250
|
+
if (inner && entries.length > 0) {
|
|
251
|
+
const trailing = splitNames(inner);
|
|
252
|
+
const last = entries[entries.length - 1];
|
|
253
|
+
if (trailing.length > 0 && !last.label)
|
|
254
|
+
last.label = trailing[0];
|
|
255
|
+
if (trailing.length > 1)
|
|
256
|
+
last.altLabels = [...(last.altLabels ?? []), ...trailing.slice(1)];
|
|
257
|
+
}
|
|
258
|
+
i++;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return entries;
|
|
262
|
+
}
|
|
263
|
+
// ── board facts extraction ─────────────────────────────────────────────────
|
|
264
|
+
function gpioRefFromProp(value) {
|
|
265
|
+
const toks = parseCells(value);
|
|
266
|
+
// Expect: &ctrl pin [flags...]
|
|
267
|
+
if (toks.length < 2 || !toks[0].startsWith('&'))
|
|
268
|
+
return undefined;
|
|
269
|
+
const pin = Number(toks[1]);
|
|
270
|
+
if (!Number.isFinite(pin))
|
|
271
|
+
return undefined;
|
|
272
|
+
// Normalize flag tokens: strip grouping parens, drop '|' separators.
|
|
273
|
+
const flags = toks
|
|
274
|
+
.slice(2)
|
|
275
|
+
.filter((t) => !t.startsWith('⟨') && t !== '|')
|
|
276
|
+
.map((t) => t.replace(/[()]/g, ''))
|
|
277
|
+
.filter(Boolean);
|
|
278
|
+
return { controller: toks[0].slice(1), pin, flags };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Read one board DTS file (following its local include chain) and extract
|
|
282
|
+
* the board-level facts. `zephyrBoardsRoot` (e.g. <zephyr>/boards) resolves
|
|
283
|
+
* `<../boards/common/...>` style includes.
|
|
284
|
+
*/
|
|
285
|
+
export function readBoardDts(dtsPath, opts = {}) {
|
|
286
|
+
const roots = { angle: opts.zephyrBoardsRoot ? [opts.zephyrBoardsRoot] : [] };
|
|
287
|
+
const seen = new Set();
|
|
288
|
+
let text = '';
|
|
289
|
+
const absorb = (file) => {
|
|
290
|
+
const real = path.resolve(file);
|
|
291
|
+
if (seen.has(real))
|
|
292
|
+
return;
|
|
293
|
+
seen.add(real);
|
|
294
|
+
const src = fs.readFileSync(real, 'utf8');
|
|
295
|
+
// Expand includes textualy (depth bounded by `seen`).
|
|
296
|
+
const includeRe = /#include\s+([<"][^>"]+[>"])/g;
|
|
297
|
+
let last = 0;
|
|
298
|
+
let m;
|
|
299
|
+
let out = '';
|
|
300
|
+
while ((m = includeRe.exec(src))) {
|
|
301
|
+
out += src.slice(last, m.index);
|
|
302
|
+
const resolved = resolveInclude(m[1], real, roots);
|
|
303
|
+
if (resolved)
|
|
304
|
+
absorb(resolved);
|
|
305
|
+
last = includeRe.lastIndex;
|
|
306
|
+
}
|
|
307
|
+
out += src.slice(last);
|
|
308
|
+
text += out + '\n';
|
|
309
|
+
};
|
|
310
|
+
absorb(dtsPath);
|
|
311
|
+
const { root: syntheticRoot, byLabel } = parseStatements(stripCommentsKeepMarkers(text));
|
|
312
|
+
// The DTS root ('/ { ... }') is a child of the synthetic outer node —
|
|
313
|
+
// resolve it so board-level nodes (aliases/chosen/leds/gpio_keys/&) sit at
|
|
314
|
+
// the depth the extractors expect.
|
|
315
|
+
const root = syntheticRoot.children.get('/') ?? syntheticRoot;
|
|
316
|
+
// aliases {} and chosen {} — phandle-ref properties
|
|
317
|
+
const aliases = {};
|
|
318
|
+
const chosen = {};
|
|
319
|
+
for (const name of ['aliases', 'chosen']) {
|
|
320
|
+
const node = root.children.get(name);
|
|
321
|
+
if (!node)
|
|
322
|
+
continue;
|
|
323
|
+
for (const [k, v] of node.props) {
|
|
324
|
+
const ref = v.match(/^&([\w,-]+)$/);
|
|
325
|
+
if (ref) {
|
|
326
|
+
if (name === 'aliases')
|
|
327
|
+
aliases[k] = ref[1];
|
|
328
|
+
else
|
|
329
|
+
chosen[k] = ref[1];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// gpio-leds / gpio-keys children (match on compatible OR node name)
|
|
334
|
+
const leds = [];
|
|
335
|
+
const buttons = [];
|
|
336
|
+
const collectGpioNodes = (node) => {
|
|
337
|
+
const compat = node.props.get('compatible') ?? '';
|
|
338
|
+
const isLeds = compat.includes('gpio-leds');
|
|
339
|
+
const isKeys = compat.includes('gpio-keys');
|
|
340
|
+
if (isLeds || isKeys) {
|
|
341
|
+
// A label can be aliased several times (led0 + mcuboot-led0); collect
|
|
342
|
+
// ALL aliases per label, then prefer the canonical led<N>/sw<N> form.
|
|
343
|
+
const aliasesByLabel = new Map();
|
|
344
|
+
for (const [alias, label] of Object.entries(aliases)) {
|
|
345
|
+
const list = aliasesByLabel.get(label) ?? [];
|
|
346
|
+
list.push(alias);
|
|
347
|
+
aliasesByLabel.set(label, list);
|
|
348
|
+
}
|
|
349
|
+
for (const child of node.children.values()) {
|
|
350
|
+
const ref = gpioRefFromProp(child.props.get('gpios') ?? '');
|
|
351
|
+
if (!ref)
|
|
352
|
+
continue;
|
|
353
|
+
const nodelabel = child.labels[0] ?? child.name;
|
|
354
|
+
const aliasCandidates = child.labels.flatMap((l) => aliasesByLabel.get(l) ?? []);
|
|
355
|
+
const alias = aliasCandidates.find((a) => /^(led|sw|button)[0-9]*$/.test(a)) ?? aliasCandidates[0];
|
|
356
|
+
const entry = {
|
|
357
|
+
...ref,
|
|
358
|
+
nodelabel,
|
|
359
|
+
alias,
|
|
360
|
+
};
|
|
361
|
+
(isLeds ? leds : buttons).push(entry);
|
|
362
|
+
}
|
|
363
|
+
return; // do not recurse into a leds/keys container
|
|
364
|
+
}
|
|
365
|
+
for (const child of node.children.values())
|
|
366
|
+
collectGpioNodes(child);
|
|
367
|
+
};
|
|
368
|
+
collectGpioNodes(root);
|
|
369
|
+
// Connector nexus nodes: any node with a gpio-map property
|
|
370
|
+
const connectors = [];
|
|
371
|
+
const findConnectors = (node) => {
|
|
372
|
+
const map = node.props.get('gpio-map');
|
|
373
|
+
if (map) {
|
|
374
|
+
const cells = Number(node.props.get('#gpio-cells')?.replace(/[<>]/g, '') ?? '2');
|
|
375
|
+
const parentCells = Number.isFinite(cells) && cells > 0 ? cells : 2;
|
|
376
|
+
const entries = parseGpioMap(map, parentCells);
|
|
377
|
+
// The LAST entry's trailing /* label */ comment can land after the
|
|
378
|
+
// property's terminating ';' — its marker never enters the value. When
|
|
379
|
+
// labeled siblings share a prefix+number pattern (D0…D9), extend it to
|
|
380
|
+
// the unlabeled tail entry (D10) instead of falling back to p<N>.
|
|
381
|
+
const labeled = entries.filter((e) => e.label);
|
|
382
|
+
const pattern = labeled.length >= 2
|
|
383
|
+
? (() => {
|
|
384
|
+
const m = labeled[0].label.match(/^([A-Za-z]+)([0-9]+)$/);
|
|
385
|
+
if (!m)
|
|
386
|
+
return undefined;
|
|
387
|
+
const prefix = m[1];
|
|
388
|
+
const consistent = labeled.every((e) => e.label.startsWith(prefix) && Number(e.label.slice(prefix.length)) === e.childSpecifier);
|
|
389
|
+
return consistent ? prefix : undefined;
|
|
390
|
+
})()
|
|
391
|
+
: undefined;
|
|
392
|
+
const pins = {};
|
|
393
|
+
for (const e of entries) {
|
|
394
|
+
const label = e.label ?? (pattern ? `${pattern}${e.childSpecifier}` : `p${e.childSpecifier}`);
|
|
395
|
+
pins[label] = e.ref;
|
|
396
|
+
for (const alt of e.altLabels ?? [])
|
|
397
|
+
pins[alt] = e.ref;
|
|
398
|
+
}
|
|
399
|
+
// Complete the analog run when the FINAL entry's comment lands after
|
|
400
|
+
// the property's ';' (its marker never enters the value): the header's
|
|
401
|
+
// D/A pairing increments in lockstep (D14/A0 … D21/A7), so a consistent
|
|
402
|
+
// D−A offset observed on the captured entries extends to the tail.
|
|
403
|
+
const observed = entries.filter((e) => {
|
|
404
|
+
const d = e.label?.match(/^D(\d+)$/);
|
|
405
|
+
return d && e.altLabels?.some((a) => /^A\d+$/.test(a));
|
|
406
|
+
});
|
|
407
|
+
if (observed.length >= 2) {
|
|
408
|
+
const offsetOf = (e) => Number(e.label.slice(1)) - Number(e.altLabels.find((a) => /^A\d+$/.test(a)).slice(1));
|
|
409
|
+
const offset = offsetOf(observed[0]);
|
|
410
|
+
if (observed.every((e) => offsetOf(e) === offset)) {
|
|
411
|
+
for (const e of entries) {
|
|
412
|
+
const d = e.label?.match(/^D(\d+)$/);
|
|
413
|
+
if (!d || e.altLabels?.some((a) => /^A\d+$/.test(a)))
|
|
414
|
+
continue;
|
|
415
|
+
// Only positive A indices within the observed run — D0…D13 sit
|
|
416
|
+
// below the analog block (offset would go negative).
|
|
417
|
+
const aIdx = Number(d[1]) - offset;
|
|
418
|
+
if (aIdx < 0)
|
|
419
|
+
continue;
|
|
420
|
+
if (!pins[`A${aIdx}`])
|
|
421
|
+
pins[`A${aIdx}`] = e.ref;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
connectors.push({
|
|
426
|
+
nodelabel: node.labels[0] ?? node.name,
|
|
427
|
+
compatible: (node.props.get('compatible') ?? '').replace(/"/g, '') || undefined,
|
|
428
|
+
pins,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
for (const child of node.children.values())
|
|
432
|
+
findConnectors(child);
|
|
433
|
+
};
|
|
434
|
+
findConnectors(root);
|
|
435
|
+
// Addressable user LED (worldsemi,ws2812-*). Three wiring forms in the
|
|
436
|
+
// tree: the RP2040 PIO variant nests a plain `gpios = <&ctrl pin …>` node;
|
|
437
|
+
// the SPI/I2S variants sit on a bus whose pinctrl names the data pad in
|
|
438
|
+
// the macro itself (SPIM3_MOSI_GPIO33, I2S0_O_SD_GPIO48 — the OUTPUT SD
|
|
439
|
+
// is the data line; I_SD is capture, wrong direction). Walks the
|
|
440
|
+
// SYNTHETIC root: the strips ride &ref blocks (siblings of the board's
|
|
441
|
+
// `/ {}` node), which the resolved root's walkers never see.
|
|
442
|
+
const stripLed = findStripLed(syntheticRoot, byLabel);
|
|
443
|
+
// PWM-driven LEDs (pwm-leds children): `pwms = <&pwmN channel [period]
|
|
444
|
+
// [flags]>`. Only aliased children are addressable by the PWM lowering
|
|
445
|
+
// (DT_ALIAS(pwm_led0)); carry the alias through.
|
|
446
|
+
const reverseAlias = new Map();
|
|
447
|
+
for (const [name, label] of Object.entries(aliases))
|
|
448
|
+
reverseAlias.set(label, name);
|
|
449
|
+
const pwmLeds = [];
|
|
450
|
+
for (const ledsNode of root.children.values()) {
|
|
451
|
+
if ((ledsNode.props.get('compatible') ?? '').replace(/"/g, '') !== 'pwm-leds')
|
|
452
|
+
continue;
|
|
453
|
+
for (const child of ledsNode.children.values()) {
|
|
454
|
+
const pwms = child.props.get('pwms');
|
|
455
|
+
if (!pwms)
|
|
456
|
+
continue;
|
|
457
|
+
const toks = parseCells(pwms);
|
|
458
|
+
if (toks.length < 2 || !toks[0].startsWith('&'))
|
|
459
|
+
continue;
|
|
460
|
+
const channel = Number(toks[1]);
|
|
461
|
+
if (!Number.isFinite(channel))
|
|
462
|
+
continue;
|
|
463
|
+
const period = Number(toks[2]);
|
|
464
|
+
const flags = toks.slice(Number.isFinite(period) ? 3 : 2)
|
|
465
|
+
.filter((t) => !t.startsWith('⟨') && !Number.isFinite(Number(t)) && !/[()]/.test(t));
|
|
466
|
+
const led = {
|
|
467
|
+
controller: toks[0].slice(1),
|
|
468
|
+
channel,
|
|
469
|
+
...(Number.isFinite(period) ? { periodNs: period } : {}),
|
|
470
|
+
...(flags.length > 0 ? { flags } : {}),
|
|
471
|
+
...(child.labels[0] && reverseAlias.has(child.labels[0]) ? { alias: reverseAlias.get(child.labels[0]) } : {}),
|
|
472
|
+
};
|
|
473
|
+
pwmLeds.push(led);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// USB device wiring: the board's &usbd / zephyr_udc0 node status. The
|
|
477
|
+
// label form (`zephyr_udc0: &usbd { … }`) attaches under both names.
|
|
478
|
+
let usbDevice;
|
|
479
|
+
let usbController;
|
|
480
|
+
for (const key of ['zephyr_udc0', 'usbd']) {
|
|
481
|
+
const n = byLabel.get(key);
|
|
482
|
+
if (!n)
|
|
483
|
+
continue;
|
|
484
|
+
const status = n.props.get('status');
|
|
485
|
+
if (status === '"okay"') {
|
|
486
|
+
usbDevice = 'enabled';
|
|
487
|
+
usbController = key;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
if (status === '"disabled"' && usbDevice === undefined)
|
|
491
|
+
usbDevice = 'disabled';
|
|
492
|
+
}
|
|
493
|
+
// Bus controller nodelabels the board wires up: &ref blocks whose status
|
|
494
|
+
// is okay (or that carry no status override but configure the bus — the
|
|
495
|
+
// app overlay decides enablement; cuttlefish's own overlay writes it).
|
|
496
|
+
// Classified by nodelabel — bus node names are vendor-stable (i2c0,
|
|
497
|
+
// spi2, usart0, eusart1, lpuart1, …).
|
|
498
|
+
const buses = { i2c: [], spi: [], uart: [] };
|
|
499
|
+
const classifyBus = (label) => {
|
|
500
|
+
if (/^i2c|^twi/.test(label))
|
|
501
|
+
return 'i2c';
|
|
502
|
+
if (/^spi|^ssp/.test(label))
|
|
503
|
+
return 'spi';
|
|
504
|
+
if (/uart|usart/.test(label))
|
|
505
|
+
return 'uart';
|
|
506
|
+
return undefined;
|
|
507
|
+
};
|
|
508
|
+
// Only &ref override blocks count (`xiao_i2c: &i2c1 { … }` → nodelabel
|
|
509
|
+
// 'i2c1' via the node's ref NAME) — label-captured pinctrl groups
|
|
510
|
+
// ('i2c0_default') are not buses. Pinctrl-group names also end in
|
|
511
|
+
// _default/_sleep; belt and braces.
|
|
512
|
+
const seenBuses = new Set();
|
|
513
|
+
for (const node of byLabel.values()) {
|
|
514
|
+
if (!node.name.startsWith('&'))
|
|
515
|
+
continue;
|
|
516
|
+
const nodelabel = node.name.slice(1);
|
|
517
|
+
if (nodelabel === 'usbd' || /_(default|sleep)$/.test(nodelabel))
|
|
518
|
+
continue;
|
|
519
|
+
const kind = classifyBus(nodelabel);
|
|
520
|
+
if (!kind || seenBuses.has(nodelabel))
|
|
521
|
+
continue;
|
|
522
|
+
const status = node.props.get('status');
|
|
523
|
+
if (status === '"disabled"')
|
|
524
|
+
continue;
|
|
525
|
+
seenBuses.add(nodelabel);
|
|
526
|
+
buses[kind].push(nodelabel);
|
|
527
|
+
}
|
|
528
|
+
for (const kind of ['i2c', 'spi', 'uart'])
|
|
529
|
+
buses[kind].sort();
|
|
530
|
+
void byLabel; // (labels already captured during parse)
|
|
531
|
+
return {
|
|
532
|
+
aliases, chosen, leds, buttons, connectors, pwmLeds, buses,
|
|
533
|
+
...(stripLed ? { stripLed } : {}),
|
|
534
|
+
...(usbDevice ? { usbDevice } : {}),
|
|
535
|
+
...(usbController ? { usbController } : {}),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
/** First `gpios` property in a subtree (the PIO strip nests it one level down). */
|
|
539
|
+
function gpiosInSubtree(node) {
|
|
540
|
+
const own = node.props.get('gpios');
|
|
541
|
+
if (own) {
|
|
542
|
+
const ref = gpioRefFromProp(own);
|
|
543
|
+
if (ref)
|
|
544
|
+
return ref;
|
|
545
|
+
}
|
|
546
|
+
for (const child of node.children.values()) {
|
|
547
|
+
const ref = gpiosInSubtree(child);
|
|
548
|
+
if (ref)
|
|
549
|
+
return ref;
|
|
550
|
+
}
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
553
|
+
/** All pinmux property text in a pinctrl group subtree. */
|
|
554
|
+
function pinmuxTextIn(node, out = []) {
|
|
555
|
+
const pm = node.props.get('pinmux');
|
|
556
|
+
if (pm)
|
|
557
|
+
out.push(pm);
|
|
558
|
+
for (const child of node.children.values())
|
|
559
|
+
pinmuxTextIn(child, out);
|
|
560
|
+
return out;
|
|
561
|
+
}
|
|
562
|
+
function findStripLed(root, byLabel) {
|
|
563
|
+
let found;
|
|
564
|
+
const walk = (node) => {
|
|
565
|
+
const compat = node.props.get('compatible') ?? '';
|
|
566
|
+
if (compat.includes('worldsemi,ws2812') && !found) {
|
|
567
|
+
found = stripRefOf(node, byLabel);
|
|
568
|
+
}
|
|
569
|
+
for (const child of node.children.values())
|
|
570
|
+
walk(child);
|
|
571
|
+
};
|
|
572
|
+
walk(root);
|
|
573
|
+
return found;
|
|
574
|
+
}
|
|
575
|
+
function stripRefOf(node, byLabel) {
|
|
576
|
+
// PIO form: a descendant carries the pad as a plain gpio ref.
|
|
577
|
+
const direct = gpiosInSubtree(node);
|
|
578
|
+
if (direct)
|
|
579
|
+
return direct;
|
|
580
|
+
// SPI/I2S forms: the enclosing bus's default pinctrl group names the
|
|
581
|
+
// data pad in its pinmux macro. Every ws2812-spi/i2s board in the tree is
|
|
582
|
+
// ESP32-family, whose controllers are the 32-pin gpio0/gpio1 pair.
|
|
583
|
+
const bus = node.parent;
|
|
584
|
+
const pcLabel = bus?.props.get('pinctrl-0')?.match(/&([\w-]+)/)?.[1];
|
|
585
|
+
const pinctrl = pcLabel ? byLabel.get(pcLabel) : undefined;
|
|
586
|
+
if (!pinctrl)
|
|
587
|
+
return undefined;
|
|
588
|
+
const text = pinmuxTextIn(pinctrl).join(' ');
|
|
589
|
+
const m = text.match(/MOSI_GPIO(\d+)|I2S\d*_O_SD_GPIO(\d+)/);
|
|
590
|
+
const global = m ? Number(m[1] ?? m[2]) : undefined;
|
|
591
|
+
if (global == null)
|
|
592
|
+
return undefined;
|
|
593
|
+
return global < 32
|
|
594
|
+
? { controller: 'gpio0', pin: global, flags: [] }
|
|
595
|
+
: { controller: 'gpio1', pin: global - 32, flags: [] };
|
|
596
|
+
}
|