esp_hal/dma/pdma/
mod.rs

1//! # Direct Memory Access
2//!
3//! ## Overview
4//! The `pdma` module is part of the DMA driver of `ESP32` and `ESP32-S2`.
5//!
6//! This module provides efficient direct data transfer capabilities between
7//! peripherals and memory without involving the CPU. It enables bidirectional
8//! data transfers through DMA channels, making it particularly useful for
9//! high-speed data transfers, such as [SPI] and [I2S] communication.
10//!
11//! [SPI]: ../spi/index.html
12//! [I2S]: ../i2s/index.html
13
14use critical_section::CriticalSection;
15use portable_atomic::AtomicBool;
16
17use crate::{asynch::AtomicWaker, dma::*, handler, interrupt::Priority, peripherals::Interrupt};
18
19#[cfg(esp32s2)]
20mod copy;
21#[cfg(esp32s2)]
22mod crypto;
23mod i2s;
24mod spi;
25
26#[cfg(esp32s2)]
27pub use copy::*;
28#[cfg(esp32s2)]
29pub use crypto::*;
30pub use i2s::*;
31pub use spi::*;
32
33#[doc(hidden)]
34pub trait PdmaChannel: crate::private::Sealed {
35    type RegisterBlock;
36
37    fn register_block(&self) -> &Self::RegisterBlock;
38    fn tx_waker(&self) -> &'static AtomicWaker;
39    fn rx_waker(&self) -> &'static AtomicWaker;
40    fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool;
41
42    fn peripheral_interrupt(&self) -> Interrupt;
43    fn async_handler(&self) -> InterruptHandler;
44    fn rx_async_flag(&self) -> &'static AtomicBool;
45    fn tx_async_flag(&self) -> &'static AtomicBool;
46}
47
48macro_rules! impl_pdma_channel {
49    ($peri:ident, $register_block:ident, $instance:ident, $int:ident, [$($compatible:ident),*]) => {
50        paste::paste! {
51            use $crate::peripherals::[< $instance >];
52            impl<'d> DmaChannel for $instance<'d> {
53                type Rx = [<$peri DmaRxChannel>]<'d>;
54                type Tx = [<$peri DmaTxChannel>]<'d>;
55
56                unsafe fn split_internal(self, _: $crate::private::Internal) -> (Self::Rx, Self::Tx) { unsafe {
57                    (
58                        [<$peri DmaRxChannel>](Self::steal().into()),
59                        [<$peri DmaTxChannel>](Self::steal().into()),
60                    )
61                }}
62            }
63
64            impl DmaChannelExt for $instance<'_> {
65                fn rx_interrupts() -> impl InterruptAccess<DmaRxInterrupt> {
66                    [<$peri DmaRxChannel>](unsafe { Self::steal() }.into())
67                }
68                fn tx_interrupts() -> impl InterruptAccess<DmaTxInterrupt> {
69                    [<$peri DmaTxChannel>](unsafe { Self::steal() }.into())
70                }
71            }
72
73            impl PdmaChannel for $instance<'_> {
74                type RegisterBlock = $register_block;
75
76                fn register_block(&self) -> &Self::RegisterBlock {
77                    $crate::peripherals::[< $instance >]::regs()
78                }
79                fn tx_waker(&self) -> &'static AtomicWaker {
80                    static WAKER: AtomicWaker = AtomicWaker::new();
81                    &WAKER
82                }
83                fn rx_waker(&self) -> &'static AtomicWaker {
84                    static WAKER: AtomicWaker = AtomicWaker::new();
85                    &WAKER
86                }
87                fn is_compatible_with(&self, peripheral: DmaPeripheral) -> bool {
88                    let compatible_peripherals = [$(DmaPeripheral::$compatible),*];
89                    compatible_peripherals.contains(&peripheral)
90                }
91
92                fn peripheral_interrupt(&self) -> Interrupt {
93                    Interrupt::$int
94                }
95
96                fn async_handler(&self) -> InterruptHandler {
97                    #[handler(priority = Priority::max())]
98                    pub(crate) fn interrupt_handler() {
99                        super::asynch::handle_in_interrupt::<$instance<'static>>();
100                        super::asynch::handle_out_interrupt::<$instance<'static>>();
101                    }
102
103                    interrupt_handler
104                }
105                fn rx_async_flag(&self) -> &'static AtomicBool {
106                    static FLAG: AtomicBool = AtomicBool::new(false);
107                    &FLAG
108                }
109                fn tx_async_flag(&self) -> &'static AtomicBool {
110                    static FLAG: AtomicBool = AtomicBool::new(false);
111                    &FLAG
112                }
113            }
114
115            impl<'d> DmaChannelConvert<[<$peri DmaChannel>]<'d>> for $instance<'d> {
116                fn degrade(self) -> [<$peri DmaChannel>]<'d> {
117                    self.into()
118                }
119            }
120
121            impl<'d> DmaChannelConvert<[<$peri DmaRxChannel>]<'d>> for $instance<'d> {
122                fn degrade(self) -> [<$peri DmaRxChannel>]<'d> {
123                    [<$peri DmaRxChannel>](self.into())
124                }
125            }
126
127            impl<'d> DmaChannelConvert<[<$peri DmaTxChannel>]<'d>> for $instance<'d> {
128                fn degrade(self) -> [<$peri DmaTxChannel>]<'d> {
129                    [<$peri DmaTxChannel>](self.into())
130                }
131            }
132        }
133    };
134}
135
136impl_pdma_channel!(AnySpi, SpiRegisterBlock, DMA_SPI2, SPI2_DMA, [Spi2]);
137impl_pdma_channel!(AnySpi, SpiRegisterBlock, DMA_SPI3, SPI3_DMA, [Spi3]);
138
139impl_pdma_channel!(AnyI2s, I2sRegisterBlock, DMA_I2S0, I2S0, [I2s0]);
140#[cfg(i2s1)]
141impl_pdma_channel!(AnyI2s, I2sRegisterBlock, DMA_I2S1, I2S1, [I2s1]);
142
143// Specific peripherals use specific channels. Note that this may be overly
144// restrictive (ESP32 allows configuring 2 SPI DMA channels between 3 different
145// peripherals), but for the current set of restrictions this is sufficient.
146crate::dma::impl_dma_eligible!([DMA_SPI2] SPI2 => Spi2);
147crate::dma::impl_dma_eligible!([DMA_SPI3] SPI3 => Spi3);
148crate::dma::impl_dma_eligible!([DMA_I2S0] I2S0 => I2s0);
149#[cfg(i2s1)]
150crate::dma::impl_dma_eligible!([DMA_I2S1] I2S1 => I2s1);
151#[cfg(esp32s2)]
152use crate::peripherals::DMA_CRYPTO;
153#[cfg(esp32s2)]
154crate::dma::impl_dma_eligible!([DMA_CRYPTO] AES => Aes);
155#[cfg(esp32s2)]
156crate::dma::impl_dma_eligible!([DMA_CRYPTO] SHA => Sha);
157
158pub(super) fn init_dma(_cs: CriticalSection<'_>) {
159    #[cfg(esp32)]
160    {
161        // (only) on ESP32 we need to configure DPORT for the SPI DMA channels
162        // This assignes the DMA channels to the SPI peripherals, which is more
163        // restrictive than necessary but we currently support the same
164        // number of SPI peripherals as SPI DMA channels so it's not a big
165        // deal.
166        use crate::peripherals::DPORT;
167
168        DPORT::regs()
169            .spi_dma_chan_sel()
170            .modify(|_, w| unsafe { w.spi2_dma_chan_sel().bits(1).spi3_dma_chan_sel().bits(2) });
171    }
172
173    #[cfg(esp32s2)]
174    {
175        // This is the only DMA channel on the S2 that needs to be enabled this way
176        // (using its own registers). Ideally this should be enabled only when
177        // the DMA channel is in use but we don't have a good mechanism for that
178        // yet. For now, we shall just turn in on forever once any DMA channel is used.
179
180        use crate::peripherals::DMA_COPY;
181
182        DMA_COPY::regs().conf().modify(|_, w| w.clk_en().set_bit());
183    }
184}
185
186impl<CH, Dm> Channel<Dm, CH>
187where
188    CH: DmaChannel,
189    Dm: DriverMode,
190{
191    /// Asserts that the channel is compatible with the given peripheral.
192    #[instability::unstable]
193    pub fn runtime_ensure_compatible(&self, peripheral: &impl DmaEligible) {
194        assert!(
195            self.tx
196                .tx_impl
197                .is_compatible_with(peripheral.dma_peripheral()),
198            "This DMA channel is not compatible with {:?}",
199            peripheral.dma_peripheral()
200        );
201    }
202}