esp-metadata 0.10.1

Metadata for Espressif devices
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
185
186
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
337
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
435
436
437
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
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
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
609
610
611
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
//! This module contains configuration used in [device.gpio], as well as
//! functions that generate code for esp-hal.

use std::str::FromStr;

use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

use crate::{
    cfg::{GenericProperty, Value},
    generate_for_each_macro,
    number,
};

/// Additional properties (besides those defined in cfg.rs) for [device.gpio].
/// These don't get turned into symbols, but are used to generate code.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct GpioPinsAndSignals {
    /// The list of GPIO pins and their properties.
    pub pins: Vec<PinConfig>,

    /// The list of peripheral input signals.
    pub input_signals: Vec<IoMuxSignal>,

    /// The list of peripheral output signals.
    pub output_signals: Vec<IoMuxSignal>,
}

impl GenericProperty for GpioPinsAndSignals {}

/// Possible special cases that may affect pin availability or functionality.
///
/// Some of these are explicitly encoded in the TOMLs, others are inferred from the pin alternate
/// function list.
#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PinLimitation {
    /// Strapping pin, signal level needs to be carefully set during boot.
    Strapping,

    /// Pin is used to interface with SPI flash.
    SpiFlash,

    /// Pin is used to interface with Octal SPI flash.
    OctalFlash,

    /// Pin is used to interface with SPI PSRAM.
    SpiPsram,

    /// Pin is used to interface with Octal SPI PSRAM.
    OctalPsram,

    /// Pin is only available on ESP32-PICO-V3.
    Esp32PicoV3,

    /// The pin has no output stage.
    InputOnly,

    /// Default UART pins.
    BootloaderUart,

    /// Debugger pins.
    Jtag,

    /// USB Serial/JTAG debugger pins.
    UsbJtag,
}

impl std::fmt::Display for PinLimitation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PinLimitation::Strapping => write!(
                f,
                "This pin is a strapping pin, it determines how the chip boots."
            ),
            PinLimitation::SpiFlash => {
                write!(
                    f,
                    "This pin may be reserved for interfacing with SPI flash."
                )
            }
            PinLimitation::OctalFlash => {
                write!(
                    f,
                    "This pin may be reserved for interfacing with Octal SPI flash."
                )
            }
            PinLimitation::SpiPsram => {
                write!(
                    f,
                    "This pin may be reserved for interfacing with SPI PSRAM."
                )
            }
            PinLimitation::OctalPsram => {
                write!(
                    f,
                    "This pin may be reserved for interfacing with Octal SPI PSRAM."
                )
            }
            PinLimitation::Esp32PicoV3 => write!(f, "This pin is only available on ESP32-PICO-V3."),
            PinLimitation::InputOnly => write!(f, "This pin can only be used as an input."),
            PinLimitation::BootloaderUart => {
                write!(
                    f,
                    "By default, this pin is used by the UART programming interface."
                )
            }
            PinLimitation::Jtag => {
                write!(
                    f,
                    "These pins may be used to debug the chip using an external JTAG debugger."
                )
            }
            PinLimitation::UsbJtag => {
                write!(f, "These pins may be used to debug the chip using USB.")
            }
        }
    }
}

/// Properties of a single GPIO pin.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct PinConfig {
    /// The GPIO pin number.
    pub pin: usize,

    /// Available IO MUX functions for this pin.
    #[serde(default)]
    pub functions: FunctionMap,

    /// Available analog functions for this pin.
    #[serde(default)]
    pub analog: AnalogMap,

    /// Available LP/RTC IO functions for this pin.
    #[serde(default, alias = "rtc")]
    pub lp: LowPowerMap,

    /// Lists cases where the GPIO needs special attention.
    #[serde(default)]
    pub limitations: Vec<PinLimitation>,
}

