mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 12:50:53 +00:00
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:
parent
1db73628c2
commit
7b4b41c0ed
@ -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
|
||||
|
||||
|
@ -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<AnyGdmaChannel<'d>>,
|
||||
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<AnyGdmaChannel<'d>>,
|
||||
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<Dm, AnyGdmaRxChannel<'d>>,
|
||||
#[cfg(esp32s2)]
|
||||
channel: ChannelRx<Dm, CopyDmaRxChannel<'d>>,
|
||||
peripheral: DmaPeripheral,
|
||||
}
|
||||
|
||||
@ -228,7 +259,10 @@ impl<M: DriverMode, BUF: DmaRxBuffer> Drop for Mem2MemRxTransfer<'_, M, BUF> {
|
||||
|
||||
/// The TX half of [Mem2Mem].
|
||||
pub struct Mem2MemTx<'d, Dm: DriverMode> {
|
||||
#[cfg(not(esp32s2))]
|
||||
channel: ChannelTx<Dm, AnyGdmaTxChannel<'d>>,
|
||||
#[cfg(esp32s2)]
|
||||
channel: ChannelTx<Dm, CopyDmaTxChannel<'d>>,
|
||||
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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
450
esp-hal/src/dma/pdma/copy.rs
Normal file
450
esp-hal/src/dma/pdma/copy.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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<CH, Dm> Channel<Dm, CH>
|
||||
|
@ -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,
|
||||
]
|
||||
}
|
||||
|
@ -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[{}] = {}",
|
||||
|
@ -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"),
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user