diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index c7de96c9d..b3b77445c 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `i2c::master::BusTimeout::Disabled` for ESP32-S2 (#3591) - The `const CHANNEL: u8` parameter of RMT channels can now be erased via `Channel::degrade()`. (#3505) - ESP32-C6: GPIO6 now implements `AnalogPin` (#3668) +- SPI master: Expose octal SPI-specific `with_sio` functions (#3702) ### Changed diff --git a/esp-hal/api-baseline/esp32.json.gz b/esp-hal/api-baseline/esp32.json.gz index 46a1d11ef..9a49f9bb1 100644 Binary files a/esp-hal/api-baseline/esp32.json.gz and b/esp-hal/api-baseline/esp32.json.gz differ diff --git a/esp-hal/api-baseline/esp32c2.json.gz b/esp-hal/api-baseline/esp32c2.json.gz index 7a4176078..4b4a13806 100644 Binary files a/esp-hal/api-baseline/esp32c2.json.gz and b/esp-hal/api-baseline/esp32c2.json.gz differ diff --git a/esp-hal/api-baseline/esp32c3.json.gz b/esp-hal/api-baseline/esp32c3.json.gz index 96ce45cc0..86997b901 100644 Binary files a/esp-hal/api-baseline/esp32c3.json.gz and b/esp-hal/api-baseline/esp32c3.json.gz differ diff --git a/esp-hal/api-baseline/esp32c6.json.gz b/esp-hal/api-baseline/esp32c6.json.gz index 2902183c6..7e0cf8c8f 100644 Binary files a/esp-hal/api-baseline/esp32c6.json.gz and b/esp-hal/api-baseline/esp32c6.json.gz differ diff --git a/esp-hal/api-baseline/esp32h2.json.gz b/esp-hal/api-baseline/esp32h2.json.gz index 3ec1fb685..2c18ee715 100644 Binary files a/esp-hal/api-baseline/esp32h2.json.gz and b/esp-hal/api-baseline/esp32h2.json.gz differ diff --git a/esp-hal/api-baseline/esp32s2.json.gz b/esp-hal/api-baseline/esp32s2.json.gz index 5a46e6a2b..bf7635204 100644 Binary files a/esp-hal/api-baseline/esp32s2.json.gz and b/esp-hal/api-baseline/esp32s2.json.gz differ diff --git a/esp-hal/api-baseline/esp32s3.json.gz b/esp-hal/api-baseline/esp32s3.json.gz index 4795b4f20..78956537c 100644 Binary files a/esp-hal/api-baseline/esp32s3.json.gz and b/esp-hal/api-baseline/esp32s3.json.gz differ diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index 1d830ecd0..222d3a07a 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -41,6 +41,7 @@ use core::marker::PhantomData; #[instability::unstable] pub use dma::*; use enumset::{EnumSet, EnumSetType}; +use procmacros::enable_doc_switch; #[cfg(place_spi_master_driver_in_ram)] use procmacros::ram; @@ -59,7 +60,7 @@ use crate::{ OutputConfig, OutputSignal, PinGuard, - interconnect::{PeripheralInput, PeripheralOutput}, + interconnect::{self, PeripheralInput, PeripheralOutput}, }, interrupt::InterruptHandler, pac::spi2::RegisterBlock, @@ -611,12 +612,20 @@ impl Config { #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] struct SpiPinGuard { - mosi_pin: PinGuard, sclk_pin: PinGuard, cs_pin: PinGuard, + sio0_pin: PinGuard, sio1_pin: PinGuard, sio2_pin: Option, sio3_pin: Option, + #[cfg(spi_master_has_octal)] + sio4_pin: Option, + #[cfg(spi_master_has_octal)] + sio5_pin: Option, + #[cfg(spi_master_has_octal)] + sio6_pin: Option, + #[cfg(spi_master_has_octal)] + sio7_pin: Option, } /// Configuration errors. @@ -679,42 +688,40 @@ impl<'d> Spi<'d, Blocking> { pub fn new(spi: impl Instance + 'd, config: Config) -> Result { let guard = PeripheralGuard::new(spi.info().peripheral); - let mosi_pin = PinGuard::new_unconnected(spi.info().mosi); - let sclk_pin = PinGuard::new_unconnected(spi.info().sclk); - let cs_pin = PinGuard::new_unconnected(spi.info().cs[0]); - let sio1_pin = PinGuard::new_unconnected(spi.info().sio1_output); - let sio2_pin = spi.info().sio2_output.map(PinGuard::new_unconnected); - let sio3_pin = spi.info().sio3_output.map(PinGuard::new_unconnected); - let mut this = Spi { - spi: spi.degrade(), _mode: PhantomData, guard, pins: SpiPinGuard { - mosi_pin, - sclk_pin, - cs_pin, - sio1_pin, - sio2_pin, - sio3_pin, + sclk_pin: PinGuard::new_unconnected(spi.info().sclk), + cs_pin: PinGuard::new_unconnected(spi.info().cs(0)), + sio0_pin: PinGuard::new_unconnected(spi.info().sio_output(0)), + sio1_pin: PinGuard::new_unconnected(spi.info().sio_output(1)), + sio2_pin: spi.info().opt_sio_output(2).map(PinGuard::new_unconnected), + sio3_pin: spi.info().opt_sio_output(3).map(PinGuard::new_unconnected), + #[cfg(spi_master_has_octal)] + sio4_pin: spi.info().opt_sio_output(4).map(PinGuard::new_unconnected), + #[cfg(spi_master_has_octal)] + sio5_pin: spi.info().opt_sio_output(5).map(PinGuard::new_unconnected), + #[cfg(spi_master_has_octal)] + sio6_pin: spi.info().opt_sio_output(6).map(PinGuard::new_unconnected), + #[cfg(spi_master_has_octal)] + sio7_pin: spi.info().opt_sio_output(7).map(PinGuard::new_unconnected), }, + spi: spi.degrade(), }; this.driver().init(); this.apply_config(&config)?; - let this = this - .with_sio0(NoPin) - .with_sio1(NoPin) - .with_sck(NoPin) - .with_cs(NoPin); + let this = this.with_sck(NoPin).with_cs(NoPin); - let is_qspi = this.driver().info.sio2_input.is_some(); - if is_qspi { - unwrap!(this.driver().info.sio2_input).connect_to(&NoPin); - unwrap!(this.driver().info.sio2_output).connect_to(&NoPin); - unwrap!(this.driver().info.sio3_input).connect_to(&NoPin); - unwrap!(this.driver().info.sio3_output).connect_to(&NoPin); + for sio in 0..8 { + if let Some(signal) = this.driver().info.opt_sio_input(sio) { + signal.connect_to(&NoPin); + } + if let Some(signal) = this.driver().info.opt_sio_output(sio) { + signal.connect_to(&NoPin); + } } Ok(this) @@ -722,7 +729,7 @@ impl<'d> Spi<'d, Blocking> { /// Converts the SPI instance into async mode. pub fn into_async(mut self) -> Spi<'d, Async> { - self.set_interrupt_handler(self.spi.handler()); + self.set_interrupt_handler(self.spi.info().async_handler); Spi { spi: self.spi, _mode: PhantomData, @@ -731,6 +738,7 @@ impl<'d> Spi<'d, Blocking> { } } + #[enable_doc_switch] /// Configures the SPI instance to use DMA with the specified channel. /// /// This method prepares the SPI instance for DMA transfers using SPI @@ -742,10 +750,9 @@ impl<'d> Spi<'d, Blocking> { /// # use esp_hal::spi::master::{Config, Spi}; /// # use esp_hal::dma::{DmaRxBuf, DmaTxBuf}; /// # use esp_hal::dma_buffers; - #[cfg_attr(any(esp32, esp32s2), doc = "let dma_channel = peripherals.DMA_SPI2;")] - #[cfg_attr( - not(any(esp32, esp32s2)), - doc = "let dma_channel = peripherals.DMA_CH0;" + #[doc_switch( + cfg(any(esp32, esp32s2)) => "let dma_channel = peripherals.DMA_SPI2;", + _ => "let dma_channel = peripherals.DMA_CH0;", )] /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = /// dma_buffers!(32000); @@ -776,13 +783,10 @@ impl<'d> Spi<'d, Blocking> { SpiDma::new(self.spi, self.pins, channel.degrade()) } - #[cfg_attr( - not(multi_core), - doc = "Registers an interrupt handler for the peripheral." - )] - #[cfg_attr( - multi_core, - doc = "Registers an interrupt handler for the peripheral on the current core." + #[enable_doc_switch] + #[doc_switch( + cfg(multi_core) => "Registers an interrupt handler for the peripheral on the current core.", + _ => "Registers an interrupt handler for the peripheral.", )] #[doc = ""] /// Note that this will replace any previously registered interrupt @@ -843,17 +847,28 @@ impl<'d> Spi<'d, Async> { } } +macro_rules! def_with_sio_pin { + ($fn:ident, $field:ident, $n:literal) => { + #[doc = concat!(" Assign the SIO", stringify!($n), " pin for the SPI instance.")] + #[doc = " "] + #[doc = " Enables both input and output functionality for the pin, and connects it"] + #[doc = concat!(" to the SIO", stringify!($n), " output and input signals.")] + #[instability::unstable] + pub fn $fn(mut self, sio: impl PeripheralOutput<'d>) -> Self { + self.pins.$field = Some(self.connect_sio_pin(sio.into(), $n)); + + self + } + }; +} + impl<'d, Dm> Spi<'d, Dm> where Dm: DriverMode, { - fn connect_sio_pin( - &self, - pin: impl PeripheralOutput<'d>, - in_signal: InputSignal, - out_signal: OutputSignal, - ) -> PinGuard { - let pin = pin.into(); + fn connect_sio_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard { + let in_signal = self.spi.info().sio_input(n); + let out_signal = self.spi.info().sio_output(n); pin.apply_input_config(&InputConfig::default()); pin.apply_output_config(&OutputConfig::default()); @@ -865,9 +880,17 @@ where pin.connect_with_guard(out_signal) } - fn connect_output_pin(&self, pin: impl PeripheralOutput<'d>, signal: OutputSignal) -> PinGuard { - let pin = pin.into(); + fn connect_sio_output_pin(&self, pin: interconnect::OutputSignal<'d>, n: usize) -> PinGuard { + let out_signal = self.spi.info().sio_output(n); + self.connect_output_pin(pin, out_signal) + } + + fn connect_output_pin( + &self, + pin: interconnect::OutputSignal<'d>, + signal: OutputSignal, + ) -> PinGuard { pin.apply_output_config(&OutputConfig::default()); pin.set_output_enable(true); // TODO turn this bool into a Yes/No/PeripheralControl trio @@ -881,7 +904,7 @@ where /// /// Disconnects the previous pin that was assigned with `with_sck`. pub fn with_sck(mut self, sclk: impl PeripheralOutput<'d>) -> Self { - self.pins.sclk_pin = self.connect_output_pin(sclk, self.driver().info.sclk); + self.pins.sclk_pin = self.connect_output_pin(sclk.into(), self.driver().info.sclk); self } @@ -895,7 +918,7 @@ where /// Disconnects the previous pin that was assigned with `with_mosi` or /// `with_sio0`. pub fn with_mosi(mut self, mosi: impl PeripheralOutput<'d>) -> Self { - self.pins.mosi_pin = self.connect_output_pin(mosi, self.driver().info.mosi); + self.pins.sio0_pin = self.connect_sio_output_pin(mosi.into(), 0); self } @@ -913,7 +936,7 @@ where miso.apply_input_config(&InputConfig::default()); miso.set_input_enable(true); - self.driver().info.miso.connect_to(&miso); + self.driver().info.sio_input(1).connect_to(&miso); self } @@ -921,20 +944,18 @@ where /// Assign the SIO0 pin for the SPI instance. /// /// Enables both input and output functionality for the pin, and connects it - /// to the MOSI signal and SIO0 input signal. + /// to the MOSI output signal and SIO0 input signal. /// /// Disconnects the previous pin that was assigned with `with_sio0` or /// `with_mosi`. /// /// Use this if any of the devices on the bus use half-duplex SPI. /// - /// The pin is configured to open-drain mode. - /// - /// Note: You do not need to call [Self::with_mosi] when this is used. + /// See also [Self::with_mosi] when you only need a one-directional MOSI + /// signal. #[instability::unstable] pub fn with_sio0(mut self, mosi: impl PeripheralOutput<'d>) -> Self { - self.pins.mosi_pin = - self.connect_sio_pin(mosi, self.driver().info.sio0_input, self.driver().info.mosi); + self.pins.sio0_pin = self.connect_sio_pin(mosi.into(), 0); self } @@ -942,55 +963,35 @@ where /// Assign the SIO1/MISO pin for the SPI instance. /// /// Enables both input and output functionality for the pin, and connects it - /// to the MISO signal and SIO1 input signal. + /// to the MISO input signal and SIO1 output signal. /// /// Disconnects the previous pin that was assigned with `with_sio1`. /// /// Use this if any of the devices on the bus use half-duplex SPI. /// - /// The pin is configured to open-drain mode. - /// - /// Note: You do not need to call [Self::with_miso] when this is used. + /// See also [Self::with_miso] when you only need a one-directional MISO + /// signal. #[instability::unstable] pub fn with_sio1(mut self, sio1: impl PeripheralOutput<'d>) -> Self { - self.pins.sio1_pin = self.connect_sio_pin( - sio1, - self.driver().info.miso, - self.driver().info.sio1_output, - ); + self.pins.sio1_pin = self.connect_sio_pin(sio1.into(), 1); self } - /// Assign the SIO2 pin for the SPI instance. - /// - /// Enables both input and output functionality for the pin, and connects it - /// to the SIO2 output and input signals. - #[instability::unstable] - pub fn with_sio2(mut self, sio2: impl PeripheralOutput<'d>) -> Self { - self.pins.sio2_pin = Some(self.connect_sio_pin( - sio2, - unwrap!(self.driver().info.sio2_input), - unwrap!(self.driver().info.sio2_output), - )); + def_with_sio_pin!(with_sio2, sio2_pin, 2); + def_with_sio_pin!(with_sio3, sio3_pin, 3); - self - } + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio4, sio4_pin, 4); - /// Assign the SIO3 pin for the SPI instance. - /// - /// Enables both input and output functionality for the pin, and connects it - /// to the SIO3 output and input signals. - #[instability::unstable] - pub fn with_sio3(mut self, sio3: impl PeripheralOutput<'d>) -> Self { - self.pins.sio3_pin = Some(self.connect_sio_pin( - sio3, - unwrap!(self.driver().info.sio3_input), - unwrap!(self.driver().info.sio3_output), - )); + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio5, sio5_pin, 5); - self - } + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio6, sio6_pin, 6); + + #[cfg(spi_master_has_octal)] + def_with_sio_pin!(with_sio7, sio7_pin, 7); /// Assign the CS (Chip Select) pin for the SPI instance. /// @@ -1005,17 +1006,20 @@ where /// mechanism to select which CS line to use. #[instability::unstable] pub fn with_cs(mut self, cs: impl PeripheralOutput<'d>) -> Self { - self.pins.cs_pin = self.connect_output_pin(cs, self.driver().info.cs[0]); + self.pins.cs_pin = self.connect_output_pin(cs.into(), self.driver().info.cs(0)); self } + #[procmacros::enable_doc_switch] /// Change the bus configuration. /// /// # Errors /// /// If frequency passed in config exceeds - #[cfg_attr(not(esp32h2), doc = " 80MHz")] - #[cfg_attr(esp32h2, doc = " 48MHz")] + #[doc_switch( + cfg(esp32h2) => " 48MHz", + _ => " 80MHz", + )] /// or is below 70kHz, /// [`ConfigError::UnsupportedFrequency`] error will be returned. pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { @@ -1185,12 +1189,15 @@ mod dma { sync::atomic::{Ordering, fence}, }; + use procmacros::enable_doc_switch; + use super::*; use crate::{ dma::{Channel, DmaRxBuf, DmaTxBuf, EmptyBuf, PeripheralDmaChannel, asynch::DmaRxFuture}, spi::master::dma::asynch::DropGuard, }; + #[enable_doc_switch] /// A DMA capable SPI instance. /// /// Using `SpiDma` is not recommended unless you wish @@ -1204,10 +1211,9 @@ mod dma { /// # use esp_hal::spi::master::{Config, Spi}; /// # use esp_hal::dma::{DmaRxBuf, DmaTxBuf}; /// # use esp_hal::dma_buffers; - #[cfg_attr(any(esp32, esp32s2), doc = "let dma_channel = peripherals.DMA_SPI2;")] - #[cfg_attr( - not(any(esp32, esp32s2)), - doc = "let dma_channel = peripherals.DMA_CH0;" + #[doc_switch( + cfg(any(esp32, esp32s2)) => "let dma_channel = peripherals.DMA_SPI2;", + _ => "let dma_channel = peripherals.DMA_CH0;", )] /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = /// dma_buffers!(32000); @@ -1254,7 +1260,7 @@ mod dma { /// Converts the SPI instance into async mode. #[instability::unstable] pub fn into_async(mut self) -> SpiDma<'d, Async> { - self.set_interrupt_handler(self.spi.handler()); + self.set_interrupt_handler(self.spi.info().async_handler); SpiDma { spi: self.spi, channel: self.channel.into_async(), @@ -2649,16 +2655,30 @@ mod ehal1 { } /// SPI peripheral instance. -#[doc(hidden)] -#[allow(private_bounds)] -pub trait PeripheralInstance: private::Sealed + DmaEligible { - /// Returns the peripheral data describing this SPI instance. - fn info(&self) -> &'static Info; +#[cfg_attr(not(feature = "unstable"), expect(private_bounds))] // DmaEligible +pub trait Instance: private::Sealed + IntoAnySpi + DmaEligible { + #[doc(hidden)] + /// Returns the peripheral data and state describing this instance. + fn parts(&self) -> (&'static Info, &'static State); + + /// Returns the peripheral data describing this instance. + #[doc(hidden)] + #[inline(always)] + fn info(&self) -> &'static Info { + self.parts().0 + } + + /// Returns the peripheral state for this instance. + #[doc(hidden)] + #[inline(always)] + fn state(&self) -> &'static State { + self.parts().1 + } } /// Marker trait for QSPI-capable SPI peripherals. #[doc(hidden)] -pub trait QspiInstance: PeripheralInstance {} +pub trait QspiInstance: Instance {} /// Peripheral data describing a particular SPI instance. #[doc(hidden)] @@ -2672,67 +2692,39 @@ pub struct Info { /// The system peripheral marker. pub peripheral: crate::system::Peripheral, + /// Interrupt handler for the asynchronous operations. + pub async_handler: InterruptHandler, + /// SCLK signal. pub sclk: OutputSignal, - /// MOSI signal. - pub mosi: OutputSignal, - - /// MISO signal. - pub miso: InputSignal, - /// Chip select signals. pub cs: &'static [OutputSignal], - /// SIO0 (MOSI) input signal for half-duplex mode. - pub sio0_input: InputSignal, + pub sio_inputs: &'static [InputSignal], + pub sio_outputs: &'static [OutputSignal], +} - /// SIO1 (MISO) output signal for half-duplex mode. - pub sio1_output: OutputSignal, +impl Info { + fn cs(&self, n: usize) -> OutputSignal { + *unwrap!(self.cs.get(n), "CS{} is not defined", n) + } - /// SIO2 output signal for QSPI mode. - pub sio2_output: Option, + fn opt_sio_input(&self, n: usize) -> Option { + self.sio_inputs.get(n).copied() + } - /// SIO2 input signal for QSPI mode. - pub sio2_input: Option, + fn opt_sio_output(&self, n: usize) -> Option { + self.sio_outputs.get(n).copied() + } - /// SIO3 output signal for QSPI mode. - pub sio3_output: Option, + fn sio_input(&self, n: usize) -> InputSignal { + unwrap!(self.opt_sio_input(n), "SIO{} is not defined", n) + } - /// SIO3 input signal for QSPI mode. - pub sio3_input: Option, - - /// SIO4 output signal for OPI mode. - #[cfg(spi_octal)] - pub sio4_output: Option, - - /// SIO4 input signal for OPI mode. - #[cfg(spi_octal)] - pub sio4_input: Option, - - /// SIO5 output signal for OPI mode. - #[cfg(spi_octal)] - pub sio5_output: Option, - - /// SIO5 input signal for OPI mode. - #[cfg(spi_octal)] - pub sio5_input: Option, - - /// SIO6 output signal for OPI mode. - #[cfg(spi_octal)] - pub sio6_output: Option, - - /// SIO6 input signal for OPI mode. - #[cfg(spi_octal)] - pub sio6_input: Option, - - /// SIO7 output signal for OPI mode. - #[cfg(spi_octal)] - pub sio7_output: Option, - - /// SIO7 input signal for OPI mode. - #[cfg(spi_octal)] - pub sio7_input: Option, + fn sio_output(&self, n: usize) -> OutputSignal { + unwrap!(self.opt_sio_output(n), "SIO{} is not defined", n) + } } struct DmaDriver { @@ -3694,89 +3686,46 @@ impl PartialEq for Info { unsafe impl Sync for Info {} -macro_rules! spi_instance { - ($num:literal, $sclk:ident, $mosi:ident, $miso:ident, [$($cs:ident),+] $(, $sio2:ident, $sio3:ident $(, $sio4:ident, $sio5:ident, $sio6:ident, $sio7:ident)?)?) => { - paste::paste! { - impl PeripheralInstance for crate::peripherals::[]<'_> { - #[inline(always)] - fn info(&self) -> &'static Info { - static INFO: Info = Info { - register_block: crate::peripherals::[]::regs(), - peripheral: crate::system::Peripheral::[], - sclk: OutputSignal::$sclk, - mosi: OutputSignal::$mosi, - miso: InputSignal::$miso, - cs: &[$(OutputSignal::$cs),+], - sio0_input: InputSignal::$mosi, - sio1_output: OutputSignal::$miso, - sio2_output: $crate::if_set!($(Some(OutputSignal::$sio2))?, None), - sio2_input: $crate::if_set!($(Some(InputSignal::$sio2))?, None), - sio3_output: $crate::if_set!($(Some(OutputSignal::$sio3))?, None), - sio3_input: $crate::if_set!($(Some(InputSignal::$sio3))?, None), - #[cfg(spi_octal)] - sio4_output: $crate::if_set!($($(Some(OutputSignal::$sio4))?)?, None), - #[cfg(spi_octal)] - sio4_input: $crate::if_set!($($(Some(InputSignal::$sio4))?)?, None), - #[cfg(spi_octal)] - sio5_output: $crate::if_set!($($(Some(OutputSignal::$sio5))?)?, None), - #[cfg(spi_octal)] - sio5_input: $crate::if_set!($($(Some(InputSignal::$sio5))?)?, None), - #[cfg(spi_octal)] - sio6_output: $crate::if_set!($($(Some(OutputSignal::$sio6))?)?, None), - #[cfg(spi_octal)] - sio6_input: $crate::if_set!($($(Some(InputSignal::$sio6))?)?, None), - #[cfg(spi_octal)] - sio7_output: $crate::if_set!($($(Some(OutputSignal::$sio7))?)?, None), - #[cfg(spi_octal)] - sio7_input: $crate::if_set!($($(Some(InputSignal::$sio7))?)?, None), - }; - - &INFO +crate::peripherals::for_each_spi_master! { + ($peri:ident, $sys:ident, $sclk:ident [$($cs:ident),+] [$($sio:ident),*] $(, $is_qspi:tt)?) => { + impl Instance for crate::peripherals::$peri<'_> { + #[inline(always)] + fn parts(&self) -> (&'static Info, &'static State) { + #[crate::handler] + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + fn irq_handler() { + handle_async(&INFO, &STATE) } + + static INFO: Info = Info { + register_block: crate::peripherals::$peri::regs(), + peripheral: crate::system::Peripheral::$sys, + async_handler: irq_handler, + sclk: OutputSignal::$sclk, + cs: &[$(OutputSignal::$cs),+], + sio_inputs: &[$(InputSignal::$sio),*], + sio_outputs: &[$(OutputSignal::$sio),*], + }; + + static STATE: State = State { + waker: AtomicWaker::new(), + #[cfg(esp32)] + esp32_hack: Esp32Hack { + timing_miso_delay: Cell::new(None), + extra_dummy: Cell::new(0), + }, + }; + + (&INFO, &STATE) } - - $( - // If the extra pins are set, implement QspiInstance - $crate::ignore!($sio2); - impl QspiInstance for crate::peripherals::[]<'_> {} - )? } - } -} -#[cfg(spi_master_spi2)] -cfg_if::cfg_if! { - if #[cfg(esp32)] { - spi_instance!(2, HSPICLK, HSPID, HSPIQ, [HSPICS0, HSPICS1, HSPICS2], HSPIWP, HSPIHD); - } else if #[cfg(any(esp32s2, esp32s3))] { - spi_instance!(2, FSPICLK, FSPID, FSPIQ, [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5], FSPIWP, FSPIHD, FSPIIO4, FSPIIO5, FSPIIO6, FSPIIO7); - } else { - spi_instance!(2, FSPICLK, FSPID, FSPIQ, [FSPICS0, FSPICS1, FSPICS2, FSPICS3, FSPICS4, FSPICS5], FSPIWP, FSPIHD); - } -} - -#[cfg(spi_master_spi3)] -cfg_if::cfg_if! { - if #[cfg(esp32)] { - spi_instance!(3, VSPICLK, VSPID, VSPIQ, [VSPICS0, VSPICS1, VSPICS2], VSPIWP, VSPIHD); - } else if #[cfg(esp32s3)] { - spi_instance!(3, SPI3_CLK, SPI3_D, SPI3_Q, [SPI3_CS0, SPI3_CS1, SPI3_CS2], SPI3_WP, SPI3_HD); - } else { - spi_instance!(3, SPI3_CLK, SPI3_D, SPI3_Q, [SPI3_CS0, SPI3_CS1, SPI3_CS2]); - } -} - -impl PeripheralInstance for AnySpi<'_> { - delegate::delegate! { - to match &self.0 { - #[cfg(spi_master_spi2)] - AnySpiInner::Spi2(spi) => spi, - #[cfg(spi_master_spi3)] - AnySpiInner::Spi3(spi) => spi, - } { - fn info(&self) -> &'static Info; - } - } + $( + // If the extra pins are set, implement QspiInstance + $crate::ignore!($is_qspi); + impl QspiInstance for crate::peripherals::$peri<'_> {} + )? + }; } impl QspiInstance for AnySpi<'_> {} @@ -3799,10 +3748,7 @@ struct Esp32Hack { unsafe impl Sync for Esp32Hack {} #[cfg_attr(place_spi_master_driver_in_ram, ram)] -fn handle_async(instance: impl Instance) { - let state = instance.state(); - let info = instance.info(); - +fn handle_async(info: &'static Info, state: &'static State) { let driver = Driver { info, state }; if driver.interrupts().contains(SpiInterrupt::TransferDone) { driver.enable_listen(SpiInterrupt::TransferDone.into(), false); @@ -3810,43 +3756,6 @@ fn handle_async(instance: impl Instance) { } } -/// A peripheral singleton compatible with the SPI master driver. -pub trait Instance: PeripheralInstance + IntoAnySpi { - #[doc(hidden)] - fn state(&self) -> &'static State; - #[doc(hidden)] - fn handler(&self) -> InterruptHandler; -} - -macro_rules! master_instance { - ($peri:ident) => { - impl Instance for $crate::peripherals::$peri<'_> { - fn state(&self) -> &'static State { - static STATE: State = State { - waker: AtomicWaker::new(), - #[cfg(esp32)] - esp32_hack: Esp32Hack { - timing_miso_delay: Cell::new(None), - extra_dummy: Cell::new(0), - }, - }; - - &STATE - } - - fn handler(&self) -> InterruptHandler { - #[$crate::handler] - #[cfg_attr(place_spi_master_driver_in_ram, ram)] - fn handle() { - handle_async(unsafe { $crate::peripherals::$peri::steal() }) - } - - handle - } - } - }; -} - /// SPI data mode #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -3860,7 +3769,7 @@ pub enum DataMode { Dual, /// 4 bit, 4 data lines. (SIO0 .. SIO3) Quad, - #[cfg(spi_octal)] + #[cfg(spi_master_has_octal)] /// 8 bit, 8 data lines. (SIO0 .. SIO7) Octal, } @@ -3891,11 +3800,6 @@ impl<'d> DmaEligible for AnySpi<'d> { } } -#[cfg(spi_master_spi2)] -master_instance!(SPI2); -#[cfg(spi_master_spi3)] -master_instance!(SPI3); - impl Instance for AnySpi<'_> { delegate::delegate! { to match &self.0 { @@ -3904,8 +3808,7 @@ impl Instance for AnySpi<'_> { #[cfg(spi_master_spi3)] AnySpiInner::Spi3(spi) => spi, } { - fn state(&self) -> &'static State; - fn handler(&self) -> InterruptHandler; + fn parts(&self) -> (&'static Info, &'static State); } } } diff --git a/esp-hal/src/spi/slave.rs b/esp-hal/src/spi/slave.rs index 79f678dc2..c87288be1 100644 --- a/esp-hal/src/spi/slave.rs +++ b/esp-hal/src/spi/slave.rs @@ -787,23 +787,21 @@ impl PartialEq for Info { unsafe impl Sync for Info {} -macro_rules! spi_instance { - ($num:literal, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident) => { - paste::paste! { - impl Instance for crate::peripherals::[]<'_> { - #[inline(always)] - fn info(&self) -> &'static Info { - static INFO: Info = Info { - register_block: crate::peripherals::[]::regs(), - peripheral: crate::system::Peripheral::[], - sclk: InputSignal::$sclk, - mosi: InputSignal::$mosi, - miso: OutputSignal::$miso, - cs: InputSignal::$cs, - }; +crate::peripherals::for_each_spi_slave! { + ($peri:ident, $sys:ident, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident) => { + impl Instance for crate::peripherals::$peri<'_> { + #[inline(always)] + fn info(&self) -> &'static Info { + static INFO: Info = Info { + register_block: crate::peripherals::$peri::regs(), + peripheral: crate::system::Peripheral::$sys, + sclk: InputSignal::$sclk, + mosi: InputSignal::$mosi, + miso: OutputSignal::$miso, + cs: InputSignal::$cs, + }; - &INFO - } + &INFO } } }; @@ -834,21 +832,6 @@ impl<'d> DmaEligible for AnySpi<'d> { } } } - -cfg_if::cfg_if! { - if #[cfg(esp32)] { - #[cfg(spi_master_spi2)] - spi_instance!(2, HSPICLK, HSPID, HSPIQ, HSPICS0); - #[cfg(spi_master_spi3)] - spi_instance!(3, VSPICLK, VSPID, VSPIQ, VSPICS0); - } else { - #[cfg(spi_master_spi2)] - spi_instance!(2, FSPICLK, FSPID, FSPIQ, FSPICS0); - #[cfg(spi_master_spi3)] - spi_instance!(3, SPI3_CLK, SPI3_D, SPI3_Q, SPI3_CS0); - } -} - impl Instance for AnySpi<'_> { delegate::delegate! { to match &self.0 { diff --git a/esp-metadata/devices/esp32.toml b/esp-metadata/devices/esp32.toml index e279a15ad..4056ce6eb 100644 --- a/esp-metadata/devices/esp32.toml +++ b/esp-metadata/devices/esp32.toml @@ -575,7 +575,16 @@ channel_ram_size = 64 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }, { name = "spi3" }] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "HSPICLK", sio = ["HSPID", "HSPIQ", "HSPIWP", "HSPIHD"], cs = ["HSPICS0", "HSPICS1", "HSPICS2"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "VSPICLK", sio = ["VSPID", "VSPIQ", "VSPIWP", "VSPIHD"], cs = ["VSPICS0", "VSPICS1", "VSPICS2"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "HSPICLK", mosi = "HSPID", miso = "HSPIQ", cs = "HSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "VSPICLK", mosi = "VSPID", miso = "VSPIQ", cs = "VSPICS0" }, +] [device.timergroup] timg_has_timer1 = true @@ -611,7 +620,6 @@ support_status = "not_supported" [device.pcnt] [device.sd_host] [device.sd_slave] -[device.spi_slave] [device.touch] [device.twai] diff --git a/esp-metadata/devices/esp32c2.toml b/esp-metadata/devices/esp32c2.toml index cdce5686f..97e84056f 100644 --- a/esp-metadata/devices/esp32c2.toml +++ b/esp-metadata/devices/esp32c2.toml @@ -215,7 +215,14 @@ status_registers = 2 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] [device.timergroup] instances = [{ name = "timg0" }] @@ -237,7 +244,6 @@ instances = [ ## Interfaces [device.ledc] -[device.spi_slave] ## Miscellaneous [device.assist_debug] diff --git a/esp-metadata/devices/esp32c3.toml b/esp-metadata/devices/esp32c3.toml index 00e4bd05e..c1775cd8f 100644 --- a/esp-metadata/devices/esp32c3.toml +++ b/esp-metadata/devices/esp32c3.toml @@ -269,7 +269,14 @@ channel_ram_size = 48 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] [device.timergroup] instances = [{ name = "timg0" }, { name = "timg1" }] @@ -296,7 +303,6 @@ support_status = "not_supported" ## Interfaces [device.i2s] [device.ledc] -[device.spi_slave] [device.twai] [device.usb_serial_jtag] diff --git a/esp-metadata/devices/esp32c6.toml b/esp-metadata/devices/esp32c6.toml index a8156d281..374b9f473 100644 --- a/esp-metadata/devices/esp32c6.toml +++ b/esp-metadata/devices/esp32c6.toml @@ -394,7 +394,14 @@ channel_ram_size = 48 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] [device.timergroup] instances = [{ name = "timg0" }, { name = "timg1" }] @@ -429,7 +436,6 @@ has_wifi6 = true [device.mcpwm] [device.parl_io] [device.pcnt] -[device.spi_slave] [device.sd_slave] [device.twai] [device.usb_serial_jtag] diff --git a/esp-metadata/devices/esp32h2.toml b/esp-metadata/devices/esp32h2.toml index 6216e628a..55650114e 100644 --- a/esp-metadata/devices/esp32h2.toml +++ b/esp-metadata/devices/esp32h2.toml @@ -339,7 +339,14 @@ channel_ram_size = 48 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, +] [device.timergroup] instances = [{ name = "timg0" }, { name = "timg1" }] @@ -370,7 +377,6 @@ support_status = "not_supported" [device.mcpwm] [device.parl_io] [device.pcnt] -[device.spi_slave] [device.twai] [device.usb_serial_jtag] diff --git a/esp-metadata/devices/esp32s2.toml b/esp-metadata/devices/esp32s2.toml index efbebdc70..83f9a5145 100644 --- a/esp-metadata/devices/esp32s2.toml +++ b/esp-metadata/devices/esp32s2.toml @@ -69,7 +69,6 @@ symbols = [ "psram", "psram_dma", "ulp_riscv_core", - "spi_octal", # ROM capabilities "rom_crc_le", @@ -360,7 +359,17 @@ channel_ram_size = 64 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }, { name = "spi3" }] +has_octal = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD", "FSPIIO4", "FSPIIO5", "FSPIIO6", "FSPIIO7"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", sio = ["SPI3_D", "SPI3_Q"], cs = ["SPI3_CS0", "SPI3_CS1", "SPI3_CS2"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", mosi = "SPI3_D", miso = "SPI3_Q", cs = "SPI3_CS0" }, +] [device.timergroup] timg_has_timer1 = true @@ -397,7 +406,6 @@ support_status = "not_supported" ## Interfaces [device.i2s] [device.ledc] -[device.spi_slave] [device.pcnt] [device.twai] [device.usb_otg] diff --git a/esp-metadata/devices/esp32s3.toml b/esp-metadata/devices/esp32s3.toml index 1b91c470f..83764bae8 100644 --- a/esp-metadata/devices/esp32s3.toml +++ b/esp-metadata/devices/esp32s3.toml @@ -82,7 +82,6 @@ symbols = [ "psram_dma", "octal_psram", "ulp_riscv_core", - "spi_octal", # ROM capabilities "rom_crc_le", @@ -512,7 +511,17 @@ channel_ram_size = 48 [device.spi_master] support_status = "supported" -instances = [{ name = "spi2" }, { name = "spi3" }] +has_octal = true +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", sio = ["FSPID", "FSPIQ", "FSPIWP", "FSPIHD", "FSPIIO4", "FSPIIO5", "FSPIIO6", "FSPIIO7"], cs = ["FSPICS0", "FSPICS1", "FSPICS2", "FSPICS3", "FSPICS4", "FSPICS5"] }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", sio = ["SPI3_D", "SPI3_Q", "SPI3_WP", "SPI3_HD"], cs = ["SPI3_CS0", "SPI3_CS1", "SPI3_CS2"] }, +] + +[device.spi_slave] +instances = [ + { name = "spi2", sys_instance = "Spi2", sclk = "FSPICLK", mosi = "FSPID", miso = "FSPIQ", cs = "FSPICS0" }, + { name = "spi3", sys_instance = "Spi3", sclk = "SPI3_CLK", mosi = "SPI3_D", miso = "SPI3_Q", cs = "SPI3_CS0" }, +] [device.timergroup] timg_has_timer1 = true @@ -549,7 +558,6 @@ support_status = "not_supported" [device.mcpwm] [device.pcnt] [device.sd_host] -[device.spi_slave] [device.twai] [device.usb_otg] [device.usb_serial_jtag] diff --git a/esp-metadata/src/cfg.rs b/esp-metadata/src/cfg.rs index 59f6a0559..2a3eeeea5 100644 --- a/esp-metadata/src/cfg.rs +++ b/esp-metadata/src/cfg.rs @@ -1,9 +1,13 @@ pub(crate) mod gpio; pub(crate) mod i2c_master; +pub(crate) mod spi_master; +pub(crate) mod spi_slave; pub(crate) mod uart; pub(crate) use gpio::*; pub(crate) use i2c_master::*; +pub(crate) use spi_master::*; +pub(crate) use spi_slave::*; pub(crate) use uart::*; /// Represents a value in the driver configuration. @@ -448,13 +452,16 @@ driver_configs![ peripherals: &["sha"], properties: {} }, - SpiMasterProperties { + SpiMasterProperties { driver: spi_master, name: "SPI master", peripherals: &["spi2", "spi3"], - properties: {} + properties: { + #[serde(default)] + has_octal: bool, + } }, - SpiSlaveProperties { + SpiSlaveProperties { driver: spi_slave, name: "SPI slave", peripherals: &["spi2", "spi3"], diff --git a/esp-metadata/src/cfg/spi_master.rs b/esp-metadata/src/cfg/spi_master.rs new file mode 100644 index 000000000..7403a67c4 --- /dev/null +++ b/esp-metadata/src/cfg/spi_master.rs @@ -0,0 +1,49 @@ +use proc_macro2::TokenStream; +use quote::format_ident; + +use crate::{cfg::SpiMasterProperties, generate_for_each_macro}; + +/// Instance configuration, used in [device.spi_slave.instances] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct SpiMasterInstanceConfig { + /// The name of the instance in the `esp_hal::system::Peripheral` enum + pub sys_instance: String, + // IOMUX signal names of the instance's signals: + pub sclk: String, + pub sio: Vec, + pub cs: Vec, +} + +/// Generates `for_each_spi_slave!` which can be used to implement the SPI +/// master Instance trait for the relevant peripherals. The macro generates code +/// for each [device.spi_master.instances[X]] instance. +pub(crate) fn generate_spi_master_peripherals(spi_slave: &SpiMasterProperties) -> TokenStream { + let instance_cfgs = spi_slave + .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 sclk = format_ident!("{}", instance_config.sclk); + let cs = instance_config.cs.iter().map(|cs| format_ident!("{cs}")); + let sio = instance_config.sio.iter().map(|cs| format_ident!("{cs}")); + + let is_qspi = if instance_config.sio.len() > 2 { + quote::quote! { , true } + } else { + quote::quote! {} + }; + + // The order and meaning of these tokens must match their use in the + // `for_each_i2c_master!` call. + quote::quote! { + #instance, #sys, #sclk [#(#cs),*] [#(#sio),*] #is_qspi + } + }) + .collect::>(); + + generate_for_each_macro("spi_master", &instance_cfgs) +} diff --git a/esp-metadata/src/cfg/spi_slave.rs b/esp-metadata/src/cfg/spi_slave.rs new file mode 100644 index 000000000..6c2b646af --- /dev/null +++ b/esp-metadata/src/cfg/spi_slave.rs @@ -0,0 +1,45 @@ +use proc_macro2::TokenStream; +use quote::format_ident; + +use crate::{cfg::SpiSlaveProperties, generate_for_each_macro}; + +/// Instance configuration, used in [device.spi_slave.instances] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] +pub(crate) struct SpiSlaveInstanceConfig { + /// The name of the instance in the `esp_hal::system::Peripheral` enum + pub sys_instance: String, + // IOMUX signal names of the instance's signals: + pub sclk: String, + pub mosi: String, + pub miso: String, + pub cs: String, +} + +/// Generates `for_each_spi_slave!` which can be used to implement the SPI +/// slave Instance trait for the relevant peripherals. The macro generates code +/// for each [device.spi_slave.instances[X]] instance. +pub(crate) fn generate_spi_slave_peripherals(spi_slave: &SpiSlaveProperties) -> TokenStream { + let instance_cfgs = spi_slave + .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 sclk = format_ident!("{}", instance_config.sclk); + let mosi = format_ident!("{}", instance_config.mosi); + let miso = format_ident!("{}", instance_config.miso); + let cs = format_ident!("{}", instance_config.cs); + + // The order and meaning of these tokens must match their use in the + // `for_each_i2c_master!` call. + quote::quote! { + #instance, #sys, #sclk, #mosi, #miso, #cs + } + }) + .collect::>(); + + generate_for_each_macro("spi_slave", &instance_cfgs) +} diff --git a/esp-metadata/src/lib.rs b/esp-metadata/src/lib.rs index b31dc059c..0ae9dd4ed 100644 --- a/esp-metadata/src/lib.rs +++ b/esp-metadata/src/lib.rs @@ -479,11 +479,17 @@ impl Config { let mut tokens = TokenStream::new(); // 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_peripherals(i2c)); + if let Some(peri) = self.device.peri_config.i2c_master.as_ref() { + tokens.extend(cfg::generate_i2c_master_peripherals(peri)); }; - if let Some(uart) = self.device.peri_config.uart.as_ref() { - tokens.extend(cfg::generate_uart_peripherals(uart)); + if let Some(peri) = self.device.peri_config.uart.as_ref() { + tokens.extend(cfg::generate_uart_peripherals(peri)); + } + if let Some(peri) = self.device.peri_config.spi_master.as_ref() { + tokens.extend(cfg::generate_spi_master_peripherals(peri)); + }; + if let Some(peri) = self.device.peri_config.spi_slave.as_ref() { + tokens.extend(cfg::generate_spi_slave_peripherals(peri)); }; save(out_dir.join(file_name), tokens);