@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -21
- package/README.md +49 -87
- package/dist/as-built.d.ts +2 -2
- package/dist/as-built.js +2 -2
- package/dist/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +288 -47
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +22 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +119 -49
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +510 -558
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +141 -38
- package/dist/dt-config/overlay.d.ts +15 -2
- package/dist/dt-config/overlay.js +433 -18
- package/dist/framework.manifest.d.ts +10 -4
- package/dist/framework.manifest.js +133 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -6
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +13 -92
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +137 -5
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/power.d.ts +3 -2
- package/dist/lowering/power.js +20 -45
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +16 -13
- package/dist/lowering/wdt.js +2 -29
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +82 -46
- package/dist/strategy.js +639 -182
- package/dist/tmp-probe.d.ts +2 -0
- package/dist/tmp-probe.js +9 -0
- package/dist/toolchain/debug-config.d.ts +50 -90
- package/dist/toolchain/debug-config.js +239 -510
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +28 -3
- package/dist/toolchain/index.js +498 -62
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +5 -2
- package/dist/toolchain/scaffold.js +79 -15
- package/dist/toolchain/west-discover.d.ts +6 -0
- package/dist/toolchain/west-discover.js +36 -13
- package/dist/toolchain/west-spawn.js +8 -2
- package/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- package/installer/README.md +328 -328
- package/installer/install.sh +2 -2
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
- package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
- package/installer/templates/project/.vscode/settings.json +1 -1
- package/installer/templates/project/README.md +2 -2
- package/package.json +5 -5
- package/src/as-built.ts +206 -206
- package/src/audit.ts +529 -0
- package/src/boardgen.ts +265 -50
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +10 -1
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +119 -49
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +732 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +647 -505
- package/src/dt-config/overlay.ts +1475 -1058
- package/src/framework.manifest.ts +659 -535
- package/src/index.ts +16 -6
- package/src/licenses.ts +346 -425
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +142 -5
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +16 -13
- package/src/lowering/wdt.ts +2 -25
- package/src/sbom.ts +1117 -0
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2680 -2309
- package/src/toolchain/debug-config.ts +262 -522
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1792 -1359
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +355 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/src/west-inventory.ts +102 -0
- package/dist/async/timer-polyfill.d.ts +0 -10
- package/dist/async/timer-polyfill.js +0 -95
- package/dist/chips/board-overrides.d.ts +0 -7
- package/dist/chips/board-overrides.js +0 -11
- package/dist/chips/esp32.d.ts +0 -2
- package/dist/chips/esp32.js +0 -71
- package/dist/chips/esp32s3.d.ts +0 -2
- package/dist/chips/esp32s3.js +0 -103
- package/dist/chips/soc/.d.ts +0 -2
- package/dist/chips/soc/.js +0 -129
- package/dist/chips/soc/esp32.d.ts +0 -2
- package/dist/chips/soc/esp32.js +0 -120
- package/dist/chips/soc/esp32c3.d.ts +0 -2
- package/dist/chips/soc/esp32c3.js +0 -90
- package/dist/chips/soc/esp32c6.d.ts +0 -2
- package/dist/chips/soc/esp32c6.js +0 -109
- package/dist/chips/soc/esp32s3.d.ts +0 -2
- package/dist/chips/soc/esp32s3.js +0 -189
- package/dist/chips/soc/index.d.ts +0 -2
- package/dist/chips/soc/index.js +0 -23
- package/dist/chips/soc/nrf52840.d.ts +0 -2
- package/dist/chips/soc/nrf52840.js +0 -130
- package/dist/chips/soc/rp2040.d.ts +0 -2
- package/dist/chips/soc/rp2040.js +0 -141
- package/dist/chips/soc/rp2350a.d.ts +0 -2
- package/dist/chips/soc/rp2350a.js +0 -145
- package/dist/chips/soc/samd21g18a.d.ts +0 -2
- package/dist/chips/soc/samd21g18a.js +0 -143
- package/dist/chips/soc/stm32f411xe.d.ts +0 -2
- package/dist/chips/soc/stm32f411xe.js +0 -251
- package/dist/chips/xiao-ble.d.ts +0 -2
- package/dist/chips/xiao-ble.js +0 -100
- package/dist/lowering/pulse.d.ts +0 -7
- package/dist/lowering/pulse.js +0 -51
- package/dist/lowering/tone.d.ts +0 -10
- package/dist/lowering/tone.js +0 -63
- package/dist/lowering/worker-backing.d.ts +0 -14
- package/dist/lowering/worker-backing.js +0 -79
- package/dist/lowering/worker.d.ts +0 -6
- package/dist/lowering/worker.js +0 -14
- package/dist/sdk/board-data.generated.d.ts +0 -2
- package/dist/sdk/board-data.generated.js +0 -4
- package/dist/sdk/catalog-walker.d.ts +0 -90
- package/dist/sdk/catalog-walker.js +0 -682
- package/dist/sdk/dts-reader.d.ts +0 -83
- package/dist/sdk/dts-reader.js +0 -596
- package/src/debug-codegen.ts +0 -207
package/src/dt-config/overlay.ts
CHANGED
|
@@ -1,1058 +1,1475 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Devicetree overlay generator
|
|
3
|
-
//
|
|
4
|
-
// Generates <project>/app/boards/<board>.overlay — the standard Zephyr overlay
|
|
5
|
-
// path. Content is additive: for each used peripheral with a DT binding the chip
|
|
6
|
-
// descriptor knows, emit a `&nodelabel { status = "okay"; }` block enabling it.
|
|
7
|
-
// West merges this over the board's base DT; we never rewrite the base.
|
|
8
|
-
//
|
|
9
|
-
// Framework library-resolution hook: the artifact
|
|
10
|
-
// that brings external capabilities into a build. (Arduino does it by parsing
|
|
11
|
-
// library headers into .d.ts; Zephyr does it by enabling DT nodes + Kconfig.)
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
lines.push(
|
|
185
|
-
lines.push(
|
|
186
|
-
lines.push(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
lines.push('
|
|
337
|
-
lines.push('
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
lines.push(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
lines.push('
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
)
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
lines.push('
|
|
505
|
-
lines.push(
|
|
506
|
-
lines.push('
|
|
507
|
-
lines.push(
|
|
508
|
-
lines.push('
|
|
509
|
-
|
|
510
|
-
lines.push(
|
|
511
|
-
lines.push('');
|
|
512
|
-
lines.push(
|
|
513
|
-
lines.push('
|
|
514
|
-
lines.push(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
lines.push('
|
|
521
|
-
lines.push(
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
lines.push(
|
|
533
|
-
lines.push('
|
|
534
|
-
lines.push(
|
|
535
|
-
lines.push('
|
|
536
|
-
lines.push('
|
|
537
|
-
lines.push('
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
lines.
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
lines.push('
|
|
609
|
-
lines.push('
|
|
610
|
-
lines.push('
|
|
611
|
-
lines.push('');
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
)
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
//
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
lines.push(
|
|
920
|
-
lines.push('
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
lines.push('
|
|
926
|
-
lines.push('
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
)
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
)
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
lines.push(
|
|
1050
|
-
lines.push(
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Devicetree overlay generator
|
|
3
|
+
//
|
|
4
|
+
// Generates <project>/app/boards/<board>.overlay — the standard Zephyr overlay
|
|
5
|
+
// path. Content is additive: for each used peripheral with a DT binding the chip
|
|
6
|
+
// descriptor knows, emit a `&nodelabel { status = "okay"; }` block enabling it.
|
|
7
|
+
// West merges this over the board's base DT; we never rewrite the base.
|
|
8
|
+
//
|
|
9
|
+
// Framework library-resolution hook: the artifact
|
|
10
|
+
// that brings external capabilities into a build. (Arduino does it by parsing
|
|
11
|
+
// library headers into .d.ts; Zephyr does it by enabling DT nodes + Kconfig.)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
15
|
+
import { controllerNodelabelForPin, controllerRawPinForPin } from '../chips/controllers.js';
|
|
16
|
+
import type { ZephyrDisplayProfile } from '../display/profiles.js';
|
|
17
|
+
import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor, transportFor, isDtCompatible } from '../display/profiles.js';
|
|
18
|
+
import { readDisplayBinding, type RequiredProp } from '../display/bindings.js';
|
|
19
|
+
import { isEinkDisplay } from '../display/profiles.js';
|
|
20
|
+
|
|
21
|
+
/** Standard values for the mono-OLED family's default-free required props
|
|
22
|
+
* (segment/page/display offsets, multiplex ratio, precharge period) — the
|
|
23
|
+
* values upstream board overlays ship; tune per panel from here. */
|
|
24
|
+
function monoPropFallback(prop: string, info: RequiredProp | undefined, nativeHeight: number): string | undefined {
|
|
25
|
+
if (prop === 'width' || prop === 'height' || prop === 'reg' || prop === 'compatible') return undefined;
|
|
26
|
+
const def = info?.default;
|
|
27
|
+
if (def?.kind === 'int') return `${prop} = <${def.value}>`;
|
|
28
|
+
if (prop === 'multiplex-ratio') return `multiplex-ratio = <${nativeHeight - 1}>`;
|
|
29
|
+
if (prop === 'prechargep') return 'prechargep = <0x22>';
|
|
30
|
+
if (prop === 'segment-offset' || prop === 'page-offset' || prop === 'display-offset') return `${prop} = <0>`;
|
|
31
|
+
// SSD1327-class (Stage 3 gray): required by solomon,ssd1327_5-common.yaml
|
|
32
|
+
// with no binding default. Panel-tunable — these are the datasheet/common-
|
|
33
|
+
// module starting points (scanline artifacts: raise oscillator-freq toward
|
|
34
|
+
// 0x70; striping: add 0x40 to remap-value per the binding's bit table).
|
|
35
|
+
if (prop === 'oscillator-freq') return 'oscillator-freq = <0x00>';
|
|
36
|
+
if (prop === 'start-line') return 'start-line = <0>';
|
|
37
|
+
if (prop === 'remap-value') return 'remap-value = <0x51>';
|
|
38
|
+
if (prop === 'phase-length') return 'phase-length = <0x1f>';
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Format one binding-required property with its harvested default. Binding
|
|
43
|
+
* props that are deliberately default-free ("panel specific" — e.g. the
|
|
44
|
+
* sitronix gamma arrays) fall back to the rig-validated tuning the curated
|
|
45
|
+
* profiles ship, so the generated node is always DT-valid. */
|
|
46
|
+
function emitBindingProp(lines: string[], prop: string, info: RequiredProp | undefined): void {
|
|
47
|
+
const def = info?.default;
|
|
48
|
+
if (def?.kind === 'bytes' && def.value.length > 0) {
|
|
49
|
+
lines.push(` ${prop} = [${def.value.map((b) => b.toString(16).padStart(2, '0')).join(' ')}];`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (def?.kind === 'bool' && def.value) {
|
|
53
|
+
lines.push(` ${prop};`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (def?.kind === 'string') {
|
|
57
|
+
lines.push(` ${prop} = "${def.value}";`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (info?.type === 'uint8-array') {
|
|
61
|
+
// No binding default (panel-specific): emit the rig-validated gamma
|
|
62
|
+
// tuning the curated st7796 profile carries. Tune per panel from here.
|
|
63
|
+
lines.push(` ${prop} = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const v = def?.kind === 'int' ? def.value : 0;
|
|
67
|
+
lines.push(` ${prop} = <${v}>;`);
|
|
68
|
+
}
|
|
69
|
+
import { SENSOR_PART_INFO } from '@typecad/hal';
|
|
70
|
+
import type { KconfigUsage } from './kconfig.js';
|
|
71
|
+
|
|
72
|
+
/** 1-Wire family codes (the high byte of the 64-bit ROM ID) for the parts
|
|
73
|
+
* the catalog carries on the w1 bus — vendor silicon data, like the ESP32
|
|
74
|
+
* DAC pin table. Unknown compatibles default to the DS18B20's 0x28. */
|
|
75
|
+
const W1_FAMILY_CODES: Record<string, number> = {
|
|
76
|
+
'maxim,ds18b20': 0x28,
|
|
77
|
+
'maxim,ds18s20': 0x10,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Display panel CS default — shared by the display block and the SPI-sensor
|
|
81
|
+
* cs-gpios merge so both sites write the same entry. */
|
|
82
|
+
const DEFAULT_DISPLAY_CS = 5;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Generate the overlay source for a board + usage. Returns the overlay text
|
|
86
|
+
* (caller writes it to app/boards/<board>.overlay via writeIfChanged).
|
|
87
|
+
*/
|
|
88
|
+
/** Display wiring from typecad-hal.config.ts (cs/dc/rst/spiFrequency/spiPins). */
|
|
89
|
+
export interface DisplayWiring {
|
|
90
|
+
cs?: number;
|
|
91
|
+
dc?: number;
|
|
92
|
+
rst?: number;
|
|
93
|
+
/** Tearing-effect (TE) GPIO from display.tearingEffectPin — emitted as
|
|
94
|
+
* te-gpios on the display DT node. Opt-in; most boards don't wire TE. */
|
|
95
|
+
tearingEffectPin?: number;
|
|
96
|
+
/** E-ink BUSY GPIO from display.busyPin — emitted as busy-gpios on the
|
|
97
|
+
* panel child (required by the ssd16xx/uc81xx bindings, active-high). */
|
|
98
|
+
busyPin?: number;
|
|
99
|
+
spiFrequency?: number;
|
|
100
|
+
/** I2C address for i2c-family panels (mono OLEDs, ssd1306-class). */
|
|
101
|
+
address?: number;
|
|
102
|
+
/** I2C bus pins for i2c-family panels. When present, the overlay remuxes
|
|
103
|
+
* the I2C controller's pinctrl to these pins — the board's default I2C
|
|
104
|
+
* pins rarely match a breakout's wiring (same rationale as the SPI pins
|
|
105
|
+
* below; the ESP32 pinctrl headers carry the named I2C0_*_GPIOn macros).
|
|
106
|
+
* Shared bus: when touch wiring also remuxes, the last assignment wins —
|
|
107
|
+
* wire both to the same pins. */
|
|
108
|
+
sda?: number;
|
|
109
|
+
scl?: number;
|
|
110
|
+
/** SPI bus pins. When present, the overlay remuxes the SPI controller's
|
|
111
|
+
* pinctrl to these pins (the board defaults rarely match a breakout's
|
|
112
|
+
* wiring — e.g. demo-st's panel is on SCK=18/MOSI=23, not the devkitc
|
|
113
|
+
* default 12/11). */
|
|
114
|
+
sck?: number;
|
|
115
|
+
mosi?: number;
|
|
116
|
+
miso?: number;
|
|
117
|
+
/** Optional GPIO driving the panel backlight. When set, the overlay emits a
|
|
118
|
+
* `gpio-leds` node + DT alias (named by the profile's `backlight`) so the
|
|
119
|
+
* display adapter can drive it high at init. When absent (e.g. the panel's
|
|
120
|
+
* backlight is hardwired to 3.3V), no node or alias is emitted — the C++
|
|
121
|
+
* backlight init is guarded by `DT_HAS_ALIAS` and compiles away. */
|
|
122
|
+
backlightPin?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Touch wiring from typecad-hal.config.ts (irq/resetPin/sda/scl; cs for SPI
|
|
126
|
+
* resistive controllers, calibration for the XPT2046 DT binding). */
|
|
127
|
+
export interface TouchWiring {
|
|
128
|
+
/** Touch controller kind — selects the DT node shape (FT6336U node on I2C0
|
|
129
|
+
* vs XPT2046 node on the display's SPI bus). Default 'ft6336u'. */
|
|
130
|
+
controller?: 'ft6336u' | 'xpt2046';
|
|
131
|
+
irq?: number;
|
|
132
|
+
resetPin?: number;
|
|
133
|
+
sda?: number;
|
|
134
|
+
scl?: number;
|
|
135
|
+
/** XPT2046 only: SPI CS pin (second cs-gpios entry on the panel's bus). */
|
|
136
|
+
cs?: number;
|
|
137
|
+
/** XPT2046 only: raw ADC calibration — feeds the binding's min-x/max-x/
|
|
138
|
+
* min-y/max-y (required props). Defaults span the full 12-bit range. */
|
|
139
|
+
calibration?: { xMin: number; xMax: number; yMin: number; yMax: number };
|
|
140
|
+
/** XPT2046 only: pen-detect Z1 threshold (binding's z-threshold). Resistive
|
|
141
|
+
* panels need a few hundred 12-bit counts; default 400. */
|
|
142
|
+
minPressure?: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface OverlayDiagnostic {
|
|
146
|
+
severity: "warning" | "error";
|
|
147
|
+
message: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function generateOverlay(
|
|
151
|
+
chip: ZephyrChipDescriptor,
|
|
152
|
+
usage: KconfigUsage,
|
|
153
|
+
display: ZephyrDisplayProfile | undefined,
|
|
154
|
+
wiring?: DisplayWiring,
|
|
155
|
+
touch?: TouchWiring,
|
|
156
|
+
diagnostics: OverlayDiagnostic[] = [],
|
|
157
|
+
): string {
|
|
158
|
+
// An I2C touch controller with no explicit bus pins: the overlay enables
|
|
159
|
+
// i2c0 and instantiates the node, but nothing remuxes the controller to
|
|
160
|
+
// the wired SDA/SCL (the board's default I2C pins rarely match a
|
|
161
|
+
// breakout). Every I2C read then fails and touch silently does nothing —
|
|
162
|
+
// surface it at build time instead of leaving it to a multimeter.
|
|
163
|
+
if (touch && touch.controller !== 'xpt2046' && (touch.sda === undefined || touch.scl === undefined)) {
|
|
164
|
+
diagnostics.push({
|
|
165
|
+
severity: "warning",
|
|
166
|
+
message: `touch: I2C controller '${touch.controller}' has no sda/scl pins in typecad-hal.config.ts — the overlay enables the bus without a pin assignment, so the controller may never answer. Add touch.sda and touch.scl (the board's default I2C pins are rarely the wired ones).`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const lines: string[] = [
|
|
170
|
+
'/* Auto-generated by @typecad/framework-zephyr from typecad-hal.config.ts. */',
|
|
171
|
+
'/* Enables peripherals the program uses. West merges this over the board DT. */',
|
|
172
|
+
'',
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// Pinctrl override blocks reference the esp32s3 pinmux tokens; the include
|
|
176
|
+
// must precede them. Emitted when the display SPI pins are overridden.
|
|
177
|
+
if (wiring?.sck !== undefined && wiring?.mosi !== undefined) {
|
|
178
|
+
lines.splice(2, 0, '#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>', '');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const block = (label: string, extra: string[] = []): void => {
|
|
182
|
+
lines.push(`&${label} {`);
|
|
183
|
+
lines.push(` status = "okay";`);
|
|
184
|
+
for (const e of extra) lines.push(` ${e}`);
|
|
185
|
+
lines.push('};');
|
|
186
|
+
lines.push('');
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Controllers whose board DT ships no default pinctrl group (rp2xxx uart1)
|
|
190
|
+
// carry synthesis data: emit the group under &pinctrl, then enable the
|
|
191
|
+
// controller referencing it. Controllers with a pinctrlRef wire an existing
|
|
192
|
+
// group instead; `props` adds raw property lines for bindings with
|
|
193
|
+
// required properties the board only sets on its own wired nodes.
|
|
194
|
+
// The pinmux header include is collected and spliced before all blocks
|
|
195
|
+
// (tokens must be defined before use).
|
|
196
|
+
const pinctrlIncludes = new Map<string, string[]>();
|
|
197
|
+
const controllerBlock = (c: { nodeLabel: string; pinctrlRef?: string; props?: readonly string[]; pinctrl?: { include: string; pinmux: readonly string[]; inputPinmux?: readonly string[]; defines?: readonly string[] } }): void => {
|
|
198
|
+
const extra: string[] = [...(c.props ?? [])];
|
|
199
|
+
if (c.pinctrl) {
|
|
200
|
+
pinctrlIncludes.set(c.pinctrl.include, c.pinctrl.defines ? [...c.pinctrl.defines] : []);
|
|
201
|
+
const group = `${c.nodeLabel}_default`;
|
|
202
|
+
lines.push('&pinctrl {');
|
|
203
|
+
lines.push(` ${group}: ${group} {`);
|
|
204
|
+
lines.push(` group1 {`);
|
|
205
|
+
lines.push(` pinmux = <${c.pinctrl.pinmux.join(', ')}>;`);
|
|
206
|
+
lines.push(` };`);
|
|
207
|
+
if (c.pinctrl.inputPinmux && c.pinctrl.inputPinmux.length > 0) {
|
|
208
|
+
lines.push(` group2 {`);
|
|
209
|
+
lines.push(` pinmux = <${c.pinctrl.inputPinmux.join(', ')}>;`);
|
|
210
|
+
lines.push(` input-enable;`);
|
|
211
|
+
lines.push(` };`);
|
|
212
|
+
}
|
|
213
|
+
lines.push(` };`);
|
|
214
|
+
lines.push('};');
|
|
215
|
+
lines.push('');
|
|
216
|
+
extra.push(`pinctrl-0 = <&${group}>;`, `pinctrl-names = "default";`);
|
|
217
|
+
} else if (c.pinctrlRef) {
|
|
218
|
+
extra.push(`pinctrl-0 = <&${c.pinctrlRef}>;`, `pinctrl-names = "default";`);
|
|
219
|
+
}
|
|
220
|
+
block(c.nodeLabel, extra);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Sensors ride a bus: a constructed sensor must enable its controller even
|
|
224
|
+
// when the program never calls i2c.*/spi.* directly (the sensor device
|
|
225
|
+
// handle is the only bus user) — merge their instances into the used sets.
|
|
226
|
+
const sensorParts = usage.sensorParts ?? [];
|
|
227
|
+
const i2cSensors = sensorParts.filter((s) => s.busKind !== 'spi');
|
|
228
|
+
const spiSensors = sensorParts.filter((s) => s.busKind === 'spi');
|
|
229
|
+
const spiTargets = usage.spiTargets ?? [];
|
|
230
|
+
const i2cInstances = new Set(usage.i2cUsedInstances ?? []);
|
|
231
|
+
for (const sp of i2cSensors) i2cInstances.add(sp.busIndex);
|
|
232
|
+
if ((usage.usesI2c || i2cSensors.length > 0) && chip.i2c) {
|
|
233
|
+
for (const [i, c] of chip.i2c.controllers.entries()) {
|
|
234
|
+
if (usage.i2cUsedInstances && !i2cInstances.has(i)) continue;
|
|
235
|
+
controllerBlock(c);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const spiInstances = new Set(usage.spiUsedInstances ?? []);
|
|
239
|
+
for (const sp of spiSensors) spiInstances.add(sp.busIndex);
|
|
240
|
+
for (const t of spiTargets) spiInstances.add(t.busIndex);
|
|
241
|
+
if ((usage.usesSpi || spiSensors.length > 0 || spiTargets.length > 0) && chip.spi) {
|
|
242
|
+
for (const [i, c] of chip.spi.controllers.entries()) {
|
|
243
|
+
if (usage.spiUsedInstances && !spiInstances.has(i)) continue;
|
|
244
|
+
controllerBlock(c);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// One DT child node per constructed I2C sensor — THE enable switch for the
|
|
248
|
+
// driver (its Kconfig is `default y` on this node's presence). The nodelabel
|
|
249
|
+
// is derived identically to the lowering's DEVICE_DT_GET target
|
|
250
|
+
// (lowering/sensor.ts sensorNames) — the two cannot drift.
|
|
251
|
+
for (const sp of i2cSensors) {
|
|
252
|
+
const ctrl = chip.i2c?.controllers[sp.busIndex];
|
|
253
|
+
const info = SENSOR_PART_INFO[sp.part.replace(/^SENSOR\./, '')];
|
|
254
|
+
if (!ctrl || !info) continue; // the lowering already threw on unknown parts
|
|
255
|
+
const nodeName = info.compatible.split(',')[1] ?? sp.part;
|
|
256
|
+
const addrHex = sp.port.toString(16);
|
|
257
|
+
lines.push(`&${ctrl.nodeLabel} {`);
|
|
258
|
+
lines.push(` tc_${sp.part.replace(/^SENSOR\./, '')}_i2c${sp.busIndex}_0x${addrHex}: ${nodeName}@${addrHex} {`);
|
|
259
|
+
lines.push(` compatible = "${info.compatible}";`);
|
|
260
|
+
lines.push(` reg = <0x${addrHex}>;`);
|
|
261
|
+
emitAlert(lines, chip, sp.alertPin, info.alert, 8);
|
|
262
|
+
lines.push(' };');
|
|
263
|
+
lines.push('};');
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
266
|
+
// One w1-gpio master + sensor child per constructed 1-Wire sensor. The
|
|
267
|
+
// master bit-bangs the data pin (open-drain, internal pull-up enabled —
|
|
268
|
+
// the binding recommends an external 4.7 kΩ, the internal one is weak);
|
|
269
|
+
// the child's family-code comes from the vendor table below (part of the
|
|
270
|
+
// 64-bit ROM ID), resolution from the construction opts. The nodelabels
|
|
271
|
+
// match lowering/sensor.ts sensorNames — the tc-pwm discipline.
|
|
272
|
+
for (const sp of sensorParts.filter((s) => s.busKind === 'w1')) {
|
|
273
|
+
const info = SENSOR_PART_INFO[sp.part.replace(/^SENSOR\./, '')];
|
|
274
|
+
const family = W1_FAMILY_CODES[info?.compatible ?? ''] ?? 0x28;
|
|
275
|
+
if (!info) continue; // the lowering already threw on unknown parts
|
|
276
|
+
const pinCtrl = controllerNodelabelForPin(chip, sp.port);
|
|
277
|
+
const pinBit = controllerRawPinForPin(chip, sp.port);
|
|
278
|
+
const nodeName = info.compatible.split(',')[1] ?? sp.part;
|
|
279
|
+
const stem = `${sp.part.replace(/^SENSOR\./, '')}_w1_p${sp.port}`;
|
|
280
|
+
lines.push('/ {');
|
|
281
|
+
lines.push(` tc_w1_p${sp.port}: tc-w1-p${sp.port} {`);
|
|
282
|
+
lines.push(' compatible = "zephyr,w1-gpio";');
|
|
283
|
+
lines.push(` gpios = <&${pinCtrl} ${pinBit} (GPIO_OPEN_DRAIN | GPIO_PULL_UP)>;`);
|
|
284
|
+
lines.push(' status = "okay";');
|
|
285
|
+
lines.push(` tc_${stem}: ${nodeName} {`);
|
|
286
|
+
lines.push(` compatible = "${info.compatible}";`);
|
|
287
|
+
lines.push(` family-code = <0x${family.toString(16)}>;`);
|
|
288
|
+
lines.push(` resolution = <${sp.resolution}>;`);
|
|
289
|
+
lines.push(' status = "okay";');
|
|
290
|
+
lines.push(' };');
|
|
291
|
+
lines.push(' };');
|
|
292
|
+
lines.push('};');
|
|
293
|
+
lines.push('');
|
|
294
|
+
}
|
|
295
|
+
if (usage.usesSpi && chip.spi) {
|
|
296
|
+
for (const [i, c] of chip.spi.controllers.entries()) {
|
|
297
|
+
if (usage.spiUsedInstances && !usage.spiUsedInstances.includes(i)) continue;
|
|
298
|
+
controllerBlock(c);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (usage.usesUart && chip.uart) {
|
|
302
|
+
for (const [i, c] of chip.uart.controllers.entries()) {
|
|
303
|
+
if (usage.uartUsedInstances && !usage.uartUsedInstances.includes(i)) continue;
|
|
304
|
+
controllerBlock(c);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Synthesized pinctrl groups reference pinmux tokens — splice their header
|
|
308
|
+
// includes ahead of every block (same slot the display path uses), with
|
|
309
|
+
// ifndef-guarded #defines for upstream header bugs in front.
|
|
310
|
+
for (const [include, defines] of pinctrlIncludes) {
|
|
311
|
+
const pre: string[] = [];
|
|
312
|
+
for (const d of defines) {
|
|
313
|
+
const name = d.trim().split(/\s+/)[0];
|
|
314
|
+
pre.push(`#ifndef ${name}`, `#define ${d}`, `#endif`);
|
|
315
|
+
}
|
|
316
|
+
lines.splice(2, 0, ...pre, `#include <${include}>`, '');
|
|
317
|
+
}
|
|
318
|
+
// Watchdog: the SoC dtsi declares the node disabled (STM32 iwdg ships
|
|
319
|
+
// status = "disabled" until a board enables it) — the wdt.* lowering
|
|
320
|
+
// addresses DEVICE_DT_GET(DT_NODELABEL(<nodeLabel>)), which needs the
|
|
321
|
+
// driver instance built, so enable the node when wdt_* is used.
|
|
322
|
+
if (usage.usesWdt && chip.wdt) {
|
|
323
|
+
block(chip.wdt.nodeLabel);
|
|
324
|
+
}
|
|
325
|
+
// USB CDC-ACM: the "next" USB device stack composes classes as devicetree
|
|
326
|
+
// children of the UDC controller node. Enable the controller and declare one
|
|
327
|
+
// cdc_acm_uart<N> child per instance the chip descriptor declares — the
|
|
328
|
+
// lowering addresses instance N as DT_NODELABEL(cdc_acm_uart<N>), so the
|
|
329
|
+
// nodelabels here and in lowering/usb.ts cannot drift. Endpoints and
|
|
330
|
+
// descriptors are assigned by the class driver at build time.
|
|
331
|
+
if (usage.usesUsb && chip.usb) {
|
|
332
|
+
lines.push(`&${chip.usb.controller} {`);
|
|
333
|
+
lines.push(' status = "okay";');
|
|
334
|
+
for (let i = 0; i < chip.usb.cdcInstances; i++) {
|
|
335
|
+
lines.push(` cdc_acm_uart${i}: cdc-acm-uart${i} {`);
|
|
336
|
+
lines.push(' compatible = "zephyr,cdc-acm-uart";');
|
|
337
|
+
lines.push(' };');
|
|
338
|
+
}
|
|
339
|
+
lines.push('};');
|
|
340
|
+
lines.push('');
|
|
341
|
+
}
|
|
342
|
+
// PWM: synthesized specs (controller + channel, no board-shipped alias) get
|
|
343
|
+
// a pwm-leds consumer node + tc-pwm<pin> alias here — the lowering addresses
|
|
344
|
+
// the channel as PWM_DT_SPEC_GET(DT_ALIAS(tc-pwm<pin>)), so the alias must
|
|
345
|
+
// exist in the merged DT. pwmDtAlias (lowering/pwm.ts) derives the same
|
|
346
|
+
// name from the pin; the two sides cannot drift. Filtered to the pins the
|
|
347
|
+
// program actually drives (pwmUsedPins) so the DT carries no dead channels;
|
|
348
|
+
// an undefined list (prepare-time overlay) emits all specs.
|
|
349
|
+
if (usage.usesPwm && chip.pwm) {
|
|
350
|
+
emitPwmNodes(lines, chip, usage.pwmUsedPins);
|
|
351
|
+
}
|
|
352
|
+
// ADC: enable the ADC device node, and on SoCs whose channels carry a
|
|
353
|
+
// pinctrl label (STM32: the pad stays in GPIO mode unless the ADC node's
|
|
354
|
+
// pinctrl-0 muxes it analog), rewrite pinctrl-0 to exactly the channels the
|
|
355
|
+
// program reads. DT assignment replaces the whole property — the board
|
|
356
|
+
// default (one pad) is deliberately dropped in favor of the used set.
|
|
357
|
+
if (usage.usesAdc && chip.adc) {
|
|
358
|
+
emitAdcNode(lines, chip, usage.adcReadPins);
|
|
359
|
+
}
|
|
360
|
+
// DAC: enable the chip's DAC device node when the program uses dac.*. The
|
|
361
|
+
// lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)); the used
|
|
362
|
+
// channels' pinctrl groups route the output to its pad.
|
|
363
|
+
if (usage.usesDac && chip.dac) {
|
|
364
|
+
emitDacNode(lines, chip, usage.dacWritePins);
|
|
365
|
+
}
|
|
366
|
+
// Counters: enable the free counter devices when the program uses hwtimer.
|
|
367
|
+
// Self form (nRF rtc1, STM32 rtc): enable the labeled node. Child form
|
|
368
|
+
// (ESP32 timer0-3): the counter device is an unlabeled `counter {}`
|
|
369
|
+
// child — the overlay defines the tc_counter<N> label the descriptor's
|
|
370
|
+
// nodeLabel references (the same label-less-child move the STM32 pwm
|
|
371
|
+
// path uses).
|
|
372
|
+
if (usage.usesHwtimer && chip.hwtimer) {
|
|
373
|
+
for (const c of chip.hwtimer.controllers) {
|
|
374
|
+
if (c.counterParent) {
|
|
375
|
+
lines.push(`&${c.counterParent} {`);
|
|
376
|
+
lines.push(' status = "okay";');
|
|
377
|
+
lines.push(` ${c.nodeLabel}: counter {`);
|
|
378
|
+
lines.push(' status = "okay";');
|
|
379
|
+
lines.push(' };');
|
|
380
|
+
lines.push('};');
|
|
381
|
+
lines.push('');
|
|
382
|
+
} else {
|
|
383
|
+
lines.push(`&${c.nodeLabel} {`);
|
|
384
|
+
lines.push(' status = "okay";');
|
|
385
|
+
lines.push('};');
|
|
386
|
+
lines.push('');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// LED strips: one ws2812-spi child per driven SPI controller. The frame
|
|
391
|
+
// tuning (6.4 MHz, one=0xf0 / zero=0xc0 — 625 ns bit halves) is the
|
|
392
|
+
// Zephyr led_strip sample's canonical SPI encoding; the color-mapping
|
|
393
|
+
// matches the WS2812's native GRB order (the driver reorders). The
|
|
394
|
+
// controller is enabled here too — a strip-only program emits no spi_*
|
|
395
|
+
// calls, so the bus enable cannot ride the generic SPI usage path.
|
|
396
|
+
// `line-idle-low` keeps MOSI low between frames, the WS2812 reset level.
|
|
397
|
+
if (usage.usesStrip && usage.strips && usage.strips.length > 0) {
|
|
398
|
+
lines.splice(2, 0, '#include <zephyr/dt-bindings/led/led.h>', '');
|
|
399
|
+
for (const s of usage.strips) {
|
|
400
|
+
const ctrl = chip.spi?.controllers[s.index];
|
|
401
|
+
if (!ctrl) continue;
|
|
402
|
+
lines.push(`&${ctrl.nodeLabel} {`);
|
|
403
|
+
lines.push(' status = "okay";');
|
|
404
|
+
lines.push(' line-idle-low;');
|
|
405
|
+
lines.push(` tc_strip${s.index}: ws2812@0 {`);
|
|
406
|
+
lines.push(' compatible = "worldsemi,ws2812-spi";');
|
|
407
|
+
lines.push(' reg = <0>;');
|
|
408
|
+
lines.push(' spi-max-frequency = <6400000>;');
|
|
409
|
+
lines.push(` chain-length = <${s.count}>;`);
|
|
410
|
+
lines.push(' spi-cpha;');
|
|
411
|
+
lines.push(' spi-one-frame = <0xf0>;');
|
|
412
|
+
lines.push(' spi-zero-frame = <0xc0>;');
|
|
413
|
+
lines.push(' color-mapping = <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>;');
|
|
414
|
+
lines.push(' };');
|
|
415
|
+
lines.push('};');
|
|
416
|
+
lines.push('');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// I2S: enable the board's own i2s@ controllers — their pinctrl groups
|
|
420
|
+
// are board-authored (the devkitC wires i2s0_default/i2s1_default), so
|
|
421
|
+
// status is the only synthesis needed. The GDMA the ESP32 I2S driver
|
|
422
|
+
// DMA-runs through also ships disabled — enable it alongside.
|
|
423
|
+
// Full-duplex: the RX side's I_BCK/I_WS inputs must be routed in the
|
|
424
|
+
// GPIO matrix to the same pads the TX outputs drive (input-enable on
|
|
425
|
+
// the TX clock pads — the same-pad GPIO matrix trick the CAN loopback
|
|
426
|
+
// uses; the devkitC's i2s0 clock pads are WS=36, BCK=37).
|
|
427
|
+
if (usage.usesI2s && chip.i2s) {
|
|
428
|
+
lines.push('&dma {');
|
|
429
|
+
lines.push(' status = "okay";');
|
|
430
|
+
lines.push('};');
|
|
431
|
+
lines.push('');
|
|
432
|
+
for (const c of chip.i2s.controllers) {
|
|
433
|
+
const n = c.nodeLabel.replace(/[^0-9]/g, '') || '0';
|
|
434
|
+
lines.push(`&${c.nodeLabel} {`);
|
|
435
|
+
lines.push(' status = "okay";');
|
|
436
|
+
lines.push('};');
|
|
437
|
+
lines.push('');
|
|
438
|
+
lines.push('&pinctrl {');
|
|
439
|
+
lines.push(' tc_i2s_clock_loop: tc-i2s-clock-loop {');
|
|
440
|
+
lines.push(' group1 {');
|
|
441
|
+
lines.push(` pinmux = <I2S${n}_I_BCK_GPIO37>,`);
|
|
442
|
+
lines.push(` <I2S${n}_I_WS_GPIO36>;`);
|
|
443
|
+
lines.push(' };');
|
|
444
|
+
lines.push(' };');
|
|
445
|
+
lines.push('};');
|
|
446
|
+
lines.push(`&${c.nodeLabel} {`);
|
|
447
|
+
lines.push(` pinctrl-0 = <&${c.nodeLabel}_default>, <&tc_i2s_clock_loop>;`);
|
|
448
|
+
lines.push(' pinctrl-names = "default";');
|
|
449
|
+
lines.push('};');
|
|
450
|
+
lines.push('');
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// CAN: enable the board's own can@ controller (the harvested node —
|
|
454
|
+
// boards that ship pinctrl groups keep them; the bitrate is the begin()
|
|
455
|
+
// default, overridden at runtime by can_set_bitrate). Classic CAN only.
|
|
456
|
+
// LOOPBACK: the SJA1000 self-test transmits on the TX pad and receives
|
|
457
|
+
// on the RX pad — with no transceiver the overlay routes BOTH TWAI
|
|
458
|
+
// functions onto the TX pad (the Zephyr test suite's own
|
|
459
|
+
// twai-enable.overlay shape: output-enable on the RX mux, input-enable
|
|
460
|
+
// on the TX mux, one shared pad).
|
|
461
|
+
if (usage.usesCan && chip.can) {
|
|
462
|
+
for (const c of chip.can.controllers) {
|
|
463
|
+
lines.push(`&${c.nodeLabel} {`);
|
|
464
|
+
lines.push(' status = "okay";');
|
|
465
|
+
lines.push(' bitrate = <500000>;');
|
|
466
|
+
lines.push('};');
|
|
467
|
+
lines.push('');
|
|
468
|
+
if (usage.canLoopback && c.txPad !== undefined) {
|
|
469
|
+
lines.push('&pinctrl {');
|
|
470
|
+
lines.push(' tc_can_loopback: tc-can-loopback {');
|
|
471
|
+
lines.push(' group1 {');
|
|
472
|
+
lines.push(` pinmux = <TWAI_RX_GPIO${c.txPad}>;`);
|
|
473
|
+
lines.push(' output-enable;');
|
|
474
|
+
lines.push(' };');
|
|
475
|
+
lines.push(' group2 {');
|
|
476
|
+
lines.push(` pinmux = <TWAI_TX_GPIO${c.txPad}>;`);
|
|
477
|
+
lines.push(' input-enable;');
|
|
478
|
+
lines.push(' };');
|
|
479
|
+
lines.push(' };');
|
|
480
|
+
lines.push('};');
|
|
481
|
+
lines.push(`&${c.nodeLabel} { pinctrl-0 = <&tc_can_loopback>; pinctrl-names = "default"; };`);
|
|
482
|
+
lines.push('');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Wall-clock shim: a zephyr,rtc-counter child on the board's first free
|
|
487
|
+
// counter node + the `rtc` alias (Zephyr's own discovery convention —
|
|
488
|
+
// the api tests filter on dt_alias_exists("rtc")). The shim is a CHILD:
|
|
489
|
+
// the counter driver keeps the parent node, so Counter and Clock
|
|
490
|
+
// coexist. V1 always synthesizes the shim (uniform semantics, no driver
|
|
491
|
+
// conflicts); preferring a board's own hardware-calendar alias — and the
|
|
492
|
+
// backup-domain persistence it buys — is the follow-up.
|
|
493
|
+
if (usage.usesClock && usage.clockShimCounter) {
|
|
494
|
+
const c = usage.clockShimCounter;
|
|
495
|
+
if (c.parent) {
|
|
496
|
+
// Child-form counters (ESP32 timerN children): the shim's parent
|
|
497
|
+
// must be the counter DEVICE, so it nests INSIDE the counter child —
|
|
498
|
+
// DT_INST_PARENT in the shim driver resolves to the counter, not the
|
|
499
|
+
// timer (which carries no device of its own).
|
|
500
|
+
lines.push(`&${c.parent} {`);
|
|
501
|
+
lines.push(' status = "okay";');
|
|
502
|
+
lines.push(` ${c.label}: counter {`);
|
|
503
|
+
lines.push(' status = "okay";');
|
|
504
|
+
lines.push(' tc_rtc: tc-rtc {');
|
|
505
|
+
lines.push(' compatible = "zephyr,rtc-counter";');
|
|
506
|
+
lines.push(' };');
|
|
507
|
+
lines.push(' };');
|
|
508
|
+
lines.push('};');
|
|
509
|
+
} else {
|
|
510
|
+
lines.push(`&${c.label} {`);
|
|
511
|
+
lines.push(' tc_rtc: tc-rtc {');
|
|
512
|
+
lines.push(' compatible = "zephyr,rtc-counter";');
|
|
513
|
+
lines.push(' };');
|
|
514
|
+
lines.push('};');
|
|
515
|
+
}
|
|
516
|
+
lines.push('/ {');
|
|
517
|
+
lines.push(' aliases {');
|
|
518
|
+
lines.push(' rtc = &tc_rtc;');
|
|
519
|
+
lines.push(' };');
|
|
520
|
+
lines.push('};');
|
|
521
|
+
lines.push('');
|
|
522
|
+
}
|
|
523
|
+
// USB HID: one zephyr,hid-device interface per program (the v1 ceiling —
|
|
524
|
+
// Keyboard OR Mouse, not both). protocol-code stays "none": the boot
|
|
525
|
+
// subclass/protocol codes (keyboard/mouse) made Windows bind the
|
|
526
|
+
// collections but silently discard every input report on this stack,
|
|
527
|
+
// while "none" (pure report-protocol device, like Zephyr's own samples)
|
|
528
|
+
// delivers input normally. The report descriptor carries the real
|
|
529
|
+
// keyboard/mouse semantics either way.
|
|
530
|
+
if (usage.usesHid) {
|
|
531
|
+
lines.push('/ {');
|
|
532
|
+
lines.push(' tc_hid: tc-hid {');
|
|
533
|
+
lines.push(' compatible = "zephyr,hid-device";');
|
|
534
|
+
lines.push(' protocol-code = "none";');
|
|
535
|
+
lines.push(' in-report-size = <8>;');
|
|
536
|
+
lines.push(' in-polling-period-us = <1000>;');
|
|
537
|
+
lines.push(' };');
|
|
538
|
+
lines.push('};');
|
|
539
|
+
lines.push('');
|
|
540
|
+
}
|
|
541
|
+
// Key matrix: the gpio-kbd-matrix node from the construction pad lists.
|
|
542
|
+
// Rows are pull-up inputs (active-low); columns are driven one at a time.
|
|
543
|
+
// Pin → controller/bit via the same mapping the gpio lowering uses.
|
|
544
|
+
if (usage.usesMatrix && usage.matrix && usage.matrix.rows.length > 0) {
|
|
545
|
+
// Same mapping the gpio lowering uses — the canonical controller
|
|
546
|
+
// resolution (single-controller fallback included). The port-relative
|
|
547
|
+
// bit comes from controllerRawPinForPin (split-SoC offsets).
|
|
548
|
+
const gpioRef = (pin: number): string => {
|
|
549
|
+
return `&${controllerNodelabelForPin(chip, pin)} ${controllerRawPinForPin(chip, pin)} (GPIO_PULL_UP | GPIO_ACTIVE_LOW)`;
|
|
550
|
+
};
|
|
551
|
+
lines.push('/ {');
|
|
552
|
+
lines.push(' tc_matrix: matrix {');
|
|
553
|
+
lines.push(' compatible = "gpio-kbd-matrix";');
|
|
554
|
+
lines.push(` row-gpios = ${usage.matrix.rows.map((pin) => `<${gpioRef(pin)}>`).join(', ')};`);
|
|
555
|
+
lines.push(` col-gpios = ${usage.matrix.cols.map((pin) => `<${gpioRef(pin)}>`).join(', ')};`);
|
|
556
|
+
lines.push(' };');
|
|
557
|
+
lines.push('};');
|
|
558
|
+
lines.push('');
|
|
559
|
+
}
|
|
560
|
+
if (display) {
|
|
561
|
+
// Emit a full display DT node definition. Boards like the ESP32 devkit
|
|
562
|
+
// have no display node in their base DT, so a bare `&display0 { status }`
|
|
563
|
+
// fails (the nodelabel doesn't exist). Define the node attached to a SPI
|
|
564
|
+
// controller with the panel's compatible string + dimensions. The pin
|
|
565
|
+
// wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
|
|
566
|
+
// real board overlay would carry its own binding.
|
|
567
|
+
// An XPT2046 on the same bus needs its CS as the second cs-gpios entry,
|
|
568
|
+
// so thread it into the display block (DT assignment replaces the whole
|
|
569
|
+
// property — both entries must be written together).
|
|
570
|
+
emitDisplayNode(
|
|
571
|
+
lines,
|
|
572
|
+
display,
|
|
573
|
+
wiring,
|
|
574
|
+
touch?.controller === 'xpt2046' ? (touch?.cs ?? DEFAULT_XPT2046_CS) : undefined,
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
// Touch — FT6336U capacitive on I2C (references DT_NODELABEL(ft6336u)) or
|
|
578
|
+
// XPT2046 resistive on the display's SPI bus (references
|
|
579
|
+
// DT_NODELABEL(xpt2046)). Same rationale as the display node: the bare
|
|
580
|
+
// devkit has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it
|
|
581
|
+
// emits even when the chip descriptor doesn't declare bus controllers.
|
|
582
|
+
if (usage.usesTouch) {
|
|
583
|
+
emitTouchNode(lines, touch, display);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Preferences (ZMS settings backend): point the settings subsystem at the
|
|
587
|
+
// board's storage_partition. The backend looks for /chosen
|
|
588
|
+
// zephyr,settings-partition first, then falls back to a fixed-partition
|
|
589
|
+
// labeled storage_partition — most Zephyr boards define that label, so this
|
|
590
|
+
// chosen entry makes the lookup explicit and survives boards that name the
|
|
591
|
+
// partition differently. It only adds a /chosen pointer (never redeclares
|
|
592
|
+
// the partition node — west errors if a node is multiply-defined).
|
|
593
|
+
if (usage.usesPreferences || usage.usesFS) {
|
|
594
|
+
// Boards whose DTS ships no storage_partition (most STM32s define only
|
|
595
|
+
// the MCUboot boot/slot/scratch set) declare a synthesis region in the
|
|
596
|
+
// chip descriptor — emit it as a partition@<offset> child of the flash0
|
|
597
|
+
// partitions node. Adding a NEW child via overlay is legal; redeclaring
|
|
598
|
+
// an existing one is not, so boards that already carry the label
|
|
599
|
+
// (descriptor storage.preexisting — ESP32's AMP layout) skip the
|
|
600
|
+
// synthesis block and only the /chosen pointer lands here.
|
|
601
|
+
if (chip.storage && !chip.storage.preexisting) {
|
|
602
|
+
lines.push('&flash0 {');
|
|
603
|
+
// Explicit compatible + cells: boards whose DTS already carries a
|
|
604
|
+
// partitions node (rp2040, STM32 MCUboot sets) merge identically, and
|
|
605
|
+
// boards without one (rpi_pico2) would otherwise hit dtc's spec
|
|
606
|
+
// default of #address-cells 2 / #size-cells 1 — a 3-cell reg and a
|
|
607
|
+
// "length 8 not divisible by 12" error for the 2-cell reg below.
|
|
608
|
+
lines.push(' partitions {');
|
|
609
|
+
lines.push(' compatible = "fixed-partitions";');
|
|
610
|
+
lines.push(' #address-cells = <1>;');
|
|
611
|
+
lines.push(' #size-cells = <1>;');
|
|
612
|
+
lines.push(` storage_partition: partition@${chip.storage.offset.toString(16)} {`);
|
|
613
|
+
lines.push(' label = "storage";');
|
|
614
|
+
lines.push(` reg = <0x${chip.storage.offset.toString(16).padStart(8, '0')} 0x${chip.storage.size.toString(16).padStart(8, '0')}>;`);
|
|
615
|
+
lines.push(' };');
|
|
616
|
+
lines.push(' };');
|
|
617
|
+
lines.push('};');
|
|
618
|
+
lines.push('');
|
|
619
|
+
}
|
|
620
|
+
if (usage.usesPreferences) {
|
|
621
|
+
lines.push('/ {');
|
|
622
|
+
lines.push(' chosen {');
|
|
623
|
+
lines.push(' zephyr,settings-partition = &storage_partition;');
|
|
624
|
+
lines.push(' };');
|
|
625
|
+
lines.push('};');
|
|
626
|
+
lines.push('');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// PSRAM: enable the psram0 DT node with the correct size. The devkitc board
|
|
631
|
+
// DT defaults to a no-PSRAM module variant (e.g. wroom_n8); a PSRAM-capable
|
|
632
|
+
// module (N16R8, N8R8) needs the node enabled + sized so the linker maps
|
|
633
|
+
// .ext_ram sections into the real PSRAM. OPI on ESP32-S3 = 8MB octal PSRAM.
|
|
634
|
+
// SPI sensors: children whose reg is their cs-gpios INDEX. DT property
|
|
635
|
+
// assignment replaces the whole property, so cs-gpios is written here with
|
|
636
|
+
// every sensor CS on the controller, after the display block — a display
|
|
637
|
+
// panel sharing the bus is a wiring conflict this layer cannot merge (its
|
|
638
|
+
// CS comes from config wiring), so warn instead of silently dropping it.
|
|
639
|
+
// spi-max-frequency defaults to 1 MHz — conservative for sensors whose
|
|
640
|
+
// datasheet max isn't in any binding; the constructor opts override it.
|
|
641
|
+
const SPI_SENSOR_HZ = 1000000;
|
|
642
|
+
const spiByCtrl = new Map<number, typeof spiSensors>();
|
|
643
|
+
for (const sp of spiSensors) {
|
|
644
|
+
const list = spiByCtrl.get(sp.busIndex) ?? [];
|
|
645
|
+
list.push(sp);
|
|
646
|
+
spiByCtrl.set(sp.busIndex, list);
|
|
647
|
+
}
|
|
648
|
+
// Target-only controllers (no sensors) still need the cs-gpios block.
|
|
649
|
+
for (const t of spiTargets) {
|
|
650
|
+
if (!spiByCtrl.has(t.busIndex)) spiByCtrl.set(t.busIndex, []);
|
|
651
|
+
}
|
|
652
|
+
for (const [idx, sensors] of spiByCtrl) {
|
|
653
|
+
const ctrl = chip.spi?.controllers[idx];
|
|
654
|
+
if (!ctrl) continue;
|
|
655
|
+
// A display panel on the same controller already wrote cs-gpios earlier
|
|
656
|
+
// in this overlay (its CS at index 0, an XPT2046 at 1). DT property
|
|
657
|
+
// assignment replaces, so this rewrite carries BOTH: the display entries
|
|
658
|
+
// first — keeping the panel/touch reg indexes stable — then the sensor CS
|
|
659
|
+
// pins. The entry construction mirrors emitDisplayNode's defaults via the
|
|
660
|
+
// shared DEFAULT_DISPLAY_CS/DEFAULT_XPT2046_CS constants.
|
|
661
|
+
const displayEntries: string[] = [];
|
|
662
|
+
if (display && (display.busLabel ?? 'spi2') === ctrl.nodeLabel) {
|
|
663
|
+
const displayCs = wiring?.cs ?? DEFAULT_DISPLAY_CS;
|
|
664
|
+
displayEntries.push(`<&gpio${displayCs <= 31 ? '0' : '1'} ${displayCs} GPIO_ACTIVE_LOW>`);
|
|
665
|
+
if (touch?.controller === 'xpt2046') {
|
|
666
|
+
const touchCs = touch.cs ?? DEFAULT_XPT2046_CS;
|
|
667
|
+
displayEntries.push(`<&gpio${touchCs <= 31 ? '0' : '1'} ${touchCs} GPIO_ACTIVE_LOW>`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Thin SPI targets ride this controller too: their CS entries append
|
|
671
|
+
// after the sensor pins, and their child nodes carry no compatible —
|
|
672
|
+
// a raw spi_dt_spec peer, not a driver node.
|
|
673
|
+
const ctrlTargets = spiTargets.filter((t) => t.busIndex === idx);
|
|
674
|
+
lines.push(`&${ctrl.nodeLabel} {`);
|
|
675
|
+
lines.push(` cs-gpios = ${displayEntries.concat(sensors.map((sp) => {
|
|
676
|
+
const gc = chip.gpioControllers?.find((g) => sp.port >= g.minPin && sp.port <= g.maxPin);
|
|
677
|
+
const ctrlLabel = gc?.nodelabel ?? 'gpio0';
|
|
678
|
+
return `<&${ctrlLabel} ${sp.port} GPIO_ACTIVE_LOW>`;
|
|
679
|
+
}), ctrlTargets.map((t) => {
|
|
680
|
+
const gc = chip.gpioControllers?.find((g) => t.cs >= g.minPin && t.cs <= g.maxPin);
|
|
681
|
+
const ctrlLabel = gc?.nodelabel ?? 'gpio0';
|
|
682
|
+
return `<&${ctrlLabel} ${t.cs} GPIO_ACTIVE_LOW>`;
|
|
683
|
+
})).join(', ')};`);
|
|
684
|
+
sensors.forEach((sp, sensorIdx) => {
|
|
685
|
+
const info = SENSOR_PART_INFO[sp.part.replace(/^SENSOR\./, '')];
|
|
686
|
+
if (!info) return;
|
|
687
|
+
const nodeName = info.compatible.split(',')[1] ?? sp.part;
|
|
688
|
+
const csIndex = displayEntries.length + sensorIdx;
|
|
689
|
+
lines.push(` tc_${sp.part}_spi${idx}_cs${sp.port}: ${nodeName}@${csIndex} {`);
|
|
690
|
+
lines.push(` compatible = "${info.compatible}";`);
|
|
691
|
+
lines.push(` reg = <${csIndex}>;`);
|
|
692
|
+
lines.push(` spi-max-frequency = <${sp.spiHz && sp.spiHz > 0 ? sp.spiHz : SPI_SENSOR_HZ}>;`);
|
|
693
|
+
// mode bits: 1 = CPHA, 2 = CPOL (presence props, no value).
|
|
694
|
+
if ((sp.spiMode ?? 0) & 2) lines.push(' spi-cpol;');
|
|
695
|
+
if ((sp.spiMode ?? 0) & 1) lines.push(' spi-cpha;');
|
|
696
|
+
emitAlert(lines, chip, sp.alertPin, info.alert, 8);
|
|
697
|
+
lines.push(' };');
|
|
698
|
+
});
|
|
699
|
+
ctrlTargets.forEach((t, targetIdx) => {
|
|
700
|
+
const csIndex = displayEntries.length + sensors.length + targetIdx;
|
|
701
|
+
lines.push(` tc_spit_spi${idx}_cs${t.cs}: spidev@${csIndex} {`);
|
|
702
|
+
// The app-local cuttlefish,spi-target binding (no driver — it makes
|
|
703
|
+
// gen_defines emit the spi properties SPI_DT_SPEC_GET reads).
|
|
704
|
+
lines.push(` compatible = "cuttlefish,spi-target";`);
|
|
705
|
+
lines.push(` reg = <${csIndex}>;`);
|
|
706
|
+
lines.push(` spi-max-frequency = <${t.hz && t.hz > 0 ? t.hz : SPI_SENSOR_HZ}>;`);
|
|
707
|
+
if ((t.mode ?? 0) & 2) lines.push(' spi-cpol;');
|
|
708
|
+
if ((t.mode ?? 0) & 1) lines.push(' spi-cpha;');
|
|
709
|
+
lines.push(' };');
|
|
710
|
+
});
|
|
711
|
+
lines.push('};');
|
|
712
|
+
lines.push('');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (usage.psram) {
|
|
716
|
+
lines.push('&psram0 {');
|
|
717
|
+
lines.push(' status = "okay";');
|
|
718
|
+
lines.push(' size = <(DT_SIZE_M(8))>;');
|
|
719
|
+
lines.push('};');
|
|
720
|
+
lines.push('');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return lines.join('\n');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Emit the pwm-leds consumer node + aliases for synthesized PWM specs.
|
|
728
|
+
*
|
|
729
|
+
* A board DTS may enable a PWM controller (`pwm4`) without aliasing any
|
|
730
|
+
* channel — the STM32 pattern — while the lowering addresses channels via
|
|
731
|
+
* DT_ALIAS. Each synthesized spec gets a `pwm-leds` child whose `pwms` cell
|
|
732
|
+
* binds the controller+channel (+ period/polarity), and a `tc-pwm<pin>`
|
|
733
|
+
* alias pointing at it. Alias/label names are valid DT identifiers (lowercase
|
|
734
|
+
* alphanumerics + dash/underscore).
|
|
735
|
+
*/
|
|
736
|
+
function emitPwmNodes(
|
|
737
|
+
lines: string[],
|
|
738
|
+
chip: ZephyrChipDescriptor,
|
|
739
|
+
usedPins?: readonly number[],
|
|
740
|
+
): void {
|
|
741
|
+
const staticSynth = (chip.pwm?.specs ?? []).filter(
|
|
742
|
+
(s) => s.controller && s.channel != null && !s.dtSpec,
|
|
743
|
+
).filter((s) => !usedPins || usedPins.includes(s.pin));
|
|
744
|
+
|
|
745
|
+
// Matrix PWM (ESP32 LEDC): channels are assigned here, over the pins the
|
|
746
|
+
// program actually drives (ascending — the same order lowering/pwm.ts
|
|
747
|
+
// emits the alias vars in; the C++ is channel-blind, so this is the single
|
|
748
|
+
// place a channel number is decided). The espressif,esp32-ledc binding
|
|
749
|
+
// needs three pieces per channel: a pinctrl pinmux token routing the
|
|
750
|
+
// channel to the pad (LEDC_CH<ch>_GPIO<pin> — every channel×pad pair
|
|
751
|
+
// exists in esp32s3-pinctrl.h, already included via the SoC dtsi), a
|
|
752
|
+
// channel child node (the driver's DT_INST_FOREACH_CHILD reads reg +
|
|
753
|
+
// timer from each), and the pwm-leds consumer the alias resolves to.
|
|
754
|
+
const m = chip.pwm?.matrix;
|
|
755
|
+
let matrixSynth: { pin: number; controller: string; channel: number }[] = [];
|
|
756
|
+
if (m) {
|
|
757
|
+
const pins = [...new Set(usedPins ? usedPins.filter((p) => m.pins.includes(p)) : m.pins)]
|
|
758
|
+
.sort((a, b) => a - b);
|
|
759
|
+
if (pins.length > m.channelCount) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`framework-zephyr: the program drives ${pins.length} PWM pins but ${chip.id}'s ` +
|
|
762
|
+
`${m.controller} exposes only ${m.channelCount} channels — drop ` +
|
|
763
|
+
`${pins.length - m.channelCount} pwm pin(s).`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
matrixSynth = pins.map((pin, ch) => ({ pin, controller: m.controller, channel: ch }));
|
|
767
|
+
// nRF shape: channels route to pads via psel (pinctrl psel order IS
|
|
768
|
+
// channel order — pwm_nrfx reads PSEL.OUT[ch] back), no channel child
|
|
769
|
+
// nodes, and the overlay carries the nrf pinctrl header for NRF_PSEL.
|
|
770
|
+
// P<port>.<pin> maps from the global number through the nRF controller
|
|
771
|
+
// layout (P0 = 0-31, P1 = 32-63).
|
|
772
|
+
const isNrfMatrix = /^nrf/.test(chip.soc);
|
|
773
|
+
if (isNrfMatrix) {
|
|
774
|
+
const inc = '#include <zephyr/dt-bindings/pinctrl/nrf-pinctrl.h>';
|
|
775
|
+
if (!lines.includes(inc)) lines.splice(2, 0, inc, '');
|
|
776
|
+
lines.push('&pinctrl {');
|
|
777
|
+
lines.push(` tc_${m.controller}_default: tc-${m.controller}-default {`);
|
|
778
|
+
lines.push(' group1 {');
|
|
779
|
+
lines.push(` psel = ${pins.map((p, ch) => `<NRF_PSEL(PWM_OUT${ch}, ${Math.floor(p / 32)}, ${p % 32})>`).join(', ')};`);
|
|
780
|
+
lines.push(' };');
|
|
781
|
+
lines.push(' };');
|
|
782
|
+
lines.push('};');
|
|
783
|
+
lines.push('');
|
|
784
|
+
lines.push(`&${m.controller} {`);
|
|
785
|
+
lines.push(' status = "okay";');
|
|
786
|
+
lines.push(` pinctrl-0 = <&tc_${m.controller}_default>;`);
|
|
787
|
+
lines.push(' pinctrl-names = "default";');
|
|
788
|
+
lines.push('};');
|
|
789
|
+
lines.push('');
|
|
790
|
+
}
|
|
791
|
+
if (!isNrfMatrix) {
|
|
792
|
+
lines.push('&pinctrl {');
|
|
793
|
+
lines.push(` tc_${m.controller}_default: tc-${m.controller}-default {`);
|
|
794
|
+
lines.push(' group1 {');
|
|
795
|
+
lines.push(` pinmux = ${pins.map((p, ch) => `<LEDC_CH${ch}_GPIO${p}>`).join(', ')};`);
|
|
796
|
+
lines.push(' output-enable;');
|
|
797
|
+
lines.push(' };');
|
|
798
|
+
lines.push(' };');
|
|
799
|
+
lines.push('};');
|
|
800
|
+
lines.push('');
|
|
801
|
+
// The four LEDC timers are round-robined so co-driven channels share a
|
|
802
|
+
// timer only past four (sharing is fine at equal periods; the driver
|
|
803
|
+
// reconfigures on pwm_set_dt when they differ).
|
|
804
|
+
lines.push(`&${m.controller} {`);
|
|
805
|
+
lines.push(' status = "okay";');
|
|
806
|
+
lines.push(` pinctrl-0 = <&tc_${m.controller}_default>;`);
|
|
807
|
+
lines.push(' pinctrl-names = "default";');
|
|
808
|
+
lines.push(' #address-cells = <1>;');
|
|
809
|
+
lines.push(' #size-cells = <0>;');
|
|
810
|
+
for (let ch = 0; ch < pins.length; ch++) {
|
|
811
|
+
lines.push(` channel${ch}@${ch} {`);
|
|
812
|
+
lines.push(` reg = <${ch}>;`);
|
|
813
|
+
lines.push(` timer = <${ch % 4}>;`);
|
|
814
|
+
lines.push(' };');
|
|
815
|
+
}
|
|
816
|
+
lines.push('};');
|
|
817
|
+
lines.push('');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const synthesized = staticSynth.concat(matrixSynth);
|
|
822
|
+
if (synthesized.length === 0) return;
|
|
823
|
+
// The pwms cells name the polarity as a macro (PWM_POLARITY_NORMAL). It is
|
|
824
|
+
// NOT universally available: only dts include chains that already use PWM
|
|
825
|
+
// bindings pull <zephyr/dt-bindings/pwm/pwm.h> in (STM32 does; the
|
|
826
|
+
// ESP32-S3 chain does not), so the overlay carries its own include.
|
|
827
|
+
// Spliced after the header comments, the established pattern (see the
|
|
828
|
+
// esp32s3-pinctrl.h splice above); guarded for the double-emit case.
|
|
829
|
+
if (!lines.includes('#include <zephyr/dt-bindings/pwm/pwm.h>')) {
|
|
830
|
+
lines.splice(2, 0, '#include <zephyr/dt-bindings/pwm/pwm.h>', '');
|
|
831
|
+
}
|
|
832
|
+
// Enable each distinct PWM controller node (idempotent when already okay).
|
|
833
|
+
// 16-bit fit: STM32 timers count period cycles in a 16-bit ARR, and the
|
|
834
|
+
// SoC dtsi default is st,prescaler = <0> (÷1) — a 20 ms servo period at
|
|
835
|
+
// 96 MHz is 1.92M cycles and pwm_stm32 rejects the channel. When the
|
|
836
|
+
// descriptor declares the timer clock, derive the smallest divider that
|
|
837
|
+
// fits the controller's slowest used period and override the prescaler
|
|
838
|
+
// on the timers parent node (the property lives there, not on the pwm
|
|
839
|
+
// child). pwm4 → timers4 is the STM32 nodelabel convention. Matrix
|
|
840
|
+
// controllers are skipped — the block above already enabled them (with
|
|
841
|
+
// their pinctrl + channel children, which this loop doesn't know).
|
|
842
|
+
const clockHz = chip.pwm?.clockHz;
|
|
843
|
+
for (const controller of [...new Set(staticSynth.map((s) => s.controller!))]) {
|
|
844
|
+
const timersMatch = controller.match(/^pwm(\d+)$/);
|
|
845
|
+
const specs = staticSynth.filter((s) => s.controller === controller);
|
|
846
|
+
// Macro-form pinctrl tokens (uppercase — DT labels are lowercase by
|
|
847
|
+
// spec): the overlay synthesizes the pad-routing group itself. RP2's
|
|
848
|
+
// macros live in a dt-bindings header the board chain may not pull in
|
|
849
|
+
// (spliced via rp2PinctrlHeader); Kinetis/LPC/GD32 macros come from the
|
|
850
|
+
// part headers the board's own pinctrl dtsi includes, and Zephyr
|
|
851
|
+
// preprocesses overlays together with the base DTS — no splice needed.
|
|
852
|
+
const isMacro = (p: string | undefined): boolean => !!p && /^[A-Z][A-Z0-9_]*$/.test(p);
|
|
853
|
+
if (!timersMatch && specs.some((s) => isMacro(s.pinctrl))) {
|
|
854
|
+
const header = rp2PinctrlHeader(chip.soc);
|
|
855
|
+
if (header) {
|
|
856
|
+
const inc = `#include <zephyr/dt-bindings/pinctrl/${header}>`;
|
|
857
|
+
if (!lines.includes(inc)) lines.splice(2, 0, inc, '');
|
|
858
|
+
}
|
|
859
|
+
lines.push('&pinctrl {');
|
|
860
|
+
lines.push(' tc_pwm_default: tc-pwm-default {');
|
|
861
|
+
lines.push(' group1 {');
|
|
862
|
+
lines.push(` pinmux = ${specs.filter((s) => s.pinctrl).map((s) => `<${s.pinctrl}>`).join(', ')};`);
|
|
863
|
+
lines.push(' };');
|
|
864
|
+
lines.push(' };');
|
|
865
|
+
lines.push('};');
|
|
866
|
+
lines.push('');
|
|
867
|
+
lines.push(`&${controller} {`);
|
|
868
|
+
lines.push(' status = "okay";');
|
|
869
|
+
// Atmel SAM TCC/TC in PWM mode: the SoC dtsi declares the base timer
|
|
870
|
+
// node (compatible atmel,sam0-tcc / …-tc, with clocks/assigned-clocks/
|
|
871
|
+
// channels/counter-size already present). The overlay overrides the
|
|
872
|
+
// compatible to the PWM binding, sets the required prescaler and the
|
|
873
|
+
// #pwm-cells the binding consts, and reuses the shared pinctrl group.
|
|
874
|
+
const samPwm = controller.match(/^(tcc|tc)(\d+)$/);
|
|
875
|
+
if (samPwm) {
|
|
876
|
+
lines.push(` compatible = "${samPwm[1] === 'tcc' ? 'atmel,sam0-tcc-pwm' : 'atmel,sam0-tc-pwm'}";`);
|
|
877
|
+
lines.push(' prescaler = <1024>;');
|
|
878
|
+
lines.push(' #pwm-cells = <2>;');
|
|
879
|
+
}
|
|
880
|
+
lines.push(' pinctrl-0 = <&tc_pwm_default>;');
|
|
881
|
+
lines.push(' pinctrl-names = "default";');
|
|
882
|
+
lines.push('};');
|
|
883
|
+
lines.push('');
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (timersMatch) {
|
|
887
|
+
// STM32 shape: the SoC dtsi declares a LABEL-LESS pwm child under
|
|
888
|
+
// timers{N} — the overlay defines the label itself (pwmN: pwm), which
|
|
889
|
+
// the synthesized pwms cell below references. One block carries the
|
|
890
|
+
// parent enable, the pad-routing pinctrl groups (harvested silicon
|
|
891
|
+
// routes on the specs — without them the PWM runs but nothing leaves
|
|
892
|
+
// the chip), the prescaler, and the child enable.
|
|
893
|
+
const groups = specs.filter((s) => s.pinctrl).map((s) => `&${s.pinctrl}`);
|
|
894
|
+
const maxPeriodNs = Math.max(...specs.map((s) => s.periodNs ?? 20_000_000));
|
|
895
|
+
// cycles = clockHz * period_s / divider ≤ 65536 (driver allows
|
|
896
|
+
// UINT16_MAX + 1); binding value is divider - 1 (CLK/(prescaler+1)).
|
|
897
|
+
const divider = clockHz
|
|
898
|
+
? Math.max(1, Math.ceil((clockHz * maxPeriodNs) / 1e9 / 65536))
|
|
899
|
+
: 1;
|
|
900
|
+
lines.push(`&timers${timersMatch[1]} {`);
|
|
901
|
+
lines.push(' status = "okay";');
|
|
902
|
+
if (divider > 1) {
|
|
903
|
+
lines.push(` st,prescaler = <${divider - 1}>;`);
|
|
904
|
+
}
|
|
905
|
+
lines.push(` ${controller}: pwm {`);
|
|
906
|
+
lines.push(' status = "okay";');
|
|
907
|
+
if (groups.length > 0) {
|
|
908
|
+
lines.push(` pinctrl-0 = <${groups.join(' ')}>;`);
|
|
909
|
+
lines.push(' pinctrl-names = "default";');
|
|
910
|
+
}
|
|
911
|
+
lines.push(' };');
|
|
912
|
+
lines.push('};');
|
|
913
|
+
lines.push('');
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
916
|
+
// Lowercase pinctrl tokens (i.MX `iomuxc_…` node labels from the part
|
|
917
|
+
// dtsi) ride the controller's pinctrl-0 directly.
|
|
918
|
+
const pinRefs = specs.filter((s) => s.pinctrl && !isMacro(s.pinctrl)).map((s) => `&${s.pinctrl}`);
|
|
919
|
+
lines.push(`&${controller} {`);
|
|
920
|
+
lines.push(' status = "okay";');
|
|
921
|
+
if (pinRefs.length > 0) {
|
|
922
|
+
lines.push(` pinctrl-0 = <${pinRefs.join(' ')}>;`);
|
|
923
|
+
lines.push(' pinctrl-names = "default";');
|
|
924
|
+
}
|
|
925
|
+
lines.push('};');
|
|
926
|
+
lines.push('');
|
|
927
|
+
}
|
|
928
|
+
lines.push('/ {');
|
|
929
|
+
lines.push(' tc_pwm_leds: tc-pwm-leds {');
|
|
930
|
+
lines.push(' compatible = "pwm-leds";');
|
|
931
|
+
for (const s of synthesized) {
|
|
932
|
+
// 20 ms / 50 Hz default — the servo convention; the duty scaling in the
|
|
933
|
+
// lowering normalizes 0-255 against whatever period is baked in.
|
|
934
|
+
const period = s.periodNs ?? 20_000_000;
|
|
935
|
+
const polarity = s.polarity ?? 'PWM_POLARITY_NORMAL';
|
|
936
|
+
lines.push(` tc_pwm_${s.pin}: pwm-led-${s.pin} {`);
|
|
937
|
+
lines.push(` pwms = <&${s.controller} ${s.channel} ${period} ${polarity}>;`);
|
|
938
|
+
lines.push(' };');
|
|
939
|
+
}
|
|
940
|
+
lines.push(' };');
|
|
941
|
+
lines.push(' aliases {');
|
|
942
|
+
for (const s of synthesized) {
|
|
943
|
+
lines.push(` tc-pwm${s.pin} = &tc_pwm_${s.pin};`);
|
|
944
|
+
}
|
|
945
|
+
lines.push(' };');
|
|
946
|
+
lines.push('};');
|
|
947
|
+
lines.push('');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Emit the ADC device-node enable (+ optional pinctrl override). The pinctrl
|
|
952
|
+
* override applies only when the descriptor's channels carry pinctrl labels
|
|
953
|
+
* AND the caller knows which pins the program reads (compile-time regen scans
|
|
954
|
+
* the emitted `__tc_adc<N>_setup()` calls); the prepare-time overlay omits it
|
|
955
|
+
* and the compile regen rewrites the file before west runs.
|
|
956
|
+
*
|
|
957
|
+
* Two pinctrl forms by family: NODE labels (STM32's harvested
|
|
958
|
+
* `adc1_in1_pa1` groups — referenced as `&label`) and MACRO tokens (RP2's
|
|
959
|
+
* `ADC_CH0_P26` — DT labels are lowercase by spec and macros uppercase, so
|
|
960
|
+
* the case decides). The macro form synthesizes its own group under
|
|
961
|
+
* &pinctrl and splices the SoC's pinctrl dt-binding header so the token
|
|
962
|
+
* resolves in the overlay's compile.
|
|
963
|
+
*
|
|
964
|
+
* Multi-controller SoCs: channels may name their owning controller (STM32
|
|
965
|
+
* adc3-only routes, ESP32 units). One enable block is emitted per controller
|
|
966
|
+
* that owns used channels — the primary first, others sorted for
|
|
967
|
+
* deterministic output. Single-controller boards emit exactly one block, as
|
|
968
|
+
* before.
|
|
969
|
+
*/
|
|
970
|
+
/** The SoC's pinctrl dt-binding header (where the macro tokens are defined)
|
|
971
|
+
* — RP2 shapes; undefined for socs that use node-label pinctrl exclusively. */
|
|
972
|
+
function rp2PinctrlHeader(soc: string): string | undefined {
|
|
973
|
+
if (soc === 'rp2040') return 'rpi-pico-rp2040-pinctrl.h';
|
|
974
|
+
if (soc.startsWith('rp2350a')) return 'rpi-pico-rp2350a-pinctrl.h';
|
|
975
|
+
if (soc.startsWith('rp2350b')) return 'rpi-pico-rp2350b-pinctrl.h';
|
|
976
|
+
return undefined;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function emitAdcNode(
|
|
980
|
+
lines: string[],
|
|
981
|
+
chip: ZephyrChipDescriptor,
|
|
982
|
+
readPins?: readonly number[],
|
|
983
|
+
): void {
|
|
984
|
+
const adc = chip.adc!;
|
|
985
|
+
const primary = adc.nodeLabel;
|
|
986
|
+
const controllerOf = (c: { controller?: string }) => c.controller ?? primary;
|
|
987
|
+
const used = readPins ? adc.channels.filter((c) => readPins.includes(c.pin)) : adc.channels;
|
|
988
|
+
const isMacro = (p: string | undefined): boolean => !!p && /^[A-Z][A-Z0-9_]*$/.test(p);
|
|
989
|
+
const macroChannels = used.filter((c) => isMacro(c.pinctrl));
|
|
990
|
+
if (macroChannels.length > 0) {
|
|
991
|
+
const header = rp2PinctrlHeader(chip.soc);
|
|
992
|
+
if (header) {
|
|
993
|
+
const inc = `#include <zephyr/dt-bindings/pinctrl/${header}>`;
|
|
994
|
+
if (!lines.includes(inc)) lines.splice(2, 0, inc, '');
|
|
995
|
+
}
|
|
996
|
+
lines.push('&pinctrl {');
|
|
997
|
+
lines.push(' tc_adc_default: tc-adc-default {');
|
|
998
|
+
lines.push(' group1 {');
|
|
999
|
+
lines.push(` pinmux = ${macroChannels.map((c) => `<${c.pinctrl}>`).join(', ')};`);
|
|
1000
|
+
lines.push(' };');
|
|
1001
|
+
lines.push(' };');
|
|
1002
|
+
lines.push('};');
|
|
1003
|
+
lines.push('');
|
|
1004
|
+
}
|
|
1005
|
+
// Every controller that owns a USED channel gets an enable block — a
|
|
1006
|
+
// controller left "disabled" in the SoC dtsi has no device instance, so a
|
|
1007
|
+
// read against it fails to link. Nothing used: enable the primary only.
|
|
1008
|
+
const controllers = used.length > 0
|
|
1009
|
+
? [...new Set(used.map(controllerOf))]
|
|
1010
|
+
.sort((a, b) => (a === primary ? -1 : b === primary ? 1 : a.localeCompare(b, undefined, { numeric: true })))
|
|
1011
|
+
: [primary];
|
|
1012
|
+
for (const label of controllers) {
|
|
1013
|
+
const mine = used.filter((c) => controllerOf(c) === label && c.pinctrl);
|
|
1014
|
+
lines.push(`&${label} {`);
|
|
1015
|
+
lines.push(' status = "okay";');
|
|
1016
|
+
if (mine.length > 0) {
|
|
1017
|
+
if (mine.every((c) => isMacro(c.pinctrl))) {
|
|
1018
|
+
lines.push(' pinctrl-0 = <&tc_adc_default>;');
|
|
1019
|
+
} else {
|
|
1020
|
+
lines.push(` pinctrl-0 = <${mine.map((c) => `&${c.pinctrl}`).join(' ')}>;`);
|
|
1021
|
+
}
|
|
1022
|
+
lines.push(' pinctrl-names = "default";');
|
|
1023
|
+
}
|
|
1024
|
+
lines.push('};');
|
|
1025
|
+
lines.push('');
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Emit the DAC device-node enable + the used output channels' pinctrl
|
|
1031
|
+
* groups (the harvested `dac1_out1_pa4`-style nodes — the st,stm32-dac
|
|
1032
|
+
* binding includes pinctrl-device, so without the group the channel never
|
|
1033
|
+
* reaches its pad). Mirrors emitAdcNode.
|
|
1034
|
+
*/
|
|
1035
|
+
function emitDacNode(
|
|
1036
|
+
lines: string[],
|
|
1037
|
+
chip: ZephyrChipDescriptor,
|
|
1038
|
+
writePins?: readonly number[],
|
|
1039
|
+
): void {
|
|
1040
|
+
const dac = chip.dac!;
|
|
1041
|
+
const labeled = dac.channels.filter((c) => c.pinctrl);
|
|
1042
|
+
const used = labeled.filter((c) => !writePins || writePins.includes(c.pin));
|
|
1043
|
+
lines.push(`&${dac.device} {`);
|
|
1044
|
+
lines.push(' status = "okay";');
|
|
1045
|
+
if (used.length > 0) {
|
|
1046
|
+
lines.push(` pinctrl-0 = <${used.map((c) => `&${c.pinctrl}`).join(' ')}>;`);
|
|
1047
|
+
lines.push(' pinctrl-names = "default";');
|
|
1048
|
+
}
|
|
1049
|
+
lines.push('};');
|
|
1050
|
+
lines.push('');
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Emit a display DT node definition. The node is attached to the profile's SPI
|
|
1055
|
+
* controller (default spi2, the ESP32's first user SPI controller) via a MIPI
|
|
1056
|
+
* DBI SPI bridge. Pin wiring comes from the display config (cs/dc/rst);
|
|
1057
|
+
* defaults match the demo-st wiring if absent. The compatible string + node
|
|
1058
|
+
* props come from the profile's panel controller (st7796s carries the required
|
|
1059
|
+
* pgc/ngc gamma + madctl; ili9341's binding defaults everything else).
|
|
1060
|
+
*
|
|
1061
|
+
* spiTouchCs: when an XPT2046 SPI touch controller shares the bus, its CS is
|
|
1062
|
+
* appended as the second cs-gpios entry (the touch node uses reg = <1>) — DT
|
|
1063
|
+
* property assignment replaces, so both entries must be written together.
|
|
1064
|
+
*/
|
|
1065
|
+
/** Emit alert-gpios for parts whose binding declares it (active high — the
|
|
1066
|
+
* bindings describe the sensor's output level, e.g. sht3xd's ALERT). */
|
|
1067
|
+
function emitAlert(lines: string[], chip: ZephyrChipDescriptor, alertPin: number | undefined, supported: boolean, indent: number): void {
|
|
1068
|
+
if (alertPin === undefined || alertPin < 0 || !supported) return;
|
|
1069
|
+
const gc = chip.gpioControllers?.find((g) => alertPin >= g.minPin && alertPin <= g.maxPin);
|
|
1070
|
+
const pad = ' '.repeat(indent);
|
|
1071
|
+
lines.push(`${pad}alert-gpios = <&${gc?.nodelabel ?? 'gpio0'} ${alertPin} GPIO_ACTIVE_HIGH>;`);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function emitDisplayNode(
|
|
1075
|
+
lines: string[],
|
|
1076
|
+
display: ZephyrDisplayProfile,
|
|
1077
|
+
wiring?: DisplayWiring,
|
|
1078
|
+
spiTouchCs?: number,
|
|
1079
|
+
): void {
|
|
1080
|
+
const bus = display.busLabel ?? 'spi2';
|
|
1081
|
+
const controller = panelControllerFor(display);
|
|
1082
|
+
// Board-provided displays (native_sim's built-in sdl_dc): the board's own
|
|
1083
|
+
// devicetree wires the panel — nothing to emit here.
|
|
1084
|
+
if (display.boardProvidesDisplay) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
// I2C-family panels (mono OLEDs, ssd1306-class): a plain &i2cN child node —
|
|
1088
|
+
// no mipi-dbi bridge, no SPI bus block, no DMA. Bus pins come from the
|
|
1089
|
+
// touch wiring's remux (shared bus) or the board's default I2C pins.
|
|
1090
|
+
if (readDisplayBinding(display.dtCompatible ?? (isDtCompatible(display.driver) ? display.driver : ''))?.busFamily === 'i2c') {
|
|
1091
|
+
const addr = wiring?.address ?? 0x3c;
|
|
1092
|
+
// Optional I2C pin remux — mirrors the touch path's remux (the board's
|
|
1093
|
+
// default I2C pins rarely match a breakout's wiring). Named macros from
|
|
1094
|
+
// the SoC pinctrl header; the group lives under &pinctrl and overrides
|
|
1095
|
+
// i2c0's default group via pinctrl-0.
|
|
1096
|
+
const sda = wiring?.sda;
|
|
1097
|
+
const scl = wiring?.scl;
|
|
1098
|
+
if (sda !== undefined && scl !== undefined) {
|
|
1099
|
+
lines.push('&pinctrl {');
|
|
1100
|
+
lines.push(' i2c0_display: i2c0_display {');
|
|
1101
|
+
lines.push(' group1 {');
|
|
1102
|
+
lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
|
|
1103
|
+
lines.push(' bias-pull-up;');
|
|
1104
|
+
lines.push(' drive-open-drain;');
|
|
1105
|
+
lines.push(' };');
|
|
1106
|
+
lines.push(' };');
|
|
1107
|
+
lines.push('};');
|
|
1108
|
+
lines.push('');
|
|
1109
|
+
}
|
|
1110
|
+
// Node name from the compatible's panel segment (ssd1306/ssd1309/sh1106);
|
|
1111
|
+
// DT node names are labels, not compatibles — but matching the panel
|
|
1112
|
+
// keeps the built zephyr.dts readable.
|
|
1113
|
+
const panel = (display.dtCompatible ?? display.driver).split(',')[1] ?? 'ssd1306';
|
|
1114
|
+
lines.push('&i2c0 {');
|
|
1115
|
+
lines.push(' status = "okay";');
|
|
1116
|
+
// Fast mode: the full-frame mono push is bandwidth-bound (a 128x64 frame
|
|
1117
|
+
// is 1KB — ~25ms at the 100kHz default, ~7ms at 400kHz), so animations
|
|
1118
|
+
// step without it. OLED modules and the esp32s3/nRF i2c controllers all
|
|
1119
|
+
// handle 400kHz; declare it whenever we own the bus node generation.
|
|
1120
|
+
lines.push(' clock-frequency = <400000>;');
|
|
1121
|
+
if (sda !== undefined && scl !== undefined) {
|
|
1122
|
+
lines.push(' pinctrl-0 = <&i2c0_display>;');
|
|
1123
|
+
lines.push(' pinctrl-names = "default";');
|
|
1124
|
+
}
|
|
1125
|
+
lines.push(` ${display.dtLabel}: ${panel}@${addr.toString(16)} {`);
|
|
1126
|
+
lines.push(` compatible = "${display.dtCompatible ?? display.driver}";`);
|
|
1127
|
+
lines.push(` reg = <0x${addr.toString(16)}>;`);
|
|
1128
|
+
lines.push(` width = <${display.nativeWidth ?? display.width}>;`);
|
|
1129
|
+
lines.push(` height = <${display.nativeHeight ?? display.height}>;`);
|
|
1130
|
+
const binding = readDisplayBinding(display.dtCompatible ?? display.driver);
|
|
1131
|
+
if (binding) {
|
|
1132
|
+
for (const [prop, info] of binding.required) {
|
|
1133
|
+
const v = monoPropFallback(prop, info, display.nativeHeight ?? display.height);
|
|
1134
|
+
if (v !== undefined) lines.push(` ${v};`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
lines.push(' };');
|
|
1138
|
+
lines.push('};');
|
|
1139
|
+
lines.push('');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
// Compatible resolution: explicit override → the registry controllers →
|
|
1143
|
+
// the drop-in path (driver IS a compatible string) → a safe default.
|
|
1144
|
+
// Drop-in drivers (DT-compatible shape) ARE the compatible — they win over
|
|
1145
|
+
// the controller-default table (which only serves registry driver ids whose
|
|
1146
|
+
// id isn't itself a compatible; the e-ink family entries exist for the
|
|
1147
|
+
// Kconfig layer, not for node naming).
|
|
1148
|
+
const compatible = display.dtCompatible
|
|
1149
|
+
?? (isDtCompatible(display.driver)
|
|
1150
|
+
? display.driver
|
|
1151
|
+
: (controller !== undefined
|
|
1152
|
+
? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible
|
|
1153
|
+
: PANEL_CONTROLLER_DEFAULTS.ili9341.dtCompatible));
|
|
1154
|
+
const dc = wiring?.dc ?? 17;
|
|
1155
|
+
const rst = wiring?.rst ?? 16;
|
|
1156
|
+
const cs = wiring?.cs ?? DEFAULT_DISPLAY_CS;
|
|
1157
|
+
// E-ink SPI tops out far below TFTs (the reel board runs ssd1673 at 4MHz;
|
|
1158
|
+
// the controllers' shift registers clock 4-10MHz). Cap the default for the
|
|
1159
|
+
// e-ink families; explicit spiFrequency always wins.
|
|
1160
|
+
const einkFamily = isEinkDisplay({ driver: display.driver, displayClass: display.displayClass });
|
|
1161
|
+
const freq = wiring?.spiFrequency ?? (einkFamily ? 4000000 : 80000000);
|
|
1162
|
+
// DT node describes the NATIVE panel geometry; the effective (rotated)
|
|
1163
|
+
// dimensions live in the display profile.
|
|
1164
|
+
const nativeW = display.nativeWidth ?? display.width;
|
|
1165
|
+
const nativeH = display.nativeHeight ?? display.height;
|
|
1166
|
+
const sck = wiring?.sck;
|
|
1167
|
+
const mosi = wiring?.mosi;
|
|
1168
|
+
const miso = wiring?.miso;
|
|
1169
|
+
// ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
|
|
1170
|
+
const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
|
|
1171
|
+
// The board's spim2_default pinctrl group usually targets the devkitc's
|
|
1172
|
+
// default SPI pins (SCLK=12/MOSI=11), which rarely match the display's
|
|
1173
|
+
// wiring. When the config declares spiPins, override the pinctrl groups to
|
|
1174
|
+
// remux the controller onto the panel's actual pins. Raw ESP32_PINMUX()
|
|
1175
|
+
// macros are used instead of the named SPIM2_*_GPIOxx tokens because the
|
|
1176
|
+
// bindings header omits GPIOs 22-25 from those lists.
|
|
1177
|
+
if (sck !== undefined && mosi !== undefined) {
|
|
1178
|
+
// 'spi2' → pinctrl group 'spim2_default' (ESP32 SPI-master naming).
|
|
1179
|
+
const pinctrlGroup = bus.replace(/^spi(\d)$/, 'spim$1') + '_default';
|
|
1180
|
+
lines.push(`&${pinctrlGroup} {`);
|
|
1181
|
+
lines.push(' group1 {');
|
|
1182
|
+
lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
|
|
1183
|
+
lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
|
|
1184
|
+
lines.push(' <ESP32_PINMUX(10, ESP_NOSIG, ESP_FSPICS0_OUT)>;');
|
|
1185
|
+
lines.push(' };');
|
|
1186
|
+
lines.push(' group2 {');
|
|
1187
|
+
lines.push(` pinmux = <ESP32_PINMUX(${mosi}, ESP_NOSIG, ESP_FSPID_OUT)>;`);
|
|
1188
|
+
lines.push(' output-low;');
|
|
1189
|
+
lines.push(' };');
|
|
1190
|
+
lines.push('};');
|
|
1191
|
+
lines.push('');
|
|
1192
|
+
}
|
|
1193
|
+
lines.push('&dma {');
|
|
1194
|
+
lines.push(' status = "okay";');
|
|
1195
|
+
lines.push('};');
|
|
1196
|
+
lines.push('');
|
|
1197
|
+
lines.push(`&${bus} {`);
|
|
1198
|
+
lines.push(' status = "okay";');
|
|
1199
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>${spiTouchCs !== undefined ? `, <&${gpioController(spiTouchCs)} ${spiTouchCs} GPIO_ACTIVE_LOW>` : ''};`);
|
|
1200
|
+
// Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
|
|
1201
|
+
// dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
|
|
1202
|
+
// without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
|
|
1203
|
+
// 80MHz requested). The GDMA driver allocates channels per peripheral based
|
|
1204
|
+
// on spi2's dma-host=<0> (set in the SoC dtsi), so the channel cells are the
|
|
1205
|
+
// GDMA instance's rx/tx slot ids (2 here = an SPI2-dedicated pair; the GDMA
|
|
1206
|
+
// binding's #dma-cells = <1> carries the channel).
|
|
1207
|
+
lines.push(' dma-enabled;');
|
|
1208
|
+
lines.push(' dmas = <&dma 0>, <&dma 1>;');
|
|
1209
|
+
lines.push(' dma-names = "rx", "tx";');
|
|
1210
|
+
lines.push('};');
|
|
1211
|
+
lines.push('');
|
|
1212
|
+
lines.push('/ {');
|
|
1213
|
+
lines.push(' mipi_dbi: mipi-dbi {');
|
|
1214
|
+
lines.push(' compatible = "zephyr,mipi-dbi-spi";');
|
|
1215
|
+
lines.push(` spi-dev = <&${bus}>;`);
|
|
1216
|
+
lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
|
|
1217
|
+
lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
|
|
1218
|
+
lines.push(' write-only;');
|
|
1219
|
+
lines.push(' #address-cells = <1>;');
|
|
1220
|
+
lines.push(' #size-cells = <0>;');
|
|
1221
|
+
lines.push(` ${display.dtLabel}: display@0 {`);
|
|
1222
|
+
lines.push(` compatible = "${compatible}";`);
|
|
1223
|
+
lines.push(' reg = <0>;');
|
|
1224
|
+
if (wiring?.tearingEffectPin !== undefined && transportFor(display) === 'direct-spi') {
|
|
1225
|
+
const tePin = wiring.tearingEffectPin!;
|
|
1226
|
+
// Tearing-effect input on the display node: GPIO_DT_SPEC_GET(
|
|
1227
|
+
// DT_NODELABEL(display0), te_gpios) in the direct-spi adapter.
|
|
1228
|
+
// Opt-in — most modules don't break the TE pad out. NOT emitted
|
|
1229
|
+
// on the zephyr-display transport: te-gpios makes the bound
|
|
1230
|
+
// in-tree driver allocate the TE GPIO interrupt (the ESP32
|
|
1231
|
+
// VECDESC_FL_SHARED conflict path) — the driver's TE handling
|
|
1232
|
+
// needs re-verification before it can be wired there.
|
|
1233
|
+
lines.push(` te-gpios = <&${gpioController(tePin)} ${tePin} GPIO_ACTIVE_HIGH>;`);
|
|
1234
|
+
}
|
|
1235
|
+
lines.push(` mipi-max-frequency = <${freq}>;`);
|
|
1236
|
+
lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
|
|
1237
|
+
if (wiring?.busyPin !== undefined) {
|
|
1238
|
+
// E-ink BUSY line — required by the ssd16xx/uc81xx bindings
|
|
1239
|
+
// (the drivers block on it through the flash cycle). Active
|
|
1240
|
+
// level fixed per family: ssd16xx BUSY is active-high.
|
|
1241
|
+
lines.push(` busy-gpios = <&${gpioController(wiring.busyPin)} ${wiring.busyPin} GPIO_ACTIVE_HIGH>;`);
|
|
1242
|
+
}
|
|
1243
|
+
lines.push(` width = <${nativeW}>;`);
|
|
1244
|
+
lines.push(` height = <${nativeH}>;`);
|
|
1245
|
+
if (controller === 'st7796s') {
|
|
1246
|
+
// MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
|
|
1247
|
+
// direct-drive init (0x28). The DT copy keeps the stock driver's init
|
|
1248
|
+
// consistent if it is ever exercised.
|
|
1249
|
+
lines.push(' madctl = <0x28>;');
|
|
1250
|
+
lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
1251
|
+
lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
1252
|
+
// rgb-is-inverted flips the format the driver REPORTS (565 <-> 565X).
|
|
1253
|
+
// Zephyr RGB_565 is little-endian storage, but an 8-bit SPI panel clocks
|
|
1254
|
+
// the LOW byte first — each pixel arrives byte-swapped, which mixes the
|
|
1255
|
+
// green channel into red/blue (white text reads purple, dark backgrounds
|
|
1256
|
+
// read green — verified on the rig). With the flag, the driver reports
|
|
1257
|
+
// 565X and the adapter byte-swaps at pack time, restoring wire order.
|
|
1258
|
+
// (The binding documents the prop for "buggy modules that display RGB as
|
|
1259
|
+
// BGR" — same mechanism, byte order.)
|
|
1260
|
+
lines.push(' rgb-is-inverted;');
|
|
1261
|
+
} else if (controller === 'ili9341') {
|
|
1262
|
+
// ILI9341: the ilitek,ili9341 binding carries defaults for every register
|
|
1263
|
+
// (gamma, power, porch) and expresses orientation via `rotation` (degrees)
|
|
1264
|
+
// instead of a raw MADCTL — no panel-specific props are required.
|
|
1265
|
+
lines.push(` rotation = <${display.rotation ?? 0}>;`);
|
|
1266
|
+
// pixel-format is REQUIRED by the lcd-controller binding the ilitek
|
|
1267
|
+
// ili9xxx family includes: 0 = RGB565 (dt-bindings/display/panel.h),
|
|
1268
|
+
// matching upstream ILI9341 boards (esp_wrover_kit) and the C++ runtime.
|
|
1269
|
+
// The sitronix,st7796s binding does NOT declare pixel-format (its branch
|
|
1270
|
+
// omits the property — emitting it there is a DTC error, caught the hard
|
|
1271
|
+
// way once the overlay stopped carrying the wrong ilitek compatible).
|
|
1272
|
+
lines.push(' pixel-format = <0>;');
|
|
1273
|
+
} else {
|
|
1274
|
+
// Generic (drop-in) path: required properties come from the panel's own
|
|
1275
|
+
// binding in the user's Zephyr tree — the harvest is the single source
|
|
1276
|
+
// of truth, so the node is valid for ANY bound panel by construction.
|
|
1277
|
+
// Geometry/frequency props above are already emitted; pixel-format is
|
|
1278
|
+
// special (required by the lcd-controller family, value fixed to RGB565).
|
|
1279
|
+
const binding = readDisplayBinding(compatible);
|
|
1280
|
+
if (binding?.requiresPixelFormat) {
|
|
1281
|
+
lines.push(' pixel-format = <0>;');
|
|
1282
|
+
}
|
|
1283
|
+
if (binding) {
|
|
1284
|
+
for (const [prop, def] of binding.required) {
|
|
1285
|
+
// Skip structural properties the generator emits itself.
|
|
1286
|
+
if (prop === 'width' || prop === 'height' || prop === 'mipi-max-frequency'
|
|
1287
|
+
|| prop === 'pixel-format' || prop === 'reg' || prop === 'compatible'
|
|
1288
|
+
|| prop === 'status' || prop === 'mipi-mode'
|
|
1289
|
+
// phandle-array props are structural (GPIO routing) — the
|
|
1290
|
+
// generator emits them from config wiring (busy-gpios above),
|
|
1291
|
+
// never from a numeric fallback.
|
|
1292
|
+
|| prop === 'busy-gpios' || prop === 'reset-gpios' || prop === 'dc-gpios') {
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
emitBindingProp(lines, prop, def);
|
|
1296
|
+
}
|
|
1297
|
+
// Rotation on madctl-style panels (st7796s family): the binding ships
|
|
1298
|
+
// a neutral portrait default and the driver writes MADCTL verbatim, so
|
|
1299
|
+
// the config's rotation MUST reach the node or the UI's landscape
|
|
1300
|
+
// writes fall outside the portrait address window — half the screen
|
|
1301
|
+
// scrambled, half fine (verified on the rig). Table = ST77xx rotation
|
|
1302
|
+
// bits with BGR set (the verified clone convention; an R/B-swapped
|
|
1303
|
+
// panel flips to the config's channelSwapRb flag instead).
|
|
1304
|
+
// Byte-order quirk flag (clone panels): flips the format the driver
|
|
1305
|
+
// reports so the adapter byte-swaps at pack time. Only bindings that
|
|
1306
|
+
// declare the prop accept it — the DTC rejects it elsewhere, honestly.
|
|
1307
|
+
if (display.rgbInverted === true) {
|
|
1308
|
+
lines.push(' rgb-is-inverted;');
|
|
1309
|
+
}
|
|
1310
|
+
if (binding.props.has('madctl') && display.rotation !== undefined) {
|
|
1311
|
+
const MADCTL_ROTATION = [0x08, 0x28, 0x48, 0xE8];
|
|
1312
|
+
const r = ((display.rotation % 4) + 4) % 4;
|
|
1313
|
+
lines.push(` madctl = <0x${MADCTL_ROTATION[r]!.toString(16)}>;`);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
lines.push(' };');
|
|
1318
|
+
lines.push(' };');
|
|
1319
|
+
lines.push('};');
|
|
1320
|
+
lines.push('');
|
|
1321
|
+
// Backlight node: emit only when the config provides a backlight GPIO. The
|
|
1322
|
+
// pin was previously hardcoded to 4, which collided with the FT6336U
|
|
1323
|
+
// reset-gpios on the same pin (demo-st's backlight is hardwired to 3.3V, so
|
|
1324
|
+
// no backlight node is correct there). When present, use the configured pin
|
|
1325
|
+
// and the profile's alias name so the display adapter can drive it.
|
|
1326
|
+
const blPin = wiring?.backlightPin;
|
|
1327
|
+
if (display.backlight && blPin !== undefined) {
|
|
1328
|
+
lines.push('/ {');
|
|
1329
|
+
lines.push(' aliases {');
|
|
1330
|
+
lines.push(` ${display.backlight} = &bl_led;`);
|
|
1331
|
+
lines.push(' };');
|
|
1332
|
+
lines.push(' bl_gpio: bl-gpio-leds {');
|
|
1333
|
+
lines.push(' compatible = "gpio-leds";');
|
|
1334
|
+
lines.push(' bl_led: led {');
|
|
1335
|
+
lines.push(` gpios = <&${gpioController(blPin)} ${blPin} GPIO_ACTIVE_HIGH>;`);
|
|
1336
|
+
lines.push(' };');
|
|
1337
|
+
lines.push(' };');
|
|
1338
|
+
lines.push('};');
|
|
1339
|
+
lines.push('');
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/** Default XPT2046 CS/IRQ pins (ESP32-S3 GPIOs clear of the demo-st display
|
|
1344
|
+
* wiring: 5/17/16/15 and the remuxed SPI pins). Config values override. */
|
|
1345
|
+
const DEFAULT_XPT2046_CS = 6;
|
|
1346
|
+
const DEFAULT_XPT2046_IRQ = 7;
|
|
1347
|
+
|
|
1348
|
+
/**
|
|
1349
|
+
* Emit the touch DT node for the configured controller.
|
|
1350
|
+
*
|
|
1351
|
+
* FT6336U (capacitive, I2C): node on i2c0 at the FT6336U default address
|
|
1352
|
+
* (0x38); the C++ touch adapter reads it via i2c_write_read_dt.
|
|
1353
|
+
*
|
|
1354
|
+
* XPT2046 (resistive, SPI): node on the display's SPI bus as CS index 1. The
|
|
1355
|
+
* in-tree xptek,xpt2046 binding (drivers/input) is register-matched for the
|
|
1356
|
+
* raw SPI access — CONFIG_INPUT stays off, so the in-tree input driver does
|
|
1357
|
+
* not build and the adapter owns the chip (same pattern as FT6336U reusing
|
|
1358
|
+
* the ft5336 binding). The binding requires int-gpios, touchscreen-size-*,
|
|
1359
|
+
* and min/max calibration props, so defaults are filled for anything the
|
|
1360
|
+
* config omits.
|
|
1361
|
+
*/
|
|
1362
|
+
function emitTouchNode(
|
|
1363
|
+
lines: string[],
|
|
1364
|
+
touch: TouchWiring | undefined,
|
|
1365
|
+
display: ZephyrDisplayProfile | undefined,
|
|
1366
|
+
): void {
|
|
1367
|
+
if (touch?.controller === 'xpt2046') {
|
|
1368
|
+
emitXpt2046Node(lines, touch, display);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
emitFt6336uNode(lines, touch);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
/** FT6336U capacitive touch node on the first I2C controller. */
|
|
1375
|
+
function emitFt6336uNode(lines: string[], touch?: TouchWiring): void {
|
|
1376
|
+
const irq = touch?.irq ?? 15;
|
|
1377
|
+
const resetPin = touch?.resetPin;
|
|
1378
|
+
const sda = touch?.sda;
|
|
1379
|
+
const scl = touch?.scl;
|
|
1380
|
+
// ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
|
|
1381
|
+
const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
|
|
1382
|
+
// Zephyr has no ft6336 binding — the FT6336U is register-compatible with the
|
|
1383
|
+
// ft5336 binding (same Focaltech register map: TD_STATUS at 0x02, coordinate
|
|
1384
|
+
// registers 0x03-0x06). The C++ touch adapter reads these directly via I2C,
|
|
1385
|
+
// so the binding just needs to exist for the DT node to resolve.
|
|
1386
|
+
// When SDA/SCL are provided, remux the I2C bus to those pins via pinctrl
|
|
1387
|
+
// (the board's default I2C pins rarely match a breakout's wiring).
|
|
1388
|
+
if (sda !== undefined && scl !== undefined) {
|
|
1389
|
+
lines.push('&pinctrl {');
|
|
1390
|
+
lines.push(' i2c0_touch: i2c0_touch {');
|
|
1391
|
+
lines.push(' group1 {');
|
|
1392
|
+
lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
|
|
1393
|
+
lines.push(' bias-pull-up;');
|
|
1394
|
+
lines.push(' drive-open-drain;');
|
|
1395
|
+
lines.push(' };');
|
|
1396
|
+
lines.push(' };');
|
|
1397
|
+
lines.push('};');
|
|
1398
|
+
lines.push('');
|
|
1399
|
+
lines.push('&i2c0 {');
|
|
1400
|
+
lines.push(' status = "okay";');
|
|
1401
|
+
lines.push(' pinctrl-0 = <&i2c0_touch>;');
|
|
1402
|
+
lines.push(' pinctrl-names = "default";');
|
|
1403
|
+
} else {
|
|
1404
|
+
lines.push('&i2c0 {');
|
|
1405
|
+
lines.push(' status = "okay";');
|
|
1406
|
+
}
|
|
1407
|
+
lines.push(' ft6336u: ft6336u@38 {');
|
|
1408
|
+
lines.push(' compatible = "focaltech,ft5336";');
|
|
1409
|
+
lines.push(' reg = <0x38>;');
|
|
1410
|
+
// NOTE: int-gpios is intentionally omitted. The driver's interrupt mode
|
|
1411
|
+
// registers a GPIO IRQ (the ESP32 VECDESC_FL_SHARED crash); with no
|
|
1412
|
+
// int-gpios the in-tree driver falls back to polling mode
|
|
1413
|
+
// (CONFIG_INPUT_FT5336_PERIOD, default 10ms) — no IRQ is registered and
|
|
1414
|
+
// the touch adapter consumes the driver's input events.
|
|
1415
|
+
// swapped-x-y: the driver reports pos(col, row) — the Y-register value
|
|
1416
|
+
// first. The swap makes its ABS_X/ABS_Y events carry the controller's
|
|
1417
|
+
// X/Y registers verbatim, which is the raw coordinate space the runtime's
|
|
1418
|
+
// calibration + rotation math (ui_poll_touch) expects from touch_readRaw.
|
|
1419
|
+
lines.push(' swapped-x-y;');
|
|
1420
|
+
if (resetPin !== undefined) {
|
|
1421
|
+
lines.push(` reset-gpios = <&${gpioController(resetPin)} ${resetPin} GPIO_ACTIVE_LOW>;`);
|
|
1422
|
+
}
|
|
1423
|
+
lines.push(' };');
|
|
1424
|
+
lines.push('};');
|
|
1425
|
+
lines.push('');
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/** XPT2046 resistive touch node on the display's SPI bus (CS index 1). */
|
|
1429
|
+
function emitXpt2046Node(
|
|
1430
|
+
lines: string[],
|
|
1431
|
+
touch: TouchWiring,
|
|
1432
|
+
display: ZephyrDisplayProfile | undefined,
|
|
1433
|
+
): void {
|
|
1434
|
+
const bus = display?.busLabel ?? 'spi2';
|
|
1435
|
+
const cs = touch.cs ?? DEFAULT_XPT2046_CS;
|
|
1436
|
+
const irq = touch.irq ?? DEFAULT_XPT2046_IRQ;
|
|
1437
|
+
const cal = touch.calibration;
|
|
1438
|
+
// ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
|
|
1439
|
+
const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
|
|
1440
|
+
const zThreshold = touch.minPressure ?? 400;
|
|
1441
|
+
// touchscreen-size-* describe the panel the touch layer sits on (the display
|
|
1442
|
+
// profile's effective size); a touch-only build falls back to the 12-bit
|
|
1443
|
+
// full-scale range so the binding's required props still resolve.
|
|
1444
|
+
const sizeX = display?.width ?? cal?.xMax ?? 320;
|
|
1445
|
+
const sizeY = display?.height ?? cal?.yMax ?? 240;
|
|
1446
|
+
// The display block already wrote cs-gpios with both entries (its CS at
|
|
1447
|
+
// index 0, the touch CS at index 1). When there is no display block, enable
|
|
1448
|
+
// the bus here with the touch CS as the only entry.
|
|
1449
|
+
if (!display) {
|
|
1450
|
+
lines.push(`&${bus} {`);
|
|
1451
|
+
lines.push(' status = "okay";');
|
|
1452
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
|
|
1453
|
+
lines.push('};');
|
|
1454
|
+
lines.push('');
|
|
1455
|
+
}
|
|
1456
|
+
lines.push(`&${bus} {`);
|
|
1457
|
+
lines.push(' xpt2046: xpt2046@1 {');
|
|
1458
|
+
lines.push(' compatible = "xptek,xpt2046";');
|
|
1459
|
+
lines.push(' reg = <1>;');
|
|
1460
|
+
// The XPT2046 datasheet max SPI clock is 2.5MHz — the panel bus may run at
|
|
1461
|
+
// 80MHz, but this node's spi-max-frequency gates only its own transactions
|
|
1462
|
+
// (the adapter's SPI_DT_SPEC picks it up).
|
|
1463
|
+
lines.push(' spi-max-frequency = <2500000>;');
|
|
1464
|
+
lines.push(` int-gpios = <&${gpioController(irq)} ${irq} GPIO_ACTIVE_LOW>;`);
|
|
1465
|
+
lines.push(` touchscreen-size-x = <${sizeX}>;`);
|
|
1466
|
+
lines.push(` touchscreen-size-y = <${sizeY}>;`);
|
|
1467
|
+
lines.push(` min-x = <${cal?.xMin ?? 0}>;`);
|
|
1468
|
+
lines.push(` max-x = <${cal?.xMax ?? 4095}>;`);
|
|
1469
|
+
lines.push(` min-y = <${cal?.yMin ?? 0}>;`);
|
|
1470
|
+
lines.push(` max-y = <${cal?.yMax ?? 4095}>;`);
|
|
1471
|
+
lines.push(` z-threshold = <${zThreshold}>;`);
|
|
1472
|
+
lines.push(' };');
|
|
1473
|
+
lines.push('};');
|
|
1474
|
+
lines.push('');
|
|
1475
|
+
}
|