mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 12:50:53 +00:00
Cleanup esp-metadata (#3686)
* Clean up esp-metadata a bit * Update esp-metadata/src/cfg/gpio.rs Co-authored-by: Juraj Sadel <jurajsadel@gmail.com> * Document public API, arrange structs better --------- Co-authored-by: Juraj Sadel <jurajsadel@gmail.com>
This commit is contained in:
parent
9d452a108f
commit
101de4eab8
@ -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 {
|
||||
(
|
||||
|
@ -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 )+ ) => {
|
||||
|
@ -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 )+ ) => {
|
||||
|
@ -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
|
||||
|
@ -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"));
|
||||
|
@ -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) => {
|
||||
|
@ -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) => {
|
||||
|
@ -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<Option<u32>> 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<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 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<PinCapability>,
|
||||
// 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<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct GpioPinsAndSignals {
|
||||
pub pins: Vec<PinConfig>,
|
||||
pub input_signals: Vec<IoMuxSignal>,
|
||||
pub output_signals: Vec<IoMuxSignal>,
|
||||
}
|
||||
|
||||
/// A peripheral instance for which a driver is implemented.
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct PeriInstance<I = EmptyInstanceConfig> {
|
||||
@ -134,9 +68,17 @@ pub(crate) struct PeriInstance<I = EmptyInstanceConfig> {
|
||||
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.<config_group>]) 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],
|
||||
}
|
||||
|
||||
|
380
esp-metadata/src/cfg/gpio.rs
Normal file
380
esp-metadata/src/cfg/gpio.rs
Normal file
@ -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<PinConfig>,
|
||||
|
||||
/// The list of peripheral input signals.
|
||||
pub input_signals: Vec<IoMuxSignal>,
|
||||
|
||||
/// The list of peripheral output signals.
|
||||
pub output_signals: Vec<IoMuxSignal>,
|
||||
}
|
||||
|
||||
/// 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<PinCapability>,
|
||||
|
||||
/// 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<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 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<usize>,
|
||||
}
|
||||
|
||||
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| {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<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 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)*
|
||||
}
|
||||
}
|
||||
}
|
48
esp-metadata/src/cfg/i2c_master.rs
Normal file
48
esp-metadata/src/cfg/i2c_master.rs
Normal file
@ -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::<Vec<_>>();
|
||||
|
||||
generate_for_each_macro("i2c_master", &i2c_master_instance_cfgs)
|
||||
}
|
@ -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::<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| {
|
||||
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::<Vec<_>>();
|
||||
|
||||
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::<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 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::<Vec<_>>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user