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}