atsamd_hal/sercom/spi/async_api/
dma.rs

1use embedded_hal_async::spi::SpiBus;
2use num_traits::{AsPrimitive, PrimInt};
3
4use super::SpiFuture;
5use crate::{
6    dmac::{
7        AnyChannel, Beat, Buffer, ReadyFuture,
8        channel::{self, Channel},
9        sram::DmacDescriptor,
10    },
11    sercom::{
12        Sercom,
13        dma::{
14            SharedSliceBuffer, SinkSourceBuffer,
15            async_dma::{self, read_dma, read_dma_linked, write_dma, write_dma_linked},
16        },
17        spi::{
18            Capability, Config, DataWidth, Duplex, Error, MasterMode, OpMode, Receive, Rx, Size,
19            Slave, Spi, Transmit, Tx, ValidConfig, ValidPads, Word,
20        },
21    },
22    typelevel::NoneT,
23};
24
25/// Convenience type for a [`SpiFuture`] with RX and TX capabilities in DMA
26/// mode.
27///
28/// The type parameter `R` represents the RX DMA channel ID (`ChX`), and
29/// `T` represents the TX DMA channel ID.
30pub type SpiFutureDuplexDma<C, R, T> =
31    SpiFuture<C, Duplex, Channel<R, ReadyFuture>, Channel<T, ReadyFuture>>;
32
33/// Convenience type for a [`SpiFuture`] with RX capabilities in DMA mode.
34///
35/// The type parameter `R` represents the RX DMA channel ID (`ChX`).
36pub type SpiFutureRxDma<C, R> = SpiFuture<C, Rx, Channel<R, ReadyFuture>, NoneT>;
37
38/// Convenience type for a [`SpiFuture`] with TX capabilities in DMA mode.
39///
40/// The type parameter `T` represents the TX DMA channel ID (`ChX`).
41pub type SpiFutureTxDma<C, T> = SpiFuture<C, Tx, NoneT, Channel<T, ReadyFuture>>;
42
43impl<C, D, T> SpiFuture<C, D, NoneT, T>
44where
45    C: ValidConfig<Sercom: Sercom, OpMode = Slave, Word: PrimInt + AsPrimitive<DataWidth>>,
46    D: Receive,
47    DataWidth: AsPrimitive<C::Word>,
48{
49    /// Attach a DMA channel to this [`SpiFuture`]. Its
50    /// [`SpiBus`](crate::ehal::spi::SpiBus) implementation will use DMA to
51    /// carry out its transactions. In Slave mode, a [`Rx`] [`SpiFuture`] only
52    /// needs a single DMA channel.
53    #[inline]
54    pub fn with_rx_dma_channel<Chan: AnyChannel<Status = ReadyFuture>>(
55        self,
56        rx_channel: Chan,
57    ) -> SpiFuture<C, D, Chan, T> {
58        SpiFuture {
59            spi: Spi {
60                config: self.spi.config,
61                capability: self.spi.capability,
62                _rx_channel: rx_channel,
63                _tx_channel: self.spi._tx_channel,
64            },
65        }
66    }
67}
68
69impl<C, D, R> SpiFuture<C, D, R, NoneT>
70where
71    C: ValidConfig<Sercom: Sercom, Word: PrimInt + AsPrimitive<DataWidth>>,
72    D: Transmit,
73    DataWidth: AsPrimitive<C::Word>,
74{
75    /// Attach a DMA channel to this [`SpiFuture`]. Its
76    /// [`SpiBus`](crate::ehal::spi::SpiBus) implementation will use DMA to
77    /// carry out its transactions. For [`Tx`] [`SpiFuture`]s, only a single DMA
78    /// channel is necessary.
79    #[inline]
80    pub fn with_tx_dma_channel<Chan: AnyChannel<Status = ReadyFuture>>(
81        self,
82        tx_channel: Chan,
83    ) -> SpiFuture<C, D, R, Chan> {
84        SpiFuture {
85            spi: Spi {
86                config: self.spi.config,
87                capability: self.spi.capability,
88                _rx_channel: self.spi._rx_channel,
89                _tx_channel: tx_channel,
90            },
91        }
92    }
93}
94
95impl<C, D> SpiFuture<C, D, NoneT, NoneT>
96where
97    C: ValidConfig<Sercom: Sercom, OpMode: MasterMode, Word: PrimInt + AsPrimitive<DataWidth>>,
98    D: Receive,
99    DataWidth: AsPrimitive<C::Word>,
100{
101    /// Attach RX and TX DMA channels to this [`SpiFuture`]. Its
102    /// [`SpiBus`](crate::ehal::spi::SpiBus) implementation will use DMA to
103    /// carry out its transactions. In Master mode, since even read SPI
104    /// transaction necessarily involve a write to shift data in, [`Rx`]-only
105    /// must take two DMA channels, just the same as if it were [`Duplex`].
106    #[inline]
107    pub fn with_dma_channels<R, T>(self, rx_channel: R, tx_channel: T) -> SpiFuture<C, D, R, T>
108    where
109        R: AnyChannel<Status = ReadyFuture>,
110        T: AnyChannel<Status = ReadyFuture>,
111    {
112        SpiFuture {
113            spi: Spi {
114                config: self.spi.config,
115                capability: self.spi.capability,
116                _rx_channel: rx_channel,
117                _tx_channel: tx_channel,
118            },
119        }
120    }
121}
122
123impl<C> SpiFuture<C, Duplex>
124where
125    C: ValidConfig<OpMode = Slave>,
126{
127    /// Attach a DMA channel to this [`SpiFuture`]. Its
128    /// [`SpiBus`](crate::ehal::spi::SpiBus) implementation will use DMA to
129    /// carry out its transactions. In Slave mode, a [`Duplex`] [`SpiFuture`]
130    /// needs two DMA channels.
131    pub fn with_dma_channels_slave<R, T>(self, rx: R, tx: T) -> SpiFuture<C, Duplex, R, T>
132    where
133        R: AnyChannel<Status = ReadyFuture>,
134        T: AnyChannel<Status = ReadyFuture>,
135    {
136        SpiFuture {
137            spi: Spi {
138                capability: self.spi.capability,
139                config: self.spi.config,
140                _rx_channel: rx,
141                _tx_channel: tx,
142            },
143        }
144    }
145}
146
147impl<C, D, R, T> SpiFuture<C, D, R, T>
148where
149    C: ValidConfig,
150    D: Capability,
151{
152    /// Reclaim both RX and TX DMA channels. Any subsequent SPI transaction will
153    /// not use DMA.
154    pub fn take_dma_channels(self) -> (SpiFuture<C, D, NoneT, NoneT>, R, T)
155    where
156        R: AnyChannel<Status = ReadyFuture>,
157        T: AnyChannel<Status = ReadyFuture>,
158    {
159        let (spi, rx, tx) = self.spi.take_dma_channels();
160        (SpiFuture { spi }, rx, tx)
161    }
162
163    /// Reclaim the RX DMA channel. Any subsequent SPI RX transaction will not
164    /// use DMA.
165    pub fn take_rx_channel(self) -> (SpiFuture<C, D, NoneT, T>, R)
166    where
167        R: AnyChannel<Status = ReadyFuture>,
168    {
169        let (spi, channel) = self.spi.take_rx_channel();
170        (SpiFuture { spi }, channel)
171    }
172
173    /// Reclaim the TX DMA channel. Any subsequent SPI TX transaction will not
174    /// use DMA.
175    pub fn take_tx_channel(self) -> (SpiFuture<C, D, R, NoneT>, T)
176    where
177        T: AnyChannel<Status = ReadyFuture>,
178    {
179        let (spi, channel) = self.spi.take_tx_channel();
180        (SpiFuture { spi }, channel)
181    }
182}
183
184// Write implementation is the same for Master and Slave SPIs.
185impl<P, M, Z, D, R, T, S> SpiFuture<Config<P, M, Z>, D, R, T>
186where
187    P: ValidPads,
188    M: OpMode,
189    Z: Size + 'static,
190    Config<P, M, Z>: ValidConfig<Sercom = S>,
191    D: Transmit,
192    S: Sercom,
193    Z::Word: PrimInt + AsPrimitive<DataWidth> + Beat,
194    DataWidth: AsPrimitive<Z::Word>,
195    T: AnyChannel<Status = ReadyFuture>,
196{
197    /// Write words from a buffer asynchronously, using DMA.
198    #[inline]
199    pub async fn write_dma(&mut self, words: &[Z::Word]) -> Result<usize, Error> {
200        if words.is_empty() {
201            return Ok(0);
202        }
203
204        // Ignore RX buffer overflows by disabling the receiver
205        self.spi.config.as_mut().regs.rx_disable();
206
207        let sercom_ptr = self.spi.sercom_ptr();
208        let tx = self.spi._tx_channel.as_mut();
209        let mut buf = SharedSliceBuffer::from_slice(words);
210
211        let tx_result = async_dma::write_dma::<_, _, S>(tx, sercom_ptr, &mut buf).await;
212
213        // Reenable receiver only if necessary
214        if D::RX_ENABLE {
215            self.spi.config.as_mut().regs.rx_enable();
216        }
217
218        tx_result?;
219        Ok(words.len())
220    }
221}
222
223impl<P, M, S, C, D, R, T> SpiFuture<Config<P, M, C>, D, R, T>
224where
225    Config<P, M, C>: ValidConfig<Sercom = S>,
226    S: Sercom,
227    P: ValidPads,
228    M: MasterMode,
229    C: Size + 'static,
230    C::Word: PrimInt + AsPrimitive<DataWidth> + Beat,
231    D: Capability,
232    DataWidth: AsPrimitive<C::Word>,
233    R: AnyChannel<Status = ReadyFuture>,
234    T: AnyChannel<Status = ReadyFuture>,
235{
236    #[inline]
237    async fn transfer_blocking<Source: Buffer<Beat = C::Word>, Dest: Buffer<Beat = C::Word>>(
238        &mut self,
239        dest: &mut Dest,
240        source: &mut Source,
241    ) -> Result<(), Error> {
242        let sercom_ptr = self.spi.sercom_ptr();
243        let rx = self.spi._rx_channel.as_mut();
244        let tx = self.spi._tx_channel.as_mut();
245
246        let (rx_result, tx_result) = futures::join!(
247            read_dma::<_, _, S>(rx, sercom_ptr.clone(), dest),
248            write_dma::<_, _, S>(tx, sercom_ptr, source)
249        );
250
251        // Check for overflows or DMA errors
252        self.spi.read_status().check_bus_error()?;
253        rx_result.and(tx_result)?;
254        Ok(())
255    }
256
257    /// Read words into a buffer asynchronously, using DMA.
258    #[inline]
259    pub async fn read_dma_master(&mut self, mut words: &mut [C::Word]) -> Result<(), Error> {
260        if words.is_empty() {
261            return Ok(());
262        }
263
264        let mut source_word = self.spi.config.nop_word.as_();
265        let mut source = SinkSourceBuffer::new(&mut source_word, words.len());
266
267        self.transfer_blocking(&mut words, &mut source).await
268    }
269}
270
271/// [`SpiBus`] implementation for [`Spi`], using DMA transfers.
272impl<P, M, C, S, R, T> SpiBus<Word<C>> for SpiFuture<Config<P, M, C>, Duplex, R, T>
273where
274    S: Sercom,
275    Config<P, M, C>: ValidConfig<Sercom = S>,
276    P: ValidPads,
277    M: MasterMode,
278    C: Size + 'static,
279    C::Word: PrimInt + AsPrimitive<DataWidth> + Beat,
280    DataWidth: AsPrimitive<C::Word>,
281    R: AnyChannel<Status = ReadyFuture>,
282    T: AnyChannel<Status = ReadyFuture>,
283{
284    #[inline]
285    async fn read(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> {
286        self.read_dma_master(words).await
287    }
288
289    #[inline]
290    async fn write(&mut self, words: &[C::Word]) -> Result<(), Self::Error> {
291        self.write_dma(words).await?;
292        Ok(())
293    }
294
295    #[inline]
296    async fn transfer(
297        &mut self,
298        mut read: &mut [C::Word],
299        write: &[C::Word],
300    ) -> Result<(), Self::Error> {
301        use core::cmp::Ordering;
302
303        // No work to do here
304        if write.is_empty() && read.is_empty() {
305            return Ok(());
306        }
307
308        // Handle 0-length special cases
309        if write.is_empty() {
310            return self.read_dma_master(read).await;
311        } else if read.is_empty() {
312            self.write_dma(write).await?;
313            return Ok(());
314        }
315
316        // Reserve space for a DMAC SRAM descriptor if we need to make a linked
317        // transfer. Must not be dropped until all transfers have completed
318        // or have been stopped.
319        let mut linked_descriptor = DmacDescriptor::default();
320
321        // If read < write, the incoming words will be written to this memory location;
322        // it will be discarded after. If read > write, all writes after the
323        // buffer has been exhausted will write the nop word to "stimulate" the slave
324        // into sending data. Must not be dropped until all transfers have
325        // completed or have been stopped.
326        let mut source_sink_word = self.spi.config.as_mut().nop_word.as_();
327        let mut sercom_ptr = self.spi.sercom_ptr();
328
329        let (read_link, write_link) = match read.len().cmp(&write.len()) {
330            Ordering::Equal => {
331                let mut write = SharedSliceBuffer::from_slice(write);
332                return self.transfer_blocking(&mut read, &mut write).await;
333            }
334
335            // `read` is shorter; link transfer to sink incoming words after the buffer has been
336            // filled.
337            Ordering::Less => {
338                let mut sink =
339                    SinkSourceBuffer::new(&mut source_sink_word, write.len() - read.len());
340                unsafe {
341                    channel::write_descriptor(
342                        &mut linked_descriptor,
343                        &mut sercom_ptr,
344                        &mut sink,
345                        // Add a null descriptor pointer to end the transfer.
346                        core::ptr::null_mut(),
347                    );
348                }
349
350                (Some(&mut linked_descriptor), None)
351            }
352
353            // `write` is shorter; link transfer to send NOP word after the buffer has been
354            // exhausted.
355            Ordering::Greater => {
356                let mut source =
357                    SinkSourceBuffer::new(&mut source_sink_word, read.len() - write.len());
358                unsafe {
359                    channel::write_descriptor(
360                        &mut linked_descriptor,
361                        &mut source,
362                        &mut sercom_ptr,
363                        // Add a null descriptor pointer to end the transfer.
364                        core::ptr::null_mut(),
365                    );
366                }
367
368                (None, Some(&mut linked_descriptor))
369            }
370        };
371
372        let rx = self.spi._rx_channel.as_mut();
373        let tx = self.spi._tx_channel.as_mut();
374
375        let mut write = SharedSliceBuffer::from_slice(write);
376
377        // SAFETY: We make sure that any DMA transfer is complete or stopped before
378        // returning. The order of operations is important; the RX transfer
379        // must be ready to receive before the TX transfer is initiated.
380        let (rx_result, tx_result) = unsafe {
381            futures::join!(
382                read_dma_linked::<_, _, S>(rx, sercom_ptr.clone(), &mut read, read_link),
383                write_dma_linked::<_, _, S>(tx, sercom_ptr, &mut write, write_link)
384            )
385        };
386
387        // Check for overflows or DMA errors
388        self.spi.read_status().check_bus_error()?;
389        rx_result.and(tx_result)?;
390        Ok(())
391    }
392
393    #[inline]
394    async fn transfer_in_place(&mut self, words: &mut [C::Word]) -> Result<(), Self::Error> {
395        // Safefy: Aliasing the buffer is only safe because the DMA read will always be
396        // lagging one word behind the write, so they don't overlap on the same memory.
397        // It's preferable to use two `SharedSliceBuffer`s here; using the `words` slice
398        // directly as a buffer could potentially cause UB issues if not careful when
399        // aliasing, as it could be easy to create two `&mut` references pointing to the
400        // same buffer. `read_buf` and `write_buf` may only be read/written to by the
401        // DMAC, otherwise an `UnsafeCell` would be necessary.
402        unsafe {
403            let mut read_buf = SharedSliceBuffer::from_slice_unchecked(words);
404            let mut write_buf = SharedSliceBuffer::from_slice(words);
405            self.transfer_blocking(&mut read_buf, &mut write_buf).await
406        }
407    }
408
409    #[inline]
410    async fn flush(&mut self) -> Result<(), Self::Error> {
411        // Wait for all transactions to complete, ignoring buffer overflow errors.
412        self.flush_tx().await;
413        Ok(())
414    }
415}
416
417/// [`embedded_io::Write`] implementation for [`Transmit`] [`SpiFuture`]s in
418/// either [`Slave`] or [`MasterMode`], using DMA transfers.
419impl<P, M, Z, D, R, T> embedded_io_async::Write for SpiFuture<Config<P, M, Z>, D, R, T>
420where
421    P: ValidPads,
422    M: OpMode,
423    Z: Size<Word = u8> + 'static,
424    Config<P, M, Z>: ValidConfig<Sercom: Sercom>,
425    D: Transmit,
426    T: AnyChannel<Status = ReadyFuture>,
427{
428    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
429        SpiFuture::write_dma(self, buf).await
430    }
431
432    async fn flush(&mut self) -> Result<(), Self::Error> {
433        self.flush_tx().await;
434        Ok(())
435    }
436}
437
438/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in
439/// [`MasterMode`], using DMA transfers.
440impl<P, M, Z, D, R, T> embedded_io_async::Read for SpiFuture<Config<P, M, Z>, D, R, T>
441where
442    P: ValidPads,
443    M: MasterMode,
444    Z: Size<Word = u8> + 'static,
445    Config<P, M, Z>: ValidConfig<Sercom: Sercom>,
446    D: Receive,
447    R: AnyChannel<Status = ReadyFuture>,
448    T: AnyChannel<Status = ReadyFuture>,
449{
450    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
451        self.read_dma_master(buf).await?;
452        Ok(buf.len())
453    }
454}
455
456/// [`embedded_io::Read`] implementation for [`Receive`] [`SpiFuture`]s in
457/// [`Slave`] mode, using DMA transfers.
458impl<P, Z, D, R, T, S> embedded_io_async::Read for SpiFuture<Config<P, Slave, Z>, D, R, T>
459where
460    P: ValidPads,
461    Z: Size<Word = u8> + 'static,
462    Config<P, Slave, Z>: ValidConfig<Sercom = S>,
463    D: Receive,
464    S: Sercom,
465    R: AnyChannel<Status = ReadyFuture>,
466{
467    async fn read(&mut self, mut buf: &mut [u8]) -> Result<usize, Self::Error> {
468        if buf.is_empty() {
469            return Ok(0);
470        }
471
472        // In Slave mode, RX words can come in even if we haven't sent anything. This
473        // means some words can arrive asynchronously while we weren't looking (similar
474        // to UART RX). We need to check if we haven't missed any.
475        self.flush_rx().await?;
476        let sercom_ptr = self.spi.sercom_ptr();
477        let rx = self.spi._rx_channel.as_mut();
478
479        // SAFETY: We make sure that any DMA transfer is complete or stopped before
480        // returning.
481        let result = read_dma::<_, _, S>(rx, sercom_ptr.clone(), &mut buf).await;
482
483        // Check for overflows or DMA errors
484        self.flush_rx().await?;
485        result?;
486        Ok(buf.len())
487    }
488}