Skip to main content

gd32e2_hal/
dma.rs

1//! Direct memory access: five independent channels moving data without the core.
2//!
3//! [`DmaExt::split`] consumes the peripheral and hands out one [`Channel`] per
4//! hardware channel:
5//!
6//! ```ignore
7//! let channels = dp.dma.split(&mut rcu);
8//! let transfer = channels.ch1.write_to(usart, b"hello", Prio::Low);
9//! let (ch1, usart, _) = transfer.wait();
10//! ```
11
12use core::marker::PhantomData;
13use core::mem::ManuallyDrop;
14use core::ptr;
15
16use crate::adc::Adc;
17use crate::pac;
18use crate::rcu::{Enable, Rcu};
19use crate::spi::{self, Spi};
20use crate::usart::{self, Usart};
21
22/// Channel priority when several channels contend for the bus.
23///
24/// Affects only arbitration latency, never the correctness of a single transfer.
25#[derive(Clone, Copy, PartialEq, Eq)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27#[allow(missing_docs)]
28pub enum Prio {
29    Low,
30    Medium,
31    High,
32    UltraHigh,
33}
34
35/// Transfer direction, selecting the `DIR` bit.
36pub(crate) enum Dir {
37    /// Peripheral to memory.
38    P2M,
39    /// Memory to peripheral.
40    M2P,
41}
42
43/// Width of one transferred unit, selecting `PWIDTH`/`MWIDTH`.
44#[derive(Clone, Copy, PartialEq, Eq)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46#[allow(missing_docs)]
47pub enum Width {
48    Bits8,
49    Bits16,
50    Bits32,
51}
52
53/// One DMA channel, identified by its number at the type level.
54pub struct Channel<const N: u8> {
55    _marker: PhantomData<()>,
56}
57
58impl<const N: u8> Channel<N> {
59    #[allow(clippy::unused_self)]
60    fn reg(&self) -> &pac::dma::RegisterBlock {
61        // SAFETY: the block lives at a fixed address for the whole program; a
62        // channel touches its own registers and its own bits of the shared ones.
63        unsafe { &*pac::Dma::ptr() }
64    }
65}
66
67/// The raw per-channel register operations, generated by the `channels!` macro.
68pub(crate) trait ChannelOps {
69    fn configure(&mut self, dir: Dir, width: Width, prio: Prio);
70    fn set_paddr(&mut self, addr: u32);
71    fn set_maddr(&mut self, addr: u32);
72    fn set_cnt(&mut self, cnt: u16);
73    fn cnt(&self) -> u16;
74    fn set_enabled(&mut self, enabled: bool);
75    fn is_complete(&self) -> bool;
76    fn is_error(&self) -> bool;
77    fn clear_flags(&mut self);
78}
79
80macro_rules! channels {
81    ($($N:literal),+ $(,)?) => {
82        paste::paste! { $(
83            impl ChannelOps for Channel<$N> {
84                /// Writes the whole of `CHxCTL` in one go: direction, width,
85                /// priority, memory increment on, peripheral increment off, and
86                /// M2M/circular/interrupts off.
87                ///
88                /// A `write`, not a `modify`, so leftover bits from an earlier use
89                /// reset to zero. Must run while `CHEN` is 0.
90                #[inline]
91                fn configure(&mut self, dir: Dir, width: Width, prio: Prio) {
92                    self.reg().[<ch $N ctl>]().write(|w| {
93                        let w = w.pnaga()
94                                 .fixed()
95                                 .mnaga()
96                                 .increment()
97                                 .m2m()
98                                 .disabled()
99                                 .cmen()
100                                 .disabled()
101                                 .errie()
102                                 .disabled()
103                                 .htfie()
104                                 .disabled()
105                                 .ftfie()
106                                 .disabled();
107                        let w = match dir {
108                            Dir::P2M => w.dir().from_peripheral(),
109                            Dir::M2P => w.dir().from_memory(),
110                        };
111                        let w = match width {
112                            Width::Bits8 => w.mwidth().bits8().pwidth().bits8(),
113                            Width::Bits16 => w.mwidth().bits16().pwidth().bits16(),
114                            Width::Bits32 => w.mwidth().bits32().pwidth().bits32(),
115                        };
116                        match prio {
117                            Prio::Low => w.prio().low(),
118                            Prio::Medium => w.prio().medium(),
119                            Prio::High => w.prio().high(),
120                            Prio::UltraHigh => w.prio().very_high(),
121                        }
122                    });
123                }
124                /// Points the channel at the peripheral data register.
125                #[inline]
126                fn set_paddr(&mut self, addr: u32) {
127                    // SAFETY: `addr` comes from `DmaPeriph::addr`, a data register.
128                    self.reg().[<ch $N paddr>]().write(|w| unsafe { w.bits(addr) });
129                }
130                /// Points the channel at the memory buffer.
131                #[inline]
132                fn set_maddr(&mut self, addr: u32) {
133                    // SAFETY: `addr` comes from a `'static` buffer of the
134                    // transfer's word type.
135                    self.reg().[<ch $N maddr>]().write(|w| unsafe { w.bits(addr) });
136                }
137                /// Sets how many transfers the channel performs.
138                #[inline]
139                fn set_cnt(&mut self, cnt: u16) {
140                    self.reg().[<ch $N cnt>]().write(|w| w.cnt().bits(cnt));
141                }
142                /// Transfers still outstanding; counts down as the channel runs.
143                #[inline]
144                fn cnt(&self) -> u16 {
145                    self.reg().[<ch $N cnt>]().read().cnt().bits()
146                }
147                /// Starts or stops the channel.
148                #[inline]
149                fn set_enabled(&mut self, enabled: bool) {
150                    self.reg().[<ch $N ctl>]().modify(|_, w| w.chen().bit(enabled));
151                }
152                /// Whether the channel has finished its last transfer.
153                #[inline]
154                fn is_complete(&self) -> bool {
155                    self.reg().intf().read().[<ftfif $N>]().bit_is_set()
156                }
157                /// Whether the channel hit a bus error.
158                #[inline]
159                fn is_error(&self) -> bool {
160                    self.reg().intf().read().[<errif $N>]().bit_is_set()
161                }
162                /// Clears every interrupt flag of this channel.
163                #[inline]
164                fn clear_flags(&mut self) {
165                    self.reg().intc().write(|w| w.[<gifc $N>]().set_bit());
166                }
167            }
168        )+ }
169    };
170}
171
172channels!(0, 1, 2, 3, 4);
173
174/// A type usable as a DMA transfer unit, mapping to its [`Width`].
175///
176/// Bounds the peripheral's word type so only `u8`/`u16`/`u32` qualify, and
177/// carries the [`Width`] that a channel writes into `PWIDTH`/`MWIDTH`.
178pub trait DmaWord {
179    /// The transfer width of this type.
180    const WIDTH: Width;
181}
182
183impl DmaWord for u8 {
184    const WIDTH: Width = Width::Bits8;
185}
186impl DmaWord for u16 {
187    const WIDTH: Width = Width::Bits16;
188}
189impl DmaWord for u32 {
190    const WIDTH: Width = Width::Bits32;
191}
192
193/// Everything a channel needs from the peripheral on the other end of it.
194pub trait DmaPeriph<const N: u8> {
195    /// The word type this peripheral transfers, fixing `PWIDTH`/`MWIDTH`.
196    type Word: DmaWord;
197    /// Address of the peripheral's data register.
198    fn addr(&self) -> u32;
199    /// Lets the peripheral raise DMA requests on its channel.
200    ///
201    /// Until this is called the channel is configured but never asked to move
202    /// anything, so a transfer would sit at its full count forever.
203    fn enable_dma(&mut self);
204    /// Stops the peripheral raising DMA requests.
205    ///
206    /// Called before the peripheral goes back to its owner, so it is handed over
207    /// in the state it arrived in rather than still driving a dead channel.
208    fn disable_dma(&mut self);
209}
210
211/// A peripheral channel `N` can read from, for peripheral-to-memory transfers.
212pub trait DmaSrc<const N: u8>: DmaPeriph<N> {}
213
214/// A peripheral channel `N` can write to, for memory-to-peripheral transfers.
215pub trait DmaDst<const N: u8>: DmaPeriph<N> {}
216
217// Request mapping, Table 8-3. Entries marked (1) apply while the SYSCFG_CFG0
218// remap bit is clear (the reset state), (2) while it is set. Only (1) is
219// implemented: the two are mutually exclusive at runtime, so implementing both
220// would let either choice compile while one is wired. Requests with no impl yet
221// are listed for completeness.
222//
223//   Ch0: ADC(1), TIMER16_CH0(1), TIMER16_UP(1)
224//   Ch1: ADC(2), SPI/I2S0_RX, USART0_TX(1), I2C0_TX, TIMER0_CH0, TIMER2_CH2,
225//        TIMER16_CH0(2), TIMER16_UP(2)
226//   Ch2: SPI/I2S0_TX, USART0_RX(1), I2C0_RX, TIMER0_CH1, TIMER2_CH3, TIMER2_UP,
227//        TIMER5_UP, TIMER15_CH0(1), TIMER15_UP(1)
228//   Ch3: SPI1_RX, USART0_TX(2), USART1_TX, I2C1_TX, TIMER0_CH3, TIMER0_TRIG,
229//        TIMER0_COM, TIMER2_CH0, TIMER2_TRIG, TIMER15_CH0(2), TIMER15_UP(2)
230//   Ch4: SPI1_TX, USART0_RX(2), USART1_RX, I2C1_RX, TIMER0_CH2, TIMER0_UP,
231//        TIMER14_CH0, TIMER14_UP, TIMER14_TRIG, TIMER14_COM, TIMER14_CH1
232macro_rules! dma_map {
233    ($($Trait:ident<$N:literal> for $Ty:ty, $W:ty, $Periph:ty, $reg:ident,
234                     $ctl:ident, $den:ident, [$($gen:ident),*];)+) => {
235        $(
236            impl<$($gen),*> DmaPeriph<$N> for $Ty {
237                type Word = $W;
238                fn addr(&self) -> u32 {
239                    // SAFETY: the block lives at a fixed address for the whole
240                    // program, and only the register's address is taken.
241                    unsafe { (*<$Periph>::ptr()).$reg().as_ptr() as u32 }
242                }
243                fn enable_dma(&mut self) {
244                    // SAFETY: `self` owns the peripheral, and `&mut self` rules
245                    // out another access to it.
246                    unsafe { (*<$Periph>::ptr()).$ctl().modify(|_, w| w.$den().set_bit()) };
247                }
248                fn disable_dma(&mut self) {
249                    // SAFETY: `self` owns the peripheral, and `&mut self` rules
250                    // out another access to it.
251                    unsafe { (*<$Periph>::ptr()).$ctl().modify(|_, w| w.$den().clear_bit()) };
252                }
253            }
254            impl<$($gen),*> $Trait<$N> for $Ty {}
255        )+
256    };
257}
258
259dma_map! {
260    DmaSrc<0> for Adc, u16, pac::Adc, rdata, ctl1, dma, [];
261
262    DmaSrc<1> for Spi<pac::Spi0, SCK, MISO, MOSI, spi::Byte>, u8, pac::Spi0, data, ctl1, dmaren, [SCK, MISO, MOSI];
263    DmaSrc<1> for Spi<pac::Spi0, SCK, MISO, MOSI, spi::Word>, u16, pac::Spi0, data, ctl1, dmaren, [SCK, MISO, MOSI];
264    DmaDst<1> for Usart<pac::Usart0, TX, RX, usart::Byte>, u8, pac::Usart0, tdata, ctl2, dent, [TX, RX];
265    DmaDst<1> for Usart<pac::Usart0, TX, RX, usart::Word>, u16, pac::Usart0, tdata, ctl2, dent, [TX, RX];
266
267    DmaDst<2> for Spi<pac::Spi0, SCK, MISO, MOSI, spi::Byte>, u8, pac::Spi0, data, ctl1, dmaten, [SCK, MISO, MOSI];
268    DmaDst<2> for Spi<pac::Spi0, SCK, MISO, MOSI, spi::Word>, u16, pac::Spi0, data, ctl1, dmaten, [SCK, MISO, MOSI];
269    DmaSrc<2> for Usart<pac::Usart0, TX, RX, usart::Byte>, u8, pac::Usart0, rdata, ctl2, denr, [TX, RX];
270    DmaSrc<2> for Usart<pac::Usart0, TX, RX, usart::Word>, u16, pac::Usart0, rdata, ctl2, denr, [TX, RX];
271
272    DmaSrc<3> for Spi<pac::Spi1, SCK, MISO, MOSI, spi::Byte>, u8, pac::Spi1, data, ctl1, dmaren, [SCK, MISO, MOSI];
273    DmaSrc<3> for Spi<pac::Spi1, SCK, MISO, MOSI, spi::Word>, u16, pac::Spi1, data, ctl1, dmaren, [SCK, MISO, MOSI];
274    DmaDst<3> for Usart<pac::Usart1, TX, RX, usart::Byte>, u8, pac::Usart1, tdata, ctl2, dent, [TX, RX];
275    DmaDst<3> for Usart<pac::Usart1, TX, RX, usart::Word>, u16, pac::Usart1, tdata, ctl2, dent, [TX, RX];
276
277    DmaDst<4> for Spi<pac::Spi1, SCK, MISO, MOSI, spi::Byte>, u8, pac::Spi1, data, ctl1, dmaten, [SCK, MISO, MOSI];
278    DmaDst<4> for Spi<pac::Spi1, SCK, MISO, MOSI, spi::Word>, u16, pac::Spi1, data, ctl1, dmaten, [SCK, MISO, MOSI];
279    DmaSrc<4> for Usart<pac::Usart1, TX, RX, usart::Byte>, u8, pac::Usart1, rdata, ctl2, denr, [TX, RX];
280    DmaSrc<4> for Usart<pac::Usart1, TX, RX, usart::Word>, u16, pac::Usart1, rdata, ctl2, denr, [TX, RX];
281}
282
283// The bound is what restricts N to the five channels that have an impl.
284#[allow(private_bounds)]
285impl<const N: u8> Channel<N>
286where
287    Channel<N>: ChannelOps,
288{
289    /// Starts a memory-to-peripheral transfer, moving `buf` into `periph`.
290    ///
291    /// All three are taken by value and come back from [`Transfer::wait`]. The
292    /// transfer width follows from the peripheral's word type, so `buf` must
293    /// match it.
294    pub fn write_to<P: DmaDst<N>>(
295        mut self,
296        mut periph: P,
297        buf: &'static [P::Word],
298        prio: Prio,
299    ) -> Transfer<N, P, &'static [P::Word]> {
300        self.set_maddr(buf.as_ptr() as u32);
301        self.set_paddr(periph.addr());
302        self.set_cnt(buf.len() as u16);
303        self.configure(Dir::M2P, <P::Word as DmaWord>::WIDTH, prio);
304        self.set_enabled(true);
305        periph.enable_dma();
306        Transfer {
307            channel: self,
308            periph,
309            buf,
310        }
311    }
312    /// Starts a peripheral-to-memory transfer, filling `buf` from `periph`.
313    ///
314    /// All three are taken by value and come back from [`Transfer::wait`], so
315    /// `buf` cannot be read before the transfer completes. The transfer width
316    /// follows from the peripheral's word type, so `buf` must match it.
317    pub fn read_from<P: DmaSrc<N>>(
318        mut self,
319        mut periph: P,
320        buf: &'static mut [P::Word],
321        prio: Prio,
322    ) -> Transfer<N, P, &'static mut [P::Word]> {
323        self.set_maddr(buf.as_mut_ptr() as u32);
324        self.set_paddr(periph.addr());
325        self.set_cnt(buf.len() as u16);
326        self.configure(Dir::P2M, <P::Word as DmaWord>::WIDTH, prio);
327        self.set_enabled(true);
328        periph.enable_dma();
329        Transfer {
330            channel: self,
331            periph,
332            buf,
333        }
334    }
335}
336
337/// A DMA transfer in progress, started by [`Channel::write_to`] or
338/// [`read_from`](Channel::read_from).
339///
340/// Owns the channel, the peripheral and the buffer for as long as the transfer
341/// runs; [`wait`](Transfer::wait) is the only way to get them back.
342///
343/// Dropping instead of waiting stops the transfer and loses all three parts.
344// The bounds sit on the declaration only because `Drop` demands it: a `Drop` impl
345// may not require more than the type it drops.
346#[allow(private_bounds)]
347#[must_use = "dropping a `Transfer` stops it at once; `wait` lets it finish"]
348pub struct Transfer<const N: u8, P, BUF>
349where
350    Channel<N>: ChannelOps,
351    P: DmaPeriph<N>,
352{
353    channel: Channel<N>,
354    periph: P,
355    buf: BUF,
356}
357
358// The bound is what restricts N to the five channels that have an impl.
359#[allow(private_bounds)]
360impl<const N: u8, P, BUF> Transfer<N, P, BUF>
361where
362    Channel<N>: ChannelOps,
363    P: DmaPeriph<N>,
364{
365    /// Blocks until the transfer completes, then returns the channel, the
366    /// peripheral and the buffer.
367    ///
368    /// Does not check `ERRIF`: every address this API can hand a channel comes
369    /// from a typed source, so a bus error is unreachable through safe code.
370    pub fn wait(mut self) -> (Channel<N>, P, BUF) {
371        while !self.channel.is_complete() {}
372        self.periph.disable_dma();
373        self.channel.set_enabled(false);
374        self.channel.clear_flags();
375        let transfer = ManuallyDrop::new(self);
376        // SAFETY: the parts are read out rather than moved out, because a type
377        // with a `Drop` impl cannot be taken apart. `ManuallyDrop` is what makes
378        // the duplicated ownership sound: the originals are never dropped, never
379        // used again, and go out of scope with this expression.
380        unsafe {
381            (
382                ptr::read(&raw const transfer.channel),
383                ptr::read(&raw const transfer.periph),
384                ptr::read(&raw const transfer.buf),
385            )
386        }
387    }
388    /// How many transfers the channel has left, without waiting for it.
389    ///
390    /// Counts transfers, not bytes: at [`Width::Bits16`] a unit is two bytes on
391    /// the wire. A snapshot only — the hardware keeps counting while the caller
392    /// looks at it, so it serves progress reporting.
393    pub fn remaining(&self) -> u16 {
394        self.channel.cnt()
395    }
396    /// Whether the channel has raised `ERRIF`, having hit a bus error.
397    ///
398    /// Useful next to [`remaining`](Transfer::remaining) to tell a stalled
399    /// channel from a failed one.
400    pub fn is_error(&self) -> bool {
401        self.channel.is_error()
402    }
403}
404
405#[allow(private_bounds)]
406impl<const N: u8, P, BUF> Drop for Transfer<N, P, BUF>
407where
408    Channel<N>: ChannelOps,
409    P: DmaPeriph<N>,
410{
411    /// Stops the transfer where it stands, without waiting for it to finish.
412    ///
413    /// The tail of [`wait`](Transfer::wait) without the blocking: a running
414    /// channel is cut off mid-buffer.
415    fn drop(&mut self) {
416        self.periph.disable_dma();
417        self.channel.set_enabled(false);
418        self.channel.clear_flags();
419    }
420}
421
422/// The DMA channels, as handed out by [`DmaExt::split`].
423#[allow(missing_docs)]
424pub struct Channels {
425    pub ch0: Channel<0>,
426    pub ch1: Channel<1>,
427    pub ch2: Channel<2>,
428    pub ch3: Channel<3>,
429    pub ch4: Channel<4>,
430}
431
432/// Splits the DMA peripheral into its individual channels.
433pub trait DmaExt {
434    /// The channels this peripheral hands out.
435    type Channels;
436
437    /// Switches the DMA clock on and hands out the channels.
438    ///
439    /// Consumes the peripheral, so the channels it returns are the only ones
440    /// that will ever exist.
441    fn split(self, rcu: &mut Rcu) -> Self::Channels;
442}
443
444impl DmaExt for pac::Dma {
445    type Channels = Channels;
446    fn split(self, rcu: &mut Rcu) -> Self::Channels {
447        <pac::Dma as Enable>::enable(rcu);
448        Channels {
449            ch0: Channel {
450                _marker: PhantomData,
451            },
452            ch1: Channel {
453                _marker: PhantomData,
454            },
455            ch2: Channel {
456                _marker: PhantomData,
457            },
458            ch3: Channel {
459                _marker: PhantomData,
460            },
461            ch4: Channel {
462                _marker: PhantomData,
463            },
464        }
465    }
466}