Skip to main content

esp_hal/dma/engine/gdma/
mod.rs

1//! # General Direct Memory Access (GDMA)
2//!
3//! ## Overview
4//! GDMA is a feature that allows peripheral-to-memory, memory-to-peripheral,
5//! and memory-to-memory data transfer at high speed. The CPU is not involved in
6//! the GDMA transfer and therefore is more efficient with less workload.
7//!
8//! The `GDMA` module provides multiple DMA channels, each capable of managing
9//! data transfer for various peripherals.
10
11use core::marker::PhantomData;
12
13use crate::{
14    asynch::AtomicWaker,
15    dma::*,
16    handler,
17    interrupt::Priority,
18    peripherals::{DMA, Interrupt, pac},
19    system::{Peripheral, PeripheralGuard},
20};
21
22#[cfg_attr(dma_gdma_version = "1", path = "ahb_v1.rs")]
23#[cfg_attr(dma_gdma_version = "2", path = "ahb_v2.rs")]
24#[cfg_attr(dma_gdma_version = "3", path = "ahb_v3.rs")]
25mod implementation;
26
27/// Immutable per-channel metadata owned by each `DMA_CH*` singleton.
28pub(crate) struct ChannelInfo {
29    /// Hardware channel index used to select the register bank.
30    pub(crate) channel: u8,
31    /// Interrupt handler for the RX (in) direction.
32    pub(crate) handler_in: Option<InterruptHandler>,
33    /// Interrupt handler for the TX (out) direction.
34    pub(crate) handler_out: Option<InterruptHandler>,
35    /// Peripheral interrupt for the RX (in) direction.
36    pub(crate) isr_in: Option<Interrupt>,
37    /// Peripheral interrupt for the TX (out) direction.
38    pub(crate) isr_out: Option<Interrupt>,
39    /// List of compatible peripheral IDs for this channel.
40    pub(crate) compatible_peripherals: &'static [u8],
41}
42
43/// Mutable per-channel runtime state (wakers and async-mode flags).
44pub(crate) struct ChannelState {
45    /// Async waker for the TX (out) half of this channel.
46    pub(crate) tx_waker: AtomicWaker,
47
48    /// Async waker for the RX (in) half of this channel.
49    pub(crate) rx_waker: AtomicWaker,
50
51    /// Whether the TX half is currently in async mode (shared-interrupt chips only).
52    #[cfg(not(dma_separate_in_out_interrupts))]
53    pub(crate) tx_is_async: portable_atomic::AtomicBool,
54
55    /// Whether the RX half is currently in async mode (shared-interrupt chips only).
56    #[cfg(not(dma_separate_in_out_interrupts))]
57    pub(crate) rx_is_async: portable_atomic::AtomicBool,
58}
59
60/// An arbitrary GDMA channel
61pub struct AhbGdmaChannel<'d> {
62    info: &'static ChannelInfo,
63    state: &'static ChannelState,
64    _lifetime: PhantomData<&'d mut ()>,
65}
66
67impl core::fmt::Debug for AhbGdmaChannel<'_> {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        f.debug_struct("AhbGdmaChannel")
70            .field("channel", &self.info.channel)
71            .finish()
72    }
73}
74
75#[cfg(feature = "defmt")]
76impl defmt::Format for AhbGdmaChannel<'_> {
77    fn format(&self, fmt: defmt::Formatter<'_>) {
78        defmt::write!(fmt, "AhbGdmaChannel {{ channel: {} }}", self.info.channel)
79    }
80}
81
82impl AhbGdmaChannel<'_> {
83    #[cfg(not(dma_mem2mem_requires_peripheral))]
84    pub(crate) fn channel_index(&self) -> u8 {
85        self.info.channel
86    }
87
88    pub(crate) unsafe fn clone_unchecked(&self) -> Self {
89        Self {
90            info: self.info,
91            state: self.state,
92            _lifetime: PhantomData,
93        }
94    }
95}
96
97impl crate::private::Sealed for AhbGdmaChannel<'_> {}
98impl<'d> DmaChannel for AhbGdmaChannel<'d> {
99    type Rx = AhbGdmaRxChannel<'d>;
100    type Tx = AhbGdmaTxChannel<'d>;
101
102    unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
103        (
104            AhbGdmaRxChannel(unsafe { self.clone_unchecked() }),
105            AhbGdmaTxChannel(self),
106        )
107    }
108}
109
110/// An arbitrary GDMA RX channel
111#[derive(Debug)]
112#[cfg_attr(feature = "defmt", derive(defmt::Format))]
113pub struct AhbGdmaRxChannel<'d>(AhbGdmaChannel<'d>);
114
115/// An arbitrary GDMA TX channel
116#[derive(Debug)]
117#[cfg_attr(feature = "defmt", derive(defmt::Format))]
118pub struct AhbGdmaTxChannel<'d>(AhbGdmaChannel<'d>);
119
120impl crate::private::Sealed for AhbGdmaTxChannel<'_> {}
121impl DmaTxChannel for AhbGdmaTxChannel<'_> {}
122
123impl crate::private::Sealed for AhbGdmaRxChannel<'_> {}
124impl DmaRxChannel for AhbGdmaRxChannel<'_> {}
125
126macro_rules! impl_channel {
127    // Single shared interrupt: one handler drives both the in and out paths.
128    ($ch:ident, $num:literal, $interrupt_in:ident, compatible = [$($compatible:ident),*]) => {
129        use $crate::peripherals::$ch;
130        impl $ch<'_> {
131            pub(super) fn info() -> &'static ChannelInfo {
132                #[handler(priority = Priority::max())]
133                fn interrupt_handler() {
134                    asynch::handle_in_interrupt::<$ch<'static>>();
135                    asynch::handle_out_interrupt::<$ch<'static>>();
136                }
137                static INFO: ChannelInfo = ChannelInfo {
138                    channel: $num,
139                    handler_in: Some(interrupt_handler),
140                    handler_out: None,
141                    isr_in: Some(Interrupt::$interrupt_in),
142                    isr_out: None,
143                    compatible_peripherals: &[$(crate::dma::DmaPeripheral::$compatible.0),*],
144                };
145                &INFO
146            }
147
148            pub(super) fn state() -> &'static ChannelState {
149                static STATE: ChannelState = ChannelState {
150                    tx_waker: AtomicWaker::new(),
151                    rx_waker: AtomicWaker::new(),
152                    tx_is_async: portable_atomic::AtomicBool::new(false),
153                    rx_is_async: portable_atomic::AtomicBool::new(false),
154                };
155                &STATE
156            }
157        }
158
159        impl<'d> From<$ch<'d>> for AhbGdmaChannel<'d> {
160            fn from(_ch: $ch<'d>) -> AhbGdmaChannel<'d> {
161                AhbGdmaChannel {
162                    info: $ch::info(),
163                    state: $ch::state(),
164                    _lifetime: core::marker::PhantomData,
165                }
166            }
167        }
168        crate::dma::impl_channel_common!(AhbGdma, $ch);
169    };
170
171    // Split interrupts: separate handlers for the in and out paths.
172    ($ch:ident, $num:literal, $interrupt_in:ident, $interrupt_out:ident, compatible = [$($compatible:ident),*]) => {
173        use $crate::peripherals::$ch;
174        impl $ch<'_> {
175            pub(super) fn info() -> &'static ChannelInfo {
176                #[handler(priority = Priority::max())]
177                fn interrupt_handler_in() {
178                    asynch::handle_in_interrupt::<$ch<'static>>();
179                }
180
181                #[handler(priority = Priority::max())]
182                fn interrupt_handler_out() {
183                    asynch::handle_out_interrupt::<$ch<'static>>();
184                }
185
186                static INFO: ChannelInfo = ChannelInfo {
187                    channel: $num,
188                    handler_in: Some(interrupt_handler_in),
189                    handler_out: Some(interrupt_handler_out),
190                    isr_in: Some(Interrupt::$interrupt_in),
191                    isr_out: Some(Interrupt::$interrupt_out),
192                    compatible_peripherals: &[$(crate::dma::DmaPeripheral::$compatible.0),*],
193                };
194                &INFO
195            }
196
197            pub(super) fn state() -> &'static ChannelState {
198                static STATE: ChannelState = ChannelState {
199                    tx_waker: AtomicWaker::new(),
200                    rx_waker: AtomicWaker::new(),
201                };
202                &STATE
203            }
204        }
205
206        impl<'d> From<$ch<'d>> for AhbGdmaChannel<'d> {
207            fn from(_ch: $ch<'d>) -> AhbGdmaChannel<'d> {
208                AhbGdmaChannel {
209                    info: $ch::info(),
210                    state: $ch::state(),
211                    _lifetime: core::marker::PhantomData,
212                }
213            }
214        }
215        crate::dma::impl_channel_common!(AhbGdma, $ch);
216    };
217}
218
219// Convert erased channel into erased TX/RX half structs
220impl<'d> From<AhbGdmaChannel<'d>> for AhbGdmaRxChannel<'d> {
221    fn from(this: AhbGdmaChannel<'d>) -> AhbGdmaRxChannel<'d> {
222        AhbGdmaRxChannel(this)
223    }
224}
225
226impl<'d> From<AhbGdmaChannel<'d>> for AhbGdmaTxChannel<'d> {
227    fn from(this: AhbGdmaChannel<'d>) -> AhbGdmaTxChannel<'d> {
228        AhbGdmaTxChannel(this)
229    }
230}
231
232for_each_dma_channel! {
233    ("AHB_GDMA", $ch:ident, $num:literal, interrupt = $interrupt:ident, compatible = [$($compatible:ident),*]) => {
234        impl_channel!($ch, $num, $interrupt, compatible = [$($compatible),*]);
235    };
236    ("AHB_GDMA", $ch:ident, $num:literal, interrupt_in = $interrupt_in:ident, interrupt_out = $interrupt_out:ident, compatible = [$($compatible:ident),*]) => {
237        impl_channel!($ch, $num, $interrupt_in, $interrupt_out, compatible = [$($compatible),*]);
238    };
239}
240
241fn init_dma_racey() {
242    // FIXME: reset/clock enable belongs to metadata
243    use crate::RegisterToggle;
244    DMA::regs()
245        .misc_conf()
246        .toggle(|w, en| w.ahbm_rst_inter().bit(en));
247    DMA::regs().misc_conf().modify(|_, w| w.clk_en().set_bit());
248
249    implementation::setup();
250}