impl PinConfig {
    pub(crate) fn limitations(&self) -> Vec<PinLimitation> {
        let mut limitations = self.limitations.clone();

        // Resolve implicit limitations - based on pin alternate functions
        let implicit: &[(&[&str], PinLimitation)] = &[
            (&["MTMS", "MTCK", "MTDO", "MTDI"], PinLimitation::Jtag),
            (&["USB_DP", "USB_DM"], PinLimitation::UsbJtag),
            (&["U0TXD", "U0RXD"], PinLimitation::BootloaderUart),
        ];

        let max = usize::max(FunctionMap::COUNT, AnalogMap::COUNT);
        for i in 0..max {
            for (pins, limitation) in implicit.iter() {
                let mut consider = |func| {
                    if pins.contains(&func) && !limitations.contains(limitation) {
                        limitations.push(*limitation);
                    }
                };

                if let Some(func) = self.functions.get(i) {
                    consider(func);
                }
                if let Some(func) = self.analog.get(i) {
                    consider(func);
                }
            }
        }

        limitations
    }
}

/// Available alternate functions for a given GPIO pin.
///
/// Alternate functions allow bypassing the GPIO matrix by selecting a different
/// path in the multiplexers controlled by MCU_SEL.
///
/// Values of this struct correspond to rows in the IO MUX Pad List table.
///
/// Used in [device.gpio.pins[X].functions]. The GPIO function is not
/// written here as that is common to all pins. The values are signal names
/// listed in [device.gpio.input_signals] or [device.gpio.output_signals].
/// `None` means the pin does not provide the given alternate function.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct FunctionMap {
    #[serde(rename = "0")]
    af0: Option<String>,
    #[serde(rename = "1")]
    af1: Option<String>,
    #[serde(rename = "2")]
    af2: Option<String>,
    #[serde(rename = "3")]
    af3: Option<String>,
    #[serde(rename = "4")]
    af4: Option<String>,
    #[serde(rename = "5")]
    af5: Option<String>,
}

impl FunctionMap {
    const COUNT: usize = 6;

    /// Returns the signal associated with the nth alternate function.
    ///
    /// Note that not all alternate functions are defined. The number of the
    /// GPIO function is available separately. Not all alternate function have
    /// IO signals.
    pub fn get(&self, af: usize) -> Option<&str> {
        match af {
            0 => self.af0.as_deref(),
            1 => self.af1.as_deref(),
            2 => self.af2.as_deref(),
            3 => self.af3.as_deref(),
            4 => self.af4.as_deref(),
            5 => self.af5.as_deref(),
            _ => None,
        }
    }
}

/// Available analog functions for a given GPIO pin.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct AnalogMap {
    #[serde(rename = "0")]
    af0: Option<String>,
    #[serde(rename = "1")]
    af1: Option<String>,
    #[serde(rename = "2")]
    af2: Option<String>,
    #[serde(rename = "3")]
    af3: Option<String>,
    #[serde(rename = "4")]
    af4: Option<String>,
    #[serde(rename = "5")]
    af5: Option<String>,
}

impl AnalogMap {
    const COUNT: usize = 6;

    /// Returns the signal associated with the nth alternate function.
    pub fn get(&self, af: usize) -> Option<&str> {
        match af {
            0 => self.af0.as_deref(),
            1 => self.af1.as_deref(),
            2 => self.af2.as_deref(),
            3 => self.af3.as_deref(),
            4 => self.af4.as_deref(),
            5 => self.af5.as_deref(),
            _ => None,
        }
    }
}

/// Available RTC/LP functions for a given GPIO pin.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct LowPowerMap {
    #[serde(rename = "0")]
    af0: Option<String>,
    #[serde(rename = "1")]
    af1: Option<String>,
    #[serde(rename = "2")]
    af2: Option<String>,
    #[serde(rename = "3")]
    af3: Option<String>,
    #[serde(rename = "4")]
    af4: Option<String>,
    #[serde(rename = "5")]
    af5: Option<String>,
}

impl LowPowerMap {
    const COUNT: usize = 6;

