From 101de4eab883ba8c1cf4c0f7abecb9974ab15025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 30 Jun 2025 10:16:22 +0200 Subject: [PATCH] Cleanup esp-metadata (#3686) * Clean up esp-metadata a bit * Update esp-metadata/src/cfg/gpio.rs Co-authored-by: Juraj Sadel * Document public API, arrange structs better --------- Co-authored-by: Juraj Sadel --- esp-hal/src/soc/esp32/gpio.rs | 2 +- esp-hal/src/soc/esp32c2/gpio.rs | 2 +- esp-hal/src/soc/esp32c3/gpio.rs | 2 +- esp-hal/src/soc/esp32c6/gpio.rs | 2 +- esp-hal/src/soc/esp32h2/gpio.rs | 2 +- esp-hal/src/soc/esp32s2/gpio.rs | 2 +- esp-hal/src/soc/esp32s3/gpio.rs | 2 +- esp-metadata/src/cfg.rs | 94 ++----- esp-metadata/src/cfg/gpio.rs | 380 +++++++++++++++++++++++++++++ esp-metadata/src/cfg/i2c_master.rs | 48 ++++ esp-metadata/src/lib.rs | 317 +----------------------- 11 files changed, 466 insertions(+), 387 deletions(-) create mode 100644 esp-metadata/src/cfg/gpio.rs create mode 100644 esp-metadata/src/cfg/i2c_master.rs diff --git a/esp-hal/src/soc/esp32/gpio.rs b/esp-hal/src/soc/esp32/gpio.rs index 89135445e..f4953aa18 100644 --- a/esp-hal/src/soc/esp32/gpio.rs +++ b/esp-hal/src/soc/esp32/gpio.rs @@ -30,7 +30,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); macro_rules! rtcio_analog { ( diff --git a/esp-hal/src/soc/esp32c2/gpio.rs b/esp-hal/src/soc/esp32c2/gpio.rs index 8a13b982b..f0b6840fe 100644 --- a/esp-hal/src/soc/esp32c2/gpio.rs +++ b/esp-hal/src/soc/esp32c2/gpio.rs @@ -27,7 +27,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); macro_rules! rtc_pins { ( $( $pin_num:expr )+ ) => { diff --git a/esp-hal/src/soc/esp32c3/gpio.rs b/esp-hal/src/soc/esp32c3/gpio.rs index 6a6757e1a..0d063b8f2 100644 --- a/esp-hal/src/soc/esp32c3/gpio.rs +++ b/esp-hal/src/soc/esp32c3/gpio.rs @@ -27,7 +27,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); macro_rules! rtc_pins { ( $( $pin_num:expr )+ ) => { diff --git a/esp-hal/src/soc/esp32c6/gpio.rs b/esp-hal/src/soc/esp32c6/gpio.rs index be77976e7..7206948e9 100644 --- a/esp-hal/src/soc/esp32c6/gpio.rs +++ b/esp-hal/src/soc/esp32c6/gpio.rs @@ -27,7 +27,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); crate::gpio::lp_io::lp_gpio! { 0 diff --git a/esp-hal/src/soc/esp32h2/gpio.rs b/esp-hal/src/soc/esp32h2/gpio.rs index e5bb5b6d9..22bdd4a7d 100644 --- a/esp-hal/src/soc/esp32h2/gpio.rs +++ b/esp-hal/src/soc/esp32h2/gpio.rs @@ -27,4 +27,4 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); diff --git a/esp-hal/src/soc/esp32s2/gpio.rs b/esp-hal/src/soc/esp32s2/gpio.rs index f819b8330..b59c6d7da 100644 --- a/esp-hal/src/soc/esp32s2/gpio.rs +++ b/esp-hal/src/soc/esp32s2/gpio.rs @@ -37,7 +37,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); macro_rules! rtcio_analog { ($pin_num:expr, $pin_reg:expr, $hold:ident) => { diff --git a/esp-hal/src/soc/esp32s3/gpio.rs b/esp-hal/src/soc/esp32s3/gpio.rs index 1dd363615..fd2082d7b 100644 --- a/esp-hal/src/soc/esp32s3/gpio.rs +++ b/esp-hal/src/soc/esp32s3/gpio.rs @@ -27,7 +27,7 @@ //! registers for both the `PRO CPU` and `APP CPU`. The implementation uses the //! `gpio` peripheral to access the appropriate registers. -include!(concat!(env!("OUT_DIR"), "/_generated_gpio_extras.rs")); +include!(concat!(env!("OUT_DIR"), "/_generated_iomux_signals.rs")); macro_rules! rtcio_analog { ($pin_num:expr, $pin_reg:expr, $hold:ident) => { diff --git a/esp-metadata/src/cfg.rs b/esp-metadata/src/cfg.rs index abf6c4ae8..056ac4dda 100644 --- a/esp-metadata/src/cfg.rs +++ b/esp-metadata/src/cfg.rs @@ -1,3 +1,9 @@ +pub(crate) mod gpio; +pub(crate) mod i2c_master; + +pub(crate) use gpio::*; +pub(crate) use i2c_master::*; + /// Represents a value in the driver configuration. pub(crate) enum Value { Unset, @@ -17,6 +23,10 @@ impl From> for Value { } } +/// The support status of a given peripheral driver. +/// +/// This is defined in device metadata, and is used to generate the peripheral +/// support table. #[derive(Debug, Default, Clone, Copy, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub(crate) enum SupportStatus { @@ -49,82 +59,6 @@ impl SupportStatus { #[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct EmptyInstanceConfig {} -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub(crate) struct I2cMasterInstanceConfig { - pub sys_instance: String, - pub scl: String, - pub sda: String, - pub interrupt: String, -} - -#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub(crate) enum PinCapability { - Input, - Output, - Analog, - Rtc, - Touch, - UsbDm, - UsbDp, - // Pin has USB pullup according to the IO MUX Function list - UsbDevice, -} - -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub(crate) struct AfMap { - #[serde(rename = "0")] - af0: Option, - #[serde(rename = "1")] - af1: Option, - #[serde(rename = "2")] - af2: Option, - #[serde(rename = "3")] - af3: Option, - #[serde(rename = "4")] - af4: Option, - #[serde(rename = "5")] - af5: Option, -} - -impl AfMap { - 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, - } - } -} - -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub(crate) struct PinConfig { - /// The GPIO pin number. - pub pin: usize, - pub kind: Vec, - // Pin => Input/OutputSignal - #[serde(default)] - pub alternate_functions: AfMap, -} - -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub(crate) struct IoMuxSignal { - pub name: String, - #[serde(default)] - pub id: Option, -} - -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub(crate) struct GpioPinsAndSignals { - pub pins: Vec, - pub input_signals: Vec, - pub output_signals: Vec, -} - /// A peripheral instance for which a driver is implemented. #[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] pub(crate) struct PeriInstance { @@ -134,9 +68,17 @@ pub(crate) struct PeriInstance { pub instance_config: I, } +/// A single cell in the peripheral support table. pub(crate) struct SupportItem { + /// The human-readable name of the driver in the table (leftmost cell.) pub name: &'static str, + /// The ID of the driver ([device.]) in the TOML, that this + /// item corresponds to. pub config_group: &'static str, + /// When the driver's configuration is not present in the device's TOML, + /// these symbols decide whether to generate a Not Available or a Not + /// Supported cell. If the device has one of these symbols, the support + /// status will be Not Supported (i.e. yet to be implemented). pub symbols: &'static [&'static str], } diff --git a/esp-metadata/src/cfg/gpio.rs b/esp-metadata/src/cfg/gpio.rs new file mode 100644 index 000000000..2330c4fcd --- /dev/null +++ b/esp-metadata/src/cfg/gpio.rs @@ -0,0 +1,380 @@ +//! 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::TokenStream; +use quote::format_ident; + +use crate::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, + + /// The list of peripheral input signals. + pub input_signals: Vec, + + /// The list of peripheral output signals. + pub output_signals: Vec, +} + +/// 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, + + /// Different capabilities implemented by this pin. + pub kind: Vec, + + /// Available alternate functions for this pin. + #[serde(default)] + pub alternate_functions: AfMap, +} + +/// Pin capabilities. Some of these will cause a trait to be implemented for the +/// given pin singleton. `UsbDevice` currently only changes what happens on GPIO +/// driver initialization. +#[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub(crate) enum PinCapability { + Input, + Output, + Analog, + Rtc, + Touch, + UsbDm, + UsbDp, + // Pin has USB pullup according to the IO MUX Function list + UsbDevice, +} + +/// 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].alternate_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 AfMap { + #[serde(rename = "0")] + af0: Option, + #[serde(rename = "1")] + af1: Option, + #[serde(rename = "2")] + af2: Option, + #[serde(rename = "3")] + af3: Option, + #[serde(rename = "4")] + af4: Option, + #[serde(rename = "5")] + af5: Option, +} + +impl AfMap { + /// 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, + } + } +} + +/// 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. +#[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, +} + +pub(crate) fn generate_gpios(gpio: &super::GpioProperties) -> TokenStream { + let pin_numbers = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| number(pin.pin)) + .collect::>(); + + let pin_peris = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| format_ident!("GPIO{}", pin.pin)) + .collect::>(); + + let pin_attrs = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| { + let mut attrs = vec![]; + pin.kind.iter().for_each(|kind| match kind { + PinCapability::Input => attrs.push(quote::quote! { Input }), + PinCapability::Output => attrs.push(quote::quote! { Output }), + PinCapability::Analog => attrs.push(quote::quote! { Analog }), + PinCapability::Rtc => { + attrs.push(quote::quote! { RtcIo }); + if pin.kind.contains(&PinCapability::Output) { + attrs.push(quote::quote! { RtcIoOutput }); + } + } + PinCapability::Touch => attrs.push(quote::quote! { Touch }), + PinCapability::UsbDm => attrs.push(quote::quote! { UsbDm }), + PinCapability::UsbDp => attrs.push(quote::quote! { UsbDp }), + PinCapability::UsbDevice => attrs.push(quote::quote! { UsbDevice }), + }); + + attrs + }) + .collect::>(); + + let pin_afs = gpio + .pins_and_signals + .pins + .iter() + .map(|pin| { + let mut input_afs = vec![]; + let mut output_afs = vec![]; + + for af in 0..6 { + let Some(signal) = pin.alternate_functions.get(af) else { + continue; + }; + + let af_variant = quote::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::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::quote! { #af_variant => #signal_tokens }); + found = true; + } + + assert!( + found, + "Signal '{signal}' not found in input signals for GPIO pin {}", + pin.pin + ); + } + + quote::quote! { + ( #(#input_afs)* ) ( #(#output_afs)* ) + } + }) + .collect::>(); + + 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 reg = format_ident!("GPIO{pin}"); + let accessor = format_ident!("gpio{pin}"); + + quote::quote! { #pin => transmute::<&'static io_mux::#reg, &'static io_mux::GPIO0>(iomux.#accessor()), } + }); + + quote::quote! { + pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { + use core::mem::transmute; + + use crate::{pac::io_mux, peripherals::IO_MUX}; + + let iomux = IO_MUX::regs(); + unsafe { + match gpio_num { + #(#iomux_pin_regs)* + other => panic!("GPIO {} does not exist", other), + } + } + } + + } + } else { + quote::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 io_type_macro_calls = vec![]; + for (pin, attrs) in pin_peris.iter().zip(pin_attrs.iter()) { + io_type_macro_calls.push(quote::quote! { + #( crate::io_type!(#attrs, #pin); )* + }) + } + + // Generates a macro that can select between a `then` and an `else` branch based + // on whether a pin implement a certain attribute. + // + // In essence this expands to (in case of pin = GPIO5, attr = Analog): + // `if typeof(GPIO5) == Analog { then_tokens } else { else_tokens }` + let if_pin_is_type = { + let mut branches = vec![]; + + for (pin, attr) in pin_peris.iter().zip(pin_attrs.iter()) { + branches.push(quote::quote! { + #( (#pin, #attr, $then_tt:tt else $else_tt:tt ) => { $then_tt }; )* + }); + + branches.push(quote::quote! { + (#pin, $t:tt, $then_tt:tt else $else_tt:tt ) => { $else_tt }; + }); + } + + quote::quote! { + macro_rules! if_pin_is_type { + #(#branches)* + } + + pub(crate) use if_pin_is_type; + } + }; + + // Delegates AnyPin functions to GPIOn functions when the pin implements a + // certain attribute. + // + // In essence this expands to (in case of attr = Analog): + // `if typeof(anypin's current value) == Analog { call $code } else { panic }` + let impl_for_pin_type = { + let mut impl_branches = vec![]; + for (gpionum, peri) in pin_numbers.iter().zip(pin_peris.iter()) { + impl_branches.push(quote::quote! { + #gpionum => $crate::peripherals::if_pin_is_type!(#peri, $on_type, {{ + #[allow(unused_unsafe, unused_mut)] + let mut $inner_ident = unsafe { $crate::peripherals::#peri::steal() }; + #[allow(unused_braces)] + $code + }} else { + $otherwise + }), + }); + } + + quote::quote! { + macro_rules! impl_for_pin_type { + ($any_pin:ident, $inner_ident:ident, $on_type:tt, $code:tt else $otherwise:tt) => { + match $any_pin.number() { + #(#impl_branches)* + _ => $otherwise, + } + }; + ($any_pin:ident, $inner_ident:ident, $on_type:tt, $code:tt) => { + impl_for_pin_type!($any_pin, $inner_ident, $on_type, $code else { panic!("Unsupported") }) + }; + } + pub(crate) use impl_for_pin_type; + } + }; + + quote::quote! { + crate::gpio! { + #( (#pin_numbers, #pin_peris #pin_afs) )* + } + + #( #io_type_macro_calls )* + + #if_pin_is_type + #impl_for_pin_type + + #io_mux_accessor + } +} + +pub(crate) fn generate_iomux_signals(gpio: &super::GpioProperties) -> TokenStream { + 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::quote! { + #input_signals + #output_signals + } +} + +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::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::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::quote! { + #name, + }); + } + + let enum_name = format_ident!("{enum_name}"); + + quote::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)* + } + } +} diff --git a/esp-metadata/src/cfg/i2c_master.rs b/esp-metadata/src/cfg/i2c_master.rs new file mode 100644 index 000000000..254e59e16 --- /dev/null +++ b/esp-metadata/src/cfg/i2c_master.rs @@ -0,0 +1,48 @@ +use proc_macro2::TokenStream; +use quote::format_ident; + +use crate::{cfg::I2cMasterProperties, generate_for_each_macro}; + +/// Instance configuration, used in [device.i2c_master.instances] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct I2cMasterInstanceConfig { + /// The name of the instance in the `esp_hal::system::Peripheral` enum + pub sys_instance: String, + + /// IOMUX signal name of the instane's SCL signal. + pub scl: String, + + /// IOMUX signal name of the instane's SDA signal. + pub sda: String, + + /// The name of the instance's interrupt handler. + pub interrupt: String, +} + +/// Generates `for_each_i2c_master!` which can be used to implement the I2C +/// master Instance trait for the relevant peripherals. The macro generates code +/// for each [device.i2c_master.instances[X]] instance. +pub(crate) fn generate_i2c_master_peripehrals(i2c: &I2cMasterProperties) -> TokenStream { + let i2c_master_instance_cfgs = i2c + .instances + .iter() + .map(|instance| { + let instance_config = &instance.instance_config; + + let instance = format_ident!("{}", instance.name.to_uppercase()); + + let sys = format_ident!("{}", instance_config.sys_instance); + let sda = format_ident!("{}", instance_config.sda); + let scl = format_ident!("{}", instance_config.scl); + let int = format_ident!("{}", instance_config.interrupt); + + // The order and meaning of these tokens must match their use in the + // `for_each_i2c_master!` call. + quote::quote! { + #instance, #sys, #scl, #sda, #int + } + }) + .collect::>(); + + generate_for_each_macro("i2c_master", &i2c_master_instance_cfgs) +} diff --git a/esp-metadata/src/lib.rs b/esp-metadata/src/lib.rs index 38e6feac3..145564bd0 100644 --- a/esp-metadata/src/lib.rs +++ b/esp-metadata/src/lib.rs @@ -10,7 +10,7 @@ use proc_macro2::TokenStream; use quote::format_ident; use strum::IntoEnumIterator; -use crate::cfg::{IoMuxSignal, SupportItem, SupportStatus, Value}; +use crate::cfg::{SupportItem, SupportStatus, Value}; macro_rules! include_toml { (Config, $file:expr) => {{ @@ -374,13 +374,11 @@ impl Config { self.generate_properties(out_dir, "_generated.rs"); self.generate_gpios(out_dir, "_generated_gpio.rs"); - self.generate_gpio_extras(out_dir, "_generated_gpio_extras.rs"); + self.generate_iomux_signals(out_dir, "_generated_iomux_signals.rs"); self.generate_peripherals(out_dir, "_generated_peris.rs"); } fn generate_properties(&self, out_dir: &Path, file_name: &str) { - let out_file = out_dir.join(file_name).to_string_lossy().to_string(); - let mut g = TokenStream::new(); let chip_name = self.name(); @@ -452,7 +450,7 @@ impl Config { } }); - save(&out_file, g); + save(out_dir.join(file_name), g); } fn generate_gpios(&self, out_dir: &Path, file_name: &str) { @@ -461,320 +459,31 @@ impl Config { return; }; - let out_file = out_dir.join(file_name).to_string_lossy().to_string(); + let tokens = cfg::generate_gpios(gpio); - let pin_numbers = gpio - .pins_and_signals - .pins - .iter() - .map(|pin| number(pin.pin)) - .collect::>(); - - let pin_peris = gpio - .pins_and_signals - .pins - .iter() - .map(|pin| format_ident!("GPIO{}", pin.pin)) - .collect::>(); - - let pin_attrs = gpio - .pins_and_signals - .pins - .iter() - .map(|pin| { - let mut attrs = vec![]; - pin.kind.iter().for_each(|kind| match kind { - cfg::PinCapability::Input => attrs.push(quote::quote! { Input }), - cfg::PinCapability::Output => attrs.push(quote::quote! { Output }), - cfg::PinCapability::Analog => attrs.push(quote::quote! { Analog }), - cfg::PinCapability::Rtc => { - attrs.push(quote::quote! { RtcIo }); - if pin.kind.contains(&cfg::PinCapability::Output) { - attrs.push(quote::quote! { RtcIoOutput }); - } - } - cfg::PinCapability::Touch => attrs.push(quote::quote! { Touch }), - cfg::PinCapability::UsbDm => attrs.push(quote::quote! { UsbDm }), - cfg::PinCapability::UsbDp => attrs.push(quote::quote! { UsbDp }), - cfg::PinCapability::UsbDevice => attrs.push(quote::quote! { UsbDevice }), - }); - - attrs - }) - .collect::>(); - - let pin_afs = gpio - .pins_and_signals - .pins - .iter() - .map(|pin| { - let mut input_afs = vec![]; - let mut output_afs = vec![]; - - for af in 0..6 { - let Some(signal) = pin.alternate_functions.get(af) else { - continue; - }; - - let af_variant = quote::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::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::quote! { #af_variant => #signal_tokens }); - found = true; - } - - assert!( - found, - "Signal '{signal}' not found in input signals for GPIO pin {}", - pin.pin - ); - } - - quote::quote! { - ( #(#input_afs)* ) ( #(#output_afs)* ) - } - }) - .collect::>(); - - 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 reg = format_ident!("GPIO{pin}"); - let accessor = format_ident!("gpio{pin}"); - - quote::quote! { #pin => transmute::<&'static io_mux::#reg, &'static io_mux::GPIO0>(iomux.#accessor()), } - }); - - quote::quote! { - pub(crate) fn io_mux_reg(gpio_num: u8) -> &'static crate::pac::io_mux::GPIO0 { - use core::mem::transmute; - - use crate::{pac::io_mux, peripherals::IO_MUX}; - - let iomux = IO_MUX::regs(); - unsafe { - match gpio_num { - #(#iomux_pin_regs)* - other => panic!("GPIO {} does not exist", other), - } - } - } - - } - } else { - quote::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 io_type_macro_calls = vec![]; - for (pin, attrs) in pin_peris.iter().zip(pin_attrs.iter()) { - io_type_macro_calls.push(quote::quote! { - #( crate::io_type!(#attrs, #pin); )* - }) - } - - // Generates a macro that can select between a `then` and an `else` branch based - // on whether a pin implement a certain attribute. - // - // In essence this expands to (in case of pin = GPIO5, attr = Analog): - // `if typeof(GPIO5) == Analog { then_tokens } else { else_tokens }` - let if_pin_is_type = { - let mut branches = vec![]; - - for (pin, attr) in pin_peris.iter().zip(pin_attrs.iter()) { - branches.push(quote::quote! { - #( (#pin, #attr, $then_tt:tt else $else_tt:tt ) => { $then_tt }; )* - }); - - branches.push(quote::quote! { - (#pin, $t:tt, $then_tt:tt else $else_tt:tt ) => { $else_tt }; - }); - } - - quote::quote! { - macro_rules! if_pin_is_type { - #(#branches)* - } - - pub(crate) use if_pin_is_type; - } - }; - - // Delegates AnyPin functions to GPIOn functions when the pin implements a - // certain attribute. - // - // In essence this expands to (in case of attr = Analog): - // `if typeof(anypin's current value) == Analog { call $code } else { panic }` - let impl_for_pin_type = { - let mut impl_branches = vec![]; - for (gpionum, peri) in pin_numbers.iter().zip(pin_peris.iter()) { - impl_branches.push(quote::quote! { - #gpionum => $crate::peripherals::if_pin_is_type!(#peri, $on_type, {{ - #[allow(unused_unsafe, unused_mut)] - let mut $inner_ident = unsafe { $crate::peripherals::#peri::steal() }; - #[allow(unused_braces)] - $code - }} else { - $otherwise - }), - }); - } - - quote::quote! { - macro_rules! impl_for_pin_type { - ($any_pin:ident, $inner_ident:ident, $on_type:tt, $code:tt else $otherwise:tt) => { - match $any_pin.number() { - #(#impl_branches)* - _ => $otherwise, - } - }; - ($any_pin:ident, $inner_ident:ident, $on_type:tt, $code:tt) => { - impl_for_pin_type!($any_pin, $inner_ident, $on_type, $code else { panic!("Unsupported") }) - }; - } - pub(crate) use impl_for_pin_type; - } - }; - - let g = quote::quote! { - crate::gpio! { - #( (#pin_numbers, #pin_peris #pin_afs) )* - } - - #( #io_type_macro_calls )* - - #if_pin_is_type - #impl_for_pin_type - - #io_mux_accessor - }; - - save(&out_file, g); + save(out_dir.join(file_name), tokens); } - // TODO temporary name, we likely don't want a new file for these - fn generate_gpio_extras(&self, out_dir: &Path, file_name: &str) { + fn generate_iomux_signals(&self, out_dir: &Path, file_name: &str) { let Some(gpio) = self.device.peri_config.gpio.as_ref() else { // No GPIOs defined, nothing to do. return; }; - let out_file = out_dir.join(file_name).to_string_lossy().to_string(); + let tokens = cfg::generate_iomux_signals(gpio); - let input_signals = render_signals("InputSignal", &gpio.pins_and_signals.input_signals); - let output_signals = render_signals("OutputSignal", &gpio.pins_and_signals.output_signals); - - let g = quote::quote! { - #input_signals - #output_signals - }; - - save(&out_file, g); + save(out_dir.join(file_name), tokens); } fn generate_peripherals(&self, out_dir: &Path, file_name: &str) { - let out_file = out_dir.join(file_name).to_string_lossy().to_string(); + let mut tokens = TokenStream::new(); - let i2c_master_instance_cfgs = self - .device - .peri_config - .i2c_master - .iter() - .flat_map(|peri| { - peri.instances.iter().map(|instance| { - let instance_config = &instance.instance_config; - - let instance = format_ident!("{}", instance.name.to_uppercase()); - - let sys = format_ident!("{}", instance_config.sys_instance); - let sda = format_ident!("{}", instance_config.sda); - let scl = format_ident!("{}", instance_config.scl); - let int = format_ident!("{}", instance_config.interrupt); - - // The order and meaning of these tokens must match their use in the - // `for_each_i2c_master!` call. - quote::quote! { - #instance, #sys, #scl, #sda, #int - } - }) - }) - .collect::>(); - - let for_each_i2c_master = generate_for_each_macro("i2c_master", &i2c_master_instance_cfgs); - - let g = quote::quote! { - #for_each_i2c_master + // TODO: repeat for all drivers that have Instance traits + if let Some(i2c) = self.device.peri_config.i2c_master.as_ref() { + tokens.extend(cfg::generate_i2c_master_peripehrals(i2c)); }; - save(&out_file, g); - } -} - -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::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::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::quote! { - #name, - }); - } - - let enum_name = format_ident!("{enum_name}"); - - quote::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)* - } + save(out_dir.join(file_name), tokens); } }