atsamd_hal/sercom/
dma.rs

1//! Use the DMA Controller to perform transfers using the SERCOM peripheral
2//!
3//! See the [`uart`], [`i2c`](crate::sercom::i2c) and
4//! [`spi`](crate::sercom::spi) modules for the corresponding DMA transfer
5//! implementations.
6
7use core::{marker::PhantomData, ops::Range};
8
9use atsamd_hal_macros::hal_macro_helper;
10
11use crate::dmac::{
12    self, Beat, Buffer, Transfer, TriggerAction,
13    channel::{AnyChannel, Busy, Channel, InterruptFlags, Ready},
14    sram::DmacDescriptor,
15    transfer::BufferPair,
16};
17use crate::sercom::{
18    Sercom,
19    uart::{self, Uart},
20};
21
22/// Wrapper type over an `&[T]` that can be used as a source buffer for DMA
23/// transfers. This is an implementation detail to make SERCOM-DMA
24/// transfers work. Should not be used outside of this crate.
25///
26/// # Safety
27///
28/// [`SharedSliceBuffer`]s should only ever be used as **source** buffers for
29/// DMA transfers, and never as destination buffers.
30#[doc(hidden)]
31pub(crate) struct SharedSliceBuffer<'a, T: Beat> {
32    ptrs: Range<*mut T>,
33    _lifetime: PhantomData<&'a T>,
34}
35
36impl<'a, T: Beat> SharedSliceBuffer<'a, T> {
37    #[inline]
38    pub(in super::super) fn from_slice(slice: &'a [T]) -> Self {
39        unsafe { Self::from_slice_unchecked(slice) }
40    }
41
42    #[inline]
43    pub(in super::super) unsafe fn from_slice_unchecked(slice: &[T]) -> Self {
44        let ptrs = slice.as_ptr_range();
45
46        let ptrs = Range {
47            start: ptrs.start.cast_mut(),
48            end: ptrs.end.cast_mut(),
49        };
50
51        Self {
52            ptrs,
53            _lifetime: PhantomData,
54        }
55    }
56}
57
58unsafe impl<T: Beat> Buffer for SharedSliceBuffer<'_, T> {
59    type Beat = T;
60    #[inline]
61    fn dma_ptr(&mut self) -> *mut Self::Beat {
62        if self.incrementing() {
63            self.ptrs.end
64        } else {
65            self.ptrs.start
66        }
67    }
68
69    #[inline]
70    fn incrementing(&self) -> bool {
71        self.buffer_len() > 1
72    }
73
74    #[inline]
75    fn buffer_len(&self) -> usize {
76        self.ptrs.end as usize - self.ptrs.start as usize
77    }
78}
79
80/// Sink/source buffer to use for unidirectional SPI-DMA transfers.
81///
82/// When reading/writing from a [`Duplex`] [`Spi`] with DMA enabled,
83/// we must always read and write the same number of words, regardless of
84/// whether we care about the result (ie, for write, we discard the read
85/// words, whereas for read, we must send a no-op word).
86///
87/// This [`Buffer`] implementation provides a source/sink for a single word,
88/// but with a variable length.
89pub(super) struct SinkSourceBuffer<'a, T: Beat> {
90    word: &'a mut T,
91    length: usize,
92}
93
94impl<'a, T: Beat> SinkSourceBuffer<'a, T> {
95    pub(super) fn new(word: &'a mut T, length: usize) -> Self {
96        Self { word, length }
97    }
98}
99unsafe impl<T: Beat> Buffer for SinkSourceBuffer<'_, T> {
100    type Beat = T;
101    #[inline]
102    fn incrementing(&self) -> bool {
103        false
104    }
105
106    #[inline]
107    fn buffer_len(&self) -> usize {
108        self.length
109    }
110
111    #[inline]
112    fn dma_ptr(&mut self) -> *mut Self::Beat {
113        self.word as *mut _
114    }
115}
116/// Wrapper type over Sercom instances to get around lifetime issues when using
117/// one as a DMA source/destination buffer. This is an implementation detail to
118/// make SERCOM-DMA transfers work.
119#[doc(hidden)]
120#[derive(Clone)]
121pub(crate) struct SercomPtr<T: Beat>(pub(in super::super) *mut T);
122
123unsafe impl<T: Beat> Buffer for SercomPtr<T> {
124    type Beat = T;
125
126    #[inline]
127    fn dma_ptr(&mut self) -> *mut Self::Beat {
128        self.0
129    }
130
131    #[inline]
132    fn incrementing(&self) -> bool {
133        false
134    }
135
136    #[inline]
137    fn buffer_len(&self) -> usize {
138        1
139    }
140}
141
142//=============================================================================
143// UART DMA transfers
144//=============================================================================
145unsafe impl<C, D> Buffer for Uart<C, D>
146where
147    C: uart::ValidConfig,
148    C::Word: Beat,
149    D: uart::Capability,
150{
151    type Beat = C::Word;
152
153    #[inline]
154    fn dma_ptr(&mut self) -> *mut Self::Beat {
155        self.data_ptr()
156    }
157
158    #[inline]
159    fn incrementing(&self) -> bool {
160        false
161    }
162
163    #[inline]
164    fn buffer_len(&self) -> usize {
165        1
166    }
167}
168
169impl<C, D> Uart<C, D>
170where
171    Self: Buffer<Beat = C::Word>,
172    C: uart::ValidConfig,
173    D: uart::Receive,
174{
175    /// Transform an [`Uart`] into a DMA [`Transfer`]) and start reveiving into
176    /// the provided buffer.
177    ///
178    /// In order to be (safely) non-blocking, his method has to take a `'static`
179    /// buffer. If you'd rather use DMA with the blocking
180    /// [`embedded_io::Read`](crate::embedded_io::Read) trait, and avoid having
181    /// to use static buffers,
182    /// use [`Uart::with_rx_channel`](Self::with_tx_channel) instead.
183    #[inline]
184    #[hal_macro_helper]
185    pub fn receive_with_dma<Ch, B>(
186        self,
187        buf: B,
188        mut channel: Ch,
189    ) -> Transfer<Channel<Ch::Id, Busy>, BufferPair<Self, B>>
190    where
191        Ch: AnyChannel<Status = Ready>,
192        B: Buffer<Beat = C::Word> + 'static,
193    {
194        channel
195            .as_mut()
196            .enable_interrupts(InterruptFlags::new().with_tcmpl(true));
197
198        #[hal_cfg("sercom0-d5x")]
199        let trigger_action = TriggerAction::Burst;
200
201        #[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
202        let trigger_action = TriggerAction::Beat;
203
204        // SAFETY: This is safe because the of the `'static` bound check
205        // for `B`, and the fact that the buffer length of an `Uart` is always 1.
206        let xfer = unsafe { dmac::Transfer::new_unchecked(channel, self, buf, false) };
207        xfer.begin(C::Sercom::DMA_RX_TRIGGER, trigger_action)
208    }
209}
210
211impl<C, D> Uart<C, D>
212where
213    Self: Buffer<Beat = C::Word>,
214    C: uart::ValidConfig,
215    D: uart::Transmit,
216{
217    /// Transform an [`Uart`] into a DMA [`Transfer`]) and start sending the
218    /// provided buffer.
219    ///
220    /// In order to be (safely) non-blocking, his method takes a `'static`
221    /// buffer. If you'd rather use DMA with the blocking
222    /// [`embedded_io::Write`](crate::embedded_io::Write) trait, and avoid
223    /// having to use static buffers,
224    /// use[`Uart::with_tx_channel`](Self::with_tx_channel) instead.
225    #[inline]
226    #[hal_macro_helper]
227    pub fn send_with_dma<Ch, B>(
228        self,
229        buf: B,
230        mut channel: Ch,
231    ) -> Transfer<Channel<Ch::Id, Busy>, BufferPair<B, Self>>
232    where
233        Ch: AnyChannel<Status = Ready>,
234        B: Buffer<Beat = C::Word> + 'static,
235    {
236        channel
237            .as_mut()
238            .enable_interrupts(InterruptFlags::new().with_tcmpl(true));
239
240        #[hal_cfg("sercom0-d5x")]
241        let trigger_action = TriggerAction::Burst;
242
243        #[hal_cfg(any("sercom0-d11", "sercom0-d21"))]
244        let trigger_action = TriggerAction::Beat;
245
246        // SAFETY: This is safe because the of the `'static` bound check
247        // for `B`, and the fact that the buffer length of an `Uart` is always 1.
248        let xfer = unsafe { dmac::Transfer::new_unchecked(channel, buf, self, false) };
249        xfer.begin(C::Sercom::DMA_TX_TRIGGER, trigger_action)
250    }
251}
252
253/// Perform a SERCOM DMA read with a provided [`Buffer`]
254///
255/// # Safety
256///
257/// You **must** guarantee that the DMA transfer is either stopped or completed
258/// before giving back control of `channel` AND `buf`.
259#[hal_macro_helper]
260pub(super) unsafe fn read_dma<T, B, S>(
261    channel: &mut impl AnyChannel<Status = Ready>,
262    sercom_ptr: SercomPtr<T>,
263    buf: &mut B,
264) where
265    T: Beat,
266    B: Buffer<Beat = T>,
267    S: Sercom,
268{
269    unsafe {
270        read_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None);
271    }
272}
273
274/// Perform a SERCOM DMA read with a provided [`Buffer`], and add an optional
275/// link to a next [`DmacDescriptor`] to support linked transfers.
276///
277/// # Safety
278///
279/// You **must** guarantee that the DMA transfer is either stopped or completed
280/// before giving back control of `channel` AND `buf`.
281#[hal_macro_helper]
282pub(super) unsafe fn read_dma_linked<T, B, S>(
283    channel: &mut impl AnyChannel<Status = Ready>,
284    mut sercom_ptr: SercomPtr<T>,
285    buf: &mut B,
286    next: Option<&mut DmacDescriptor>,
287) where
288    T: Beat,
289    B: Buffer<Beat = T>,
290    S: Sercom,
291{
292    #[hal_cfg("dmac-d5x")]
293    let trigger_action = TriggerAction::Burst;
294
295    #[hal_cfg(any("dmac-d11", "dmac-d21"))]
296    let trigger_action = TriggerAction::Beat;
297
298    // Safety: It is safe to bypass the buffer length check because `SercomPtr`
299    // always has a buffer length of 1.
300    unsafe {
301        channel.as_mut().transfer_unchecked(
302            &mut sercom_ptr,
303            buf,
304            S::DMA_RX_TRIGGER,
305            trigger_action,
306            next,
307        );
308    }
309}
310
311/// Perform a SERCOM DMA write with a provided [`Buffer`]
312///
313/// # Safety
314///
315/// You **must** guarantee that the DMA transfer is either stopped or completed
316/// before giving back control of `channel` AND `buf`.
317#[hal_macro_helper]
318pub(super) unsafe fn write_dma<T, B, S>(
319    channel: &mut impl AnyChannel<Status = Ready>,
320    sercom_ptr: SercomPtr<T>,
321    buf: &mut B,
322) where
323    T: Beat,
324    B: Buffer<Beat = T>,
325    S: Sercom,
326{
327    unsafe {
328        write_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None);
329    }
330}
331
332/// Perform a SERCOM DMA write with a provided [`Buffer`], and add an optional
333/// link to a next [`DmacDescriptor`] to support linked transfers.
334///
335/// # Safety
336///
337/// You **must** guarantee that the DMA transfer is either stopped or completed
338/// before giving back control of `channel` AND `buf`.
339#[hal_macro_helper]
340pub(super) unsafe fn write_dma_linked<T, B, S>(
341    channel: &mut impl AnyChannel<Status = Ready>,
342    mut sercom_ptr: SercomPtr<T>,
343    buf: &mut B,
344    next: Option<&mut DmacDescriptor>,
345) where
346    T: Beat,
347    B: Buffer<Beat = T>,
348    S: Sercom,
349{
350    #[hal_cfg("dmac-d5x")]
351    let trigger_action = TriggerAction::Burst;
352
353    #[hal_cfg(any("dmac-d11", "dmac-d21"))]
354    let trigger_action = TriggerAction::Beat;
355
356    // Safety: It is safe to bypass the buffer length check because `SercomPtr`
357    // always has a buffer length of 1.
358    unsafe {
359        channel.as_mut().transfer_unchecked(
360            buf,
361            &mut sercom_ptr,
362            S::DMA_TX_TRIGGER,
363            trigger_action,
364            next,
365        );
366    }
367}
368
369/// Use the DMA Controller to perform async transfers using the SERCOM
370/// peripheral
371///
372/// See the [`mod@uart`], [`mod@i2c`] and [`mod@spi`] modules for the
373/// corresponding DMA transfer implementations.
374#[cfg(feature = "async")]
375pub(crate) mod async_dma {
376    use dmac::{Error, ReadyFuture};
377
378    use super::*;
379
380    /// Perform a SERCOM DMA read with a provided `&mut [T]`
381    #[inline]
382    pub(in super::super) async fn read_dma<T, B, S>(
383        channel: &mut impl AnyChannel<Status = ReadyFuture>,
384        sercom_ptr: SercomPtr<T>,
385        buf: &mut B,
386    ) -> Result<(), Error>
387    where
388        B: Buffer<Beat = T>,
389        T: Beat,
390        S: Sercom,
391    {
392        unsafe { read_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None).await }
393    }
394
395    /// Perform a SERCOM DMA read with a provided [`Buffer`], and add an
396    /// optional link to a next [`DmacDescriptor`] to support linked
397    /// transfers.
398    ///
399    /// # Safety
400    ///
401    /// You **must** guarantee that the DMA transfer is either stopped or
402    /// completed before giving back control of `channel` AND `buf`.
403    #[inline]
404    #[hal_macro_helper]
405    pub(in super::super) async unsafe fn read_dma_linked<T, B, S>(
406        channel: &mut impl AnyChannel<Status = ReadyFuture>,
407        mut sercom_ptr: SercomPtr<T>,
408        buf: &mut B,
409        next: Option<&mut DmacDescriptor>,
410    ) -> Result<(), Error>
411    where
412        T: Beat,
413        B: Buffer<Beat = T>,
414        S: Sercom,
415    {
416        #[hal_cfg("dmac-d5x")]
417        let trigger_action = TriggerAction::Burst;
418
419        #[hal_cfg(any("dmac-d11", "dmac-d21"))]
420        let trigger_action = TriggerAction::Beat;
421
422        // Safety: It is safe to bypass the buffer length check because `SercomPtr`
423        // always has a buffer length of 1.
424        unsafe {
425            channel
426                .as_mut()
427                .transfer_future_linked(
428                    &mut sercom_ptr,
429                    buf,
430                    S::DMA_RX_TRIGGER,
431                    trigger_action,
432                    next,
433                )
434                .await
435        }
436    }
437
438    /// Perform a SERCOM DMA write with a provided `&[T]`
439    #[inline]
440    pub(in super::super) async fn write_dma<T, B, S>(
441        channel: &mut impl AnyChannel<Status = ReadyFuture>,
442        sercom_ptr: SercomPtr<T>,
443        buf: &mut B,
444    ) -> Result<(), Error>
445    where
446        B: Buffer<Beat = T>,
447        T: Beat,
448        S: Sercom,
449    {
450        unsafe { write_dma_linked::<_, _, S>(channel, sercom_ptr, buf, None).await }
451    }
452
453    /// Perform a SERCOM DMA write with a provided [`Buffer`], and add an
454    /// optional link to a next [`DmacDescriptor`] to support linked
455    /// transfers.
456    ///
457    /// # Safety
458    ///
459    /// You **must** guarantee that the DMA transfer is either stopped or
460    /// completed before giving back control of `channel` AND `buf`.
461    #[inline]
462    #[hal_macro_helper]
463    pub(in super::super) async unsafe fn write_dma_linked<T, B, S>(
464        channel: &mut impl AnyChannel<Status = ReadyFuture>,
465        mut sercom_ptr: SercomPtr<T>,
466        buf: &mut B,
467        next: Option<&mut DmacDescriptor>,
468    ) -> Result<(), Error>
469    where
470        B: Buffer<Beat = T>,
471        T: Beat,
472        S: Sercom,
473    {
474        #[hal_cfg("dmac-d5x")]
475        let trigger_action = TriggerAction::Burst;
476
477        #[hal_cfg(any("dmac-d11", "dmac-d21"))]
478        let trigger_action = TriggerAction::Beat;
479
480        // Safety: It is safe to bypass the buffer length check because `SercomPtr`
481        // always has a buffer length of 1.
482        unsafe {
483            channel
484                .as_mut()
485                .transfer_future_linked(
486                    buf,
487                    &mut sercom_ptr,
488                    S::DMA_TX_TRIGGER,
489                    trigger_action,
490                    next,
491                )
492                .await
493        }
494    }
495}