Skip to main content

esp_hal/dma/engine/
copy.rs

1use enumset::EnumSet;
2use portable_atomic::{AtomicBool, Ordering};
3
4use crate::{
5    RegisterToggle,
6    asynch::AtomicWaker,
7    dma::{
8        BurstConfig,
9        DmaChannel,
10        DmaExtMemBKSize,
11        DmaRxChannel,
12        DmaRxInterrupt,
13        DmaTxChannel,
14        DmaTxInterrupt,
15        InterruptAccess,
16        RegisterAccess,
17        RxRegisterAccess,
18        TxRegisterAccess,
19        asynch,
20    },
21    interrupt::InterruptHandler,
22    peripherals::{DMA_COPY, Interrupt},
23    system::{Peripheral, PeripheralGuard},
24};
25
26/// Immutable per-channel metadata.
27#[doc(hidden)]
28pub struct ChannelInfo {
29    #[expect(dead_code)]
30    pub(crate) peripheral_interrupt: Interrupt,
31
32    #[expect(dead_code)]
33    pub(crate) async_handler: InterruptHandler,
34
35    /// Peripheral IDs this channel can serve. An empty slice means no runtime check is needed.
36    pub(crate) compatible_peripherals: &'static [u8],
37}
38
39/// Mutable per-channel runtime state (wakers and async-mode flags).
40pub(crate) struct ChannelState {
41    /// Async waker for the TX (out) half of this channel.
42    pub(crate) tx_waker: AtomicWaker,
43
44    /// Async waker for the RX (in) half of this channel.
45    pub(crate) rx_waker: AtomicWaker,
46
47    /// Whether the TX half is currently in async mode.
48    pub(crate) tx_async_flag: portable_atomic::AtomicBool,
49
50    /// Whether the RX half is currently in async mode.
51    pub(crate) rx_async_flag: portable_atomic::AtomicBool,
52}
53
54pub(super) type CopyRegisterBlock = crate::pac::copy_dma::RegisterBlock;
55
56/// The RX half of a Copy DMA channel.
57#[derive(Debug)]
58#[cfg_attr(feature = "defmt", derive(defmt::Format))]
59pub struct CopyDmaRxChannel<'d>(CopyDmaChannel<'d>);
60
61impl CopyDmaRxChannel<'_> {
62    fn regs(&self) -> &CopyRegisterBlock {
63        self.0.register_block()
64    }
65}
66
67impl crate::private::Sealed for CopyDmaRxChannel<'_> {}
68impl DmaRxChannel for CopyDmaRxChannel<'_> {}
69
70/// The TX half of a Copy DMA channel.
71#[derive(Debug)]
72#[cfg_attr(feature = "defmt", derive(defmt::Format))]
73pub struct CopyDmaTxChannel<'d>(CopyDmaChannel<'d>);
74
75impl CopyDmaTxChannel<'_> {
76    fn regs(&self) -> &CopyRegisterBlock {
77        self.0.register_block()
78    }
79}
80
81impl crate::private::Sealed for CopyDmaTxChannel<'_> {}
82impl DmaTxChannel for CopyDmaTxChannel<'_> {}
83
84impl RegisterAccess for CopyDmaTxChannel<'_> {
85    #[allow(private_interfaces)]
86    fn enable(&self) -> Option<PeripheralGuard> {
87        Some(PeripheralGuard::new(Peripheral::CopyDma))
88    }
89
90    fn reset(&self) {
91        self.regs().conf().toggle(|w, bit| w.out_rst().bit(bit));
92    }
93
94    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
95
96    fn set_descr_burst_mode(&self, _burst_mode: bool) {}
97
98    fn set_link_addr(&self, address: u32) {
99        self.regs()
100            .out_link()
101            .modify(|_, w| unsafe { w.outlink_addr().bits(address) });
102    }
103
104    fn start(&self) {
105        self.regs()
106            .out_link()
107            .modify(|_, w| w.outlink_start().set_bit());
108    }
109
110    fn stop(&self) {
111        self.regs()
112            .out_link()
113            .modify(|_, w| w.outlink_stop().set_bit());
114    }
115
116    fn restart(&self) {
117        self.regs()
118            .out_link()
119            .modify(|_, w| w.outlink_restart().set_bit());
120    }
121
122    fn set_check_owner(&self, check_owner: Option<bool>) {
123        if check_owner == Some(true) {
124            panic!("Copy DMA does not support checking descriptor ownership");
125        }
126    }
127
128    #[cfg(dma_ext_mem_configurable_block_size)]
129    fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) {
130        // not supported
131    }
132
133    #[cfg(dma_can_access_psram)]
134    fn can_access_psram(&self) -> bool {
135        false
136    }
137
138    fn compatible_peripherals(&self) -> &[u8] {
139        self.0.info().compatible_peripherals
140    }
141}
142
143impl TxRegisterAccess for CopyDmaTxChannel<'_> {
144    fn is_fifo_empty(&self) -> bool {
145        self.regs().in_st().read().fifo_empty().bit()
146    }
147
148    fn set_auto_write_back(&self, enable: bool) {
149        self.regs()
150            .conf()
151            .modify(|_, w| w.out_auto_wrback().bit(enable));
152    }
153
154    fn last_dscr_address(&self) -> usize {
155        self.regs()
156            .out_eof_des_addr()
157            .read()
158            .out_eof_des_addr()
159            .bits() as usize
160    }
161
162    fn peripheral_interrupt(&self) -> Option<Interrupt> {
163        None
164    }
165
166    fn async_handler(&self) -> Option<InterruptHandler> {
167        None
168    }
169}
170
171impl InterruptAccess<DmaTxInterrupt> for CopyDmaTxChannel<'_> {
172    fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
173        self.regs().int_ena().modify(|_, w| {
174            for interrupt in interrupts {
175                match interrupt {
176                    DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
177                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable),
178                    DmaTxInterrupt::Eof => w.out_eof().bit(enable),
179                    DmaTxInterrupt::Done => w.out_done().bit(enable),
180                };
181            }
182            w
183        });
184    }
185
186    fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
187        let mut result = EnumSet::new();
188
189        let int_ena = self.regs().int_ena().read();
190        if int_ena.out_total_eof().bit_is_set() {
191            result |= DmaTxInterrupt::TotalEof;
192        }
193        if int_ena.out_dscr_err().bit_is_set() {
194            result |= DmaTxInterrupt::DescriptorError;
195        }
196        if int_ena.out_eof().bit_is_set() {
197            result |= DmaTxInterrupt::Eof;
198        }
199        if int_ena.out_done().bit_is_set() {
200            result |= DmaTxInterrupt::Done;
201        }
202
203        result
204    }
205
206    fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
207        self.regs().int_clr().write(|w| {
208            for interrupt in interrupts.into() {
209                match interrupt {
210                    DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
211                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
212                    DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
213                    DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
214                };
215            }
216            w
217        });
218    }
219
220    fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
221        let mut result = EnumSet::new();
222
223        let int_raw = self.regs().int_raw().read();
224        if int_raw.out_total_eof().bit_is_set() {
225            result |= DmaTxInterrupt::TotalEof;
226        }
227        if int_raw.out_dscr_err().bit_is_set() {
228            result |= DmaTxInterrupt::DescriptorError;
229        }
230        if int_raw.out_eof().bit_is_set() {
231            result |= DmaTxInterrupt::Eof;
232        }
233        if int_raw.out_done().bit_is_set() {
234            result |= DmaTxInterrupt::Done;
235        }
236
237        result
238    }
239
240    fn waker(&self) -> &'static AtomicWaker {
241        &self.0.state().tx_waker
242    }
243
244    fn is_async(&self) -> bool {
245        self.0.state().tx_async_flag.load(Ordering::Acquire)
246    }
247
248    fn set_async(&self, is_async: bool) {
249        self.0
250            .state()
251            .tx_async_flag
252            .store(is_async, Ordering::Release);
253    }
254}
255
256impl RegisterAccess for CopyDmaRxChannel<'_> {
257    #[allow(private_interfaces)]
258    fn enable(&self) -> Option<PeripheralGuard> {
259        Some(PeripheralGuard::new(Peripheral::CopyDma))
260    }
261
262    fn reset(&self) {
263        self.regs().conf().toggle(|w, bit| w.in_rst().bit(bit));
264    }
265
266    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
267
268    fn set_descr_burst_mode(&self, _burst_mode: bool) {}
269
270    fn set_link_addr(&self, address: u32) {
271        self.regs()
272            .in_link()
273            .modify(|_, w| unsafe { w.inlink_addr().bits(address) });
274    }
275
276    fn start(&self) {
277        self.regs()
278            .in_link()
279            .modify(|_, w| w.inlink_start().set_bit());
280    }
281
282    fn stop(&self) {
283        self.regs()
284            .in_link()
285            .modify(|_, w| w.inlink_stop().set_bit());
286    }
287
288    fn restart(&self) {
289        self.regs()
290            .in_link()
291            .modify(|_, w| w.inlink_restart().set_bit());
292    }
293
294    fn set_check_owner(&self, check_owner: Option<bool>) {
295        if check_owner == Some(true) {
296            panic!("Copy DMA does not support checking descriptor ownership");
297        }
298    }
299
300    #[cfg(dma_ext_mem_configurable_block_size)]
301    fn set_ext_mem_block_size(&self, _size: DmaExtMemBKSize) {
302        // not supported
303    }
304
305    #[cfg(dma_can_access_psram)]
306    fn can_access_psram(&self) -> bool {
307        false
308    }
309
310    fn compatible_peripherals(&self) -> &[u8] {
311        self.0.info().compatible_peripherals
312    }
313}
314
315impl RxRegisterAccess for CopyDmaRxChannel<'_> {
316    #[cfg(dma_supports_mem2mem)]
317    fn set_mem2mem_mode(&self, _value: bool) {}
318
319    fn peripheral_interrupt(&self) -> Option<Interrupt> {
320        None
321    }
322
323    fn async_handler(&self) -> Option<InterruptHandler> {
324        None
325    }
326}
327
328impl InterruptAccess<DmaRxInterrupt> for CopyDmaRxChannel<'_> {
329    fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
330        self.regs().int_ena().modify(|_, w| {
331            for interrupt in interrupts {
332                match interrupt {
333                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
334                    DmaRxInterrupt::ErrorEof => unimplemented!(),
335                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable),
336                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable),
337                    DmaRxInterrupt::Done => w.in_done().bit(enable),
338                };
339            }
340            w
341        });
342    }
343
344    fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
345        let mut result = EnumSet::new();
346
347        let int_ena = self.regs().int_ena().read();
348        if int_ena.in_dscr_err().bit_is_set() {
349            result |= DmaRxInterrupt::DescriptorError;
350        }
351        if int_ena.in_dscr_empty().bit_is_set() {
352            result |= DmaRxInterrupt::DescriptorEmpty;
353        }
354        if int_ena.in_suc_eof().bit_is_set() {
355            result |= DmaRxInterrupt::SuccessfulEof;
356        }
357        if int_ena.in_done().bit_is_set() {
358            result |= DmaRxInterrupt::Done;
359        }
360
361        result
362    }
363
364    fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
365        self.regs().int_clr().write(|w| {
366            for interrupt in interrupts.into() {
367                match interrupt {
368                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
369                    DmaRxInterrupt::ErrorEof => continue,
370                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(),
371                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
372                    DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
373                };
374            }
375            w
376        });
377    }
378
379    fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
380        let mut result = EnumSet::new();
381
382        let int_raw = self.regs().int_raw().read();
383        if int_raw.in_dscr_err().bit_is_set() {
384            result |= DmaRxInterrupt::DescriptorError;
385        }
386        if int_raw.in_dscr_empty().bit_is_set() {
387            result |= DmaRxInterrupt::DescriptorEmpty;
388        }
389        if int_raw.in_suc_eof().bit_is_set() {
390            result |= DmaRxInterrupt::SuccessfulEof;
391        }
392        if int_raw.in_done().bit_is_set() {
393            result |= DmaRxInterrupt::Done;
394        }
395
396        result
397    }
398
399    fn waker(&self) -> &'static AtomicWaker {
400        &self.0.state().rx_waker
401    }
402
403    fn is_async(&self) -> bool {
404        self.0.state().rx_async_flag.load(Ordering::Relaxed)
405    }
406
407    fn set_async(&self, is_async: bool) {
408        self.0
409            .state()
410            .rx_async_flag
411            .store(is_async, Ordering::Relaxed);
412    }
413}
414
415/// A type-erased Copy DMA channel.
416pub type CopyDmaChannel<'d> = DMA_COPY<'d>;
417
418impl DMA_COPY<'_> {
419    pub(super) fn info(&self) -> &'static ChannelInfo {
420        #[crate::handler(priority = crate::interrupt::Priority::max())]
421        fn interrupt_handler() {
422            asynch::handle_in_interrupt::<DMA_COPY<'static>>();
423            asynch::handle_out_interrupt::<DMA_COPY<'static>>();
424        }
425        static INFO: ChannelInfo = ChannelInfo {
426            peripheral_interrupt: Interrupt::DMA_COPY,
427            async_handler: interrupt_handler,
428            compatible_peripherals: &[],
429        };
430        &INFO
431    }
432    pub(super) fn state(&self) -> &'static ChannelState {
433        static STATE: ChannelState = ChannelState {
434            tx_waker: AtomicWaker::new(),
435            rx_waker: AtomicWaker::new(),
436            tx_async_flag: AtomicBool::new(false),
437            rx_async_flag: AtomicBool::new(false),
438        };
439        &STATE
440    }
441}
442
443crate::dma::impl_channel_common!(CopyDma, DMA_COPY);