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
This commit is contained in:
Dominic Fischer 2025-04-09 14:57:15 +01:00 committed by GitHub
parent 1db73628c2
commit 7b4b41c0ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 560 additions and 64 deletions

View File

@ -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) - 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) - `i2c::master::Operation` now implements `defmt::Format` (#3348)
- ESP32-S2: Support for light-/deep-sleep (#3341) - ESP32-S2: Support for light-/deep-sleep (#3341)
- Add DMA memcpy support to the S2 (#3352)
### Changed ### Changed

View File

@ -3,18 +3,23 @@ use core::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use crate::{ #[cfg(not(esp32s2))]
dma::{ use crate::dma::{
AnyGdmaChannel, AnyGdmaChannel,
AnyGdmaRxChannel, AnyGdmaRxChannel,
AnyGdmaTxChannel, AnyGdmaTxChannel,
DmaChannelConvert,
DmaEligible,
};
#[cfg(esp32s2)]
use crate::dma::{CopyDmaChannel, CopyDmaRxChannel, CopyDmaTxChannel};
use crate::{
dma::{
BurstConfig, BurstConfig,
Channel, Channel,
ChannelRx, ChannelRx,
ChannelTx, ChannelTx,
DmaChannelConvert,
DmaDescriptor, DmaDescriptor,
DmaEligible,
DmaError, DmaError,
DmaPeripheral, DmaPeripheral,
DmaRxBuf, DmaRxBuf,
@ -46,6 +51,7 @@ where
impl<'d> Mem2Mem<'d, Blocking> { impl<'d> Mem2Mem<'d, Blocking> {
/// Create a new Mem2Mem instance. /// Create a new Mem2Mem instance.
#[cfg(not(esp32s2))]
pub fn new( pub fn new(
channel: impl DmaChannelConvert<AnyGdmaChannel<'d>>, channel: impl DmaChannelConvert<AnyGdmaChannel<'d>>,
peripheral: impl DmaEligible, 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 /// You must ensure that you're not using DMA for the same peripheral and
/// that you're the only one using the DmaPeripheral. /// that you're the only one using the DmaPeripheral.
#[cfg(not(esp32s2))]
pub unsafe fn new_unsafe( pub unsafe fn new_unsafe(
channel: impl DmaChannelConvert<AnyGdmaChannel<'d>>, channel: impl DmaChannelConvert<AnyGdmaChannel<'d>>,
peripheral: DmaPeripheral, 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] /// Shortcut to create a [SimpleMem2Mem]
pub fn with_descriptors( pub fn with_descriptors(
self, self,
@ -100,7 +128,10 @@ impl<'d> Mem2Mem<'d, Blocking> {
/// The RX half of [Mem2Mem]. /// The RX half of [Mem2Mem].
pub struct Mem2MemRx<'d, Dm: DriverMode> { pub struct Mem2MemRx<'d, Dm: DriverMode> {
#[cfg(not(esp32s2))]
channel: ChannelRx<Dm, AnyGdmaRxChannel<'d>>, channel: ChannelRx<Dm, AnyGdmaRxChannel<'d>>,
#[cfg(esp32s2)]
channel: ChannelRx<Dm, CopyDmaRxChannel<'d>>,
peripheral: DmaPeripheral, peripheral: DmaPeripheral,
} }
@ -228,7 +259,10 @@ impl<M: DriverMode, BUF: DmaRxBuffer> Drop for Mem2MemRxTransfer<'_, M, BUF> {
/// The TX half of [Mem2Mem]. /// The TX half of [Mem2Mem].
pub struct Mem2MemTx<'d, Dm: DriverMode> { pub struct Mem2MemTx<'d, Dm: DriverMode> {
#[cfg(not(esp32s2))]
channel: ChannelTx<Dm, AnyGdmaTxChannel<'d>>, channel: ChannelTx<Dm, AnyGdmaTxChannel<'d>>,
#[cfg(esp32s2)]
channel: ChannelTx<Dm, CopyDmaTxChannel<'d>>,
peripheral: DmaPeripheral, 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()) core::slice::from_raw_parts_mut(tx_descriptors.as_mut_ptr(), tx_descriptors.len())
}; };
let dma_tx_buf = unwrap!( // Note: The ESP32-S2 insists that RX is started before TX. Contrary to the TRM
DmaTxBuf::new_with_config(tx_descriptors, tx_buffer, self.config), // and every other chip.
"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);
}
};
let dma_rx_buf = unwrap!( let dma_rx_buf = unwrap!(
DmaRxBuf::new_with_config(rx_descriptors, rx_buffer, self.config), DmaRxBuf::new_with_config(rx_descriptors, rx_buffer, self.config),
@ -444,8 +463,26 @@ impl<'d, Dm: DriverMode> SimpleMem2Mem<'d, Dm> {
Ok(rx) => rx, Ok(rx) => rx,
Err((err, rx, buf)) => { Err((err, rx, buf)) => {
let (rx_descriptors, _rx_buffer) = buf.split(); 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 (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); self.state = State::Idle(Mem2Mem { rx, tx }, rx_descriptors, tx_descriptors);
return Err(err); return Err(err);
} }

View File

@ -56,7 +56,7 @@ use enumset::{EnumSet, EnumSetType};
pub use self::buffers::*; pub use self::buffers::*;
#[cfg(gdma)] #[cfg(gdma)]
pub use self::gdma::*; pub use self::gdma::*;
#[cfg(gdma)] #[cfg(any(gdma, esp32s2))]
pub use self::m2m::*; pub use self::m2m::*;
#[cfg(pdma)] #[cfg(pdma)]
pub use self::pdma::*; pub use self::pdma::*;
@ -373,7 +373,7 @@ unsafe impl Send for DmaDescriptor {}
mod buffers; mod buffers;
#[cfg(gdma)] #[cfg(gdma)]
mod gdma; mod gdma;
#[cfg(gdma)] #[cfg(any(gdma, esp32s2))]
mod m2m; mod m2m;
#[cfg(pdma)] #[cfg(pdma)]
mod pdma; mod pdma;

View File

@ -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<bool>) {
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<Interrupt> {
None
}
fn async_handler(&self) -> Option<InterruptHandler> {
None
}
}
impl InterruptAccess<DmaTxInterrupt> for CopyDmaTxChannel<'_> {
fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, 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<DmaTxInterrupt> {
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<EnumSet<DmaTxInterrupt>>) {
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<DmaTxInterrupt> {
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<bool>) {
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<Interrupt> {
None
}
fn async_handler(&self) -> Option<InterruptHandler> {
None
}
}
impl InterruptAccess<DmaRxInterrupt> for CopyDmaRxChannel<'_> {
fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, 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<DmaRxInterrupt> {
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<EnumSet<DmaRxInterrupt>>) {
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<DmaRxInterrupt> {
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<DmaRxInterrupt> {
CopyDmaRxChannel(unsafe { Self::steal() })
}
fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
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::<CopyDmaChannel<'static>>();
super::asynch::handle_out_interrupt::<CopyDmaChannel<'static>>();
}
#[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<CopyDmaRxChannel<'d>> for CopyDmaChannel<'d> {
fn degrade(self) -> CopyDmaRxChannel<'d> {
CopyDmaRxChannel(self)
}
}
impl<'d> DmaChannelConvert<CopyDmaTxChannel<'d>> for CopyDmaChannel<'d> {
fn degrade(self) -> CopyDmaTxChannel<'d> {
CopyDmaTxChannel(self)
}
}

View File

@ -16,11 +16,15 @@ use portable_atomic::AtomicBool;
use crate::{asynch::AtomicWaker, dma::*, handler, interrupt::Priority, peripherals::Interrupt}; use crate::{asynch::AtomicWaker, dma::*, handler, interrupt::Priority, peripherals::Interrupt};
#[cfg(esp32s2)]
mod copy;
#[cfg(esp32s2)] #[cfg(esp32s2)]
mod crypto; mod crypto;
mod i2s; mod i2s;
mod spi; mod spi;
#[cfg(esp32s2)]
pub use copy::*;
#[cfg(esp32s2)] #[cfg(esp32s2)]
pub use crypto::*; pub use crypto::*;
pub use i2s::*; pub use i2s::*;
@ -164,6 +168,18 @@ pub(super) fn init_dma(_cs: CriticalSection<'_>) {
.spi_dma_chan_sel() .spi_dma_chan_sel()
.modify(|_, w| unsafe { w.spi2_dma_chan_sel().bits(1).spi3_dma_chan_sel().bits(2) }); .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<CH, Dm> Channel<Dm, CH> impl<CH, Dm> Channel<Dm, CH>

View File

@ -33,6 +33,7 @@ crate::peripherals! {
APB_SARADC <= APB_SARADC, APB_SARADC <= APB_SARADC,
DAC1 <= virtual, DAC1 <= virtual,
DAC2 <= virtual, DAC2 <= virtual,
COPY_DMA <= COPY_DMA,
CRYPTO_DMA <= CRYPTO_DMA, CRYPTO_DMA <= CRYPTO_DMA,
DEDICATED_GPIO <= DEDICATED_GPIO, DEDICATED_GPIO <= DEDICATED_GPIO,
DS <= DS, DS <= DS,
@ -128,5 +129,6 @@ crate::peripherals! {
DMA_SPI3: Spi3DmaChannel, DMA_SPI3: Spi3DmaChannel,
DMA_I2S0: I2s0DmaChannel, DMA_I2S0: I2s0DmaChannel,
DMA_CRYPTO: CryptoDmaChannel, DMA_CRYPTO: CryptoDmaChannel,
DMA_COPY: CopyDmaChannel,
] ]
} }

View File

@ -1,7 +1,7 @@
//! Uses DMA to copy memory to memory. //! Uses DMA to copy memory to memory.
//% FEATURES: esp-hal/log esp-hal/unstable //% FEATURES: esp-hal/log esp-hal/unstable
//% CHIPS: esp32s3 esp32c2 esp32c3 esp32c6 esp32h2 //% CHIPS: esp32s2 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2
#![no_std] #![no_std]
#![no_main] #![no_main]
@ -29,18 +29,20 @@ fn main() -> ! {
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE); let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE);
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(any(feature = "esp32c2", feature = "esp32c3", feature = "esp32s3"))] { if #[cfg(feature = "esp32s2")] {
let dma_peripheral = peripherals.SPI2; 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 { } 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()) .with_descriptors(rx_descriptors, tx_descriptors, BurstConfig::default())
.unwrap(); .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; tx_buffer[i] = (i % 256) as u8;
} }
@ -52,7 +54,7 @@ fn main() -> ! {
dma_wait.wait().unwrap(); dma_wait.wait().unwrap();
info!("Transfer completed, comparing buffer"); info!("Transfer completed, comparing buffer");
let mut error = false; 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] { if rx_buffer[i] != tx_buffer[i] {
error!( error!(
"Error: tx_buffer[{}] = {}, rx_buffer[{}] = {}", "Error: tx_buffer[{}] = {}, rx_buffer[{}] = {}",

View File

@ -1,31 +1,23 @@
//! DMA Mem2Mem Tests //! DMA Mem2Mem Tests
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s3 //% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable //% FEATURES: unstable
#![no_std] #![no_std]
#![no_main] #![no_main]
use esp_hal::{ use esp_hal::{
dma::{AnyGdmaChannel, DmaChannelConvert, DmaError, Mem2Mem}, dma::{DmaError, Mem2Mem},
dma_buffers, dma_buffers,
dma_descriptors, dma_descriptors,
Blocking,
}; };
use hil_test as _; use hil_test as _;
const DATA_SIZE: usize = 1024 * 10; 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 { struct Context {
channel: AnyGdmaChannel<'static>, mem2mem: Mem2Mem<'static, Blocking>,
dma_peripheral: DmaPeripheralType<'static>,
} }
#[cfg(test)] #[cfg(test)]
@ -37,27 +29,25 @@ mod tests {
fn init() -> Context { fn init() -> Context {
let peripherals = esp_hal::init(esp_hal::Config::default()); let peripherals = esp_hal::init(esp_hal::Config::default());
let dma_channel = peripherals.DMA_CH0;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(any(esp32c2, esp32c6, esp32h2))] { if #[cfg(esp32s2)] {
let dma_peripheral = peripherals.MEM2MEM1; let mem2mem = Mem2Mem::new(peripherals.DMA_COPY);
} else if #[cfg(any(esp32c2, esp32c6, esp32h2))] {
let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.MEM2MEM1);
} else { } else {
let dma_peripheral = peripherals.SPI2; let mem2mem = Mem2Mem::new(peripherals.DMA_CH0, peripherals.SPI2);
} }
} }
Context { Context { mem2mem }
channel: dma_channel.degrade(),
dma_peripheral,
}
} }
#[test] #[test]
fn test_internal_mem2mem(ctx: Context) { fn test_internal_mem2mem(ctx: Context) {
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE); 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()) .with_descriptors(rx_descriptors, tx_descriptors, Default::default())
.unwrap(); .unwrap();
@ -74,11 +64,10 @@ mod tests {
#[test] #[test]
fn test_mem2mem_errors_zero_tx(ctx: Context) { fn test_mem2mem_errors_zero_tx(ctx: Context) {
let (rx_descriptors, tx_descriptors) = dma_descriptors!(1024, 0); let (rx_descriptors, tx_descriptors) = dma_descriptors!(1024, 0);
match Mem2Mem::new(ctx.channel, ctx.dma_peripheral).with_descriptors( match ctx
rx_descriptors, .mem2mem
tx_descriptors, .with_descriptors(rx_descriptors, tx_descriptors, Default::default())
Default::default(), {
) {
Err(DmaError::OutOfDescriptors) => (), Err(DmaError::OutOfDescriptors) => (),
_ => panic!("Expected OutOfDescriptors"), _ => panic!("Expected OutOfDescriptors"),
} }
@ -87,11 +76,10 @@ mod tests {
#[test] #[test]
fn test_mem2mem_errors_zero_rx(ctx: Context) { fn test_mem2mem_errors_zero_rx(ctx: Context) {
let (rx_descriptors, tx_descriptors) = dma_descriptors!(0, 1024); let (rx_descriptors, tx_descriptors) = dma_descriptors!(0, 1024);
match Mem2Mem::new(ctx.channel, ctx.dma_peripheral).with_descriptors( match ctx
rx_descriptors, .mem2mem
tx_descriptors, .with_descriptors(rx_descriptors, tx_descriptors, Default::default())
Default::default(), {
) {
Err(DmaError::OutOfDescriptors) => (), Err(DmaError::OutOfDescriptors) => (),
_ => panic!("Expected OutOfDescriptors"), _ => panic!("Expected OutOfDescriptors"),
} }