diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 12288e3625f..ec6cd9ff2cb 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - The clock frequency accessor functions no longer need to lock the clock tree (#5461) +- SPI: `SpiDmaBus` has been merged into `SpiDma`. `with_buffers` now returns `SpiDma` directly, and the buffer-taking transfer methods have been renamed to `read_buffer`, `write_buffer`, `transfer_buffers`, `half_duplex_read_buffer` and `half_duplex_write_buffer` to avoid conflicts with the `SpiBus` trait methods. (#5272) ### Fixed diff --git a/esp-hal/MIGRATING-1.1.0.md b/esp-hal/MIGRATING-1.1.0.md index 7fcf954783e..1ded5258abf 100644 --- a/esp-hal/MIGRATING-1.1.0.md +++ b/esp-hal/MIGRATING-1.1.0.md @@ -23,3 +23,27 @@ and (for chip-specific clocks) `esp_hal::clock::ll`. +let apb_freq = Rate::from_hz(clock::ll::apb_clk_frequency()); +let xtal_freq = clock::xtal_clock(); ``` + +## Merged DMA-driven SPI drivers + +`SpiDmaBus` no longer exists. `SpiDma::with_buffers` now returns `SpiDma` itself, +which implements `embedded_hal::spi::SpiBus` and `embedded_hal_async::spi::SpiBus` +directly. + +```diff,rust +-let mut spi: SpiDmaBus<_> = spi_dma.with_buffers(dma_rx_buf, dma_tx_buf); ++let mut spi: SpiDma<_> = spi_dma.with_buffers(dma_rx_buf, dma_tx_buf); +``` + +Several methods on `SpiDma` were renamed to avoid conflicts with the `SpiBus` trait: + +| Before | After | +|---|---| +| `read` | `read_buffer` | +| `write` | `write_buffer` | +| `transfer` | `transfer_buffers` | +| `half_duplex_read` | `half_duplex_read_buffer` | +| `half_duplex_write` | `half_duplex_write_buffer` | + +`SpiDmaBus::split` is no longer available; to recover the buffers, use +`SpiDmaTransfer::wait` on the transfer returned by the buffer methods. diff --git a/esp-hal/src/spi/master/dma.rs b/esp-hal/src/spi/master/dma.rs index 0d8593bc9e9..300867d355d 100644 --- a/esp-hal/src/spi/master/dma.rs +++ b/esp-hal/src/spi/master/dma.rs @@ -35,39 +35,27 @@ impl<'d> Spi<'d, Blocking> { _ => "DMA_CH0", } )] - /// Configures the SPI instance to use DMA with the specified channel. + /// Converts the driver into an [`SpiDma`] driver that uses the specified DMA channel. /// - /// This method prepares the SPI instance for DMA transfers using SPI - /// and returns an instance of `SpiDma` that supports DMA - /// operations. /// ```rust, no_run /// # {before_snippet} - /// use esp_hal::{ - /// dma::{DmaRxBuf, DmaTxBuf}, - /// dma_buffers, - /// spi::{ - /// Mode, - /// master::{Config, Spi}, - /// }, + /// use esp_hal::spi::{ + /// Mode, + /// master::{Config, Spi}, /// }; - /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000); /// - /// let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; - /// let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer)?; - /// - /// let mut spi = Spi::new( + /// let mut spi_dma = Spi::new( /// peripherals.SPI2, /// Config::default() /// .with_frequency(Rate::from_khz(100)) /// .with_mode(Mode::_0), /// )? - /// .with_dma(peripherals.__dma_channel__) - /// .with_buffers(dma_rx_buf, dma_tx_buf); + /// .with_dma(peripherals.__dma_channel__); /// # {after_snippet} /// ``` #[instability::unstable] pub fn with_dma(self, channel: impl DmaChannelFor>) -> SpiDma<'d, Blocking> { - SpiDma::new(self, channel.degrade()) + SpiDma::new_from_spi(self, channel.degrade()) } } @@ -77,13 +65,25 @@ impl<'d> Spi<'d, Blocking> { _ => "DMA_CH0", } )] -/// A DMA capable SPI instance. +/// DMA-controlled SPI driver. +/// +/// This driver uses DMA to transfer data, allowing the CPU to continue working while the SPI +/// transfer is in progress. +/// +/// The driver provides two separate approaches to transferring data: +/// +/// - The slice-based API allows transferring data from/to slices of memory. The data will be copied +/// into an internal buffer before the transfer begins. These copy buffers must be set up by +/// passing them to [`with_buffers`](SpiDma::with_buffers) before the first transfer begins. +/// - The buffer API allows transferring externally managed buffers. In this mode, you provide the +/// buffers to be transferred. The buffer objects ensure that data is located in appropriate +/// memory regions. The buffers and the driver object are moved into transfer objects for the +/// duration of the transfer. These functions take [`DmaRxBuf`] and [`DmaTxBuf`] objects as +/// arguments as well as the number of bytes to transfer, and their names end with `_buffer`. +/// +/// These approaches provide different trade-offs between memory usage / CPU overhead and ease of +/// use. `embedded-hal` traits are implemented by the slice-based API's functions. /// -/// Using `SpiDma` is not recommended unless you wish -/// to manage buffers yourself. It's recommended to use -/// [`SpiDmaBus`] via `with_buffers` to get access -/// to a DMA capable SPI bus that implements the -/// embedded-hal traits. /// ```rust, no_run /// # {before_snippet} /// use esp_hal::{ @@ -94,6 +94,8 @@ impl<'d> Spi<'d, Blocking> { /// master::{Config, Spi}, /// }, /// }; +/// +/// // Optional: create and set up 32000 byte copy buffers. /// let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32000); /// /// let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer)?; @@ -110,6 +112,7 @@ impl<'d> Spi<'d, Blocking> { /// # /// # {after_snippet} /// ``` +// TODO: update docs once the driver can transfer data without copying #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SpiDma<'d, Dm> where @@ -121,8 +124,100 @@ where impl crate::private::Sealed for SpiDma<'_, Dm> where Dm: DriverMode {} +impl core::fmt::Debug for SpiDma<'_, Dm> +where + Dm: DriverMode + core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SpiDma").field("spi", &self.spi).finish() + } +} + +#[instability::unstable] +impl crate::interrupt::InterruptConfigurable for SpiDma<'_, Blocking> { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler); + } +} + +#[instability::unstable] +impl embassy_embedded_hal::SetConfig for SpiDma<'_, Dm> +where + Dm: DriverMode, +{ + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.apply_config(config) + } +} + +#[instability::unstable] +impl ErrorType for SpiDma<'_, Dm> +where + Dm: DriverMode, +{ + type Error = Error; +} + +#[instability::unstable] +impl SpiBus for SpiDma<'_, Dm> +where + Dm: DriverMode, +{ + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + // DMA limitation - we must ensure the transfers complete before returning + // to user code, otherwise the user might access the buffers while the transfer + // is still in progress. Therefore, there is no such thing as "flushing". + Ok(()) + } +} + +#[instability::unstable] +impl embedded_hal_async::spi::SpiBus for SpiDma<'_, Async> { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read_async(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write_async(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer_async(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place_async(words).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + // DMA limitation - we must ensure the transfers complete before returning + // to user code, otherwise the user might access the buffers while the transfer + // is still in progress. Therefore, there is no such thing as "flushing". + Ok(()) + } +} + impl<'d> SpiDma<'d, Blocking> { - /// Converts the SPI instance into async mode. + /// Converts the SPI driver into async mode. #[instability::unstable] pub fn into_async(self) -> SpiDma<'d, Async> { self.spi @@ -133,12 +228,7 @@ impl<'d> SpiDma<'d, Blocking> { } } - pub(super) fn new( - spi_driver: Spi<'d, Blocking>, - channel: PeripheralDmaChannel>, - ) -> Self { - let spi = spi_driver.spi; - + fn new_inner(spi: SpiWrapper<'d>, channel: PeripheralDmaChannel>) -> Self { let channel = Channel::new(channel); channel.runtime_ensure_compatible(&spi.spi); @@ -161,25 +251,34 @@ impl<'d> SpiDma<'d, Blocking> { static mut RX_DESCRIPTORS: [[DmaDescriptor; 1]; SPI_NUM] = [[DmaDescriptor::EMPTY]; SPI_NUM]; - let empty_rx_buffer = unwrap!(DmaRxBuf::new(unsafe { &mut RX_DESCRIPTORS[id] }, &mut [])); + let rx_buffer = unwrap!(DmaRxBuf::new(unsafe { &mut RX_DESCRIPTORS[id] }, &mut [])); cfg_if::cfg_if! { if #[cfg(all(esp32, spi_address_workaround))] { static mut BUFFERS: [[u32; 1]; SPI_NUM] = [[0]; SPI_NUM]; let buffer = crate::dma::as_mut_byte_array!(BUFFERS[id], 4); - let empty_tx_buffer = unwrap!(DmaTxBuf::new(unsafe { &mut TX_DESCRIPTORS[id] }, buffer)); + let tx_buffer = unwrap!(DmaTxBuf::new(unsafe { &mut TX_DESCRIPTORS[id] }, buffer)); } else { - let empty_tx_buffer = unwrap!(DmaTxBuf::new(unsafe { &mut TX_DESCRIPTORS[id] }, &mut [])); + let tx_buffer = unwrap!(DmaTxBuf::new(unsafe { &mut TX_DESCRIPTORS[id] }, &mut [])); } } // The buffers must be set up when creating the driver. - unsafe { (&mut *state.empty_tx_buffer.get()).write(empty_tx_buffer) }; - unsafe { (&mut *state.empty_rx_buffer.get()).write(empty_rx_buffer) }; + unsafe { (&mut *state.tx_buffer.get()).write(tx_buffer) }; + unsafe { (&mut *state.rx_buffer.get()).write(rx_buffer) }; Self { spi, channel } } + pub(super) fn new_from_spi( + spi_driver: Spi<'d, Blocking>, + channel: PeripheralDmaChannel>, + ) -> Self { + let spi = spi_driver.spi; + + Self::new_inner(spi, channel) + } + /// Listen for the given interrupts #[instability::unstable] pub fn listen(&mut self, interrupts: impl Into>) { @@ -230,7 +329,7 @@ impl<'d> SpiDma<'d, Blocking> { } impl<'d> SpiDma<'d, Async> { - /// Converts the SPI instance into async mode. + /// Converts the SPI driver into async mode. #[instability::unstable] pub fn into_blocking(self) -> SpiDma<'d, Blocking> { self.spi.disable_peri_interrupt_on_all_cores(); @@ -290,32 +389,141 @@ impl<'d> SpiDma<'d, Async> { self.dma_driver().state.tx_transfer_in_progress.set(false); } } -} -impl core::fmt::Debug for SpiDma<'_, Dm> -where - Dm: DriverMode + core::fmt::Debug, -{ - /// Formats the `SpiDma` instance for debugging purposes. - /// - /// This method returns a debug struct with the name "SpiDma" without - /// exposing internal details. - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("SpiDma").field("spi", &self.spi).finish() + /// Fill the given buffer with data from the bus. + #[instability::unstable] + pub async fn read_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.wait_for_idle_async().await; + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = rx_buffer.capacity().min(MAX_DMA_SIZE); + + for chunk in words.chunks_mut(chunk_size) { + let mut spi = DropGuard::new(&mut *self, |spi| spi.cancel_transfer()); + + unsafe { spi.start_dma_transfer(chunk.len(), 0, rx_buffer, tx_buffer)? }; + + spi.wait_for_idle_async().await; + + chunk.copy_from_slice(&rx_buffer.as_slice()[..chunk.len()]); + + spi.defuse(); + } + + Ok(()) } -} -#[instability::unstable] -impl crate::interrupt::InterruptConfigurable for SpiDma<'_, Blocking> { - /// Sets the interrupt handler - /// - /// Interrupts are not enabled at the peripheral level here. - fn set_interrupt_handler(&mut self, handler: InterruptHandler) { - self.set_interrupt_handler(handler); + /// Transmit the given buffer to the bus. + #[instability::unstable] + pub async fn write_async(&mut self, words: &[u8]) -> Result<(), Error> { + self.wait_for_idle_async().await; + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let mut spi = DropGuard::new(self, |spi| spi.cancel_transfer()); + let chunk_size = tx_buffer.capacity().min(MAX_DMA_SIZE); + + for chunk in words.chunks(chunk_size) { + tx_buffer.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { spi.start_dma_transfer(0, chunk.len(), rx_buffer, tx_buffer)? }; + + spi.wait_for_idle_async().await; + } + spi.defuse(); + + Ok(()) + } + + /// Transfer by writing out a buffer and reading the response from + /// the bus into another buffer. + #[instability::unstable] + pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + self.wait_for_idle_async().await; + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 || tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let mut spi = DropGuard::new(&mut *self, |spi| spi.cancel_transfer()); + let chunk_size = min(rx_buffer.capacity(), tx_buffer.capacity()).min(MAX_DMA_SIZE); + + let common_length = min(read.len(), write.len()); + let (read_common, read_remainder) = read.split_at_mut(common_length); + let (write_common, write_remainder) = write.split_at(common_length); + + for (read_chunk, write_chunk) in read_common + .chunks_mut(chunk_size) + .zip(write_common.chunks(chunk_size)) + { + tx_buffer.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); + + unsafe { + spi.start_dma_transfer(read_chunk.len(), write_chunk.len(), rx_buffer, tx_buffer)?; + } + spi.wait_for_idle_async().await; + + read_chunk.copy_from_slice(&rx_buffer.as_slice()[..read_chunk.len()]); + } + + spi.defuse(); + + if !read_remainder.is_empty() { + self.read_async(read_remainder).await + } else if !write_remainder.is_empty() { + self.write_async(write_remainder).await + } else { + Ok(()) + } + } + + /// Transfer by writing out a buffer and reading the response from + /// the bus into the same buffer. + #[instability::unstable] + pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.wait_for_idle_async().await; + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 || tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = tx_buffer.capacity().min(MAX_DMA_SIZE); + + let mut spi = DropGuard::new(self, |spi| spi.cancel_transfer()); + for chunk in words.chunks_mut(chunk_size) { + tx_buffer.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + + unsafe { + spi.start_dma_transfer(chunk.len(), chunk.len(), rx_buffer, tx_buffer)?; + } + spi.wait_for_idle_async().await; + chunk.copy_from_slice(&rx_buffer.as_slice()[..chunk.len()]); + } + + spi.defuse(); + + Ok(()) } } -impl SpiDma<'_, Dm> +impl<'d, Dm> SpiDma<'d, Dm> where Dm: DriverMode, { @@ -420,7 +628,7 @@ where return Err(Error::Unsupported); } - let buffer = unsafe { self.spi.dma_state().empty_tx_buffer() }; + let buffer = unsafe { self.dma_driver().tx_buffer() }; let bytes_to_write = address.width().div_ceil(8); // The address register is read in big-endian order, @@ -439,9 +647,9 @@ where address.mode(), )?; - let empty_rx_buffer = unsafe { self.dma_driver().empty_rx_buffer() }; + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; - unsafe { self.start_transfer_dma(false, 0, bytes_to_write, empty_rx_buffer, buffer) } + unsafe { self.start_transfer_dma(false, 0, bytes_to_write, rx_buffer, buffer) } } fn cancel_transfer(&mut self) { @@ -460,136 +668,40 @@ where } } } -} -#[instability::unstable] -impl embassy_embedded_hal::SetConfig for SpiDma<'_, Dm> -where - Dm: DriverMode, -{ - type Config = Config; - type ConfigError = ConfigError; + /// # Safety: + /// + /// The caller must ensure that the buffers are not accessed while the + /// transfer is in progress. Moving the buffers is allowed. + #[cfg_attr(place_spi_master_driver_in_ram, ram)] + unsafe fn start_dma_write( + &mut self, + bytes_to_write: usize, + buffer: &mut impl DmaTxBuffer, + ) -> Result<(), Error> { + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; - fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { - self.apply_config(config) + unsafe { self.start_dma_transfer(0, bytes_to_write, rx_buffer, buffer) } } -} - -/// A structure representing a DMA transfer for SPI. -/// -/// This structure holds references to the SPI instance, DMA buffers, and -/// transfer status. -#[instability::unstable] -pub struct SpiDmaTransfer<'d, Dm, Buf> -where - Dm: DriverMode, -{ - spi_dma: ManuallyDrop>, - dma_buf: ManuallyDrop, -} -impl SpiDmaTransfer<'_, Async, Buf> { - /// Waits for the DMA transfer to complete asynchronously. + /// Assigns copy buffers to the SPI driver. /// - /// This method awaits the completion of both RX and TX operations. + /// These buffers will be used to copy data when using the slice-based transfer functions. + /// + /// The maximum useful size for these buffers is 32736 bytes, any additional memory will + /// be wasted. + // TODO: update docs once the driver can actually transfer data without copying - we'll need to + // explain when this function is necessary. #[instability::unstable] - pub async fn wait_for_done(&mut self) { - self.spi_dma.wait_for_idle_async().await; - } -} - -impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf> -where - Dm: DriverMode, -{ - fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf) -> Self { - Self { - spi_dma: ManuallyDrop::new(spi_dma), - dma_buf: ManuallyDrop::new(dma_buf), + pub fn with_buffers(self, dma_rx_buf: DmaRxBuf, dma_tx_buf: DmaTxBuf) -> SpiDma<'d, Dm> { + unsafe { + (&mut *self.dma_driver().state.rx_buffer.get()).write(dma_rx_buf); + (&mut *self.dma_driver().state.tx_buffer.get()).write(dma_tx_buf); } + self } - /// Checks if the transfer is complete. - /// - /// This method returns `true` if both RX and TX operations are done, - /// and the SPI instance is no longer busy. - pub fn is_done(&self) -> bool { - self.spi_dma.is_done() - } - - /// Waits for the DMA transfer to complete. - /// - /// This method blocks until the transfer is finished and returns the - /// `SpiDma` instance and the associated buffer. - #[instability::unstable] - pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) { - self.spi_dma.wait_for_idle(); - let retval = unsafe { - ( - ManuallyDrop::take(&mut self.spi_dma), - ManuallyDrop::take(&mut self.dma_buf), - ) - }; - core::mem::forget(self); - retval - } - - /// Cancels the DMA transfer. - #[instability::unstable] - pub fn cancel(&mut self) { - if !self.spi_dma.is_done() { - self.spi_dma.cancel_transfer(); - } - } -} - -impl Drop for SpiDmaTransfer<'_, Dm, Buf> -where - Dm: DriverMode, -{ - fn drop(&mut self) { - if !self.is_done() { - self.spi_dma.cancel_transfer(); - self.spi_dma.wait_for_idle(); - - unsafe { - ManuallyDrop::drop(&mut self.spi_dma); - ManuallyDrop::drop(&mut self.dma_buf); - } - } - } -} - -impl<'d, Dm> SpiDma<'d, Dm> -where - Dm: DriverMode, -{ - /// # Safety: - /// - /// The caller must ensure that the buffers are not accessed while the - /// transfer is in progress. Moving the buffers is allowed. - #[cfg_attr(place_spi_master_driver_in_ram, ram)] - unsafe fn start_dma_write( - &mut self, - bytes_to_write: usize, - buffer: &mut impl DmaTxBuffer, - ) -> Result<(), Error> { - let empty_rx_buffer = unsafe { self.dma_driver().empty_rx_buffer() }; - - unsafe { self.start_dma_transfer(0, bytes_to_write, empty_rx_buffer, buffer) } - } - - /// Configures the DMA buffers for the SPI instance. - /// - /// This method sets up both RX and TX buffers for DMA transfers. - /// It returns an instance of `SpiDmaBus` that can be used for SPI - /// communication. - #[instability::unstable] - pub fn with_buffers(self, dma_rx_buf: DmaRxBuf, dma_tx_buf: DmaTxBuf) -> SpiDmaBus<'d, Dm> { - SpiDmaBus::new(self, dma_rx_buf, dma_tx_buf) - } - - /// Perform a DMA write. + /// Perform a DMA write. /// /// This will return a [SpiDmaTransfer] owning the buffer and the /// SPI instance. The maximum amount of data to be sent is 32736 @@ -597,7 +709,7 @@ where #[allow(clippy::type_complexity)] #[cfg_attr(place_spi_master_driver_in_ram, ram)] #[instability::unstable] - pub fn write( + pub fn write_buffer( mut self, bytes_to_write: usize, mut buffer: TX, @@ -622,9 +734,9 @@ where bytes_to_read: usize, buffer: &mut impl DmaRxBuffer, ) -> Result<(), Error> { - let empty_tx_buffer = unsafe { self.dma_driver().empty_tx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; - unsafe { self.start_dma_transfer(bytes_to_read, 0, buffer, empty_tx_buffer) } + unsafe { self.start_dma_transfer(bytes_to_read, 0, buffer, tx_buffer) } } /// Perform a DMA read. @@ -635,7 +747,7 @@ where #[allow(clippy::type_complexity)] #[cfg_attr(place_spi_master_driver_in_ram, ram)] #[instability::unstable] - pub fn read( + pub fn read_buffer( mut self, bytes_to_read: usize, mut buffer: RX, @@ -675,7 +787,7 @@ where #[allow(clippy::type_complexity)] #[cfg_attr(place_spi_master_driver_in_ram, ram)] #[instability::unstable] - pub fn transfer( + pub fn transfer_buffers( mut self, bytes_to_read: usize, mut rx_buffer: RX, @@ -723,16 +835,16 @@ where data_mode, )?; - let empty_tx_buffer = unsafe { self.dma_driver().empty_tx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; - unsafe { self.start_transfer_dma(false, bytes_to_read, 0, buffer, empty_tx_buffer) } + unsafe { self.start_transfer_dma(false, bytes_to_read, 0, buffer, tx_buffer) } } /// Perform a half-duplex read operation using DMA. #[allow(clippy::type_complexity)] #[cfg_attr(place_spi_master_driver_in_ram, ram)] #[instability::unstable] - pub fn half_duplex_read( + pub fn half_duplex_read_buffer( mut self, data_mode: DataMode, cmd: Command, @@ -784,16 +896,16 @@ where data_mode, )?; - let empty_rx_buffer = unsafe { self.dma_driver().empty_rx_buffer() }; + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; - unsafe { self.start_transfer_dma(false, 0, bytes_to_write, empty_rx_buffer, buffer) } + unsafe { self.start_transfer_dma(false, 0, bytes_to_write, rx_buffer, buffer) } } /// Perform a half-duplex write operation using DMA. #[allow(clippy::type_complexity)] #[cfg_attr(place_spi_master_driver_in_ram, ram)] #[instability::unstable] - pub fn half_duplex_write( + pub fn half_duplex_write_buffer( mut self, data_mode: DataMode, cmd: Command, @@ -832,252 +944,28 @@ where pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { self.driver().apply_config(config) } -} - -/// A DMA-capable SPI bus. -/// -/// This structure is responsible for managing SPI transfers using DMA -/// buffers. -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[instability::unstable] -pub struct SpiDmaBus<'d, Dm> -where - Dm: DriverMode, -{ - spi_dma: SpiDma<'d, Dm>, - rx_buf: DmaRxBuf, - tx_buf: DmaTxBuf, -} - -impl crate::private::Sealed for SpiDmaBus<'_, Dm> where Dm: DriverMode {} - -impl<'d> SpiDmaBus<'d, Blocking> { - /// Converts the SPI instance into async mode. - #[instability::unstable] - pub fn into_async(self) -> SpiDmaBus<'d, Async> { - SpiDmaBus { - spi_dma: self.spi_dma.into_async(), - rx_buf: self.rx_buf, - tx_buf: self.tx_buf, - } - } - - /// Listen for the given interrupts - #[instability::unstable] - pub fn listen(&mut self, interrupts: impl Into>) { - self.spi_dma.listen(interrupts.into()); - } - - /// Unlisten the given interrupts - #[instability::unstable] - pub fn unlisten(&mut self, interrupts: impl Into>) { - self.spi_dma.unlisten(interrupts.into()); - } - - /// Gets asserted interrupts - #[instability::unstable] - pub fn interrupts(&mut self) -> EnumSet { - self.spi_dma.interrupts() - } - - /// Resets asserted interrupts - #[instability::unstable] - pub fn clear_interrupts(&mut self, interrupts: impl Into>) { - self.spi_dma.clear_interrupts(interrupts.into()); - } -} - -impl<'d> SpiDmaBus<'d, Async> { - /// Converts the SPI instance into async mode. - #[instability::unstable] - pub fn into_blocking(self) -> SpiDmaBus<'d, Blocking> { - SpiDmaBus { - spi_dma: self.spi_dma.into_blocking(), - rx_buf: self.rx_buf, - tx_buf: self.tx_buf, - } - } - - /// Fill the given buffer with data from the bus. - #[instability::unstable] - pub async fn read_async(&mut self, words: &mut [u8]) -> Result<(), Error> { - self.spi_dma.wait_for_idle_async().await; - self.spi_dma.driver().setup_full_duplex()?; - let chunk_size = self.rx_buf.capacity(); - - let empty_tx_buffer = unsafe { self.spi_dma.dma_driver().empty_tx_buffer() }; - - for chunk in words.chunks_mut(chunk_size) { - let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); - - unsafe { spi.start_dma_transfer(chunk.len(), 0, &mut self.rx_buf, empty_tx_buffer)? }; - - spi.wait_for_idle_async().await; - - chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); - - spi.defuse(); - } - - Ok(()) - } - - /// Transmit the given buffer to the bus. - #[instability::unstable] - pub async fn write_async(&mut self, words: &[u8]) -> Result<(), Error> { - self.spi_dma.wait_for_idle_async().await; - self.spi_dma.driver().setup_full_duplex()?; - - let empty_rx_buffer = unsafe { self.spi_dma.dma_driver().empty_rx_buffer() }; - - let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); - let chunk_size = self.tx_buf.capacity(); - - for chunk in words.chunks(chunk_size) { - self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); - - unsafe { spi.start_dma_transfer(0, chunk.len(), empty_rx_buffer, &mut self.tx_buf)? }; - - spi.wait_for_idle_async().await; - } - spi.defuse(); - - Ok(()) - } - - /// Transfer by writing out a buffer and reading the response from - /// the bus into another buffer. - #[instability::unstable] - pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { - self.spi_dma.wait_for_idle_async().await; - self.spi_dma.driver().setup_full_duplex()?; - - let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); - let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); - - let common_length = min(read.len(), write.len()); - let (read_common, read_remainder) = read.split_at_mut(common_length); - let (write_common, write_remainder) = write.split_at(common_length); - - for (read_chunk, write_chunk) in read_common - .chunks_mut(chunk_size) - .zip(write_common.chunks(chunk_size)) - { - self.tx_buf.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); - - unsafe { - spi.start_dma_transfer( - read_chunk.len(), - write_chunk.len(), - &mut self.rx_buf, - &mut self.tx_buf, - )?; - } - spi.wait_for_idle_async().await; - - read_chunk.copy_from_slice(&self.rx_buf.as_slice()[..read_chunk.len()]); - } - - spi.defuse(); - - if !read_remainder.is_empty() { - self.read_async(read_remainder).await - } else if !write_remainder.is_empty() { - self.write_async(write_remainder).await - } else { - Ok(()) - } - } - - /// Transfer by writing out a buffer and reading the response from - /// the bus into the same buffer. - #[instability::unstable] - pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> { - self.spi_dma.wait_for_idle_async().await; - self.spi_dma.driver().setup_full_duplex()?; - - let mut spi = DropGuard::new(&mut self.spi_dma, |spi| spi.cancel_transfer()); - for chunk in words.chunks_mut(self.tx_buf.capacity()) { - self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); - - unsafe { - spi.start_dma_transfer( - chunk.len(), - chunk.len(), - &mut self.rx_buf, - &mut self.tx_buf, - )?; - } - spi.wait_for_idle_async().await; - chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); - } - - spi.defuse(); - - Ok(()) - } -} - -impl<'d, Dm> SpiDmaBus<'d, Dm> -where - Dm: DriverMode, -{ - /// Creates a new `SpiDmaBus` with the specified SPI instance and DMA - /// buffers. - pub fn new(spi_dma: SpiDma<'d, Dm>, rx_buf: DmaRxBuf, tx_buf: DmaTxBuf) -> Self { - Self { - spi_dma, - rx_buf, - tx_buf, - } - } - - /// Splits [SpiDmaBus] back into [SpiDma], [DmaRxBuf] and [DmaTxBuf]. - #[instability::unstable] - pub fn split(mut self) -> (SpiDma<'d, Dm>, DmaRxBuf, DmaTxBuf) { - self.wait_for_idle(); - (self.spi_dma, self.rx_buf, self.tx_buf) - } - - fn wait_for_idle(&mut self) { - self.spi_dma.wait_for_idle(); - } - - /// Change the bus configuration. - /// - /// # Errors - /// - /// If frequency passed in config exceeds - #[cfg_attr(not(esp32h2), doc = " 80MHz")] - #[cfg_attr(esp32h2, doc = " 48MHz")] - /// or is below 70kHz, - /// [`ConfigError::UnsupportedFrequency`] error will be returned. - #[instability::unstable] - pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> { - self.spi_dma.apply_config(config) - } /// Reads data from the SPI bus using DMA. #[instability::unstable] pub fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { self.wait_for_idle(); - self.spi_dma.driver().setup_full_duplex()?; + self.driver().setup_full_duplex()?; - let empty_tx_buffer = unsafe { self.spi_dma.dma_driver().empty_tx_buffer() }; + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = rx_buffer.capacity().min(MAX_DMA_SIZE); - for chunk in words.chunks_mut(self.rx_buf.capacity()) { + for chunk in words.chunks_mut(chunk_size) { unsafe { - self.spi_dma.start_dma_transfer( - chunk.len(), - 0, - &mut self.rx_buf, - empty_tx_buffer, - )?; + self.start_dma_transfer(chunk.len(), 0, rx_buffer, tx_buffer)?; } self.wait_for_idle(); - chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + chunk.copy_from_slice(&rx_buffer.as_slice()[..chunk.len()]); } Ok(()) @@ -1087,19 +975,21 @@ where #[instability::unstable] pub fn write(&mut self, words: &[u8]) -> Result<(), Error> { self.wait_for_idle(); - self.spi_dma.driver().setup_full_duplex()?; - let empty_rx_buffer = unsafe { self.spi_dma.dma_driver().empty_rx_buffer() }; + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = tx_buffer.capacity().min(MAX_DMA_SIZE); - for chunk in words.chunks(self.tx_buf.capacity()) { - self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + for chunk in words.chunks(chunk_size) { + tx_buffer.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); unsafe { - self.spi_dma.start_dma_transfer( - 0, - chunk.len(), - empty_rx_buffer, - &mut self.tx_buf, - )?; + self.start_dma_transfer(0, chunk.len(), rx_buffer, tx_buffer)?; } self.wait_for_idle(); @@ -1112,8 +1002,15 @@ where #[instability::unstable] pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { self.wait_for_idle(); - self.spi_dma.driver().setup_full_duplex()?; - let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 || tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = min(rx_buffer.capacity(), tx_buffer.capacity()).min(MAX_DMA_SIZE); let common_length = min(read.len(), write.len()); let (read_common, read_remainder) = read.split_at_mut(common_length); @@ -1123,19 +1020,14 @@ where .chunks_mut(chunk_size) .zip(write_common.chunks(chunk_size)) { - self.tx_buf.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); + tx_buffer.as_mut_slice()[..write_chunk.len()].copy_from_slice(write_chunk); unsafe { - self.spi_dma.start_dma_transfer( - read_chunk.len(), - write_chunk.len(), - &mut self.rx_buf, - &mut self.tx_buf, - )?; + self.start_dma_transfer(read_chunk.len(), write_chunk.len(), rx_buffer, tx_buffer)?; } self.wait_for_idle(); - read_chunk.copy_from_slice(&self.rx_buf.as_slice()[..read_chunk.len()]); + read_chunk.copy_from_slice(&rx_buffer.as_slice()[..read_chunk.len()]); } if !read_remainder.is_empty() { @@ -1151,22 +1043,24 @@ where #[instability::unstable] pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { self.wait_for_idle(); - self.spi_dma.driver().setup_full_duplex()?; - let chunk_size = min(self.tx_buf.capacity(), self.rx_buf.capacity()); + self.driver().setup_full_duplex()?; + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if rx_buffer.capacity() == 0 || tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + + let chunk_size = min(rx_buffer.capacity(), tx_buffer.capacity()).min(MAX_DMA_SIZE); for chunk in words.chunks_mut(chunk_size) { - self.tx_buf.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); + tx_buffer.as_mut_slice()[..chunk.len()].copy_from_slice(chunk); unsafe { - self.spi_dma.start_dma_transfer( - chunk.len(), - chunk.len(), - &mut self.rx_buf, - &mut self.tx_buf, - )?; + self.start_dma_transfer(chunk.len(), chunk.len(), rx_buffer, tx_buffer)?; } self.wait_for_idle(); - chunk.copy_from_slice(&self.rx_buf.as_slice()[..chunk.len()]); + chunk.copy_from_slice(&rx_buffer.as_slice()[..chunk.len()]); } Ok(()) @@ -1182,25 +1076,23 @@ where dummy: u8, buffer: &mut [u8], ) -> Result<(), Error> { - if buffer.len() > self.rx_buf.capacity() { + self.wait_for_idle(); + + let rx_buffer = unsafe { self.dma_driver().rx_buffer() }; + if rx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + if buffer.len() > rx_buffer.capacity() { return Err(Error::from(DmaError::Overflow)); } - self.wait_for_idle(); unsafe { - self.spi_dma.start_half_duplex_read( - data_mode, - cmd, - address, - dummy, - buffer.len(), - &mut self.rx_buf, - )?; + self.start_half_duplex_read(data_mode, cmd, address, dummy, buffer.len(), rx_buffer)?; } self.wait_for_idle(); - buffer.copy_from_slice(&self.rx_buf.as_slice()[..buffer.len()]); + buffer.copy_from_slice(&rx_buffer.as_slice()[..buffer.len()]); Ok(()) } @@ -1215,21 +1107,20 @@ where dummy: u8, buffer: &[u8], ) -> Result<(), Error> { - if buffer.len() > self.tx_buf.capacity() { + self.wait_for_idle(); + + let tx_buffer = unsafe { self.dma_driver().tx_buffer() }; + if tx_buffer.capacity() == 0 { + return Err(Error::from(DmaError::BufferTooSmall)); + } + if buffer.len() > tx_buffer.capacity() { return Err(Error::from(DmaError::Overflow)); } - self.wait_for_idle(); - self.tx_buf.as_mut_slice()[..buffer.len()].copy_from_slice(buffer); + + tx_buffer.as_mut_slice()[..buffer.len()].copy_from_slice(buffer); unsafe { - self.spi_dma.start_half_duplex_write( - data_mode, - cmd, - address, - dummy, - buffer.len(), - &mut self.tx_buf, - )?; + self.start_half_duplex_write(data_mode, cmd, address, dummy, buffer.len(), tx_buffer)?; } self.wait_for_idle(); @@ -1238,26 +1129,88 @@ where } } +/// A structure representing a DMA transfer for SPI. +/// +/// This structure holds references to the SPI instance, DMA buffers, and +/// transfer status. #[instability::unstable] -impl crate::interrupt::InterruptConfigurable for SpiDmaBus<'_, Blocking> { - /// Sets the interrupt handler +pub struct SpiDmaTransfer<'d, Dm, Buf> +where + Dm: DriverMode, +{ + spi_dma: ManuallyDrop>, + dma_buf: ManuallyDrop, +} + +impl SpiDmaTransfer<'_, Async, Buf> { + /// Waits for the DMA transfer to complete asynchronously. /// - /// Interrupts are not enabled at the peripheral level here. - fn set_interrupt_handler(&mut self, handler: InterruptHandler) { - self.spi_dma.set_interrupt_handler(handler); + /// This method awaits the completion of both RX and TX operations. + #[instability::unstable] + pub async fn wait_for_done(&mut self) { + self.spi_dma.wait_for_idle_async().await; } } -#[instability::unstable] -impl embassy_embedded_hal::SetConfig for SpiDmaBus<'_, Dm> +impl<'d, Dm, Buf> SpiDmaTransfer<'d, Dm, Buf> where Dm: DriverMode, { - type Config = Config; - type ConfigError = ConfigError; + fn new(spi_dma: SpiDma<'d, Dm>, dma_buf: Buf) -> Self { + Self { + spi_dma: ManuallyDrop::new(spi_dma), + dma_buf: ManuallyDrop::new(dma_buf), + } + } - fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { - self.apply_config(config) + /// Checks if the transfer is complete. + /// + /// This method returns `true` if both RX and TX operations are done, + /// and the SPI instance is no longer busy. + pub fn is_done(&self) -> bool { + self.spi_dma.is_done() + } + + /// Waits for the DMA transfer to complete. + /// + /// This method blocks until the transfer is finished and returns the + /// `SpiDma` instance and the associated buffer. + #[instability::unstable] + pub fn wait(mut self) -> (SpiDma<'d, Dm>, Buf) { + self.spi_dma.wait_for_idle(); + let retval = unsafe { + ( + ManuallyDrop::take(&mut self.spi_dma), + ManuallyDrop::take(&mut self.dma_buf), + ) + }; + core::mem::forget(self); + retval + } + + /// Cancels the DMA transfer. + #[instability::unstable] + pub fn cancel(&mut self) { + if !self.spi_dma.is_done() { + self.spi_dma.cancel_transfer(); + } + } +} + +impl Drop for SpiDmaTransfer<'_, Dm, Buf> +where + Dm: DriverMode, +{ + fn drop(&mut self) { + if !self.is_done() { + self.spi_dma.cancel_transfer(); + self.spi_dma.wait_for_idle(); + + unsafe { + ManuallyDrop::drop(&mut self.spi_dma); + ManuallyDrop::drop(&mut self.dma_buf); + } + } } } @@ -1268,12 +1221,12 @@ pub(super) struct DmaDriver { } impl DmaDriver { - unsafe fn empty_rx_buffer(&self) -> &'static mut DmaRxBuf { - unsafe { self.state.empty_rx_buffer() } + unsafe fn rx_buffer(&self) -> &'static mut DmaRxBuf { + unsafe { self.state.rx_buffer() } } - unsafe fn empty_tx_buffer(&self) -> &'static mut DmaTxBuf { - unsafe { self.state.empty_tx_buffer() } + unsafe fn tx_buffer(&self) -> &'static mut DmaTxBuf { + unsafe { self.state.tx_buffer() } } fn abort_transfer(&self) { @@ -1426,65 +1379,6 @@ impl<'d> DmaEligible for AnySpi<'d> { } } -#[instability::unstable] -impl embedded_hal_async::spi::SpiBus for SpiDmaBus<'_, Async> { - async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.read_async(words).await - } - - async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.write_async(words).await - } - - async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - self.transfer_async(read, write).await - } - - async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.transfer_in_place_async(words).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - // All operations currently flush so this is no-op. - Ok(()) - } -} - -#[instability::unstable] -impl ErrorType for SpiDmaBus<'_, Dm> -where - Dm: DriverMode, -{ - type Error = Error; -} - -#[instability::unstable] -impl SpiBus for SpiDmaBus<'_, Dm> -where - Dm: DriverMode, -{ - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.read(words) - } - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.write(words) - } - - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - self.transfer(read, write) - } - - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - self.transfer_in_place(words) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - // All operations currently flush so this is no-op. - Ok(()) - } -} - struct DmaInfo { dma_peripheral: crate::dma::DmaPeripheral, } @@ -1492,8 +1386,8 @@ struct DmaState { tx_transfer_in_progress: Cell, rx_transfer_in_progress: Cell, - empty_rx_buffer: UnsafeCell>, - empty_tx_buffer: UnsafeCell>, + rx_buffer: UnsafeCell>, + tx_buffer: UnsafeCell>, } impl DmaState { @@ -1506,8 +1400,8 @@ impl DmaState { clippy::mut_from_ref, reason = "Safety requirements ensure this is okay" )] - unsafe fn empty_rx_buffer(&self) -> &mut DmaRxBuf { - unsafe { (&mut *self.empty_rx_buffer.get()).assume_init_mut() } + unsafe fn rx_buffer(&self) -> &mut DmaRxBuf { + unsafe { (&mut *self.rx_buffer.get()).assume_init_mut() } } // Syntactic helper to get a mutable reference to the "empty" TX DMA buffer. @@ -1519,8 +1413,8 @@ impl DmaState { clippy::mut_from_ref, reason = "Safety requirements ensure this is okay" )] - unsafe fn empty_tx_buffer(&self) -> &mut DmaTxBuf { - unsafe { (&mut *self.empty_tx_buffer.get()).assume_init_mut() } + unsafe fn tx_buffer(&self) -> &mut DmaTxBuf { + unsafe { (&mut *self.tx_buffer.get()).assume_init_mut() } } } @@ -1544,8 +1438,8 @@ for_each_spi_master!( tx_transfer_in_progress: Cell::new(false), rx_transfer_in_progress: Cell::new(false), - empty_rx_buffer: UnsafeCell::new(MaybeUninit::uninit()), - empty_tx_buffer: UnsafeCell::new(MaybeUninit::uninit()), + rx_buffer: UnsafeCell::new(MaybeUninit::uninit()), + tx_buffer: UnsafeCell::new(MaybeUninit::uninit()), }; (&DMA_INFO, &DMA_STATE) diff --git a/examples/peripheral/spi/loopback_dma_psram/src/main.rs b/examples/peripheral/spi/loopback_dma_psram/src/main.rs index 06775b1a87d..0ef56fc9074 100644 --- a/examples/peripheral/spi/loopback_dma_psram/src/main.rs +++ b/examples/peripheral/spi/loopback_dma_psram/src/main.rs @@ -112,7 +112,7 @@ fn main() -> ! { i = i.wrapping_add(1); let transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); diff --git a/hil-test/src/bin/spi_full_duplex.rs b/hil-test/src/bin/spi_full_duplex.rs index af2b25bf432..76fb6941743 100644 --- a/hil-test/src/bin/spi_full_duplex.rs +++ b/hil-test/src/bin/spi_full_duplex.rs @@ -468,14 +468,14 @@ mod tests { for i in 1..4 { dma_rx_buf.as_mut_slice()[..TRANSFER_SIZE].copy_from_slice(&[5; TRANSFER_SIZE]); let transfer = spi - .read(TRANSFER_SIZE, dma_rx_buf) + .read_buffer(TRANSFER_SIZE, dma_rx_buf) .map_err(|e| e.0) .unwrap(); (spi, dma_rx_buf) = transfer.wait(); assert_eq!(&dma_rx_buf.as_slice()[..TRANSFER_SIZE], &[0; TRANSFER_SIZE]); let transfer = spi - .write(TRANSFER_SIZE, dma_tx_buf) + .write_buffer(TRANSFER_SIZE, dma_tx_buf) .map_err(|e| e.0) .unwrap(); (spi, dma_tx_buf) = transfer.wait(); @@ -506,14 +506,14 @@ mod tests { for i in 1..4 { dma_rx_buf.as_mut_slice()[..TRANSFER_SIZE].copy_from_slice(&[5; TRANSFER_SIZE]); let transfer = spi - .read(TRANSFER_SIZE, dma_rx_buf) + .read_buffer(TRANSFER_SIZE, dma_rx_buf) .map_err(|e| e.0) .unwrap(); (spi, dma_rx_buf) = transfer.wait(); assert_eq!(&dma_rx_buf.as_slice()[..TRANSFER_SIZE], &[0; TRANSFER_SIZE]); let transfer = spi - .transfer(TRANSFER_SIZE, dma_rx_buf, TRANSFER_SIZE, dma_tx_buf) + .transfer_buffers(TRANSFER_SIZE, dma_rx_buf, TRANSFER_SIZE, dma_tx_buf) .map_err(|e| e.0) .unwrap(); (spi, (dma_rx_buf, dma_tx_buf)) = transfer.wait(); @@ -539,7 +539,7 @@ mod tests { dma_tx_buf.as_mut_slice()[0] = i as u8; *dma_tx_buf.as_mut_slice().last_mut().unwrap() = i as u8; let transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); @@ -565,7 +565,7 @@ mod tests { let spi = ctx.spi.with_dma(ctx.dma_channel); let transfer = spi - .transfer(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf) + .transfer_buffers(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf) .map_err(|e| e.0) .unwrap(); let (spi, (dma_rx_buf, mut dma_tx_buf)) = transfer.wait(); @@ -579,7 +579,7 @@ mod tests { dma_tx_buf.fill(&[0xaa, 0xdd, 0xef, 0xbe]); let transfer = spi - .transfer(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf) + .transfer_buffers(READ_SIZE, dma_rx_buf, WRITE_SIZE, dma_tx_buf) .map_err(|e| e.0) .unwrap(); let (_, (dma_rx_buf, dma_tx_buf)) = transfer.wait(); @@ -777,27 +777,27 @@ mod tests { dma_tx_buf.fill(&[0xde, 0xad, 0xbe, 0xef]); let transfer = spi - .write(dma_tx_buf.len(), dma_tx_buf) + .write_buffer(dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); let (spi, dma_tx_buf) = transfer.wait(); dma_rx_buf.as_mut_slice().fill(0); let transfer = spi - .read(dma_rx_buf.len(), dma_rx_buf) + .read_buffer(dma_rx_buf.len(), dma_rx_buf) .map_err(|e| e.0) .unwrap(); let (spi, mut dma_rx_buf) = transfer.wait(); let transfer = spi - .write(dma_tx_buf.len(), dma_tx_buf) + .write_buffer(dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); let (spi, _dma_tx_buf) = transfer.wait(); dma_rx_buf.as_mut_slice().fill(0); let transfer = spi - .read(dma_rx_buf.len(), dma_rx_buf) + .read_buffer(dma_rx_buf.len(), dma_rx_buf) .map_err(|e| e.0) .unwrap(); let (_, dma_rx_buf) = transfer.wait(); @@ -865,7 +865,7 @@ mod tests { let spi = ctx.spi.with_dma(ctx.dma_channel); let mut transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); @@ -888,7 +888,7 @@ mod tests { let mut spi = ctx.spi.with_dma(ctx.dma_channel); let mut transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); @@ -899,7 +899,7 @@ mod tests { .unwrap(); let transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); @@ -921,7 +921,7 @@ mod tests { let spi = ctx.spi.with_dma(ctx.dma_channel).into_async(); let mut transfer = spi - .transfer(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) + .transfer_buffers(dma_rx_buf.len(), dma_rx_buf, dma_tx_buf.len(), dma_tx_buf) .map_err(|e| e.0) .unwrap(); @@ -998,6 +998,57 @@ mod tests { assert_eq!(ctx.pcnt_unit.value(), 480); } + #[test] + #[cfg(all(spi_master_supports_dma, pcnt_driver_supported, feature = "unstable"))] + fn dma_transfer_works_with_nothing_to_read(ctx: Context) { + ctx.pcnt_unit + .channel0 + .set_edge_signal(ctx.miso_input.peripheral_input()); + ctx.pcnt_unit + .channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4); + let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); + let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + + let mut spi = ctx + .spi + .with_dma(ctx.dma_channel) + .with_buffers(dma_rx_buf, dma_tx_buf); + + let tx_buf = [0x02, 0x02, 0x02, 0x02]; + spi.transfer(&mut [], &tx_buf).unwrap(); + + assert_eq!(ctx.pcnt_unit.value(), 4); + } + + #[test] + #[cfg(all(spi_master_supports_dma, pcnt_driver_supported, feature = "unstable"))] + async fn async_dma_transfer_works_with_nothing_to_read(ctx: Context) { + ctx.pcnt_unit + .channel0 + .set_edge_signal(ctx.miso_input.peripheral_input()); + ctx.pcnt_unit + .channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(4); + let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); + let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + + let mut spi = ctx + .spi + .with_dma(ctx.dma_channel) + .with_buffers(dma_rx_buf, dma_tx_buf) + .into_async(); + + let tx_buf = [0x02, 0x02, 0x02, 0x02]; + spi.transfer_async(&mut [], &tx_buf).await.unwrap(); + + assert_eq!(ctx.pcnt_unit.value(), 4); + } + #[test] #[cfg(all(pcnt_driver_supported, feature = "unstable"))] fn half_duplex_operation_resets_size_before_empty_write(mut ctx: Context) { diff --git a/hil-test/src/bin/spi_half_duplex_slave_qspi.rs b/hil-test/src/bin/spi_half_duplex_slave_qspi.rs index 0217db518dc..38e3559eef6 100644 --- a/hil-test/src/bin/spi_half_duplex_slave_qspi.rs +++ b/hil-test/src/bin/spi_half_duplex_slave_qspi.rs @@ -128,7 +128,7 @@ mod read { let mut spi = ctx.spi.with_dma(ctx.dma_channel); let transfer = spi - .half_duplex_read( + .half_duplex_read_buffer( DataMode::SingleTwoDataLines, Command::None, Address::None, @@ -146,7 +146,7 @@ mod read { ctx.miso_mirror.set_high(); let transfer = spi - .half_duplex_read( + .half_duplex_read_buffer( DataMode::SingleTwoDataLines, Command::None, Address::None, @@ -391,7 +391,7 @@ mod write { dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( mode, Command::None, Address::None, @@ -406,7 +406,7 @@ mod write { assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( mode, Command::None, Address::None, @@ -739,7 +739,7 @@ mod qspi_dma { command: Command, ) -> (SpiUnderTest, DmaRxBuf) { let transfer = spi - .half_duplex_read( + .half_duplex_read_buffer( DataMode::Quad, command, Address::None, @@ -759,7 +759,7 @@ mod qspi_dma { command_data_mode: DataMode, ) -> (SpiUnderTest, DmaTxBuf) { let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::Quad, Command::_8Bit(write as u16, command_data_mode), Address::_24Bit( diff --git a/hil-test/src/bin/spi_half_duplex_write_psram.rs b/hil-test/src/bin/spi_half_duplex_write_psram.rs index 807a240da48..b64ad7497ca 100644 --- a/hil-test/src/bin/spi_half_duplex_write_psram.rs +++ b/hil-test/src/bin/spi_half_duplex_write_psram.rs @@ -105,7 +105,7 @@ mod tests { // Fill the buffer where each byte has 3 pos edges. dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::SingleTwoDataLines, Command::None, Address::None, @@ -120,7 +120,7 @@ mod tests { assert_eq!(unit.value(), (3 * DMA_BUFFER_SIZE) as _); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::SingleTwoDataLines, Command::None, Address::None, diff --git a/qa-test/src/bin/qspi_flash.rs b/qa-test/src/bin/qspi_flash.rs index 64a03f850c0..bd9d06fe884 100644 --- a/qa-test/src/bin/qspi_flash.rs +++ b/qa-test/src/bin/qspi_flash.rs @@ -98,7 +98,7 @@ fn main() -> ! { // write enable dma_tx_buf.set_length(0); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::SingleTwoDataLines, Command::_8Bit(0x06, DataMode::SingleTwoDataLines), Address::None, @@ -113,7 +113,7 @@ fn main() -> ! { // erase sector let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::SingleTwoDataLines, Command::_8Bit(0x20, DataMode::SingleTwoDataLines), Address::_24Bit(0x000000, DataMode::SingleTwoDataLines), @@ -128,7 +128,7 @@ fn main() -> ! { // write enable let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::SingleTwoDataLines, Command::_8Bit(0x06, DataMode::SingleTwoDataLines), Address::None, @@ -146,7 +146,7 @@ fn main() -> ! { dma_tx_buf.as_mut_slice().fill(b'!'); dma_tx_buf.as_mut_slice()[0..][..5].copy_from_slice(&b"Hello"[..]); let transfer = spi - .half_duplex_write( + .half_duplex_write_buffer( DataMode::Quad, Command::_8Bit(0x32, DataMode::SingleTwoDataLines), Address::_24Bit(0x000000, DataMode::SingleTwoDataLines), @@ -162,7 +162,7 @@ fn main() -> ! { loop { // quad fast read let transfer = spi - .half_duplex_read( + .half_duplex_read_buffer( DataMode::Quad, Command::_8Bit(0xeb, DataMode::SingleTwoDataLines), Address::_32Bit(0x000000 << 8, DataMode::Quad),