Skip to main content

esp_hal/dma/engine/
spi.rs

1use enumset::EnumSet;
2use portable_atomic::Ordering;
3
4use crate::{
5    RegisterToggle,
6    asynch::AtomicWaker,
7    dma::{
8        BurstConfig,
9        DmaChannel,
10        DmaRxChannel,
11        DmaRxInterrupt,
12        DmaTxChannel,
13        DmaTxInterrupt,
14        InterruptAccess,
15        RegisterAccess,
16        RxRegisterAccess,
17        TxRegisterAccess,
18    },
19    interrupt::InterruptHandler,
20    peripherals::Interrupt,
21    system::{Peripheral, PeripheralGuard},
22};
23
24/// Immutable per-channel metadata.
25#[doc(hidden)]
26pub struct ChannelInfo {
27    pub(crate) peripheral_interrupt: Interrupt,
28
29    pub(crate) async_handler: InterruptHandler,
30
31    /// Peripheral IDs this channel can serve. An empty slice means no runtime check is needed.
32    pub(crate) compatible_peripherals: &'static [u8],
33}
34
35/// Mutable per-channel runtime state (wakers and async-mode flags).
36pub(crate) struct ChannelState {
37    /// Async waker for the TX (out) half of this channel.
38    pub(crate) tx_waker: AtomicWaker,
39
40    /// Async waker for the RX (in) half of this channel.
41    pub(crate) rx_waker: AtomicWaker,
42
43    /// Whether the TX half is currently in async mode.
44    pub(crate) tx_async_flag: portable_atomic::AtomicBool,
45
46    /// Whether the RX half is currently in async mode.
47    pub(crate) rx_async_flag: portable_atomic::AtomicBool,
48}
49
50pub(super) type SpiRegisterBlock = crate::pac::spi2::RegisterBlock;
51
52/// The RX half of an arbitrary SPI DMA channel.
53#[derive(Debug)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub struct SpiDmaRxChannel<'d>(pub(crate) SpiDmaChannel<'d>);
56
57impl SpiDmaRxChannel<'_> {
58    fn regs(&self) -> &SpiRegisterBlock {
59        self.0.register_block()
60    }
61}
62
63impl crate::private::Sealed for SpiDmaRxChannel<'_> {}
64impl DmaRxChannel for SpiDmaRxChannel<'_> {}
65
66/// The TX half of an arbitrary SPI DMA channel.
67#[derive(Debug)]
68#[cfg_attr(feature = "defmt", derive(defmt::Format))]
69pub struct SpiDmaTxChannel<'d>(pub(crate) SpiDmaChannel<'d>);
70
71impl SpiDmaTxChannel<'_> {
72    fn regs(&self) -> &SpiRegisterBlock {
73        self.0.register_block()
74    }
75}
76
77impl crate::private::Sealed for SpiDmaTxChannel<'_> {}
78impl DmaTxChannel for SpiDmaTxChannel<'_> {}
79
80impl RegisterAccess for SpiDmaTxChannel<'_> {
81    #[allow(private_interfaces)]
82    fn enable(&self) -> Option<PeripheralGuard> {
83        cfg_select! {
84            esp32 => {
85                let clock = Peripheral::SpiDma;
86            }
87            _ => {
88                let clock = match self.0 {
89                    SpiDmaChannel(any::Inner::Spi2(_)) => Peripheral::Spi2Dma,
90                    SpiDmaChannel(any::Inner::Spi3(_)) => Peripheral::Spi3Dma,
91                };
92            }
93        }
94
95        Some(PeripheralGuard::new_with(clock, enable_spi_dma))
96    }
97
98    fn reset(&self) {
99        self.regs().dma_conf().toggle(|w, bit| w.out_rst().bit(bit));
100    }
101
102    fn set_burst_mode(&self, burst_mode: BurstConfig) {
103        self.regs()
104            .dma_conf()
105            .modify(|_, w| w.out_data_burst_en().bit(burst_mode.is_burst_enabled()));
106    }
107
108    fn set_descr_burst_mode(&self, burst_mode: bool) {
109        self.regs()
110            .dma_conf()
111            .modify(|_, w| w.outdscr_burst_en().bit(burst_mode));
112    }
113
114    fn set_link_addr(&self, address: u32) {
115        self.regs()
116            .dma_out_link()
117            .modify(|_, w| unsafe { w.outlink_addr().bits(address) });
118    }
119
120    fn start(&self) {
121        self.regs()
122            .dma_out_link()
123            .modify(|_, w| w.outlink_start().set_bit());
124    }
125
126    fn stop(&self) {
127        self.regs()
128            .dma_out_link()
129            .modify(|_, w| w.outlink_stop().set_bit());
130    }
131
132    fn restart(&self) {
133        self.regs()
134            .dma_out_link()
135            .modify(|_, w| w.outlink_restart().set_bit());
136    }
137
138    fn set_check_owner(&self, check_owner: Option<bool>) {
139        if check_owner == Some(true) {
140            panic!("SPI DMA does not support checking descriptor ownership");
141        }
142    }
143
144    #[cfg(dma_ext_mem_configurable_block_size)]
145    fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) {
146        self.regs()
147            .dma_conf()
148            .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
149    }
150
151    #[cfg(dma_can_access_psram)]
152    fn can_access_psram(&self) -> bool {
153        matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_)))
154    }
155
156    fn compatible_peripherals(&self) -> &[u8] {
157        self.0.info().compatible_peripherals
158    }
159}
160
161impl TxRegisterAccess for SpiDmaTxChannel<'_> {
162    fn is_fifo_empty(&self) -> bool {
163        cfg_select! {
164            esp32 => self.regs().dma_rstatus().read().dma_out_status().bits() & 0x80000000 != 0,
165            _ => self
166                .regs()
167                .dma_outstatus()
168                .read()
169                .dma_outfifo_empty()
170                .bit_is_set(),
171        }
172    }
173
174    fn set_auto_write_back(&self, enable: bool) {
175        // there is no `auto_wrback` for SPI
176        assert!(!enable);
177    }
178
179    fn last_dscr_address(&self) -> usize {
180        self.regs()
181            .out_eof_des_addr()
182            .read()
183            .dma_out_eof_des_addr()
184            .bits() as usize
185    }
186
187    fn peripheral_interrupt(&self) -> Option<Interrupt> {
188        None
189    }
190
191    fn async_handler(&self) -> Option<InterruptHandler> {
192        None
193    }
194}
195
196impl InterruptAccess<DmaTxInterrupt> for SpiDmaTxChannel<'_> {
197    fn enable_listen(&self, interrupts: EnumSet<DmaTxInterrupt>, enable: bool) {
198        self.regs().dma_int_ena().modify(|_, w| {
199            for interrupt in interrupts {
200                match interrupt {
201                    DmaTxInterrupt::TotalEof => w.out_total_eof().bit(enable),
202                    DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().bit(enable),
203                    DmaTxInterrupt::Eof => w.out_eof().bit(enable),
204                    DmaTxInterrupt::Done => w.out_done().bit(enable),
205                };
206            }
207            w
208        });
209    }
210
211    fn is_listening(&self) -> EnumSet<DmaTxInterrupt> {
212        let mut result = EnumSet::new();
213
214        let int_ena = self.regs().dma_int_ena().read();
215        if int_ena.out_total_eof().bit_is_set() {
216            result |= DmaTxInterrupt::TotalEof;
217        }
218        if int_ena.outlink_dscr_error().bit_is_set() {
219            result |= DmaTxInterrupt::DescriptorError;
220        }
221        if int_ena.out_eof().bit_is_set() {
222            result |= DmaTxInterrupt::Eof;
223        }
224        if int_ena.out_done().bit_is_set() {
225            result |= DmaTxInterrupt::Done;
226        }
227
228        result
229    }
230
231    fn clear(&self, interrupts: impl Into<EnumSet<DmaTxInterrupt>>) {
232        self.regs().dma_int_clr().write(|w| {
233            for interrupt in interrupts.into() {
234                match interrupt {
235                    DmaTxInterrupt::TotalEof => w.out_total_eof().clear_bit_by_one(),
236                    DmaTxInterrupt::DescriptorError => w.outlink_dscr_error().clear_bit_by_one(),
237                    DmaTxInterrupt::Eof => w.out_eof().clear_bit_by_one(),
238                    DmaTxInterrupt::Done => w.out_done().clear_bit_by_one(),
239                };
240            }
241            w
242        });
243    }
244
245    fn pending_interrupts(&self) -> EnumSet<DmaTxInterrupt> {
246        let mut result = EnumSet::new();
247
248        let int_raw = self.regs().dma_int_raw().read();
249        if int_raw.out_total_eof().bit_is_set() {
250            result |= DmaTxInterrupt::TotalEof;
251        }
252        if int_raw.outlink_dscr_error().bit_is_set() {
253            result |= DmaTxInterrupt::DescriptorError;
254        }
255        if int_raw.out_eof().bit_is_set() {
256            result |= DmaTxInterrupt::Eof;
257        }
258        if int_raw.out_done().bit_is_set() {
259            result |= DmaTxInterrupt::Done;
260        }
261
262        result
263    }
264
265    fn waker(&self) -> &'static AtomicWaker {
266        &self.0.state().tx_waker
267    }
268
269    fn is_async(&self) -> bool {
270        self.0.state().tx_async_flag.load(Ordering::Acquire)
271    }
272
273    fn set_async(&self, is_async: bool) {
274        self.0
275            .state()
276            .tx_async_flag
277            .store(is_async, Ordering::Release);
278    }
279}
280
281impl RegisterAccess for SpiDmaRxChannel<'_> {
282    #[allow(private_interfaces)]
283    fn enable(&self) -> Option<PeripheralGuard> {
284        cfg_select! {
285            esp32 => {
286                let clock = Peripheral::SpiDma;
287            }
288            _ => {
289                let clock = match self.0 {
290                    SpiDmaChannel(any::Inner::Spi2(_)) => Peripheral::Spi2Dma,
291                    SpiDmaChannel(any::Inner::Spi3(_)) => Peripheral::Spi3Dma,
292                };
293            }
294        }
295
296        Some(PeripheralGuard::new_with(clock, enable_spi_dma))
297    }
298
299    fn reset(&self) {
300        self.regs().dma_conf().toggle(|w, bit| w.in_rst().bit(bit));
301    }
302
303    fn set_burst_mode(&self, _burst_mode: BurstConfig) {}
304
305    fn set_descr_burst_mode(&self, burst_mode: bool) {
306        self.regs()
307            .dma_conf()
308            .modify(|_, w| w.indscr_burst_en().bit(burst_mode));
309    }
310
311    fn set_link_addr(&self, address: u32) {
312        self.regs()
313            .dma_in_link()
314            .modify(|_, w| unsafe { w.inlink_addr().bits(address) });
315    }
316
317    fn start(&self) {
318        self.regs()
319            .dma_in_link()
320            .modify(|_, w| w.inlink_start().set_bit());
321    }
322
323    fn stop(&self) {
324        self.regs()
325            .dma_in_link()
326            .modify(|_, w| w.inlink_stop().set_bit());
327    }
328
329    fn restart(&self) {
330        self.regs()
331            .dma_in_link()
332            .modify(|_, w| w.inlink_restart().set_bit());
333    }
334
335    fn set_check_owner(&self, check_owner: Option<bool>) {
336        if check_owner == Some(true) {
337            panic!("SPI DMA does not support checking descriptor ownership");
338        }
339    }
340
341    #[cfg(dma_ext_mem_configurable_block_size)]
342    fn set_ext_mem_block_size(&self, size: crate::dma::DmaExtMemBKSize) {
343        self.regs()
344            .dma_conf()
345            .modify(|_, w| unsafe { w.ext_mem_bk_size().bits(size as u8) });
346    }
347
348    #[cfg(dma_can_access_psram)]
349    fn can_access_psram(&self) -> bool {
350        matches!(self.0, SpiDmaChannel(any::Inner::Spi2(_)))
351    }
352
353    fn compatible_peripherals(&self) -> &[u8] {
354        self.0.info().compatible_peripherals
355    }
356}
357
358impl RxRegisterAccess for SpiDmaRxChannel<'_> {
359    #[cfg(dma_supports_mem2mem)]
360    fn set_mem2mem_mode(&self, en: bool) {
361        self.regs()
362            .dma_conf()
363            .modify(|_, w| w.mem_trans_en().bit(en));
364    }
365
366    fn peripheral_interrupt(&self) -> Option<Interrupt> {
367        Some(self.0.info().peripheral_interrupt)
368    }
369
370    fn async_handler(&self) -> Option<InterruptHandler> {
371        Some(self.0.info().async_handler)
372    }
373}
374
375impl InterruptAccess<DmaRxInterrupt> for SpiDmaRxChannel<'_> {
376    fn enable_listen(&self, interrupts: EnumSet<DmaRxInterrupt>, enable: bool) {
377        self.regs().dma_int_ena().modify(|_, w| {
378            for interrupt in interrupts {
379                match interrupt {
380                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().bit(enable),
381                    DmaRxInterrupt::ErrorEof => w.in_err_eof().bit(enable),
382                    DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().bit(enable),
383                    DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().bit(enable),
384                    DmaRxInterrupt::Done => w.in_done().bit(enable),
385                };
386            }
387            w
388        });
389    }
390
391    fn is_listening(&self) -> EnumSet<DmaRxInterrupt> {
392        let mut result = EnumSet::new();
393
394        let int_ena = self.regs().dma_int_ena().read();
395        if int_ena.inlink_dscr_error().bit_is_set() {
396            result |= DmaRxInterrupt::DescriptorError;
397        }
398        if int_ena.inlink_dscr_empty().bit_is_set() {
399            result |= DmaRxInterrupt::DescriptorEmpty;
400        }
401        if int_ena.in_suc_eof().bit_is_set() {
402            result |= DmaRxInterrupt::SuccessfulEof;
403        }
404        if int_ena.in_err_eof().bit_is_set() {
405            result |= DmaRxInterrupt::ErrorEof;
406        }
407        if int_ena.in_done().bit_is_set() {
408            result |= DmaRxInterrupt::Done;
409        }
410
411        result
412    }
413
414    fn clear(&self, interrupts: impl Into<EnumSet<DmaRxInterrupt>>) {
415        self.regs().dma_int_clr().modify(|_, w| {
416            for interrupt in interrupts.into() {
417                match interrupt {
418                    DmaRxInterrupt::SuccessfulEof => w.in_suc_eof().clear_bit_by_one(),
419                    DmaRxInterrupt::ErrorEof => w.in_err_eof().clear_bit_by_one(),
420                    DmaRxInterrupt::DescriptorError => w.inlink_dscr_error().clear_bit_by_one(),
421                    DmaRxInterrupt::DescriptorEmpty => w.inlink_dscr_empty().clear_bit_by_one(),
422                    DmaRxInterrupt::Done => w.in_done().clear_bit_by_one(),
423                };
424            }
425            w
426        });
427    }
428
429    fn pending_interrupts(&self) -> EnumSet<DmaRxInterrupt> {
430        let mut result = EnumSet::new();
431
432        let int_raw = self.regs().dma_int_raw().read();
433        if int_raw.inlink_dscr_error().bit_is_set() {
434            result |= DmaRxInterrupt::DescriptorError;
435        }
436        if int_raw.inlink_dscr_empty().bit_is_set() {
437            result |= DmaRxInterrupt::DescriptorEmpty;
438        }
439        if int_raw.in_suc_eof().bit_is_set() {
440            result |= DmaRxInterrupt::SuccessfulEof;
441        }
442        if int_raw.in_err_eof().bit_is_set() {
443            result |= DmaRxInterrupt::ErrorEof;
444        }
445        if int_raw.in_done().bit_is_set() {
446            result |= DmaRxInterrupt::Done;
447        }
448
449        result
450    }
451
452    fn waker(&self) -> &'static AtomicWaker {
453        &self.0.state().rx_waker
454    }
455
456    fn is_async(&self) -> bool {
457        self.0.state().rx_async_flag.load(Ordering::Relaxed)
458    }
459
460    fn set_async(&self, is_async: bool) {
461        self.0
462            .state()
463            .rx_async_flag
464            .store(is_async, Ordering::Relaxed);
465    }
466}
467
468crate::any_peripheral! {
469    /// An SPI-compatible type-erased DMA channel.
470    pub peripheral SpiDmaChannel<'d> {
471        Spi2(DMA_SPI2<'d>),
472        Spi3(DMA_SPI3<'d>),
473    }
474}
475
476impl<'d> DmaChannel for SpiDmaChannel<'d> {
477    type Rx = SpiDmaRxChannel<'d>;
478    type Tx = SpiDmaTxChannel<'d>;
479
480    unsafe fn split_internal(self, _: crate::private::Internal) -> (Self::Rx, Self::Tx) {
481        (
482            SpiDmaRxChannel(unsafe { self.clone_unchecked() }),
483            SpiDmaTxChannel(self),
484        )
485    }
486}
487
488impl SpiDmaChannel<'_> {
489    delegate::delegate! {
490        to match &self.0 {
491            any::Inner::Spi2(channel) => channel,
492            any::Inner::Spi3(channel) => channel,
493        } {
494            fn register_block(&self) -> &SpiRegisterBlock;
495            fn info(&self) -> &'static ChannelInfo;
496            fn state(&self) -> &'static ChannelState;
497        }
498    }
499}
500
501// Convert erased channel into erased TX/RX half structs
502impl<'d> From<SpiDmaChannel<'d>> for SpiDmaRxChannel<'d> {
503    fn from(this: SpiDmaChannel<'d>) -> SpiDmaRxChannel<'d> {
504        SpiDmaRxChannel(this)
505    }
506}
507
508impl<'d> From<SpiDmaChannel<'d>> for SpiDmaTxChannel<'d> {
509    fn from(this: SpiDmaChannel<'d>) -> SpiDmaTxChannel<'d> {
510        SpiDmaTxChannel(this)
511    }
512}
513
514for_each_dma_channel_peri_pair! {
515    ("SPI_DMA", $dma_peri:ident, $peri:ident) => {
516        use $crate::peripherals::$dma_peri;
517        impl $dma_peri<'_> {
518            pub(super) fn info(&self) -> &'static ChannelInfo {
519                #[crate::handler(priority = crate::interrupt::Priority::max())]
520                fn interrupt_handler() {
521                    crate::dma::asynch::handle_in_interrupt::<$dma_peri<'static>>();
522                    crate::dma::asynch::handle_out_interrupt::<$dma_peri<'static>>();
523                }
524
525                static INFO: ChannelInfo = ChannelInfo {
526                    peripheral_interrupt: paste::paste! { Interrupt::[<$peri _DMA>] },
527                    async_handler: interrupt_handler,
528                    compatible_peripherals: &[crate::dma::DmaPeripheral::$peri.0],
529                };
530
531                &INFO
532            }
533
534            pub(super) fn state(&self) -> &'static ChannelState {
535                static STATE: ChannelState = ChannelState {
536                    tx_waker: AtomicWaker::new(),
537                    rx_waker: AtomicWaker::new(),
538                    tx_async_flag: portable_atomic::AtomicBool::new(false),
539                    rx_async_flag: portable_atomic::AtomicBool::new(false),
540                };
541                &STATE
542            }
543        }
544
545        crate::dma::impl_channel_common!(SpiDma, $dma_peri);
546    };
547}
548
549pub(super) fn enable_spi_dma() {
550    #[cfg(esp32)]
551    {
552        // (only) on ESP32 we need to configure DPORT for the SPI DMA channels
553        // This assigns the DMA channels to the SPI peripherals, which is more
554        // restrictive than necessary but we currently support the same
555        // number of SPI peripherals as SPI DMA channels so it's not a big
556        // deal.
557        use crate::peripherals::DPORT;
558
559        DPORT::regs().spi_dma_chan_sel().modify(|_, w| unsafe {
560            w.spi2_dma_chan_sel().bits(1);
561            w.spi3_dma_chan_sel().bits(2)
562        });
563    }
564}