    /// Returns the signal associated with the nth alternate function.
    pub fn get(&self, af: usize) -> Option<&str> {
        match af {
            0 => self.af0.as_deref(),
            1 => self.af1.as_deref(),
            2 => self.af2.as_deref(),
            3 => self.af3.as_deref(),
            4 => self.af4.as_deref(),
            5 => self.af5.as_deref(),
            _ => None,
        }
    }
}

/// An input or output peripheral signal. The names usually match the signal
/// name in the Peripheral Signal List table, without the `in` or `out` suffix.
/// If the `id` is `None`, the signal cannot be routed through the GPIO matrix.
///
/// If the TRM's signal table says "no" to Direct Input/Output via IO MUX, the
/// signal does not have an Alternate Function and must be routed through the
/// GPIO matrix.
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct IoMuxSignal {
    /// The name of the signal.
    pub name: String,

    /// The numeric ID of the signal, if the signal can be routed through the
    /// GPIO matrix.
    #[serde(default)]
    pub id: Option<usize>,
}

impl super::GpioProperties {
    pub(super) fn computed_properties(&self) -> impl Iterator<Item = (&str, bool, Value)> {
        let input_max = self
            .pins_and_signals
            .input_signals
            .iter()
            .filter_map(|s| s.id)
            .max()
            .unwrap_or(0) as u32;
        let output_max = self
            .pins_and_signals
            .output_signals
            .iter()
            .filter_map(|s| s.id)
            .max()
            .unwrap_or(0) as u32;

        [
            ("gpio.input_signal_max", false, Value::Number(input_max)),
            ("gpio.output_signal_max", false, Value::Number(output_max)),
        ]
        .into_iter()
    }
}

