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