use std::str::FromStr;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use crate::{
cfg::{GenericProperty, Value},
generate_for_each_macro,
number,
};
#[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>,
}
impl GenericProperty for GpioPinsAndSignals {}
#[derive(Debug, Clone, Copy, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum PinLimitation {
Strapping,
SpiFlash,
OctalFlash,
SpiPsram,
OctalPsram,
Esp32PicoV3,
InputOnly,
BootloaderUart,
Jtag,
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.")
}
}
}
}
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub(crate) struct PinConfig {
pub pin: usize,
#[serde(default)]
pub functions: FunctionMap,
#[serde(default)]
pub analog: AnalogMap,
#[serde(default, alias = "rtc")]
pub lp: LowPowerMap,
#[serde(default)]
pub limitations: Vec<PinLimitation>,
}
impl PinConfig {
pub(crate) fn limitations(&self) -> Vec<PinLimitation> {
let mut limitations = self.limitations.clone();
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
}
}
#[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;
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 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;
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 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;
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 IoMuxSignal {
pub name: String,
#[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| {
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;
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;
}
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,
) {
fn split_signal_with_number(fragment: &str) -> Option<(&str, usize)> {
let Some(breakpoint) = fragment
.char_indices()
.filter_map(|(idx, c)| if c.is_alphabetic() { None } else { Some(idx) })
.next()
else {
return None;
};
let number: usize = fragment[breakpoint..].parse().ok()?;
Some((&fragment[..breakpoint], number))
}
let signal_name = TokenStream::from_str(signal).unwrap();
let full_signal = {
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! {
#for_each_gpio
#for_each_analog
#for_each_lp
#[macro_export]
#[cfg_attr(docsrs, doc(cfg(feature = "_device-selected")))]
macro_rules! define_io_mux_signals {
() => {
#input_signals
#output_signals
};
}
#[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() {
return quote! {};
}
let mut variants = vec![];
for signal in signals {
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 {
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 {
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)
};
}
}
}