From 03356a261801d7ee234490809eef3eac3c27cc52 Mon Sep 17 00:00:00 2001 From: Bogdan Petru Chircu Mare Date: Tue, 25 Nov 2025 22:09:01 -0800 Subject: feat(dma): add DMA driver with 10 verified examples Initial DMA driver implementation for MCXA276 with: Core DMA Features: - DmaChannel type with ownership tracking via Channel trait - Transfer, RingBuffer, and ScatterGatherBuilder abstractions - Support for mem-to-mem, mem-to-peripheral, peripheral-to-mem transfers - Interrupt-driven completion with embassy async/await integration - Word size abstraction (u8, u16, u32) via Word trait LPUART DMA Integration: - LpuartTxDma and LpuartRxDma drivers for async UART with DMA - LpuartDma combined TX/RX driver - Automatic chunking for buffers > 0x7FFF bytes - DMA guards with Drop impl for safe cancellation 10 Verified Examples: - dma_mem2mem: Basic memory-to-memory copy - dma_memset: Memory fill with pattern - dma_uart_tx: UART transmit via DMA - dma_uart_rx: UART receive via DMA - dma_uart_loopback: Combined TX/RX loopback test - dma_scatter_gather: Linked descriptor chains - dma_channel_linking: Major/minor loop channel linking - dma_ring_buffer: Circular buffer for continuous streaming - dma_ping_pong: Double-buffering pattern - dma_software_trigger: Manual transfer triggering PR Feedback Addressed: - Use PAC accessor for LPUART DATA register instead of manual offset - Add EnableInterrupt enum to replace boolean parameter for readability - Add DMA guards with Drop impl for safe async cancellation - Automatic chunking for large buffers instead of returning error - Use NonNull<[W]> + PhantomData for RingBuffer (DMA acts like separate thread) - Remove edma parameter from all methods (single eDMA instance steals ptr internally) - Make edma_tcd() non-public (HAL should not expose PAC items) --- examples/src/bin/dma_wrap_transfer.rs | 231 ++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 examples/src/bin/dma_wrap_transfer.rs (limited to 'examples/src/bin/dma_wrap_transfer.rs') diff --git a/examples/src/bin/dma_wrap_transfer.rs b/examples/src/bin/dma_wrap_transfer.rs new file mode 100644 index 000000000..b115a2c19 --- /dev/null +++ b/examples/src/bin/dma_wrap_transfer.rs @@ -0,0 +1,231 @@ +//! DMA wrap transfer example for MCXA276. +//! +//! This example demonstrates using DMA with modulo addressing to wrap around +//! a source buffer, effectively repeating the source data in the destination. +//! +//! # Embassy-style features demonstrated: +//! - `dma::edma_tcd()` accessor for simplified register access +//! - `DmaChannel::is_done()` and `clear_done()` helper methods +//! - No need to pass register block around + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clocks::config::Div8; +use embassy_mcxa::clocks::Gate; +use embassy_mcxa::dma::{edma_tcd, DmaChannel, DmaCh0InterruptHandler}; +use embassy_mcxa::{bind_interrupts, dma}; +use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; +use embassy_mcxa::pac; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +// Bind DMA channel 0 interrupt using Embassy-style macro +bind_interrupts!(struct Irqs { + DMA_CH0 => DmaCh0InterruptHandler; +}); + +// Source buffer: 4 words (16 bytes), aligned to 16 bytes for modulo +#[repr(align(16))] +struct AlignedSrc([u32; 4]); + +static mut SRC: AlignedSrc = AlignedSrc([0; 4]); +static mut DST: [u32; 8] = [0; 8]; + +/// Helper to write a u32 as decimal ASCII to UART +fn write_u32(tx: &mut LpuartTx<'_, Blocking>, val: u32) { + let mut buf = [0u8; 10]; + let mut n = val; + let mut i = buf.len(); + + if n == 0 { + tx.blocking_write(b"0").ok(); + return; + } + + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + tx.blocking_write(&buf[i..]).ok(); +} + +/// Helper to print a buffer to UART +fn print_buffer(tx: &mut LpuartTx<'_, Blocking>, buf_ptr: *const u32, len: usize) { + tx.blocking_write(b"[").ok(); + unsafe { + for i in 0..len { + write_u32(tx, *buf_ptr.add(i)); + if i < len - 1 { + tx.blocking_write(b", ").ok(); + } + } + } + tx.blocking_write(b"]").ok(); +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Small delay to allow probe-rs to attach after reset + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + let mut cfg = hal::config::Config::default(); + cfg.clock_cfg.sirc.fro_12m_enabled = true; + cfg.clock_cfg.sirc.fro_lf_div = Some(Div8::no_div()); + let p = hal::init(cfg); + + defmt::info!("DMA wrap transfer example starting..."); + + // Enable DMA0 clock and release reset + unsafe { + hal::peripherals::DMA0::enable_clock(); + hal::peripherals::DMA0::release_reset(); + } + + let pac_periphs = unsafe { pac::Peripherals::steal() }; + + unsafe { + dma::init(&pac_periphs); + } + + // Enable DMA interrupt + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0); + } + + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: false, + ..Default::default() + }; + + let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); + let (mut tx, _rx) = lpuart.split(); + + tx.blocking_write(b"EDMA wrap transfer example begin.\r\n\r\n") + .unwrap(); + + // Initialize buffers + unsafe { + SRC.0 = [1, 2, 3, 4]; + DST = [0; 8]; + } + + tx.blocking_write(b"Source Buffer: ").unwrap(); + print_buffer(&mut tx, unsafe { core::ptr::addr_of!(SRC.0) } as *const u32, 4); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Destination Buffer (before): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n").unwrap(); + + tx.blocking_write(b"Configuring DMA with Embassy-style API...\r\n") + .unwrap(); + + // Create DMA channel using Embassy-style API + let dma_ch0 = DmaChannel::new(p.DMA_CH0); + + // Use edma_tcd() accessor instead of passing register block around + let edma = edma_tcd(); + + // Configure wrap transfer using direct TCD access: + // SRC is 16 bytes (4 * u32). We want to transfer 32 bytes (8 * u32). + // SRC modulo is 16 bytes (2^4 = 16) - wraps source address. + // DST modulo is 0 (disabled). + // This causes the source address to wrap around after 16 bytes, + // effectively repeating the source data. + unsafe { + let t = edma.tcd(0); + + // Reset channel state + t.ch_csr().write(|w| { + w.erq().disable() + .earq().disable() + .eei().no_error() + .ebw().disable() + .done().clear_bit_by_one() + }); + t.ch_es().write(|w| w.bits(0)); + t.ch_int().write(|w| w.int().clear_bit_by_one()); + + // Source/destination addresses + t.tcd_saddr().write(|w| w.saddr().bits(core::ptr::addr_of!(SRC.0) as u32)); + t.tcd_daddr().write(|w| w.daddr().bits(core::ptr::addr_of_mut!(DST) as u32)); + + // Offsets: both increment by 4 bytes + t.tcd_soff().write(|w| w.soff().bits(4)); + t.tcd_doff().write(|w| w.doff().bits(4)); + + // Attributes: 32-bit transfers (size = 2) + // SMOD = 4 (2^4 = 16 byte modulo for source), DMOD = 0 (disabled) + t.tcd_attr().write(|w| { + w.ssize().bits(2) + .dsize().bits(2) + .smod().bits(4) // Source modulo: 2^4 = 16 bytes + .dmod().bits(0) // Dest modulo: disabled + }); + + // Transfer 32 bytes total in one minor loop + let nbytes = 32u32; + t.tcd_nbytes_mloffno().write(|w| w.nbytes().bits(nbytes)); + + // Source wraps via modulo, no adjustment needed + t.tcd_slast_sda().write(|w| w.slast_sda().bits(0)); + // Reset dest address after major loop + t.tcd_dlast_sga().write(|w| w.dlast_sga().bits(-(nbytes as i32) as u32)); + + // Major loop count = 1 + t.tcd_biter_elinkno().write(|w| w.biter().bits(1)); + t.tcd_citer_elinkno().write(|w| w.citer().bits(1)); + + // Enable interrupt on major loop completion + t.tcd_csr().write(|w| w.intmajor().set_bit()); + + cortex_m::asm::dsb(); + + tx.blocking_write(b"Triggering transfer...\r\n").unwrap(); + dma_ch0.trigger_start(edma); + } + + // Wait for completion using channel helper method + while !dma_ch0.is_done(edma) { + cortex_m::asm::nop(); + } + unsafe { dma_ch0.clear_done(edma); } + + tx.blocking_write(b"\r\nEDMA wrap transfer example finish.\r\n\r\n") + .unwrap(); + tx.blocking_write(b"Destination Buffer (after): ").unwrap(); + print_buffer(&mut tx, core::ptr::addr_of!(DST) as *const u32, 8); + tx.blocking_write(b"\r\n\r\n").unwrap(); + + // Verify: DST should be [1, 2, 3, 4, 1, 2, 3, 4] + let expected = [1u32, 2, 3, 4, 1, 2, 3, 4]; + let mut mismatch = false; + unsafe { + for i in 0..8 { + if DST[i] != expected[i] { + mismatch = true; + break; + } + } + } + + if mismatch { + tx.blocking_write(b"FAIL: Mismatch detected!\r\n").unwrap(); + defmt::error!("FAIL: Mismatch detected!"); + } else { + tx.blocking_write(b"PASS: Data verified.\r\n").unwrap(); + defmt::info!("PASS: Data verified."); + } + + loop { + cortex_m::asm::wfe(); + } +} + -- cgit