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}