Skip to main content

ws63_hal/
dma.rs

1//! DMA (Direct Memory Access) driver for WS63.
2//!
3//! The WS63 has two DMA controllers:
4//! - **DMA** (MDMA) at 0x4A00_0000 — 4 channels, used for general-purpose transfers
5//! - **SDMA** (Secure DMA) at 0x520A_0000 — 4 additional channels, same register layout
6//!
7//! Each channel supports:
8//! - Memory-to-memory, memory-to-peripheral, peripheral-to-memory transfers
9//! - Linked list (scatter-gather) mode
10//! - Configurable source/destination burst sizes and widths
11//! - Address increment control
12//! - Transfer complete and error interrupts
13//!
14//! # Channel addressing
15//!
16//! The WS63 numbers DMA channels in a single **logical** space:
17//! - **MDMA** (`Dma0`) owns logical channels **0-3**
18//! - **SDMA** (`Sdma0`) owns logical channels **8-11**
19//!
20//! Both are backed by physical channels 0-3 on their own register block, so
21//! `DmaDriver<Sdma0>` accepts channel numbers 8-11 and translates them to the
22//! controller-local 0-3 internally (matching the C SDK `hal_dma_ch_get` /
23//! `hal_dma_type_get` convention in fbb_ws63 `hal_dmac_v151.c`). Passing a
24//! channel outside a controller's logical range panics.
25
26use crate::peripherals::{Dma, Sdma};
27use core::marker::PhantomData;
28
29// ── Type-level DMA instance markers ───────────────────────────────
30
31/// DMA instance trait.
32pub trait DmaInstance {
33    /// Returns the PAC pointer for this DMA controller.
34    fn ptr() -> *const ws63_pac::dma::RegisterBlock;
35
36    /// Logical channel number of this controller's first physical channel.
37    ///
38    /// MDMA exposes logical channels `CHANNEL_BASE..CHANNEL_BASE+4` (0-3); SDMA
39    /// exposes 8-11. The driver subtracts this base to index the controller's
40    /// physical channels 0-3 — see the module-level "Channel addressing" docs.
41    const CHANNEL_BASE: u8;
42}
43
44/// Marker type for the primary DMA controller (logical channels 0-3).
45pub struct Dma0;
46impl DmaInstance for Dma0 {
47    fn ptr() -> *const ws63_pac::dma::RegisterBlock {
48        Dma::ptr()
49    }
50    const CHANNEL_BASE: u8 = 0;
51}
52
53/// Marker type for the secure DMA controller (logical channels 8-11).
54pub struct Sdma0;
55impl DmaInstance for Sdma0 {
56    fn ptr() -> *const ws63_pac::dma::RegisterBlock {
57        Sdma::ptr()
58    }
59    const CHANNEL_BASE: u8 = 8;
60}
61
62/// Translate a logical channel number to this controller's physical channel
63/// index (0-3), validating it falls in the controller's logical range.
64#[inline]
65fn physical_channel_index(base: u8, channel: u8) -> usize {
66    assert!(channel >= base && channel < base + 4, "DMA channel out of range for this controller");
67    (channel - base) as usize
68}
69
70// ── Configuration types ───────────────────────────────────────────
71
72/// Transfer width (data size per beat).
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum TransferWidth {
75    /// 8 bits (1 byte).
76    Width8 = 0,
77    /// 16 bits (2 bytes).
78    Width16 = 1,
79    /// 32 bits (4 bytes).
80    Width32 = 2,
81}
82
83/// Burst size (number of beats per burst).
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum BurstSize {
86    /// 1 beat.
87    Beats1 = 0,
88    /// 4 beats.
89    Beats4 = 1,
90    /// 8 beats.
91    Beats8 = 2,
92    /// 16 beats.
93    Beats16 = 3,
94    /// 32 beats.
95    Beats32 = 4,
96    /// 64 beats.
97    Beats64 = 5,
98    /// 128 beats.
99    Beats128 = 6,
100    /// 256 beats.
101    Beats256 = 7,
102}
103
104/// DMA transfer direction / flow control.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum FlowControl {
107    /// Memory-to-memory transfer.
108    MemToMem = 0,
109    /// Memory-to-peripheral transfer.
110    MemToPeripheral = 1,
111    /// Peripheral-to-memory transfer.
112    PeripheralToMem = 2,
113    /// Peripheral-to-peripheral transfer.
114    PeripheralToPeripheral = 3,
115}
116
117/// DMA channel configuration.
118#[derive(Debug, Clone, Copy)]
119pub struct DmaChannelConfig {
120    /// Source peripheral select (0-15).
121    pub src_peripheral: u8,
122    /// Destination peripheral select (0-15).
123    pub dst_peripheral: u8,
124    /// Flow control mode.
125    pub flow_control: FlowControl,
126    /// Source transfer width.
127    pub src_width: TransferWidth,
128    /// Destination transfer width.
129    pub dst_width: TransferWidth,
130    /// Source burst size.
131    pub src_burst: BurstSize,
132    /// Destination burst size.
133    pub dst_burst: BurstSize,
134    /// Increment source address after each beat.
135    pub src_inc: bool,
136    /// Increment destination address after each beat.
137    pub dst_inc: bool,
138    /// Enable transfer complete interrupt.
139    pub transfer_int: bool,
140    /// Enable error interrupt.
141    pub error_int: bool,
142    /// Bus lock during transfer.
143    pub bus_lock: bool,
144}
145
146impl Default for DmaChannelConfig {
147    fn default() -> Self {
148        Self {
149            src_peripheral: 0,
150            dst_peripheral: 0,
151            flow_control: FlowControl::MemToMem,
152            src_width: TransferWidth::Width32,
153            dst_width: TransferWidth::Width32,
154            src_burst: BurstSize::Beats1,
155            dst_burst: BurstSize::Beats1,
156            src_inc: true,
157            dst_inc: true,
158            transfer_int: false,
159            error_int: false,
160            bus_lock: false,
161        }
162    }
163}
164
165// ── DMA driver ────────────────────────────────────────────────────
166
167/// DMA controller driver.
168pub struct DmaDriver<'d, T: DmaInstance> {
169    _instance: PhantomData<&'d T>,
170}
171
172impl<'d, T: DmaInstance> DmaDriver<'d, T> {
173    /// Create a new DMA driver from a DMA peripheral.
174    pub fn new(_dma: impl Into<PhantomData<&'d T>>) -> Self {
175        Self { _instance: PhantomData }
176    }
177
178    fn regs() -> &'static ws63_pac::dma::RegisterBlock {
179        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid
180        unsafe { &*T::ptr() }
181    }
182
183    /// Translate a logical channel number (0-3 for MDMA, 8-11 for SDMA) to this
184    /// controller's physical channel index (0-3). Panics if out of range.
185    #[inline]
186    fn physical_channel(channel: u8) -> usize {
187        physical_channel_index(T::CHANNEL_BASE, channel)
188    }
189
190    /// Enable the DMA controller.
191    pub fn enable_controller(&mut self) {
192        let r = Self::regs();
193        unsafe {
194            r.dmac_config().write(|w| w.bits(0x01));
195        }
196    }
197
198    /// Disable the DMA controller.
199    pub fn disable_controller(&mut self) {
200        unsafe {
201            Self::regs().dmac_config().write(|w| w.bits(0));
202        }
203    }
204
205    /// Configure a DMA channel.
206    ///
207    /// * `channel` — Logical channel number (0-3 for MDMA, 8-11 for SDMA).
208    /// * `src_addr` — Source address.
209    /// * `dst_addr` — Destination address.
210    /// * `transfer_size` — Number of source-width beats to transfer.
211    /// * `config` — Channel configuration.
212    pub fn configure_channel(
213        &mut self,
214        channel: u8,
215        src_addr: u32,
216        dst_addr: u32,
217        transfer_size: u16,
218        config: &DmaChannelConfig,
219    ) {
220        let ch = Self::physical_channel(channel);
221        let r = Self::regs();
222
223        // Disable channel before configuration
224        unsafe {
225            r.dmac_chn_config_0(ch).write(|w| w.bits(0));
226        }
227
228        // Set source address
229        unsafe {
230            r.dmac_s_addr_0(ch).write(|w| w.bits(src_addr));
231        }
232
233        // Set destination address
234        unsafe {
235            r.dmac_d_addr_0(ch).write(|w| w.bits(dst_addr));
236        }
237
238        // Clear linked list pointer
239        unsafe {
240            r.dmac_lli_0(ch).write(|w| w.bits(0));
241        }
242
243        // Build control register
244        let mut control: u32 = 0;
245        control |= (transfer_size as u32) & 0xFFF; // trans_size [0:11]
246        control |= ((config.src_burst as u32) & 0x07) << 12; // s_bsize [12:14]
247        control |= ((config.dst_burst as u32) & 0x07) << 15; // d_bsize [15:17]
248        control |= ((config.src_width as u32) & 0x07) << 18; // s_width [18:20]
249        control |= ((config.dst_width as u32) & 0x07) << 21; // d_width [21:23]
250        control |= 0 << 24; // s_master = M1
251        control |= 0 << 25; // d_master = M1
252        if config.src_inc {
253            control |= 1 << 26;
254        }
255        if config.dst_inc {
256            control |= 1 << 27;
257        }
258        control |= 0 << 28; // prot = 0
259        if config.transfer_int {
260            control |= 1 << 31;
261        }
262
263        unsafe {
264            r.dmac_chn_control_0(ch).write(|w| w.bits(control));
265        }
266
267        // Build channel config register
268        let mut ch_cfg: u32 = 0;
269        ch_cfg |= 0x01; // chn_en
270        ch_cfg |= ((config.src_peripheral as u32) & 0x0F) << 1; // s_peripheral [1:4]
271        ch_cfg |= ((config.dst_peripheral as u32) & 0x0F) << 5; // d_peripheral [5:8]
272        ch_cfg |= ((config.flow_control as u32) & 0x07) << 9; // flow_ctl [9:11]
273        if config.error_int {
274            ch_cfg |= 1 << 12; // int_en
275        }
276        if config.transfer_int {
277            ch_cfg |= 1 << 13; // int_tc
278        }
279        if config.bus_lock {
280            ch_cfg |= 1 << 14; // lock
281        }
282
283        unsafe {
284            r.dmac_chn_config_0(ch).write(|w| w.bits(ch_cfg));
285        }
286    }
287
288    /// Enable a specific DMA channel.
289    pub fn enable_channel(&mut self, channel: u8) {
290        let ch = Self::physical_channel(channel);
291        let r = Self::regs();
292        let cfg = r.dmac_chn_config_0(ch).read().bits();
293        unsafe {
294            r.dmac_chn_config_0(ch).write(|w| w.bits(cfg | 0x01));
295        }
296    }
297
298    /// Disable a specific DMA channel.
299    pub fn disable_channel(&mut self, channel: u8) {
300        let ch = Self::physical_channel(channel);
301        let r = Self::regs();
302        let cfg = r.dmac_chn_config_0(ch).read().bits();
303        unsafe {
304            r.dmac_chn_config_0(ch).write(|w| w.bits(cfg & !0x01));
305        }
306    }
307
308    /// Check if a DMA channel is enabled.
309    pub fn channel_enabled(&self, channel: u8) -> bool {
310        let ch = Self::physical_channel(channel);
311        let mask = 1u32 << ch;
312        Self::regs().dmac_en_chns().read().bits() & mask != 0
313    }
314
315    /// Check if a channel has data in its FIFO (active transfer).
316    pub fn channel_active(&self, channel: u8) -> bool {
317        let ch = Self::physical_channel(channel);
318        Self::regs().dmac_chn_config_0(ch).read().bits() & (1 << 15) != 0
319    }
320
321    /// Halt a DMA channel (ignore further DMA requests).
322    pub fn halt_channel(&mut self, channel: u8) {
323        let ch = Self::physical_channel(channel);
324        let r = Self::regs();
325        let cfg = r.dmac_chn_config_0(ch).read().bits();
326        unsafe {
327            r.dmac_chn_config_0(ch).write(|w| w.bits(cfg | (1 << 16)));
328        }
329    }
330
331    /// Resume a halted DMA channel.
332    pub fn resume_channel(&mut self, channel: u8) {
333        let ch = Self::physical_channel(channel);
334        let r = Self::regs();
335        let cfg = r.dmac_chn_config_0(ch).read().bits();
336        unsafe {
337            r.dmac_chn_config_0(ch).write(|w| w.bits(cfg & !(1 << 16)));
338        }
339    }
340
341    /// Issue a software burst request for a channel.
342    pub fn burst_request(&mut self, channel: u8) {
343        let ch = Self::physical_channel(channel);
344        unsafe {
345            Self::regs().dmac_burst_req().write(|w| w.bits(1 << ch));
346        }
347    }
348
349    /// Issue a software single request for a channel.
350    pub fn single_request(&mut self, channel: u8) {
351        let ch = Self::physical_channel(channel);
352        unsafe {
353            Self::regs().dmac_single_req().write(|w| w.bits(1 << ch));
354        }
355    }
356
357    /// Get the raw interrupt status.
358    ///
359    /// Returns `(transfer_done_mask, error_mask)`. Bit `n` of each mask is the
360    /// **physical** channel `n` of this controller — i.e. logical channel
361    /// `CHANNEL_BASE + n`. For SDMA, logical channel 8 is bit 0, channel 9 is
362    /// bit 1, etc. (the controller's per-channel status registers are local).
363    pub fn raw_interrupt_status(&self) -> (u8, u8) {
364        let sts = Self::regs().dmac_ori_int_st().read().bits();
365        ((sts & 0xFF) as u8, ((sts >> 8) & 0xFF) as u8)
366    }
367
368    /// Get the masked interrupt status.
369    ///
370    /// Returns `(transfer_done_mask, error_mask)`, with the same **physical**
371    /// channel bit indexing as [`raw_interrupt_status`](Self::raw_interrupt_status)
372    /// (bit `n` = physical channel `n` = logical `CHANNEL_BASE + n`).
373    pub fn interrupt_status(&self) -> (u8, u8) {
374        let sts = Self::regs().dmac_int_st().read().bits();
375        ((sts & 0xFF) as u8, ((sts >> 16) & 0xFF) as u8)
376    }
377
378    /// Clear transfer complete interrupt for a channel.
379    pub fn clear_transfer_interrupt(&mut self, channel: u8) {
380        let ch = Self::physical_channel(channel);
381        unsafe {
382            Self::regs().dmac_int_clr().write(|w| w.bits(1 << ch));
383        }
384    }
385
386    /// Clear error interrupt for a channel.
387    pub fn clear_error_interrupt(&mut self, channel: u8) {
388        let ch = Self::physical_channel(channel);
389        unsafe {
390            Self::regs().dmac_int_clr().write(|w| w.bits(1 << (ch + 8)));
391        }
392    }
393
394    /// Set DMA sync configuration.
395    ///
396    /// Each bit controls sync for the corresponding channel
397    /// (0 = enable sync logic, 1 = disable sync logic).
398    pub fn set_sync(&mut self, sync_mask: u16) {
399        unsafe {
400            Self::regs().dmac_sync().write(|w| w.bits(sync_mask as u32));
401        }
402    }
403}
404
405// ── Convenience constructors ──────────────────────────────────────
406
407impl<'d> DmaDriver<'d, Dma0> {
408    /// Create a new primary DMA driver.
409    pub fn new_dma(_dma: Dma<'d>) -> Self {
410        Self { _instance: PhantomData }
411    }
412}
413
414// ── DMA peripheral handshaking request IDs ────────────────────────
415
416/// DMA peripheral hardware-handshaking request ID.
417///
418/// Values are the `HAL_DMA_HANDSHAKING_*` indices from fbb_ws63
419/// `drivers/chips/ws63/porting/dma/dma_porting.h` — the hardware request line a
420/// channel uses for peripheral-paced flow control. They go into the channel
421/// config's `src_peripheral` / `dst_peripheral` field (a 4-bit field; all the
422/// IDs below fit). UART bus mapping per `platform_core.h`: UART0 = UART_L,
423/// UART1 = UART_H0, UART2 = UART_H1.
424///
425/// (These superseded the earlier fabricated sequential 0..11 values; only the
426/// main-DMA (MDMA) sources ws63-hal models are listed — the SDMA-group I2C IDs
427/// (≥29) don't fit the 4-bit field and aren't modelled here.)
428#[derive(Debug, Clone, Copy, PartialEq, Eq)]
429#[repr(u8)]
430pub enum DmaPeripheral {
431    /// No handshaking (tie-off) — used for memory-to-memory transfers.
432    Tie0 = 0,
433    /// UART0 (UART_L) transmit.
434    Uart0Tx = 1,
435    /// UART0 (UART_L) receive.
436    Uart0Rx = 2,
437    /// UART1 (UART_H0) transmit.
438    Uart1Tx = 3,
439    /// UART1 (UART_H0) receive.
440    Uart1Rx = 4,
441    /// UART2 (UART_H1) transmit.
442    Uart2Tx = 5,
443    /// UART2 (UART_H1) receive.
444    Uart2Rx = 6,
445    /// SPI0 (SPI_MS0) transmit.
446    Spi0Tx = 7,
447    /// SPI0 (SPI_MS0) receive.
448    Spi0Rx = 8,
449    /// I2S transmit.
450    I2sTx = 11,
451    /// I2S receive.
452    I2sRx = 12,
453    /// SPI1 (SPI_MS1) transmit.
454    Spi1Tx = 13,
455    /// SPI1 (SPI_MS1) receive.
456    Spi1Rx = 14,
457}
458
459impl DmaPeripheral {
460    /// The hardware handshaking request ID (the `dma_porting.h` index), as
461    /// programmed into the channel config's peripheral-select field.
462    pub const fn request_id(self) -> u8 {
463        self as u8
464    }
465}
466
467/// DMA transfer direction.
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
469pub enum DmaDirection {
470    /// Transmit (memory to peripheral).
471    Tx,
472    /// Receive (peripheral to memory).
473    Rx,
474}
475
476impl DmaChannelConfig {
477    /// Configure this channel for a **memory → peripheral** transfer to `peri`:
478    /// sets `MemToPeripheral` flow control, the destination handshaking ID, and
479    /// holds the destination address fixed (a peripheral data register).
480    pub fn mem_to_peripheral(mut self, peri: DmaPeripheral) -> Self {
481        self.flow_control = FlowControl::MemToPeripheral;
482        self.dst_peripheral = peri.request_id();
483        self.dst_inc = false;
484        self
485    }
486
487    /// Configure this channel for a **peripheral → memory** transfer from `peri`:
488    /// sets `PeripheralToMem` flow control, the source handshaking ID, and holds
489    /// the source address fixed (a peripheral data register).
490    pub fn peripheral_to_mem(mut self, peri: DmaPeripheral) -> Self {
491        self.flow_control = FlowControl::PeripheralToMem;
492        self.src_peripheral = peri.request_id();
493        self.src_inc = false;
494        self
495    }
496}
497
498// The `DmaEligible` / `DmaChannelFor` binding traits were removed (dead — impl'd
499// for Spi0/Spi1 but never called; no DmaChannelFor impls). Peripheral-paced DMA
500// is now wired through [`DmaPeripheral`] + [`DmaChannelConfig::mem_to_peripheral`]
501// / [`peripheral_to_mem`](DmaChannelConfig::peripheral_to_mem), which feed the
502// correct handshaking ID + flow control into `configure_channel`.
503
504impl<'d> DmaDriver<'d, Sdma0> {
505    /// Create a new secure DMA driver.
506    pub fn new_sdma(_sdma: Sdma<'d>) -> Self {
507        Self { _instance: PhantomData }
508    }
509}
510
511// ── Tests ──────────────────────────────────────────────────────
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516
517    #[test]
518    fn test_dma_direction_tx_rx_distinct() {
519        // TX and RX directions must map to different peripheral IDs
520        assert_ne!(DmaDirection::Tx as u8, DmaDirection::Rx as u8);
521    }
522
523    // The request IDs below are the HAL_DMA_HANDSHAKING_* indices from fbb_ws63
524    // dma_porting.h (the single source of truth) — NOT a fabricated 0..11 run.
525
526    #[test]
527    fn test_dma_peripheral_spi_handshaking_ids() {
528        // SPI_MS0 = 7/8, SPI_MS1 = 13/14.
529        assert_eq!(DmaPeripheral::Spi0Tx.request_id(), 7);
530        assert_eq!(DmaPeripheral::Spi0Rx.request_id(), 8);
531        assert_eq!(DmaPeripheral::Spi1Tx.request_id(), 13);
532        assert_eq!(DmaPeripheral::Spi1Rx.request_id(), 14);
533    }
534
535    #[test]
536    fn test_dma_peripheral_uart_handshaking_ids() {
537        // UART0=UART_L (1/2), UART1=UART_H0 (3/4), UART2=UART_H1 (5/6).
538        assert_eq!(DmaPeripheral::Uart0Tx.request_id(), 1);
539        assert_eq!(DmaPeripheral::Uart0Rx.request_id(), 2);
540        assert_eq!(DmaPeripheral::Uart1Tx.request_id(), 3);
541        assert_eq!(DmaPeripheral::Uart1Rx.request_id(), 4);
542        assert_eq!(DmaPeripheral::Uart2Tx.request_id(), 5);
543        assert_eq!(DmaPeripheral::Uart2Rx.request_id(), 6);
544    }
545
546    #[test]
547    fn test_dma_peripheral_i2s_handshaking_ids() {
548        assert_eq!(DmaPeripheral::I2sTx.request_id(), 11);
549        assert_eq!(DmaPeripheral::I2sRx.request_id(), 12);
550    }
551
552    #[test]
553    fn test_dma_peripheral_ids_fit_4bit_field() {
554        // src/dst_peripheral is a 4-bit channel-config field.
555        for p in [
556            DmaPeripheral::Uart0Tx,
557            DmaPeripheral::Uart2Rx,
558            DmaPeripheral::Spi0Tx,
559            DmaPeripheral::Spi1Rx,
560            DmaPeripheral::I2sTx,
561            DmaPeripheral::I2sRx,
562        ] {
563            assert!(p.request_id() <= 0x0F, "{:?} id {} > 4 bits", p, p.request_id());
564        }
565    }
566
567    #[test]
568    fn test_dma_channel_config_peripheral_wiring() {
569        // mem_to_peripheral / peripheral_to_mem set flow control + the handshaking
570        // ID on the correct side, and hold the peripheral-register address fixed.
571        let tx = DmaChannelConfig::default().mem_to_peripheral(DmaPeripheral::Spi0Tx);
572        assert_eq!(tx.flow_control, FlowControl::MemToPeripheral);
573        assert_eq!(tx.dst_peripheral, 7);
574        assert!(!tx.dst_inc);
575
576        let rx = DmaChannelConfig::default().peripheral_to_mem(DmaPeripheral::Uart1Rx);
577        assert_eq!(rx.flow_control, FlowControl::PeripheralToMem);
578        assert_eq!(rx.src_peripheral, 4);
579        assert!(!rx.src_inc);
580    }
581
582    #[test]
583    fn test_channel_base_consts() {
584        // MDMA owns logical channels 0-3; SDMA owns 8-11.
585        assert_eq!(Dma0::CHANNEL_BASE, 0);
586        assert_eq!(Sdma0::CHANNEL_BASE, 8);
587    }
588
589    #[test]
590    fn test_mdma_logical_to_physical() {
591        // MDMA: logical channel n == physical channel n.
592        for ch in 0u8..4 {
593            assert_eq!(physical_channel_index(Dma0::CHANNEL_BASE, ch), ch as usize);
594        }
595    }
596
597    #[test]
598    fn test_sdma_logical_to_physical() {
599        // SDMA: logical channels 8-11 map to physical 0-3 on the secure block
600        // (matches fbb_ws63 hal_dma_ch_get: ch % 4 with the SDMA base).
601        assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 8), 0);
602        assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 9), 1);
603        assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 10), 2);
604        assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 11), 3);
605    }
606
607    #[test]
608    #[should_panic(expected = "out of range")]
609    fn test_mdma_channel_4_panics() {
610        // 4 is out of MDMA's logical range (0-3).
611        physical_channel_index(Dma0::CHANNEL_BASE, 4);
612    }
613
614    #[test]
615    #[should_panic(expected = "out of range")]
616    fn test_sdma_channel_7_panics() {
617        // 7 is below SDMA's logical range (8-11) — passing an MDMA-style index
618        // to the secure controller must not silently alias physical channel 0.
619        physical_channel_index(Sdma0::CHANNEL_BASE, 7);
620    }
621
622    #[test]
623    #[should_panic(expected = "out of range")]
624    fn test_sdma_channel_12_panics() {
625        // 12 is above SDMA's logical range (8-11).
626        physical_channel_index(Sdma0::CHANNEL_BASE, 12);
627    }
628}
629
630// ── Async DMA completion (bespoke; DMA_INT = IRQ 59) ────────────────────────
631#[cfg(feature = "async")]
632mod asynch_impl {
633    use super::{Dma0, DmaDriver};
634    use crate::asynch::IrqSignal;
635    use crate::interrupt::{self, Interrupt};
636    use core::future::Future;
637    use core::pin::Pin;
638    use core::task::{Context, Poll};
639
640    static DMA_SIGNAL: IrqSignal = IrqSignal::new();
641
642    /// DMA trap hook (IRQ 59): wake the awaiting transfer.
643    pub fn on_interrupt() {
644        DMA_SIGNAL.signal();
645        interrupt::clear_pending(Interrupt::DMA_INT);
646    }
647
648    struct DmaDoneFuture;
649    impl Future for DmaDoneFuture {
650        type Output = ();
651        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
652            if DMA_SIGNAL.take_fired() {
653                Poll::Ready(())
654            } else {
655                DMA_SIGNAL.register(cx.waker());
656                Poll::Pending
657            }
658        }
659    }
660
661    impl DmaDriver<'_, Dma0> {
662        /// Await transfer-complete for `channel` (after configuring + enabling it),
663        /// then clear its interrupt. Parks on the DMA IRQ on hardware; the WS63
664        /// model completes the copy synchronously, so the fast path returns at once.
665        pub async fn wait_transfer_done(&mut self, channel: u8) {
666            let bit = 1u8 << channel; // Dma0: physical channel == logical channel
667            if self.raw_interrupt_status().0 & bit == 0 {
668                DMA_SIGNAL.reset();
669                // SAFETY: enabling a known, fixed WS63 IRQ line.
670                unsafe { interrupt::enable(Interrupt::DMA_INT) };
671                if self.raw_interrupt_status().0 & bit == 0 {
672                    DmaDoneFuture.await;
673                }
674            }
675            self.clear_transfer_interrupt(channel);
676        }
677    }
678}
679
680#[cfg(feature = "async")]
681pub use asynch_impl::on_interrupt;