Skip to main content

esp_hal/dma/engine/
axi_gdma.rs

1//! # General Direct Memory Access (AXI-GDMA)
2//!
3//! AXI-GDMA provides peripheral-to-memory, memory-to-peripheral, and
4//! memory-to-memory transfers over the AXI bus. All three channels have
5//! separate IN and OUT interrupt lines.
6
7use core::marker::PhantomData;
8
9use crate::{
10    RegisterToggle,
11    asynch::AtomicWaker,
12    dma::*,
13    handler,
14    interrupt::Priority,
15    peripherals::{AXI_GDMA, Interrupt, pac},
16    system::{Peripheral, PeripheralGuard},
17};
18
19/// Immutable per-channel metadata owned by each `AXI_DMA_CH*` singleton.
20pub(crate) struct ChannelInfo {
21    pub(crate) channel: u8,
22    pub(crate) handler_in: Option<InterruptHandler>,
23    pub(crate) handler_out: Option<InterruptHandler>,
24    pub(crate) isr_in: Option<Interrupt>,
25    pub(crate) isr_out: Option<Interrupt>,
26    pub(crate) compatible_peripherals: &'static [u8],
27}
28
29/// Mutable per-channel runtime state (wakers).
30pub(crate) struct ChannelState {
31    pub(crate) tx_waker: AtomicWaker,
32    pub(crate) rx_waker: AtomicWaker,
33}
34
35/// An arbitrary AXI-GDMA channel.
36pub struct AxiGdmaChannel<'d> {
37    info: &'static ChannelInfo,
38    state: &'static ChannelState,
39    _lifetime: PhantomData<&'d mut ()>,
40}
41
42impl core::fmt::Debug for AxiGdmaChannel<'_> {
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        f.debug_struct("AxiGdmaChannel")
45            .field("channel", &self.info.channel)
46            .finish()
47    }
48}
49
50#[cfg(feature = "defmt")]
51impl defmt::Format for AxiGdmaChannel<'_> {
52    fn format(&self, fmt: defmt::Formatter<'_>) {
53        defmt::write!(fmt, "AxiGdmaChannel {{ channel: {} }}", self.info.channel)
54    }
55}
56
57impl AxiGdmaChannel<'_> {
58    pub(crate) fn channel_index(&self) -> u8 {
59        self.info.channel
60    }
61
62    pub(crate) unsafe fn clone_unchecked(&self) -> Self {
63        Self {
64            info: self.info,
65            state: self.state,
66            _lifetime: PhantomData,
67        }
68    }
69}
70
71impl crate::private::Sealed for AxiGdmaChannel<'_> {}
72impl<'d> DmaChannel for AxiGdmaChannel<'d> {
73    type Rx = AxiGdmaRxChannel<'d>;
74    type Tx = AxiGdmaTxChannel<'d>;
75
76    unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
77        (
78            AxiGdmaRxChannel(unsafe { self.clone_unchecked() }),
79            AxiGdmaTxChannel(self),
80        )
81    }
82}
83
84/// An arbitrary AXI-GDMA RX channel.
85#[derive(Debug)]
86#[cfg_attr(feature = "defmt", derive(defmt::Format))]
87pub struct AxiGdmaRxChannel<'d>(AxiGdmaChannel<'d>);
88
89/// An arbitrary AXI-GDMA TX channel.
90#[derive(Debug)]
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
92pub struct AxiGdmaTxChannel<'d>(AxiGdmaChannel<'d>);
93
94impl crate::private::Sealed for AxiGdmaTxChannel<'_> {}
95impl DmaTxChannel for AxiGdmaTxChannel<'_> {}
96
97impl crate::private::Sealed for AxiGdmaRxChannel<'_> {}
98impl DmaRxChannel for AxiGdmaRxChannel<'_> {}
99
100impl<'d> From<AxiGdmaChannel<'d>> for AxiGdmaRxChannel<'d> {
101    fn from(this: AxiGdmaChannel<'d>) -> AxiGdmaRxChannel<'d> {
102        AxiGdmaRxChannel(this)
103    }
104}
105
106impl<'d> From<AxiGdmaChannel<'d>> for AxiGdmaTxChannel<'d> {
107    fn from(this: AxiGdmaChannel<'d>) -> AxiGdmaTxChannel<'d> {
108        AxiGdmaTxChannel(this)
109    }
110}
111
112impl AxiGdmaTxChannel<'_> {
113    #[inline(always)]
114    fn ch(&self) -> &pac::axi_dma::OUT_CH {
115        AXI_GDMA::regs().out_ch(self.0.info.channel as usize)
116    }
117}
118
119impl RegisterAccess for AxiGdmaTxChannel<'_> {
120    #[allow(private_interfaces)]
121    fn enable(&self) -> Option<PeripheralGuard> {
122        Some(PeripheralGuard::new_with(
123            Peripheral::AxiGdma,
124            init_axi_dma_racey,
125        ))
126    }
127
128    fn reset(&self) {
129        self.ch().out_conf0().toggle(|w, en| w.out_rst().bit(en));
130    }
131
132    // AXI-DMA data burst is always enabled; nothing to configure.
133    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
134
135    fn set_descr_burst_mode(&self, burst_mode: bool) {
136        self.ch()
137            .out_conf0()
138            .modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
139    }
140
141    fn set_priority(&self, priority: DmaPriority) {
142        self.ch()
143            .out_pri()
144            .write(|w| unsafe { w.tx_pri().bits(priority as u8) });
145    }
146
147    fn set_peripheral(&self, peripheral: u8) {
148        self.ch()
149            .out_peri_sel()
150            .write(|w| unsafe { w.peri_out_sel().bits(peripheral) });
151    }
152
153    fn set_link_addr(&self, address: u32) {
154        trace!("Setting out-link address to 0x{:08X}", address);
155        self.ch()
156            .out_link2()
157            .write(|w| unsafe { w.outlink_addr().bits(address) });
158    }
159
160    fn start(&self) {
161        self.ch()
162            .out_link1()
163            .modify(|_, w| w.outlink_start().set_bit());
164    }
165
166    fn stop(&self) {
167        self.ch()
168            .out_link1()
169            .modify(|_, w| w.outlink_stop().set_bit());
170    }
171
172    fn restart(&self) {
173        self.ch()
174            .out_link1()
175            .modify(|_, w| w.outlink_restart().set_bit());
176    }
177
178    fn set_check_owner(&self, check_owner: Option<bool>) {
179        self.ch()
180            .out_conf1()
181            .modify(|_, w| w.out_check_owner().bit(check_owner.unwrap_or(true)));
182    }
183
184    #[cfg(dma_can_access_psram)]
185    fn can_access_psram(&self) -> bool {
186        true
187    }
188
189    fn compatible_peripherals(&self) -> &[u8] {
190        self.0.info.compatible_peripherals
191    }
192}
193
194impl TxRegisterAccess for AxiGdmaTxChannel<'_> {
195    fn is_fifo_empty(&self) -> bool {
196        self.ch()
197            .outfifo_status()
198            .read()
199            .outfifo_l3_empty()
200            .bit_is_set()
201    }
202
203    fn set_auto_write_back(&self, enable: bool) {
204        self.ch()
205            .out_conf0()
206            .modify(|_, w| w.out_auto_wrback().bit(enable));
207    }
208
209    fn last_dscr_address(&self) -> usize {
210        self.ch()
211            .out_eof_des_addr()
212            .read()
213            .out_eof_des_addr()
214            .bits() as _
215    }
216
217    fn async_handler(&self) -> Option<InterruptHandler> {
218        self.0.info.handler_out
219    }
220
221    fn peripheral_interrupt(&self) -> Option<Interrupt> {
222        self.0.info.isr_out
223    }
224}
225
226impl InterruptAccess<DmaTxInterrupt> for AxiGdmaTxChannel<'_> {
227    fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
228        self.ch().out_int().ena().modify(|_, w| {
229            for interrupt in interrupts {
230                match interrupt {
231                    DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
232                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().bit(enable),
233                    DmaTxInterrupt::Eof => w.out_eof().bit(enable),
234                    DmaTxInterrupt::Done => w.out_done().bit(enable),
235                };
236            }
237            w
238        });
239    }
240
241    fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
242        let mut result = EnumSet::new();
243        let ena = self.ch().out_int().ena().read();
244        if ena.out_total_eof().bit_is_set() {
245            result |= DmaTxInterrupt::TotalEof;
246        }
247        if ena.out_dscr_err().bit_is_set() {
248            result |= DmaTxInterrupt::DescriptorError;
249        }
250        if ena.out_eof().bit_is_set() {
251            result |= DmaTxInterrupt::Eof;
252        }
253        if ena.out_done().bit_is_set() {
254            result |= DmaTxInterrupt::Done;
255        }
256        result
257    }
258
259    fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
260        self.ch().out_int().clr().write(|w| {
261            for interrupt in interrupts.into() {
262                match interrupt {
263                    DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
264                    DmaTxInterrupt::DescriptorError => w.out_dscr_err().clear_bit_by_one(),
265                    DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
266                    DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
267                };
268            }
269            w
270        });
271    }
272
273    fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
274        let mut result = EnumSet::new();
275        let raw = self.ch().out_int().raw().read();
276        if raw.out_total_eof().bit_is_set() {
277            result |= DmaTxInterrupt::TotalEof;
278        }
279        if raw.out_dscr_err().bit_is_set() {
280            result |= DmaTxInterrupt::DescriptorError;
281        }
282        if raw.out_eof().bit_is_set() {
283            result |= DmaTxInterrupt::Eof;
284        }
285        if raw.out_done().bit_is_set() {
286            result |= DmaTxInterrupt::Done;
287        }
288        result
289    }
290
291    fn waker(&self) -> &'static AtomicWaker {
292        &self.0.state.tx_waker
293    }
294
295    fn is_async(&self) -> bool {
296        true
297    }
298
299    fn set_async(&self, _is_async: bool) {}
300}
301
302impl AxiGdmaRxChannel<'_> {
303    #[inline(always)]
304    fn ch(&self) -> &pac::axi_dma::IN_CH {
305        AXI_GDMA::regs().in_ch(self.0.info.channel as usize)
306    }
307}
308
309impl RegisterAccess for AxiGdmaRxChannel<'_> {
310    #[allow(private_interfaces)]
311    fn enable(&self) -> Option<PeripheralGuard> {
312        Some(PeripheralGuard::new_with(
313            Peripheral::AxiGdma,
314            init_axi_dma_racey,
315        ))
316    }
317
318    fn reset(&self) {
319        self.ch().in_conf0().toggle(|w, en| w.in_rst().bit(en));
320    }
321
322    // AXI-DMA data burst is always enabled; nothing to configure.
323    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
324
325    fn set_descr_burst_mode(&self, burst_mode: bool) {
326        self.ch()
327            .in_conf0()
328            .modify(|_, w| w.indscr_burst_en().bit(burst_mode));
329    }
330
331    fn set_priority(&self, priority: DmaPriority) {
332        self.ch()
333            .in_pri()
334            .write(|w| unsafe { w.rx_pri().bits(priority as u8) });
335    }
336
337    fn set_peripheral(&self, peripheral: u8) {
338        self.ch()
339            .in_peri_sel()
340            .write(|w| unsafe { w.peri_in_sel().bits(peripheral) });
341    }
342
343    fn set_link_addr(&self, address: u32) {
344        trace!("Setting in-link address to 0x{:08X}", address);
345        self.ch()
346            .in_link2()
347            .write(|w| unsafe { w.inlink_addr().bits(address) });
348    }
349
350    fn start(&self) {
351        self.ch()
352            .in_link1()
353            .modify(|_, w| w.inlink_start().set_bit());
354    }
355
356    fn stop(&self) {
357        self.ch()
358            .in_link1()
359            .modify(|_, w| w.inlink_stop().set_bit());
360    }
361
362    fn restart(&self) {
363        self.ch()
364            .in_link1()
365            .modify(|_, w| w.inlink_restart().set_bit());
366    }
367
368    fn set_check_owner(&self, check_owner: Option<bool>) {
369        self.ch()
370            .in_conf1()
371            .modify(|_, w| w.in_check_owner().bit(check_owner.unwrap_or(true)));
372    }
373
374    #[cfg(dma_can_access_psram)]
375    fn can_access_psram(&self) -> bool {
376        true
377    }
378
379    fn compatible_peripherals(&self) -> &[u8] {
380        self.0.info.compatible_peripherals
381    }
382}
383
384impl RxRegisterAccess for AxiGdmaRxChannel<'_> {
385    #[cfg(dma_supports_mem2mem)]
386    fn set_mem2mem_mode(&self, value: bool) {
387        self.ch()
388            .in_conf0()
389            .modify(|_, w| w.mem_trans_en().bit(value));
390    }
391
392    fn async_handler(&self) -> Option<InterruptHandler> {
393        self.0.info.handler_in
394    }
395
396    fn peripheral_interrupt(&self) -> Option<Interrupt> {
397        self.0.info.isr_in
398    }
399}
400
401impl InterruptAccess<DmaRxInterrupt> for AxiGdmaRxChannel<'_> {
402    fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
403        self.ch().in_int().ena().modify(|_, w| {
404            for interrupt in interrupts {
405                match interrupt {
406                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
407                    DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
408                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().bit(enable),
409                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().bit(enable),
410                    DmaRxInterrupt::Done => w.in_done().bit(enable),
411                };
412            }
413            w
414        });
415    }
416
417    fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
418        let mut result = EnumSet::new();
419        let ena = self.ch().in_int().ena().read();
420        if ena.in_suc_eof().bit_is_set() {
421            result |= DmaRxInterrupt::SuccessfulEof;
422        }
423        if ena.in_err_eof().bit_is_set() {
424            result |= DmaRxInterrupt::ErrorEof;
425        }
426        if ena.in_dscr_err().bit_is_set() {
427            result |= DmaRxInterrupt::DescriptorError;
428        }
429        if ena.in_dscr_empty().bit_is_set() {
430            result |= DmaRxInterrupt::DescriptorEmpty;
431        }
432        if ena.in_done().bit_is_set() {
433            result |= DmaRxInterrupt::Done;
434        }
435        result
436    }
437
438    fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
439        self.ch().in_int().clr().write(|w| {
440            for interrupt in interrupts.into() {
441                match interrupt {
442                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
443                    DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
444                    DmaRxInterrupt::DescriptorError => w.in_dscr_err().clear_bit_by_one(),
445                    DmaRxInterrupt::DescriptorEmpty => w.in_dscr_empty().clear_bit_by_one(),
446                    DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
447                };
448            }
449            w
450        });
451    }
452
453    fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
454        let mut result = EnumSet::new();
455        let raw = self.ch().in_int().raw().read();
456        if raw.in_suc_eof().bit_is_set() {
457            result |= DmaRxInterrupt::SuccessfulEof;
458        }
459        if raw.in_err_eof().bit_is_set() {
460            result |= DmaRxInterrupt::ErrorEof;
461        }
462        if raw.in_dscr_err().bit_is_set() {
463            result |= DmaRxInterrupt::DescriptorError;
464        }
465        if raw.in_dscr_empty().bit_is_set() {
466            result |= DmaRxInterrupt::DescriptorEmpty;
467        }
468        if raw.in_done().bit_is_set() {
469            result |= DmaRxInterrupt::Done;
470        }
471        result
472    }
473
474    fn waker(&self) -> &'static AtomicWaker {
475        &self.0.state.rx_waker
476    }
477
478    fn is_async(&self) -> bool {
479        true
480    }
481
482    fn set_async(&self, _is_async: bool) {}
483}
484
485macro_rules! impl_channel {
486    ($ch:ident, $num:literal, $interrupt_in:ident, $interrupt_out:ident, compatible = [$($compatible:ident),*]) => {
487        use $crate::peripherals::$ch;
488        impl $ch<'_> {
489            pub(super) fn info() -> &'static ChannelInfo {
490                #[handler(priority = Priority::max())]
491                fn interrupt_handler_in() {
492                    asynch::handle_in_interrupt::<$ch<'static>>();
493                }
494
495                #[handler(priority = Priority::max())]
496                fn interrupt_handler_out() {
497                    asynch::handle_out_interrupt::<$ch<'static>>();
498                }
499
500                static INFO: ChannelInfo = ChannelInfo {
501                    channel: $num,
502                    handler_in: Some(interrupt_handler_in),
503                    handler_out: Some(interrupt_handler_out),
504                    isr_in: Some(Interrupt::$interrupt_in),
505                    isr_out: Some(Interrupt::$interrupt_out),
506                    compatible_peripherals: &[$(crate::dma::DmaPeripheral::$compatible.0),*],
507                };
508                &INFO
509            }
510
511            pub(super) fn state() -> &'static ChannelState {
512                static STATE: ChannelState = ChannelState {
513                    tx_waker: AtomicWaker::new(),
514                    rx_waker: AtomicWaker::new(),
515                };
516                &STATE
517            }
518        }
519
520        impl<'d> From<$ch<'d>> for AxiGdmaChannel<'d> {
521            fn from(_ch: $ch<'d>) -> AxiGdmaChannel<'d> {
522                AxiGdmaChannel {
523                    info: $ch::info(),
524                    state: $ch::state(),
525                    _lifetime: core::marker::PhantomData,
526                }
527            }
528        }
529        crate::dma::impl_channel_common!(AxiGdma, $ch);
530    };
531}
532
533for_each_dma_channel! {
534    ("AXI_GDMA", $ch:ident, $num:literal, interrupt_in = $interrupt_in:ident, interrupt_out = $interrupt_out:ident, compatible = [$($compatible:ident),*]) => {
535        impl_channel!($ch, $num, $interrupt_in, $interrupt_out, compatible = [$($compatible),*]);
536    };
537}
538
539fn init_axi_dma_racey() {
540    let regs = AXI_GDMA::regs();
541
542    // Reset the AXI master read and write FSMs.
543    regs.misc_conf().toggle(|w, en| {
544        w.axim_rst_rd_inter().bit(en);
545        w.axim_rst_wr_inter().bit(en)
546    });
547
548    regs.misc_conf().modify(|_, w| w.clk_en().set_bit());
549
550    // AXI-DMA can access internal RAM, ROM, MSPI Flash and MSPI PSRAM.
551    // TODO: move memory window limits to device metadata
552    cfg_select! {
553        esp32s31 => {
554            regs.intr_mem_start_addr()
555                .write(|w| unsafe { w.access_intr_mem_start_addr().bits(0x2F00_0000) });
556            regs.intr_mem_end_addr()
557                .write(|w| unsafe { w.access_intr_mem_end_addr().bits(0x2F07_FFFF) });
558            regs.extr_mem_start_addr()
559                .write(|w| unsafe { w.access_extr_mem_start_addr().bits(0x4000_0000) });
560            regs.extr_mem_end_addr()
561                .write(|w| unsafe { w.access_extr_mem_end_addr().bits(0x53FF_FFFF) });
562        }
563        _ => {
564            regs.intr_mem_start_addr()
565                .write(|w| unsafe { w.access_intr_mem_start_addr().bits(0x4FC0_0000) });
566            regs.intr_mem_end_addr()
567                .write(|w| unsafe { w.access_intr_mem_end_addr().bits(0x4FFC_0000) });
568            regs.extr_mem_start_addr()
569                .write(|w| unsafe { w.access_extr_mem_start_addr().bits(0x4000_0000) });
570            regs.extr_mem_end_addr()
571                .write(|w| unsafe { w.access_extr_mem_end_addr().bits(0x4C00_0000) });
572        }
573    }
574}