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

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.
@@ -1,423 +1,450 @@
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
- // This is the Zephyr analog of Arduino's library-resolution hooks: 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 type { ZephyrDisplayProfile } from '../display/profiles.js';
16
- import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor } from '../display/profiles.js';
17
- import type { KconfigUsage } from './kconfig.js';
18
-
19
- /**
20
- * Generate the overlay source for a board + usage. Returns the overlay text
21
- * (caller writes it to app/boards/<board>.overlay via writeIfChanged).
22
- */
23
- /** Display wiring from cuttlefish.config.ts (cs/dc/rst/spiFrequency/spiPins). */
24
- export interface DisplayWiring {
25
- cs?: number;
26
- dc?: number;
27
- rst?: number;
28
- spiFrequency?: number;
29
- /** SPI bus pins. When present, the overlay remuxes the SPI controller's
30
- * pinctrl to these pins (the board defaults rarely match a breakout's
31
- * wiring — e.g. demo-st's panel is on SCK=18/MOSI=23, not the devkitc
32
- * default 12/11). */
33
- sck?: number;
34
- mosi?: number;
35
- miso?: number;
36
- /** Optional GPIO driving the panel backlight. When set, the overlay emits a
37
- * `gpio-leds` node + DT alias (named by the profile's `backlight`) so the
38
- * display adapter can drive it high at init. When absent (e.g. the panel's
39
- * backlight is hardwired to 3.3V), no node or alias is emitted the C++
40
- * backlight init is guarded by `DT_HAS_ALIAS` and compiles away. */
41
- backlightPin?: number;
42
- }
43
-
44
- /** Touch wiring from cuttlefish.config.ts (irq/resetPin/sda/scl; cs for SPI
45
- * resistive controllers, calibration for the XPT2046 DT binding). */
46
- export interface TouchWiring {
47
- /** Touch controller kind selects the DT node shape (FT6336U node on I2C0
48
- * vs XPT2046 node on the display's SPI bus). Default 'ft6336u'. */
49
- controller?: 'ft6336u' | 'xpt2046';
50
- irq?: number;
51
- resetPin?: number;
52
- sda?: number;
53
- scl?: number;
54
- /** XPT2046 only: SPI CS pin (second cs-gpios entry on the panel's bus). */
55
- cs?: number;
56
- /** XPT2046 only: raw ADC calibration — feeds the binding's min-x/max-x/
57
- * min-y/max-y (required props). Defaults span the full 12-bit range. */
58
- calibration?: { xMin: number; xMax: number; yMin: number; yMax: number };
59
- /** XPT2046 only: pen-detect Z1 threshold (binding's z-threshold). Resistive
60
- * panels need a few hundred 12-bit counts; default 400. */
61
- minPressure?: number;
62
- }
63
-
64
- export function generateOverlay(
65
- chip: ZephyrChipDescriptor,
66
- usage: KconfigUsage,
67
- display: ZephyrDisplayProfile | undefined,
68
- wiring?: DisplayWiring,
69
- touch?: TouchWiring,
70
- ): string {
71
- const lines: string[] = [
72
- '/* Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts. */',
73
- '/* Enables peripherals the program uses. West merges this over the board DT. */',
74
- '',
75
- ];
76
-
77
- // Pinctrl override blocks reference the esp32s3 pinmux tokens; the include
78
- // must precede them. Emitted when the display SPI pins are overridden.
79
- if (wiring?.sck !== undefined && wiring?.mosi !== undefined) {
80
- lines.splice(2, 0, '#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>', '');
81
- }
82
-
83
- const block = (label: string, extra: string[] = []): void => {
84
- lines.push(`&${label} {`);
85
- lines.push(` status = "okay";`);
86
- for (const e of extra) lines.push(` ${e}`);
87
- lines.push('};');
88
- lines.push('');
89
- };
90
-
91
- if (usage.usesI2c && chip.i2c) {
92
- for (const c of chip.i2c.controllers) block(c.nodeLabel);
93
- }
94
- if (usage.usesSpi && chip.spi) {
95
- for (const c of chip.spi.controllers) block(c.nodeLabel);
96
- }
97
- if (usage.usesUart && chip.uart) {
98
- for (const c of chip.uart.controllers) block(c.nodeLabel);
99
- }
100
- // DAC: enable the chip's DAC device node when the program uses dac.*. The
101
- // lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)).
102
- if (usage.usesDac && chip.dac) {
103
- block(chip.dac.device);
104
- }
105
- if (display) {
106
- // Emit a full display DT node definition. Boards like the ESP32 devkit
107
- // have no display node in their base DT, so a bare `&display0 { status }`
108
- // fails (the nodelabel doesn't exist). Define the node attached to a SPI
109
- // controller with the panel's compatible string + dimensions. The pin
110
- // wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
111
- // real board overlay would carry its own binding.
112
- // An XPT2046 on the same bus needs its CS as the second cs-gpios entry,
113
- // so thread it into the display block (DT assignment replaces the whole
114
- // property both entries must be written together).
115
- emitDisplayNode(
116
- lines,
117
- display,
118
- wiring,
119
- touch?.controller === 'xpt2046' ? (touch?.cs ?? DEFAULT_XPT2046_CS) : undefined,
120
- );
121
- }
122
- // Touch FT6336U capacitive on I2C (references DT_NODELABEL(ft6336u)) or
123
- // XPT2046 resistive on the display's SPI bus (references
124
- // DT_NODELABEL(xpt2046)). Same rationale as the display node: the bare
125
- // devkit has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it
126
- // emits even when the chip descriptor doesn't declare bus controllers.
127
- if (usage.usesTouch) {
128
- emitTouchNode(lines, touch, display);
129
- }
130
-
131
- // Preferences (ZMS settings backend): point the settings subsystem at the
132
- // board's storage_partition. The backend looks for /chosen
133
- // zephyr,settings-partition first, then falls back to a fixed-partition
134
- // labeled storage_partition most Zephyr boards define that label, so this
135
- // chosen entry makes the lookup explicit and survives boards that name the
136
- // partition differently. It only adds a /chosen pointer (never redeclares
137
- // the partition node — west errors if a node is multiply-defined).
138
- if (usage.usesPreferences) {
139
- lines.push('/ {');
140
- lines.push(' chosen {');
141
- lines.push(' zephyr,settings-partition = &storage_partition;');
142
- lines.push(' };');
143
- lines.push('};');
144
- lines.push('');
145
- }
146
-
147
- // PSRAM: enable the psram0 DT node with the correct size. The devkitc board
148
- // DT defaults to a no-PSRAM module variant (e.g. wroom_n8); a PSRAM-capable
149
- // module (N16R8, N8R8) needs the node enabled + sized so the linker maps
150
- // .ext_ram sections into the real PSRAM. OPI on ESP32-S3 = 8MB octal PSRAM.
151
- if (usage.psram) {
152
- lines.push('&psram0 {');
153
- lines.push(' status = "okay";');
154
- lines.push(' size = <(DT_SIZE_M(8))>;');
155
- lines.push('};');
156
- lines.push('');
157
- }
158
-
159
- return lines.join('\n');
160
- }
161
-
162
- /**
163
- * Emit a display DT node definition. The node is attached to the profile's SPI
164
- * controller (default spi2, the ESP32's first user SPI controller) via a MIPI
165
- * DBI SPI bridge. Pin wiring comes from the display config (cs/dc/rst);
166
- * defaults match the demo-st wiring if absent. The compatible string + node
167
- * props come from the profile's panel controller (st7796s carries the required
168
- * pgc/ngc gamma + madctl; ili9341's binding defaults everything else).
169
- *
170
- * spiTouchCs: when an XPT2046 SPI touch controller shares the bus, its CS is
171
- * appended as the second cs-gpios entry (the touch node uses reg = <1>) — DT
172
- * property assignment replaces, so both entries must be written together.
173
- */
174
- function emitDisplayNode(
175
- lines: string[],
176
- display: ZephyrDisplayProfile,
177
- wiring?: DisplayWiring,
178
- spiTouchCs?: number,
179
- ): void {
180
- const bus = display.busLabel ?? 'spi2';
181
- const controller = panelControllerFor(display);
182
- const compatible = display.dtCompatible ?? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible;
183
- const dc = wiring?.dc ?? 17;
184
- const rst = wiring?.rst ?? 16;
185
- const cs = wiring?.cs ?? 5;
186
- const freq = wiring?.spiFrequency ?? 80000000;
187
- // DT node describes the NATIVE panel geometry; the effective (rotated)
188
- // dimensions live in the display profile.
189
- const nativeW = display.nativeWidth ?? display.width;
190
- const nativeH = display.nativeHeight ?? display.height;
191
- const sck = wiring?.sck;
192
- const mosi = wiring?.mosi;
193
- const miso = wiring?.miso;
194
- // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
195
- const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
196
- // The board's spim2_default pinctrl group usually targets the devkitc's
197
- // default SPI pins (SCLK=12/MOSI=11), which rarely match the display's
198
- // wiring. When the config declares spiPins, override the pinctrl groups to
199
- // remux the controller onto the panel's actual pins. Raw ESP32_PINMUX()
200
- // macros are used instead of the named SPIM2_*_GPIOxx tokens because the
201
- // bindings header omits GPIOs 22-25 from those lists.
202
- if (sck !== undefined && mosi !== undefined) {
203
- // 'spi2' pinctrl group 'spim2_default' (ESP32 SPI-master naming).
204
- const pinctrlGroup = bus.replace(/^spi(\d)$/, 'spim$1') + '_default';
205
- lines.push(`&${pinctrlGroup} {`);
206
- lines.push(' group1 {');
207
- lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
208
- lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
209
- lines.push(' <ESP32_PINMUX(10, ESP_NOSIG, ESP_FSPICS0_OUT)>;');
210
- lines.push(' };');
211
- lines.push(' group2 {');
212
- lines.push(` pinmux = <ESP32_PINMUX(${mosi}, ESP_NOSIG, ESP_FSPID_OUT)>;`);
213
- lines.push(' output-low;');
214
- lines.push(' };');
215
- lines.push('};');
216
- lines.push('');
217
- }
218
- lines.push('&dma {');
219
- lines.push(' status = "okay";');
220
- lines.push('};');
221
- lines.push('');
222
- lines.push(`&${bus} {`);
223
- lines.push(' status = "okay";');
224
- lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>${spiTouchCs !== undefined ? `, <&${gpioController(spiTouchCs)} ${spiTouchCs} GPIO_ACTIVE_LOW>` : ''};`);
225
- // Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
226
- // dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
227
- // without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
228
- // 80MHz requested). The GDMA driver allocates channels per peripheral based
229
- // on spi2's dma-host=<0> (set in the SoC dtsi), so the channel cells are the
230
- // GDMA instance's rx/tx slot ids (2 here = an SPI2-dedicated pair; the GDMA
231
- // binding's #dma-cells = <1> carries the channel).
232
- lines.push(' dma-enabled;');
233
- lines.push(' dmas = <&dma 0>, <&dma 1>;');
234
- lines.push(' dma-names = "rx", "tx";');
235
- lines.push('};');
236
- lines.push('');
237
- lines.push('/ {');
238
- lines.push(' mipi_dbi: mipi-dbi {');
239
- lines.push(' compatible = "zephyr,mipi-dbi-spi";');
240
- lines.push(` spi-dev = <&${bus}>;`);
241
- lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
242
- lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
243
- lines.push(' write-only;');
244
- lines.push(' #address-cells = <1>;');
245
- lines.push(' #size-cells = <0>;');
246
- lines.push(` ${display.dtLabel}: display@0 {`);
247
- lines.push(` compatible = "${compatible}";`);
248
- lines.push(' reg = <0>;');
249
- lines.push(` mipi-max-frequency = <${freq}>;`);
250
- lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
251
- // Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
252
- // matching upstream ILI9341 boards (esp_wrover_kit) and the C++
253
- // runtime, which drives these SPI TFTs as RGB565.
254
- lines.push(' pixel-format = <0>;');
255
- lines.push(` width = <${nativeW}>;`);
256
- lines.push(` height = <${nativeH}>;`);
257
- if (controller === 'st7796s') {
258
- // MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
259
- // direct-drive init (0x28). The DT copy keeps the stock driver's init
260
- // consistent if it is ever exercised.
261
- lines.push(' madctl = <0x28>;');
262
- lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
263
- lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
264
- } else {
265
- // ILI9341: the ilitek,ili9341 binding carries defaults for every register
266
- // (gamma, power, porch) and expresses orientation via `rotation` (degrees)
267
- // instead of a raw MADCTL — no panel-specific props are required.
268
- lines.push(` rotation = <${display.rotation ?? 0}>;`);
269
- }
270
- lines.push(' };');
271
- lines.push(' };');
272
- lines.push('};');
273
- lines.push('');
274
- // Backlight node: emit only when the config provides a backlight GPIO. The
275
- // pin was previously hardcoded to 4, which collided with the FT6336U
276
- // reset-gpios on the same pin (demo-st's backlight is hardwired to 3.3V, so
277
- // no backlight node is correct there). When present, use the configured pin
278
- // and the profile's alias name so the display adapter can drive it.
279
- const blPin = wiring?.backlightPin;
280
- if (display.backlight && blPin !== undefined) {
281
- lines.push('/ {');
282
- lines.push(' aliases {');
283
- lines.push(` ${display.backlight} = &bl_led;`);
284
- lines.push(' };');
285
- lines.push(' bl_gpio: bl-gpio-leds {');
286
- lines.push(' compatible = "gpio-leds";');
287
- lines.push(' bl_led: led {');
288
- lines.push(` gpios = <&${gpioController(blPin)} ${blPin} GPIO_ACTIVE_HIGH>;`);
289
- lines.push(' };');
290
- lines.push(' };');
291
- lines.push('};');
292
- lines.push('');
293
- }
294
- }
295
-
296
- /** Default XPT2046 CS/IRQ pins (ESP32-S3 GPIOs clear of the demo-st display
297
- * wiring: 5/17/16/15 and the remuxed SPI pins). Config values override. */
298
- const DEFAULT_XPT2046_CS = 6;
299
- const DEFAULT_XPT2046_IRQ = 7;
300
-
301
- /**
302
- * Emit the touch DT node for the configured controller.
303
- *
304
- * FT6336U (capacitive, I2C): node on i2c0 at the FT6336U default address
305
- * (0x38); the C++ touch adapter reads it via i2c_write_read_dt.
306
- *
307
- * XPT2046 (resistive, SPI): node on the display's SPI bus as CS index 1. The
308
- * in-tree xptek,xpt2046 binding (drivers/input) is register-matched for the
309
- * raw SPI access — CONFIG_INPUT stays off, so the in-tree input driver does
310
- * not build and the adapter owns the chip (same pattern as FT6336U reusing
311
- * the ft5336 binding). The binding requires int-gpios, touchscreen-size-*,
312
- * and min/max calibration props, so defaults are filled for anything the
313
- * config omits.
314
- */
315
- function emitTouchNode(
316
- lines: string[],
317
- touch: TouchWiring | undefined,
318
- display: ZephyrDisplayProfile | undefined,
319
- ): void {
320
- if (touch?.controller === 'xpt2046') {
321
- emitXpt2046Node(lines, touch, display);
322
- return;
323
- }
324
- emitFt6336uNode(lines, touch);
325
- }
326
-
327
- /** FT6336U capacitive touch node on the first I2C controller. */
328
- function emitFt6336uNode(lines: string[], touch?: TouchWiring): void {
329
- const irq = touch?.irq ?? 15;
330
- const resetPin = touch?.resetPin;
331
- const sda = touch?.sda;
332
- const scl = touch?.scl;
333
- // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
334
- const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
335
- // Zephyr has no ft6336 binding the FT6336U is register-compatible with the
336
- // ft5336 binding (same Focaltech register map: TD_STATUS at 0x02, coordinate
337
- // registers 0x03-0x06). The C++ touch adapter reads these directly via I2C,
338
- // so the binding just needs to exist for the DT node to resolve.
339
- // When SDA/SCL are provided, remux the I2C bus to those pins via pinctrl
340
- // (the board's default I2C pins rarely match a breakout's wiring).
341
- if (sda !== undefined && scl !== undefined) {
342
- lines.push('&pinctrl {');
343
- lines.push(' i2c0_touch: i2c0_touch {');
344
- lines.push(' group1 {');
345
- lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
346
- lines.push(' bias-pull-up;');
347
- lines.push(' drive-open-drain;');
348
- lines.push(' };');
349
- lines.push(' };');
350
- lines.push('};');
351
- lines.push('');
352
- lines.push('&i2c0 {');
353
- lines.push(' status = "okay";');
354
- lines.push(' pinctrl-0 = <&i2c0_touch>;');
355
- lines.push(' pinctrl-names = "default";');
356
- } else {
357
- lines.push('&i2c0 {');
358
- lines.push(' status = "okay";');
359
- }
360
- lines.push(' ft6336u: ft6336u@38 {');
361
- lines.push(' compatible = "focaltech,ft5336";');
362
- lines.push(' reg = <0x38>;');
363
- // NOTE: int-gpios is intentionally omitted. The ft5336 Zephyr driver
364
- // registers a GPIO interrupt on int-gpios, which triggers an assertion
365
- // failure in the ESP32 interrupt controller (VECDESC_FL_SHARED conflict).
366
- // The cuttlefish touch adapter polls touch_isTouched() via I2C every frame
367
- // it never uses the IRQ pin, so the interrupt registration is unnecessary.
368
- if (resetPin !== undefined) {
369
- lines.push(` reset-gpios = <&${gpioController(resetPin)} ${resetPin} GPIO_ACTIVE_LOW>;`);
370
- }
371
- lines.push(' };');
372
- lines.push('};');
373
- lines.push('');
374
- }
375
-
376
- /** XPT2046 resistive touch node on the display's SPI bus (CS index 1). */
377
- function emitXpt2046Node(
378
- lines: string[],
379
- touch: TouchWiring,
380
- display: ZephyrDisplayProfile | undefined,
381
- ): void {
382
- const bus = display?.busLabel ?? 'spi2';
383
- const cs = touch.cs ?? DEFAULT_XPT2046_CS;
384
- const irq = touch.irq ?? DEFAULT_XPT2046_IRQ;
385
- const cal = touch.calibration;
386
- // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
387
- const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
388
- const zThreshold = touch.minPressure ?? 400;
389
- // touchscreen-size-* describe the panel the touch layer sits on (the display
390
- // profile's effective size); a touch-only build falls back to the 12-bit
391
- // full-scale range so the binding's required props still resolve.
392
- const sizeX = display?.width ?? cal?.xMax ?? 320;
393
- const sizeY = display?.height ?? cal?.yMax ?? 240;
394
- // The display block already wrote cs-gpios with both entries (its CS at
395
- // index 0, the touch CS at index 1). When there is no display block, enable
396
- // the bus here with the touch CS as the only entry.
397
- if (!display) {
398
- lines.push(`&${bus} {`);
399
- lines.push(' status = "okay";');
400
- lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
401
- lines.push('};');
402
- lines.push('');
403
- }
404
- lines.push(`&${bus} {`);
405
- lines.push(' xpt2046: xpt2046@1 {');
406
- lines.push(' compatible = "xptek,xpt2046";');
407
- lines.push(' reg = <1>;');
408
- // The XPT2046 datasheet max SPI clock is 2.5MHz — the panel bus may run at
409
- // 80MHz, but this node's spi-max-frequency gates only its own transactions
410
- // (the adapter's SPI_DT_SPEC picks it up).
411
- lines.push(' spi-max-frequency = <2500000>;');
412
- lines.push(` int-gpios = <&${gpioController(irq)} ${irq} GPIO_ACTIVE_LOW>;`);
413
- lines.push(` touchscreen-size-x = <${sizeX}>;`);
414
- lines.push(` touchscreen-size-y = <${sizeY}>;`);
415
- lines.push(` min-x = <${cal?.xMin ?? 0}>;`);
416
- lines.push(` max-x = <${cal?.xMax ?? 4095}>;`);
417
- lines.push(` min-y = <${cal?.yMin ?? 0}>;`);
418
- lines.push(` max-y = <${cal?.yMax ?? 4095}>;`);
419
- lines.push(` z-threshold = <${zThreshold}>;`);
420
- lines.push(' };');
421
- lines.push('};');
422
- lines.push('');
423
- }
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
+ // This is the Zephyr analog of Arduino's library-resolution hooks: 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 type { ZephyrDisplayProfile } from '../display/profiles.js';
16
+ import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor } from '../display/profiles.js';
17
+ import type { KconfigUsage } from './kconfig.js';
18
+
19
+ /**
20
+ * Generate the overlay source for a board + usage. Returns the overlay text
21
+ * (caller writes it to app/boards/<board>.overlay via writeIfChanged).
22
+ */
23
+ /** Display wiring from cuttlefish.config.ts (cs/dc/rst/spiFrequency/spiPins). */
24
+ export interface DisplayWiring {
25
+ cs?: number;
26
+ dc?: number;
27
+ rst?: number;
28
+ /** Tearing-effect (TE) GPIO from display.tearingEffectPin — emitted as
29
+ * te-gpios on the display DT node. Opt-in; most boards don't wire TE. */
30
+ tearingEffectPin?: number;
31
+ spiFrequency?: number;
32
+ /** SPI bus pins. When present, the overlay remuxes the SPI controller's
33
+ * pinctrl to these pins (the board defaults rarely match a breakout's
34
+ * wiring — e.g. demo-st's panel is on SCK=18/MOSI=23, not the devkitc
35
+ * default 12/11). */
36
+ sck?: number;
37
+ mosi?: number;
38
+ miso?: number;
39
+ /** Optional GPIO driving the panel backlight. When set, the overlay emits a
40
+ * `gpio-leds` node + DT alias (named by the profile's `backlight`) so the
41
+ * display adapter can drive it high at init. When absent (e.g. the panel's
42
+ * backlight is hardwired to 3.3V), no node or alias is emitted — the C++
43
+ * backlight init is guarded by `DT_HAS_ALIAS` and compiles away. */
44
+ backlightPin?: number;
45
+ }
46
+
47
+ /** Touch wiring from cuttlefish.config.ts (irq/resetPin/sda/scl; cs for SPI
48
+ * resistive controllers, calibration for the XPT2046 DT binding). */
49
+ export interface TouchWiring {
50
+ /** Touch controller kind — selects the DT node shape (FT6336U node on I2C0
51
+ * vs XPT2046 node on the display's SPI bus). Default 'ft6336u'. */
52
+ controller?: 'ft6336u' | 'xpt2046';
53
+ irq?: number;
54
+ resetPin?: number;
55
+ sda?: number;
56
+ scl?: number;
57
+ /** XPT2046 only: SPI CS pin (second cs-gpios entry on the panel's bus). */
58
+ cs?: number;
59
+ /** XPT2046 only: raw ADC calibration — feeds the binding's min-x/max-x/
60
+ * min-y/max-y (required props). Defaults span the full 12-bit range. */
61
+ calibration?: { xMin: number; xMax: number; yMin: number; yMax: number };
62
+ /** XPT2046 only: pen-detect Z1 threshold (binding's z-threshold). Resistive
63
+ * panels need a few hundred 12-bit counts; default 400. */
64
+ minPressure?: number;
65
+ }
66
+
67
+ export interface OverlayDiagnostic {
68
+ severity: "warning" | "error";
69
+ message: string;
70
+ }
71
+
72
+ export function generateOverlay(
73
+ chip: ZephyrChipDescriptor,
74
+ usage: KconfigUsage,
75
+ display: ZephyrDisplayProfile | undefined,
76
+ wiring?: DisplayWiring,
77
+ touch?: TouchWiring,
78
+ diagnostics: OverlayDiagnostic[] = [],
79
+ ): string {
80
+ // An I2C touch controller with no explicit bus pins: the overlay enables
81
+ // i2c0 and instantiates the node, but nothing remuxes the controller to
82
+ // the wired SDA/SCL (the board's default I2C pins rarely match a
83
+ // breakout). Every I2C read then fails and touch silently does nothing —
84
+ // surface it at build time instead of leaving it to a multimeter.
85
+ if (touch && touch.controller !== 'xpt2046' && (touch.sda === undefined || touch.scl === undefined)) {
86
+ diagnostics.push({
87
+ severity: "warning",
88
+ message: `touch: I2C controller '${touch.controller}' has no sda/scl pins in cuttlefish.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).`,
89
+ });
90
+ }
91
+ const lines: string[] = [
92
+ '/* Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts. */',
93
+ '/* Enables peripherals the program uses. West merges this over the board DT. */',
94
+ '',
95
+ ];
96
+
97
+ // Pinctrl override blocks reference the esp32s3 pinmux tokens; the include
98
+ // must precede them. Emitted when the display SPI pins are overridden.
99
+ if (wiring?.sck !== undefined && wiring?.mosi !== undefined) {
100
+ lines.splice(2, 0, '#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>', '');
101
+ }
102
+
103
+ const block = (label: string, extra: string[] = []): void => {
104
+ lines.push(`&${label} {`);
105
+ lines.push(` status = "okay";`);
106
+ for (const e of extra) lines.push(` ${e}`);
107
+ lines.push('};');
108
+ lines.push('');
109
+ };
110
+
111
+ if (usage.usesI2c && chip.i2c) {
112
+ for (const c of chip.i2c.controllers) block(c.nodeLabel);
113
+ }
114
+ if (usage.usesSpi && chip.spi) {
115
+ for (const c of chip.spi.controllers) block(c.nodeLabel);
116
+ }
117
+ if (usage.usesUart && chip.uart) {
118
+ for (const c of chip.uart.controllers) block(c.nodeLabel);
119
+ }
120
+ // DAC: enable the chip's DAC device node when the program uses dac.*. The
121
+ // lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)).
122
+ if (usage.usesDac && chip.dac) {
123
+ block(chip.dac.device);
124
+ }
125
+ if (display) {
126
+ // Emit a full display DT node definition. Boards like the ESP32 devkit
127
+ // have no display node in their base DT, so a bare `&display0 { status }`
128
+ // fails (the nodelabel doesn't exist). Define the node attached to a SPI
129
+ // controller with the panel's compatible string + dimensions. The pin
130
+ // wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
131
+ // real board overlay would carry its own binding.
132
+ // An XPT2046 on the same bus needs its CS as the second cs-gpios entry,
133
+ // so thread it into the display block (DT assignment replaces the whole
134
+ // propertyboth entries must be written together).
135
+ emitDisplayNode(
136
+ lines,
137
+ display,
138
+ wiring,
139
+ touch?.controller === 'xpt2046' ? (touch?.cs ?? DEFAULT_XPT2046_CS) : undefined,
140
+ );
141
+ }
142
+ // Touch — FT6336U capacitive on I2C (references DT_NODELABEL(ft6336u)) or
143
+ // XPT2046 resistive on the display's SPI bus (references
144
+ // DT_NODELABEL(xpt2046)). Same rationale as the display node: the bare
145
+ // devkit has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it
146
+ // emits even when the chip descriptor doesn't declare bus controllers.
147
+ if (usage.usesTouch) {
148
+ emitTouchNode(lines, touch, display);
149
+ }
150
+
151
+ // Preferences (ZMS settings backend): point the settings subsystem at the
152
+ // board's storage_partition. The backend looks for /chosen
153
+ // zephyr,settings-partition first, then falls back to a fixed-partition
154
+ // labeled storage_partition — most Zephyr boards define that label, so this
155
+ // chosen entry makes the lookup explicit and survives boards that name the
156
+ // partition differently. It only adds a /chosen pointer (never redeclares
157
+ // the partition node — west errors if a node is multiply-defined).
158
+ if (usage.usesPreferences) {
159
+ lines.push('/ {');
160
+ lines.push(' chosen {');
161
+ lines.push(' zephyr,settings-partition = &storage_partition;');
162
+ lines.push(' };');
163
+ lines.push('};');
164
+ lines.push('');
165
+ }
166
+
167
+ // PSRAM: enable the psram0 DT node with the correct size. The devkitc board
168
+ // DT defaults to a no-PSRAM module variant (e.g. wroom_n8); a PSRAM-capable
169
+ // module (N16R8, N8R8) needs the node enabled + sized so the linker maps
170
+ // .ext_ram sections into the real PSRAM. OPI on ESP32-S3 = 8MB octal PSRAM.
171
+ if (usage.psram) {
172
+ lines.push('&psram0 {');
173
+ lines.push(' status = "okay";');
174
+ lines.push(' size = <(DT_SIZE_M(8))>;');
175
+ lines.push('};');
176
+ lines.push('');
177
+ }
178
+
179
+ return lines.join('\n');
180
+ }
181
+
182
+ /**
183
+ * Emit a display DT node definition. The node is attached to the profile's SPI
184
+ * controller (default spi2, the ESP32's first user SPI controller) via a MIPI
185
+ * DBI SPI bridge. Pin wiring comes from the display config (cs/dc/rst);
186
+ * defaults match the demo-st wiring if absent. The compatible string + node
187
+ * props come from the profile's panel controller (st7796s carries the required
188
+ * pgc/ngc gamma + madctl; ili9341's binding defaults everything else).
189
+ *
190
+ * spiTouchCs: when an XPT2046 SPI touch controller shares the bus, its CS is
191
+ * appended as the second cs-gpios entry (the touch node uses reg = <1>) — DT
192
+ * property assignment replaces, so both entries must be written together.
193
+ */
194
+ function emitDisplayNode(
195
+ lines: string[],
196
+ display: ZephyrDisplayProfile,
197
+ wiring?: DisplayWiring,
198
+ spiTouchCs?: number,
199
+ ): void {
200
+ const bus = display.busLabel ?? 'spi2';
201
+ const controller = panelControllerFor(display);
202
+ const compatible = display.dtCompatible ?? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible;
203
+ const dc = wiring?.dc ?? 17;
204
+ const rst = wiring?.rst ?? 16;
205
+ const cs = wiring?.cs ?? 5;
206
+ const freq = wiring?.spiFrequency ?? 80000000;
207
+ // DT node describes the NATIVE panel geometry; the effective (rotated)
208
+ // dimensions live in the display profile.
209
+ const nativeW = display.nativeWidth ?? display.width;
210
+ const nativeH = display.nativeHeight ?? display.height;
211
+ const sck = wiring?.sck;
212
+ const mosi = wiring?.mosi;
213
+ const miso = wiring?.miso;
214
+ // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
215
+ const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
216
+ // The board's spim2_default pinctrl group usually targets the devkitc's
217
+ // default SPI pins (SCLK=12/MOSI=11), which rarely match the display's
218
+ // wiring. When the config declares spiPins, override the pinctrl groups to
219
+ // remux the controller onto the panel's actual pins. Raw ESP32_PINMUX()
220
+ // macros are used instead of the named SPIM2_*_GPIOxx tokens because the
221
+ // bindings header omits GPIOs 22-25 from those lists.
222
+ if (sck !== undefined && mosi !== undefined) {
223
+ // 'spi2' → pinctrl group 'spim2_default' (ESP32 SPI-master naming).
224
+ const pinctrlGroup = bus.replace(/^spi(\d)$/, 'spim$1') + '_default';
225
+ lines.push(`&${pinctrlGroup} {`);
226
+ lines.push(' group1 {');
227
+ lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
228
+ lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
229
+ lines.push(' <ESP32_PINMUX(10, ESP_NOSIG, ESP_FSPICS0_OUT)>;');
230
+ lines.push(' };');
231
+ lines.push(' group2 {');
232
+ lines.push(` pinmux = <ESP32_PINMUX(${mosi}, ESP_NOSIG, ESP_FSPID_OUT)>;`);
233
+ lines.push(' output-low;');
234
+ lines.push(' };');
235
+ lines.push('};');
236
+ lines.push('');
237
+ }
238
+ lines.push('&dma {');
239
+ lines.push(' status = "okay";');
240
+ lines.push('};');
241
+ lines.push('');
242
+ lines.push(`&${bus} {`);
243
+ lines.push(' status = "okay";');
244
+ lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>${spiTouchCs !== undefined ? `, <&${gpioController(spiTouchCs)} ${spiTouchCs} GPIO_ACTIVE_LOW>` : ''};`);
245
+ // Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
246
+ // dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
247
+ // without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
248
+ // 80MHz requested). The GDMA driver allocates channels per peripheral based
249
+ // on spi2's dma-host=<0> (set in the SoC dtsi), so the channel cells are the
250
+ // GDMA instance's rx/tx slot ids (2 here = an SPI2-dedicated pair; the GDMA
251
+ // binding's #dma-cells = <1> carries the channel).
252
+ lines.push(' dma-enabled;');
253
+ lines.push(' dmas = <&dma 0>, <&dma 1>;');
254
+ lines.push(' dma-names = "rx", "tx";');
255
+ lines.push('};');
256
+ lines.push('');
257
+ lines.push('/ {');
258
+ lines.push(' mipi_dbi: mipi-dbi {');
259
+ lines.push(' compatible = "zephyr,mipi-dbi-spi";');
260
+ lines.push(` spi-dev = <&${bus}>;`);
261
+ lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
262
+ lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
263
+ lines.push(' write-only;');
264
+ lines.push(' #address-cells = <1>;');
265
+ lines.push(' #size-cells = <0>;');
266
+ lines.push(` ${display.dtLabel}: display@0 {`);
267
+ lines.push(` compatible = "${compatible}";`);
268
+ lines.push(' reg = <0>;');
269
+ if (wiring?.tearingEffectPin !== undefined) {
270
+ const tePin = wiring.tearingEffectPin!;
271
+ // Tearing-effect input on the display node: GPIO_DT_SPEC_GET(
272
+ // DT_NODELABEL(display0), te_gpios) in the adapter. Opt-in —
273
+ // most modules don't break the TE pad out.
274
+ lines.push(` te-gpios = <&${gpioController(tePin)} ${tePin} GPIO_ACTIVE_HIGH>;`);
275
+ }
276
+ lines.push(` mipi-max-frequency = <${freq}>;`);
277
+ lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
278
+ // Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
279
+ // matching upstream ILI9341 boards (esp_wrover_kit) and the C++
280
+ // runtime, which drives these SPI TFTs as RGB565.
281
+ lines.push(' pixel-format = <0>;');
282
+ lines.push(` width = <${nativeW}>;`);
283
+ lines.push(` height = <${nativeH}>;`);
284
+ if (controller === 'st7796s') {
285
+ // MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
286
+ // direct-drive init (0x28). The DT copy keeps the stock driver's init
287
+ // consistent if it is ever exercised.
288
+ lines.push(' madctl = <0x28>;');
289
+ lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
290
+ lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
291
+ } else {
292
+ // ILI9341: the ilitek,ili9341 binding carries defaults for every register
293
+ // (gamma, power, porch) and expresses orientation via `rotation` (degrees)
294
+ // instead of a raw MADCTL — no panel-specific props are required.
295
+ lines.push(` rotation = <${display.rotation ?? 0}>;`);
296
+ }
297
+ lines.push(' };');
298
+ lines.push(' };');
299
+ lines.push('};');
300
+ lines.push('');
301
+ // Backlight node: emit only when the config provides a backlight GPIO. The
302
+ // pin was previously hardcoded to 4, which collided with the FT6336U
303
+ // reset-gpios on the same pin (demo-st's backlight is hardwired to 3.3V, so
304
+ // no backlight node is correct there). When present, use the configured pin
305
+ // and the profile's alias name so the display adapter can drive it.
306
+ const blPin = wiring?.backlightPin;
307
+ if (display.backlight && blPin !== undefined) {
308
+ lines.push('/ {');
309
+ lines.push(' aliases {');
310
+ lines.push(` ${display.backlight} = &bl_led;`);
311
+ lines.push(' };');
312
+ lines.push(' bl_gpio: bl-gpio-leds {');
313
+ lines.push(' compatible = "gpio-leds";');
314
+ lines.push(' bl_led: led {');
315
+ lines.push(` gpios = <&${gpioController(blPin)} ${blPin} GPIO_ACTIVE_HIGH>;`);
316
+ lines.push(' };');
317
+ lines.push(' };');
318
+ lines.push('};');
319
+ lines.push('');
320
+ }
321
+ }
322
+
323
+ /** Default XPT2046 CS/IRQ pins (ESP32-S3 GPIOs clear of the demo-st display
324
+ * wiring: 5/17/16/15 and the remuxed SPI pins). Config values override. */
325
+ const DEFAULT_XPT2046_CS = 6;
326
+ const DEFAULT_XPT2046_IRQ = 7;
327
+
328
+ /**
329
+ * Emit the touch DT node for the configured controller.
330
+ *
331
+ * FT6336U (capacitive, I2C): node on i2c0 at the FT6336U default address
332
+ * (0x38); the C++ touch adapter reads it via i2c_write_read_dt.
333
+ *
334
+ * XPT2046 (resistive, SPI): node on the display's SPI bus as CS index 1. The
335
+ * in-tree xptek,xpt2046 binding (drivers/input) is register-matched for the
336
+ * raw SPI access CONFIG_INPUT stays off, so the in-tree input driver does
337
+ * not build and the adapter owns the chip (same pattern as FT6336U reusing
338
+ * the ft5336 binding). The binding requires int-gpios, touchscreen-size-*,
339
+ * and min/max calibration props, so defaults are filled for anything the
340
+ * config omits.
341
+ */
342
+ function emitTouchNode(
343
+ lines: string[],
344
+ touch: TouchWiring | undefined,
345
+ display: ZephyrDisplayProfile | undefined,
346
+ ): void {
347
+ if (touch?.controller === 'xpt2046') {
348
+ emitXpt2046Node(lines, touch, display);
349
+ return;
350
+ }
351
+ emitFt6336uNode(lines, touch);
352
+ }
353
+
354
+ /** FT6336U capacitive touch node on the first I2C controller. */
355
+ function emitFt6336uNode(lines: string[], touch?: TouchWiring): void {
356
+ const irq = touch?.irq ?? 15;
357
+ const resetPin = touch?.resetPin;
358
+ const sda = touch?.sda;
359
+ const scl = touch?.scl;
360
+ // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
361
+ const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
362
+ // Zephyr has no ft6336 binding — the FT6336U is register-compatible with the
363
+ // ft5336 binding (same Focaltech register map: TD_STATUS at 0x02, coordinate
364
+ // registers 0x03-0x06). The C++ touch adapter reads these directly via I2C,
365
+ // so the binding just needs to exist for the DT node to resolve.
366
+ // When SDA/SCL are provided, remux the I2C bus to those pins via pinctrl
367
+ // (the board's default I2C pins rarely match a breakout's wiring).
368
+ if (sda !== undefined && scl !== undefined) {
369
+ lines.push('&pinctrl {');
370
+ lines.push(' i2c0_touch: i2c0_touch {');
371
+ lines.push(' group1 {');
372
+ lines.push(` pinmux = <I2C0_SDA_GPIO${sda}>, <I2C0_SCL_GPIO${scl}>;`);
373
+ lines.push(' bias-pull-up;');
374
+ lines.push(' drive-open-drain;');
375
+ lines.push(' };');
376
+ lines.push(' };');
377
+ lines.push('};');
378
+ lines.push('');
379
+ lines.push('&i2c0 {');
380
+ lines.push(' status = "okay";');
381
+ lines.push(' pinctrl-0 = <&i2c0_touch>;');
382
+ lines.push(' pinctrl-names = "default";');
383
+ } else {
384
+ lines.push('&i2c0 {');
385
+ lines.push(' status = "okay";');
386
+ }
387
+ lines.push(' ft6336u: ft6336u@38 {');
388
+ lines.push(' compatible = "focaltech,ft5336";');
389
+ lines.push(' reg = <0x38>;');
390
+ // NOTE: int-gpios is intentionally omitted. The ft5336 Zephyr driver
391
+ // registers a GPIO interrupt on int-gpios, which triggers an assertion
392
+ // failure in the ESP32 interrupt controller (VECDESC_FL_SHARED conflict).
393
+ // The cuttlefish touch adapter polls touch_isTouched() via I2C every frame
394
+ // it never uses the IRQ pin, so the interrupt registration is unnecessary.
395
+ if (resetPin !== undefined) {
396
+ lines.push(` reset-gpios = <&${gpioController(resetPin)} ${resetPin} GPIO_ACTIVE_LOW>;`);
397
+ }
398
+ lines.push(' };');
399
+ lines.push('};');
400
+ lines.push('');
401
+ }
402
+
403
+ /** XPT2046 resistive touch node on the display's SPI bus (CS index 1). */
404
+ function emitXpt2046Node(
405
+ lines: string[],
406
+ touch: TouchWiring,
407
+ display: ZephyrDisplayProfile | undefined,
408
+ ): void {
409
+ const bus = display?.busLabel ?? 'spi2';
410
+ const cs = touch.cs ?? DEFAULT_XPT2046_CS;
411
+ const irq = touch.irq ?? DEFAULT_XPT2046_IRQ;
412
+ const cal = touch.calibration;
413
+ // ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
414
+ const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
415
+ const zThreshold = touch.minPressure ?? 400;
416
+ // touchscreen-size-* describe the panel the touch layer sits on (the display
417
+ // profile's effective size); a touch-only build falls back to the 12-bit
418
+ // full-scale range so the binding's required props still resolve.
419
+ const sizeX = display?.width ?? cal?.xMax ?? 320;
420
+ const sizeY = display?.height ?? cal?.yMax ?? 240;
421
+ // The display block already wrote cs-gpios with both entries (its CS at
422
+ // index 0, the touch CS at index 1). When there is no display block, enable
423
+ // the bus here with the touch CS as the only entry.
424
+ if (!display) {
425
+ lines.push(`&${bus} {`);
426
+ lines.push(' status = "okay";');
427
+ lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
428
+ lines.push('};');
429
+ lines.push('');
430
+ }
431
+ lines.push(`&${bus} {`);
432
+ lines.push(' xpt2046: xpt2046@1 {');
433
+ lines.push(' compatible = "xptek,xpt2046";');
434
+ lines.push(' reg = <1>;');
435
+ // The XPT2046 datasheet max SPI clock is 2.5MHz — the panel bus may run at
436
+ // 80MHz, but this node's spi-max-frequency gates only its own transactions
437
+ // (the adapter's SPI_DT_SPEC picks it up).
438
+ lines.push(' spi-max-frequency = <2500000>;');
439
+ lines.push(` int-gpios = <&${gpioController(irq)} ${irq} GPIO_ACTIVE_LOW>;`);
440
+ lines.push(` touchscreen-size-x = <${sizeX}>;`);
441
+ lines.push(` touchscreen-size-y = <${sizeY}>;`);
442
+ lines.push(` min-x = <${cal?.xMin ?? 0}>;`);
443
+ lines.push(` max-x = <${cal?.xMax ?? 4095}>;`);
444
+ lines.push(` min-y = <${cal?.yMin ?? 0}>;`);
445
+ lines.push(` max-y = <${cal?.yMax ?? 4095}>;`);
446
+ lines.push(` z-threshold = <${zThreshold}>;`);
447
+ lines.push(' };');
448
+ lines.push('};');
449
+ lines.push('');
450
+ }