pub(crate) fn generate_gpios(gpio: &super::GpioProperties) -> TokenStream {
    let pin_numbers = gpio
        .pins_and_signals
        .pins
        .iter()
        .map(|pin| number(pin.pin))
        .collect::<Vec<_>>();

    let pin_peris = gpio
        .pins_and_signals
        .pins
        .iter()
        .map(|pin| format_ident!("GPIO{}", pin.pin))
        .collect::<Vec<_>>();

    let pin_attrs = gpio
        .pins_and_signals
        .pins
        .iter()
        .map(|pin| {
            // Input must come first
            if pin.limitations.contains(&PinLimitation::InputOnly) {
                vec![quote! { Input }, quote! {}]
            } else {
                vec![quote! { Input }, quote! { Output }]
            }
        })
        .collect::<Vec<_>>();

    let mut lp_functions = vec![];
    let mut expanded_lp_functions = vec![];
    let mut analog_functions = vec![];
    let mut expanded_analog_functions = vec![];

    let pin_afs = gpio
        .pins_and_signals
        .pins
        .iter()
        .map(|pin| {
            let mut input_afs = vec![];
            let mut output_afs = vec![];

            let pin_peri = format_ident!("GPIO{}", pin.pin);

            for af in 0..FunctionMap::COUNT {
                let Some(signal) = pin.functions.get(af) else {
                    continue;
                };

                let af_variant = format_ident!("_{af}");
                let mut found = false;

                // Is the signal present among the input signals?
                if let Some(signal) = gpio
                    .pins_and_signals
                    .input_signals
                    .iter()
                    .find(|s| s.name == signal)
                {
                    let signal_tokens = TokenStream::from_str(&signal.name).unwrap();
                    input_afs.push(quote! { #af_variant => #signal_tokens });
                    found = true;
                }

                // Is the signal present among the output signals?
                if let Some(signal) = gpio
                    .pins_and_signals
                    .output_signals
                    .iter()
                    .find(|s| s.name == signal)
                {
                    let signal_tokens = TokenStream::from_str(&signal.name).unwrap();
                    output_afs.push(quote! { #af_variant => #signal_tokens });
                    found = true;
                }

                assert!(
                    found,
                    "Signal '{signal}' not found in input signals for GPIO pin {}",
                    pin.pin
                );
            }

            fn create_matchers_for_signal(
                branches: &mut Vec<TokenStream>,
                pin_peri: &Ident,
                signal: &str,
            ) {
                // Split "NAMEnumber" format fragments into the NAME and the number. The function
                // returns `None` if the input string is not in this format. The NAME part can be
                // empty (i.e. this function can return `Some("", number)`).
                fn split_signal_with_number(fragment: &str) -> Option<(&str, usize)> {
                    // Find the first character that is not a letter.
                    let Some(breakpoint) = fragment
                        .char_indices()
                        .filter_map(|(idx, c)| if c.is_alphabetic() { None } else { Some(idx) })
                        .next()
                    else {
                        // fragment only contains letters
                        return None;
                    };

                    let number: usize = fragment[breakpoint..].parse().ok()?;

                    Some((&fragment[..breakpoint], number))
                }

                let signal_name = TokenStream::from_str(signal).unwrap();

                let full_signal = {
                    // The signal name, with numbers replaced with placeholders
                    let mut pattern = String::new();
                    let mut numbers = vec![];

                    let placeholders = ['n', 'm'];

                    let mut separator = "";
                    for fragment in signal.split('_') {
                        if let Some((prefix, n)) = split_signal_with_number(fragment) {
                            let placeholder = placeholders[numbers.len()];
                            numbers.push(number(n));
                            pattern = format!("{pattern}{separator}{prefix}{placeholder}")
                        } else {
                            pattern = format!("{pattern}{separator}{fragment}");
                        };

                        separator = "_";
                    }

                    if pattern == signal {
                        None
                    } else {
                        let pattern = format_ident!("{pattern}");

                        Some(quote! {
                            ( #signal_name, #pattern #(, #numbers)* )
                        })
                    }
                };

                if let Some(full_signal) = full_signal {
                    branches.push(quote! {
                        #full_signal, #pin_peri
                    });
                }
            }

            for af in 0..AnalogMap::COUNT {
                if let Some(signal) = pin.analog.get(af) {
                    let signal_name = TokenStream::from_str(signal).unwrap();
                    analog_functions.push(quote! { #signal_name, #pin_peri });
                    create_matchers_for_signal(&mut expanded_analog_functions, &pin_peri, signal);
                }
            }

            for af in 0..LowPowerMap::COUNT {
                if let Some(signal) = pin.lp.get(af) {
                    let signal_name = TokenStream::from_str(signal).unwrap();
                    lp_functions.push(quote! { #signal_name, #pin_peri });
                    create_matchers_for_signal(&mut expanded_lp_functions, &pin_peri, signal);
                }
            }

            quote! {
                ( #(#input_afs)* ) ( #(#output_afs)* )
            }
        })
        .collect::<Vec<_>>();

    let io_mux_accessor = if gpio.remap_iomux_pin_registers {
        let iomux_pin_regs = gpio.pins_and_signals.pins.iter().map(|pin| {
            let pin = number(pin.pin);
            let accessor = format_ident!("gpio{pin}");

            quote! { #pin => iomux.#accessor(), }
        });

        quote! {
            pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 {
                let iomux = crate::peripherals::IO_MUX::regs();
                match gpio_num {
                    #(#iomux_pin_regs)*
                    other => panic!("GPIO {} does not exist", other),
                }
            }

        }
    } else {
        quote! {
            pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO {
                crate::peripherals::IO_MUX::regs().gpio(gpio_num as usize)
            }
        }
    };

    let mut branches = vec![];
    for (((n, p), af), attrs) in pin_numbers
        .iter()
        .zip(pin_peris.iter())
        .zip(pin_afs.iter())
        .zip(pin_attrs.iter())
    {
        branches.push(quote! {
            #n, #p #af (#([#attrs])*)
        })
    }

    let for_each_gpio = generate_for_each_macro("gpio", &[("all", &branches)]);
    let for_each_analog = generate_for_each_macro(
        "analog_function",
        &[
            ("all", &analog_functions),
            ("all_expanded", &expanded_analog_functions),
        ],
    );
    let for_each_lp = generate_for_each_macro(
        "lp_function",
        &[
            ("all", &lp_functions),
            ("all_expanded", &expanded_lp_functions),
        ],
    );
    let input_signals = render_signals("InputSignal", &gpio.pins_and_signals.input_signals);
    let output_signals = render_signals("OutputSignal", &gpio.pins_and_signals.output_signals);

    quote! {
        /// This macro can be used to generate code for each `GPIOn` instance.
        ///
        /// For an explanation on the general syntax, as well as usage of individual/repeated
        /// matchers, refer to [the crate-level documentation][crate#for_each-macros].
        ///
        /// This macro has one option for its "Individual matcher" case:
        ///
        /// Syntax: `($n:literal, $gpio:ident ($($digital_input_function:ident => $digital_input_signal:ident)*) ($($digital_output_function:ident => $digital_output_signal:ident)*) ($([$pin_attribute:ident])*))`
        ///
        /// Macro fragments:
        ///
        /// - `$n`: the number of the GPIO. For `GPIO0`, `$n` is 0.
        /// - `$gpio`: the name of the GPIO.
        /// - `$digital_input_function`: the number of the digital function, as an identifier (i.e. for function 0 this is `_0`).
        /// - `$digital_input_function`: the name of the digital function, as an identifier.
        /// - `$digital_output_function`: the number of the digital function, as an identifier (i.e. for function 0 this is `_0`).
        /// - `$digital_output_function`: the name of the digital function, as an identifier.
        /// - `$pin_attribute`: `Input` and/or `Output`, marks the possible directions of the GPIO. Bracketed so that they can also be matched as optional fragments. Order is always Input first.
        ///
        /// Example data: `(0, GPIO0 (_5 => EMAC_TX_CLK) (_1 => CLK_OUT1 _5 => EMAC_TX_CLK) ([Input] [Output]))`
        #for_each_gpio

        /// This macro can be used to generate code for each analog function of each GPIO.
        ///
        /// For an explanation on the general syntax, as well as usage of individual/repeated
        /// matchers, refer to [the crate-level documentation][crate#for_each-macros].
        ///
        /// This macro has two options for its "Individual matcher" case:
        ///
        /// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers
        /// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - expanded signal case, where you need the number(s) of a signal, or the general group to which the signal belongs. For example, in case of `ADC2_CH3` the expanded form looks like `(ADC2_CH3, ADCn_CHm, 2, 3)`.
        ///
        /// Macro fragments:
        ///
        /// - `$signal`: the name of the signal.
        /// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this is `ADCn_CHm`.
        /// - `$number`: the numbers extracted from `$signal`.
        /// - `$gpio`: the name of the GPIO.
        ///
        /// Example data:
        /// - `(ADC2_CH5, GPIO12)`
        /// - `((ADC2_CH5, ADCn_CHm, 2, 5), GPIO12)`
        ///
        /// The expanded syntax is only available when the signal has at least one numbered component.
        #for_each_analog

        /// This macro can be used to generate code for each LP/RTC function of each GPIO.
        ///
        /// For an explanation on the general syntax, as well as usage of individual/repeated
        /// matchers, refer to [the crate-level documentation][crate#for_each-macros].
        ///
        /// This macro has two options for its "Individual matcher" case:
        ///
        /// - `all`: `($signal:ident, $gpio:ident)` - simple case where you only need identifiers
        /// - `all_expanded`: `(($signal:ident, $group:ident $(, $number:literal)+), $gpio:ident)` - expanded signal case, where you need the number(s) of a signal, or the general group to which the signal belongs. For example, in case of `SAR_I2C_SCL_1` the expanded form looks like `(SAR_I2C_SCL_1, SAR_I2C_SCL_n, 1)`.
        ///
        /// Macro fragments:
        ///
        /// - `$signal`: the name of the signal.
        /// - `$group`: the name of the signal, with numbers replaced by placeholders. For `ADC2_CH3` this is `ADCn_CHm`.
        /// - `$number`: the numbers extracted from `$signal`.
        /// - `$gpio`: the name of the GPIO.
        ///
        /// Example data:
        /// - `(RTC_GPIO15, GPIO12)`
        /// - `((RTC_GPIO15, RTC_GPIOn, 15), GPIO12)`
        ///
        /// The expanded syntax is only available when the signal has at least one numbered component.
        #for_each_lp

        /// Defines the `InputSignal` and `OutputSignal` enums.
        ///
        /// This macro is intended to be called in esp-hal only.
        #[macro_export]
        #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
        macro_rules! define_io_mux_signals {
            () => {
                #input_signals
                #output_signals
            };
        }

        /// Defines and implements the `io_mux_reg` function.
        ///
        /// The generated function has the following signature:
        ///
        /// ```rust,ignore
        /// pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 {
        ///     // ...
        /// # unimplemented!()
        /// }
        /// ```
        ///
        /// This macro is intended to be called in esp-hal only.
        #[macro_export]
        #[expect(clippy::crate_in_macro_def)]
        #[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
        macro_rules! define_io_mux_reg {
            () => {
                #io_mux_accessor
            };
        }
    }
}

fn render_signals(enum_name: &str, signals: &[IoMuxSignal]) -> TokenStream {
    if signals.is_empty() {
        // If there are no signals, we don't need to generate an enum.
        return quote! {};
    }
    let mut variants = vec![];

    for signal in signals {
        // First, process only signals that have an ID.
        let Some(id) = signal.id else {
            continue;
        };

        let name = format_ident!("{}", signal.name);
        let value = number(id);
        variants.push(quote! {
            #name = #value,
        });
    }

    for signal in signals {
        // Now process signals that do not have an ID.
        if signal.id.is_some() {
            continue;
        };

        let name = format_ident!("{}", signal.name);
        variants.push(quote! {
            #name,
        });
    }

    let enum_name = format_ident!("{enum_name}");

    quote! {
        #[allow(non_camel_case_types, clippy::upper_case_acronyms)]
        #[derive(Debug, PartialEq, Copy, Clone)]
        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
        #[doc(hidden)]
        pub enum #enum_name {
            #(#variants)*
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize)]
pub struct DedicatedGpioChannels {
    // Cpu -> list of Signals
    channels: Vec<Vec<String>>,
}

impl DedicatedGpioChannels {
    fn channel_count(&self) -> usize {
        assert!(
            self.channels
                .iter()
                .all(|channel| channel.len() == self.channels[0].len()),
            "All cores must have the same number of dedicated GPIO channels"
        );
        self.channels[0].len()
    }
}

impl GenericProperty for DedicatedGpioChannels {
    fn macros(&self) -> Option<proc_macro2::TokenStream> {
        let channel_count = self.channel_count();
        let channel_branches = (0..channel_count).map(number).collect::<Vec<_>>();
        let signal_branches = self
            .channels
            .iter()
            .enumerate()
            .flat_map(|(core, channels)| {
                channels.iter().enumerate().map(move |(channel, signal)| {
                    let signal = format_ident!("{signal}");
                    let core = number(core);
                    let channel = number(channel);
                    quote! { #core, #channel, #signal }
                })
            })
            .collect::<Vec<_>>();

        Some(generate_for_each_macro(
            "dedicated_gpio",
            &[
                ("channels", &channel_branches),
                ("signals", &signal_branches),
            ],
        ))
    }

    fn property_macro_branches(&self) -> proc_macro2::TokenStream {
        let channel_count = number(self.channel_count());
        quote::quote! {
            ("dedicated_gpio.channel_count") => {
                #channel_count
            };
            ("dedicated_gpio.channel_count", str) => {
                stringify!(#channel_count)
            };
        }
    }
}