From 7b4b41c0ed3e6b23d4a11394038b893658cec61b Mon Sep 17 00:00:00 2001 From: Dominic Fischer <14130965+Dominaezzz@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:57:15 +0100 Subject: [PATCH] Add DMA memcpy support to the S2 (#3352) * Expose CopyDmaChannel * Add DMA memcpy support to the S2 * whoops, esp32 is a thing * clear can be a no-op * sigh * rust * enable the DMA channel * The S2 wants RX first... * fmt --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/dma/m2m.rs | 83 +++-- esp-hal/src/dma/mod.rs | 4 +- esp-hal/src/dma/pdma/copy.rs | 450 +++++++++++++++++++++++++ esp-hal/src/dma/pdma/mod.rs | 16 + esp-hal/src/soc/esp32s2/peripherals.rs | 2 + examples/src/bin/dma_mem2mem.rs | 16 +- hil-test/tests/dma_mem2mem.rs | 52 ++- 8 files changed, 560 insertions(+), 64 deletions(-) create mode 100644 esp-hal/src/dma/pdma/copy.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 39ac25443..12cb51608 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - All peripheral singletons (`GpioPin<...>`, `SPIn`, ...) now have a lifetime, as well as `steal`, `reborrow` and `clone_unchecked` methods (#3305) - `i2c::master::Operation` now implements `defmt::Format` (#3348) - ESP32-S2: Support for light-/deep-sleep (#3341) +- Add DMA memcpy support to the S2 (#3352) ### Changed diff --git a/esp-hal/src/dma/m2m.rs b/esp-hal/src/dma/m2m.rs index ad265716c..6f2111ca5 100644 --- a/esp-hal/src/dma/m2m.rs +++ b/esp-hal/src/dma/m2m.rs @@ -3,18 +3,23 @@ use core::{ ops::{Deref, DerefMut}, }; +#[cfg(not(esp32s2))] +use crate::dma::{ + AnyGdmaChannel, + AnyGdmaRxChannel, + AnyGdmaTxChannel, + DmaChannelConvert, + DmaEligible, +}; +#[cfg(esp32s2)] +use crate::dma::{CopyDmaChannel, CopyDmaRxChannel, CopyDmaTxChannel}; use crate::{ dma::{ - AnyGdmaChannel, - AnyGdmaRxChannel, - AnyGdmaTxChannel, BurstConfig, Channel, ChannelRx, ChannelTx, - DmaChannelConvert, DmaDescriptor, - DmaEligible, DmaError, DmaPeripheral, DmaRxBuf, @@ -46,6 +51,7 @@ where impl<'d> Mem2Mem<'d, Blocking> { /// Create a new Mem2Mem instance. + #[cfg(not(esp32s2))] pub fn new( channel: impl DmaChannelConvert>, peripheral: impl DmaEligible, @@ -59,6 +65,7 @@ impl<'d> Mem2Mem<'d, Blocking> { /// /// You must ensure that you're not using DMA for the same peripheral and /// that you're the only one using the DmaPeripheral. + #[cfg(not(esp32s2))] pub unsafe fn new_unsafe( channel: impl DmaChannelConvert>, peripheral: DmaPeripheral, @@ -79,6 +86,27 @@ impl<'d> Mem2Mem<'d, Blocking> { } } + /// Create a new Mem2Mem instance. + #[cfg(esp32s2)] + pub fn new(channel: CopyDmaChannel<'d>) -> Self { + let channel = Channel::new(channel); + + // The S2's COPY DMA channel doesn't care about this. Once support for other + // channels are added, this will need updating. + let peripheral = DmaPeripheral::Spi2; + + Mem2Mem { + rx: Mem2MemRx { + channel: channel.rx, + peripheral, + }, + tx: Mem2MemTx { + channel: channel.tx, + peripheral, + }, + } + } + /// Shortcut to create a [SimpleMem2Mem] pub fn with_descriptors( self, @@ -100,7 +128,10 @@ impl<'d> Mem2Mem<'d, Blocking> { /// The RX half of [Mem2Mem]. pub struct Mem2MemRx<'d, Dm: DriverMode> { + #[cfg(not(esp32s2))] channel: ChannelRx>, + #[cfg(esp32s2)] + channel: ChannelRx>, peripheral: DmaPeripheral, } @@ -228,7 +259,10 @@ impl Drop for Mem2MemRxTransfer<'_, M, BUF> { /// The TX half of [Mem2Mem]. pub struct Mem2MemTx<'d, Dm: DriverMode> { + #[cfg(not(esp32s2))] channel: ChannelTx>, + #[cfg(esp32s2)] + channel: ChannelTx>, peripheral: DmaPeripheral, } @@ -417,23 +451,8 @@ impl<'d, Dm: DriverMode> SimpleMem2Mem<'d, Dm> { core::slice::from_raw_parts_mut(tx_descriptors.as_mut_ptr(), tx_descriptors.len()) }; - let dma_tx_buf = unwrap!( - DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config), - "There's no way to get the descriptors back yet" - ); - - let tx = match mem2mem.tx.send(dma_tx_buf) { - Ok(tx) => tx, - Err((err, tx, buf)) => { - let (tx_descriptors, _tx_buffer) = buf.split(); - self.state = State::Idle( - Mem2Mem { rx: mem2mem.rx, tx }, - rx_descriptors, - tx_descriptors, - ); - return Err(err); - } - }; + // Note: The ESP32-S2 insists that RX is started before TX. Contrary to the TRM + // and every other chip. let dma_rx_buf = unwrap!( DmaRxBuf::new_with_config(rx_descriptors, rx_buffer, self.config), @@ -444,8 +463,26 @@ impl<'d, Dm: DriverMode> SimpleMem2Mem<'d, Dm> { Ok(rx) => rx, Err((err, rx, buf)) => { let (rx_descriptors, _rx_buffer) = buf.split(); - let (tx, buf) = tx.stop(); + self.state = State::Idle( + Mem2Mem { rx, tx: mem2mem.tx }, + rx_descriptors, + tx_descriptors, + ); + return Err(err); + } + }; + + let dma_tx_buf = unwrap!( + DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config), + "There's no way to get the descriptors back yet" + ); + + let tx = match mem2mem.tx.send(dma_tx_buf) { + Ok(tx) => tx, + Err((err, tx, buf)) => { let (tx_descriptors, _tx_buffer) = buf.split(); + let (rx, buf) = rx.stop(); + let (rx_descriptors, _rx_buffer) = buf.split(); self.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors); return Err(err); } diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 1f6411818..e14708f38 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -56,7 +56,7 @@ use enumset::{EnumSet, EnumSetType}; pub use self::buffers::*; #[cfg(gdma)] pub use self::gdma::*; -#[cfg(gdma)] +#[cfg(any(gdma, esp32s2))] pub use self::m2m::*; #[cfg(pdma)] pub use self::pdma::*; @@ -373,7 +373,7 @@ unsafe impl Send for DmaDescriptor {} mod buffers; #[cfg(gdma)] mod gdma; -#[cfg(gdma)] +#[cfg(any(gdma, esp32s2))] mod m2m; #[cfg(pdma)] mod pdma; diff --git a/esp-hal/src/dma/pdma/copy.rs b/esp-hal/src/dma/pdma/copy.rs new file mode 100644 index 000000000..f2bdb7f9c --- /dev/null +++ b/esp-hal/src/dma/pdma/copy.rs @@ -0,0 +1,450 @@ +use portable_atomic::{AtomicBool, Ordering}; + +use crate::{ + asynch::AtomicWaker, + dma::*, + interrupt::{InterruptHandler, Priority}, + peripherals::{Interrupt, COPY_DMA}, +}; + +pub(super) type CopyRegisterBlock = crate::pac::copy_dma::RegisterBlock; + +/// The RX half of a Copy DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CopyDmaRxChannel<'d>(pub(crate) CopyDmaChannel<'d>); + +impl CopyDmaRxChannel<'_> { + fn regs(&self) -> &CopyRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CopyDmaRxChannel<'_> {} +impl DmaRxChannel for CopyDmaRxChannel<'_> {} + +/// The TX half of a Copy DMA channel. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CopyDmaTxChannel<'d>(pub(crate) CopyDmaChannel<'d>); + +impl CopyDmaTxChannel<'_> { + fn regs(&self) -> &CopyRegisterBlock { + self.0.register_block() + } +} + +impl crate::private::Sealed for CopyDmaTxChannel<'_> {} +impl DmaTxChannel for CopyDmaTxChannel<'_> {} + +impl RegisterAccess for CopyDmaTxChannel<'_> { + fn reset(&self) { + self.regs().conf().modify(|_, w| w.out_rst().set_bit()); + self.regs().conf().modify(|_, w| w.out_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, _burst_mode: bool) {} + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .out_link() + .modify(|_, w| unsafe { w.outlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .out_link() + .modify(|_, w| w.outlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Copy DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { + // not supported + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + false + } +} + +impl TxRegisterAccess for CopyDmaTxChannel<'_> { + fn is_fifo_empty(&self) -> bool { + self.regs().in_st().read().fifo_empty().bit() + } + + fn set_auto_write_back(&self, enable: bool) { + self.regs() + .conf() + .modify(|_, w| w.out_auto_wrback().bit(enable)); + } + + fn last_dscr_address(&self) -> usize { + self.regs() + .out_eof_des_addr() + .read() + .out_eof_des_addr() + .bits() as usize + } + + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CopyDmaTxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable), + DmaTxInterrupt::Eof => w.out_eof().bit(enable), + DmaTxInterrupt::Done => w.out_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_ena.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_ena.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_ena.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(), + DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(), + DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(), + DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.out_total_eof().bit_is_set() { + result |= DmaTxInterrupt::TotalEof; + } + if int_raw.out_dscr_err().bit_is_set() { + result |= DmaTxInterrupt::DescriptorError; + } + if int_raw.out_eof().bit_is_set() { + result |= DmaTxInterrupt::Eof; + } + if int_raw.out_done().bit_is_set() { + result |= DmaTxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.tx_waker() + } + + fn is_async(&self) -> bool { + self.0.tx_async_flag().load(Ordering::Acquire) + } + + fn set_async(&self, is_async: bool) { + self.0.tx_async_flag().store(is_async, Ordering::Release); + } +} + +impl RegisterAccess for CopyDmaRxChannel<'_> { + fn reset(&self) { + self.regs().conf().modify(|_, w| w.in_rst().set_bit()); + self.regs().conf().modify(|_, w| w.in_rst().clear_bit()); + } + + fn set_burst_mode(&self, _burst_mode: BurstConfig) {} + + fn set_descr_burst_mode(&self, _burst_mode: bool) {} + + fn set_peripheral(&self, _peripheral: u8) { + // no-op + } + + fn set_link_addr(&self, address: u32) { + self.regs() + .in_link() + .modify(|_, w| unsafe { w.inlink_addr().bits(address) }); + } + + fn start(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_start().set_bit()); + } + + fn stop(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_stop().set_bit()); + } + + fn restart(&self) { + self.regs() + .in_link() + .modify(|_, w| w.inlink_restart().set_bit()); + } + + fn set_check_owner(&self, check_owner: Option) { + if check_owner == Some(true) { + panic!("Copy DMA does not support checking descriptor ownership"); + } + } + + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + self.0.is_compatible_with(peripheral) + } + + #[cfg(psram_dma)] + fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) { + // not supported + } + + #[cfg(psram_dma)] + fn can_access_psram(&self) -> bool { + false + } +} + +impl RxRegisterAccess for CopyDmaRxChannel<'_> { + fn peripheral_interrupt(&self) -> Option { + None + } + + fn async_handler(&self) -> Option { + None + } +} + +impl InterruptAccess for CopyDmaRxChannel<'_> { + fn enable_listen(&self, interrupts: EnumSet, enable: bool) { + self.regs().int_ena().modify(|_, w| { + for interrupt in interrupts { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable), + DmaRxInterrupt::ErrorEof => unimplemented!(), + DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable), + DmaRxInterrupt::Done => w.in_done().bit(enable), + }; + } + w + }); + } + + fn is_listening(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_ena = self.regs().int_ena().read(); + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_ena.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_ena.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_ena.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn clear(&self, interrupts: impl Into>) { + self.regs().int_clr().write(|w| { + for interrupt in interrupts.into() { + match interrupt { + DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(), + DmaRxInterrupt::ErrorEof => continue, + DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(), + DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(), + DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(), + }; + } + w + }); + } + + fn pending_interrupts(&self) -> EnumSet { + let mut result = EnumSet::new(); + + let int_raw = self.regs().int_raw().read(); + if int_raw.in_dscr_err().bit_is_set() { + result |= DmaRxInterrupt::DescriptorError; + } + if int_raw.in_dscr_empty().bit_is_set() { + result |= DmaRxInterrupt::DescriptorEmpty; + } + if int_raw.in_suc_eof().bit_is_set() { + result |= DmaRxInterrupt::SuccessfulEof; + } + if int_raw.in_done().bit_is_set() { + result |= DmaRxInterrupt::Done; + } + + result + } + + fn waker(&self) -> &'static AtomicWaker { + self.0.rx_waker() + } + + fn is_async(&self) -> bool { + self.0.rx_async_flag().load(Ordering::Relaxed) + } + + fn set_async(&self, _is_async: bool) { + self.0.rx_async_flag().store(_is_async, Ordering::Relaxed); + } +} + +#[doc = "DMA channel suitable for COPY"] +#[non_exhaustive] +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CopyDmaChannel<'d> { + _lifetime: core::marker::PhantomData<&'d mut ()>, +} + +impl crate::private::Sealed for CopyDmaChannel<'_> {} + +impl CopyDmaChannel<'_> { + #[doc = r" Unsafely constructs a new DMA channel."] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r""] + #[doc = r" The caller must ensure that only a single instance is used."] + pub unsafe fn steal() -> Self { + Self { + _lifetime: core::marker::PhantomData, + } + } +} +impl<'d> DmaChannel for CopyDmaChannel<'d> { + type Rx = CopyDmaRxChannel<'d>; + type Tx = CopyDmaTxChannel<'d>; + unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) { + ( + CopyDmaRxChannel(unsafe { Self::steal() }), + CopyDmaTxChannel(unsafe { Self::steal() }), + ) + } +} +impl DmaChannelExt for CopyDmaChannel<'_> { + fn rx_interrupts() -> impl InterruptAccess { + CopyDmaRxChannel(unsafe { Self::steal() }) + } + fn tx_interrupts() -> impl InterruptAccess { + CopyDmaTxChannel(unsafe { Self::steal() }) + } +} +impl PdmaChannel for CopyDmaChannel<'_> { + type RegisterBlock = CopyRegisterBlock; + fn register_block(&self) -> &Self::RegisterBlock { + COPY_DMA::regs() + } + fn tx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn rx_waker(&self) -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } + fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool { + let compatible_peripherals = [DmaPeripheral::Aes, DmaPeripheral::Sha]; + compatible_peripherals.contains(&peripheral) + } + fn peripheral_interrupt(&self) -> Interrupt { + Interrupt::DMA_COPY + } + fn async_handler(&self) -> InterruptHandler { + pub(crate) extern "C" fn __esp_hal_internal_interrupt_handler() { + super::asynch::handle_in_interrupt::>(); + super::asynch::handle_out_interrupt::>(); + } + #[allow(non_upper_case_globals)] + pub(crate) static interrupt_handler: InterruptHandler = + InterruptHandler::new(__esp_hal_internal_interrupt_handler, Priority::max()); + interrupt_handler + } + fn rx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } + fn tx_async_flag(&self) -> &'static AtomicBool { + static FLAG: AtomicBool = AtomicBool::new(false); + &FLAG + } +} +impl<'d> DmaChannelConvert> for CopyDmaChannel<'d> { + fn degrade(self) -> CopyDmaRxChannel<'d> { + CopyDmaRxChannel(self) + } +} +impl<'d> DmaChannelConvert> for CopyDmaChannel<'d> { + fn degrade(self) -> CopyDmaTxChannel<'d> { + CopyDmaTxChannel(self) + } +} diff --git a/esp-hal/src/dma/pdma/mod.rs b/esp-hal/src/dma/pdma/mod.rs index be8d8c608..246aea557 100644 --- a/esp-hal/src/dma/pdma/mod.rs +++ b/esp-hal/src/dma/pdma/mod.rs @@ -16,11 +16,15 @@ use portable_atomic::AtomicBool; use crate::{asynch::AtomicWaker, dma::*, handler, interrupt::Priority, peripherals::Interrupt}; +#[cfg(esp32s2)] +mod copy; #[cfg(esp32s2)] mod crypto; mod i2s; mod spi; +#[cfg(esp32s2)] +pub use copy::*; #[cfg(esp32s2)] pub use crypto::*; pub use i2s::*; @@ -164,6 +168,18 @@ pub(super) fn init_dma(_cs: CriticalSection<'_>) { .spi_dma_chan_sel() .modify(|_, w| unsafe { w.spi2_dma_chan_sel().bits(1).spi3_dma_chan_sel().bits(2) }); } + + #[cfg(esp32s2)] + { + // This is the only DMA channel on the S2 that needs to be enabled this way + // (using its own registers). Ideally this should be enabled only when + // the DMA channel is in use but we don't have a good mechanism for that + // yet. For now, we shall just turn in on forever once any DMA channel is used. + + use crate::peripherals::COPY_DMA; + + COPY_DMA::regs().conf().modify(|_, w| w.clk_en().set_bit()); + } } impl Channel diff --git a/esp-hal/src/soc/esp32s2/peripherals.rs b/esp-hal/src/soc/esp32s2/peripherals.rs index d63816652..e31c933c3 100644 --- a/esp-hal/src/soc/esp32s2/peripherals.rs +++ b/esp-hal/src/soc/esp32s2/peripherals.rs @@ -33,6 +33,7 @@ crate::peripherals! { APB_SARADC <= APB_SARADC, DAC1 <= virtual, DAC2 <= virtual, + COPY_DMA <= COPY_DMA, CRYPTO_DMA <= CRYPTO_DMA, DEDICATED_GPIO <= DEDICATED_GPIO, DS <= DS, @@ -128,5 +129,6 @@ crate::peripherals! { DMA_SPI3: Spi3DmaChannel, DMA_I2S0: I2s0DmaChannel, DMA_CRYPTO: CryptoDmaChannel, + DMA_COPY: CopyDmaChannel, ] } diff --git a/examples/src/bin/dma_mem2mem.rs b/examples/src/bin/dma_mem2mem.rs index 4ecbf3844..a6a9d7643 100644 --- a/examples/src/bin/dma_mem2mem.rs +++ b/examples/src/bin/dma_mem2mem.rs @@ -1,7 +1,7 @@ //! Uses DMA to copy memory to memory. //% FEATURES: esp-hal/log esp-hal/unstable -//% CHIPS: esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 +//% CHIPS: esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 #![no_std] #![no_main] @@ -29,18 +29,20 @@ fn main() -> ! { let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE); cfg_if::cfg_if! { - if #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))] { - let dma_peripheral = peripherals.SPI2; + if #[cfg(feature = "esp32s2")] { + let mem2mem = Mem2Mem::new(peripherals.DMA_COPY); + } else if #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))] { + let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.SPI2); } else { - let dma_peripheral = peripherals.MEM2MEM1; + let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.MEM2MEM1); } } - let mut mem2mem = Mem2Mem::new(peripherals.DMA_CH0, dma_peripheral) + let mut mem2mem = mem2mem .with_descriptors(rx_descriptors, tx_descriptors, BurstConfig::default()) .unwrap(); - for i in 0..core::mem::size_of_val(tx_buffer) { + for i in 0..size_of_val(tx_buffer) { tx_buffer[i] = (i % 256) as u8; } @@ -52,7 +54,7 @@ fn main() -> ! { dma_wait.wait().unwrap(); info!("Transfer completed, comparing buffer"); let mut error = false; - for i in 0..core::mem::size_of_val(tx_buffer) { + for i in 0..size_of_val(tx_buffer) { if rx_buffer[i] != tx_buffer[i] { error!( "Error: tx_buffer[{}] = {}, rx_buffer[{}] = {}", diff --git a/hil-test/tests/dma_mem2mem.rs b/hil-test/tests/dma_mem2mem.rs index 78c1c3f24..7396f214a 100644 --- a/hil-test/tests/dma_mem2mem.rs +++ b/hil-test/tests/dma_mem2mem.rs @@ -1,31 +1,23 @@ //! DMA Mem2Mem Tests -//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s3 +//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: unstable #![no_std] #![no_main] use esp_hal::{ - dma::{AnyGdmaChannel, DmaChannelConvert, DmaError, Mem2Mem}, + dma::{DmaError, Mem2Mem}, dma_buffers, dma_descriptors, + Blocking, }; use hil_test as _; const DATA_SIZE: usize = 1024 * 10; -cfg_if::cfg_if! { - if #[cfg(any(esp32c2, esp32c6, esp32h2))] { - type DmaPeripheralType<'d> = esp_hal::peripherals::MEM2MEM1<'d>; - } else { - type DmaPeripheralType<'d> = esp_hal::peripherals::SPI2<'d>; - } -} - struct Context { - channel: AnyGdmaChannel<'static>, - dma_peripheral: DmaPeripheralType<'static>, + mem2mem: Mem2Mem<'static, Blocking>, } #[cfg(test)] @@ -37,27 +29,25 @@ mod tests { fn init() -> Context { let peripherals = esp_hal::init(esp_hal::Config::default()); - let dma_channel = peripherals.DMA_CH0; - cfg_if::cfg_if! { - if #[cfg(any(esp32c2, esp32c6, esp32h2))] { - let dma_peripheral = peripherals.MEM2MEM1; + if #[cfg(esp32s2)] { + let mem2mem = Mem2Mem::new(peripherals.DMA_COPY); + } else if #[cfg(any(esp32c2, esp32c6, esp32h2))] { + let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.MEM2MEM1); } else { - let dma_peripheral = peripherals.SPI2; + let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.SPI2); } } - Context { - channel: dma_channel.degrade(), - dma_peripheral, - } + Context { mem2mem } } #[test] fn test_internal_mem2mem(ctx: Context) { let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE); - let mut mem2mem = Mem2Mem::new(ctx.channel, ctx.dma_peripheral) + let mut mem2mem = ctx + .mem2mem .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) .unwrap(); @@ -74,11 +64,10 @@ mod tests { #[test] fn test_mem2mem_errors_zero_tx(ctx: Context) { let (rx_descriptors, tx_descriptors) = dma_descriptors!(1024, 0); - match Mem2Mem::new(ctx.channel, ctx.dma_peripheral).with_descriptors( - rx_descriptors, - tx_descriptors, - Default::default(), - ) { + match ctx + .mem2mem + .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) + { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), } @@ -87,11 +76,10 @@ mod tests { #[test] fn test_mem2mem_errors_zero_rx(ctx: Context) { let (rx_descriptors, tx_descriptors) = dma_descriptors!(0, 1024); - match Mem2Mem::new(ctx.channel, ctx.dma_peripheral).with_descriptors( - rx_descriptors, - tx_descriptors, - Default::default(), - ) { + match ctx + .mem2mem + .with_descriptors(rx_descriptors, tx_descriptors, Default::default()) + { Err(DmaError::OutOfDescriptors) => (), _ => panic!("Expected OutOfDescriptors"), }