diff options
| -rw-r--r-- | embassy-mcxa/src/dma.rs | 1 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_channel_link.rs | 1 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs | 55 | ||||
| -rw-r--r-- | examples/mcxa/src/bin/raw_dma_scatter_gather.rs | 115 |
4 files changed, 62 insertions, 110 deletions
diff --git a/embassy-mcxa/src/dma.rs b/embassy-mcxa/src/dma.rs index 1293f8445..ff9ebeb05 100644 --- a/embassy-mcxa/src/dma.rs +++ b/embassy-mcxa/src/dma.rs | |||
| @@ -1659,6 +1659,7 @@ impl<C: Channel> DmaChannel<C> { | |||
| 1659 | /// This matches the hardware layout (32 bytes). | 1659 | /// This matches the hardware layout (32 bytes). |
| 1660 | #[repr(C, align(32))] | 1660 | #[repr(C, align(32))] |
| 1661 | #[derive(Clone, Copy, Debug, Default)] | 1661 | #[derive(Clone, Copy, Debug, Default)] |
| 1662 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 1662 | pub struct Tcd { | 1663 | pub struct Tcd { |
| 1663 | pub saddr: u32, | 1664 | pub saddr: u32, |
| 1664 | pub soff: i16, | 1665 | pub soff: i16, |
diff --git a/examples/mcxa/src/bin/raw_dma_channel_link.rs b/examples/mcxa/src/bin/raw_dma_channel_link.rs index 987f1ba43..74785e4f3 100644 --- a/examples/mcxa/src/bin/raw_dma_channel_link.rs +++ b/examples/mcxa/src/bin/raw_dma_channel_link.rs | |||
| @@ -15,7 +15,6 @@ | |||
| 15 | //! - `DmaChannel::new()` for channel creation | 15 | //! - `DmaChannel::new()` for channel creation |
| 16 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods | 16 | //! - `DmaChannel::is_done()` and `clear_done()` helper methods |
| 17 | //! - Channel linking with `set_minor_link()` and `set_major_link()` | 17 | //! - Channel linking with `set_minor_link()` and `set_major_link()` |
| 18 | //! - Standard `DmaCh*InterruptHandler` with `bind_interrupts!` macro | ||
| 19 | 18 | ||
| 20 | #![no_std] | 19 | #![no_std] |
| 21 | #![no_main] | 20 | #![no_main] |
diff --git a/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs index 4a64b2498..51a7bc275 100644 --- a/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs +++ b/examples/mcxa/src/bin/raw_dma_ping_pong_transfer.rs | |||
| @@ -27,12 +27,10 @@ | |||
| 27 | #![no_std] | 27 | #![no_std] |
| 28 | #![no_main] | 28 | #![no_main] |
| 29 | 29 | ||
| 30 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 31 | |||
| 32 | use embassy_executor::Spawner; | 30 | use embassy_executor::Spawner; |
| 33 | use embassy_mcxa::clocks::config::Div8; | 31 | use embassy_mcxa::clocks::config::Div8; |
| 34 | use embassy_mcxa::dma::{self, DmaChannel, Tcd, TransferOptions}; | 32 | use embassy_mcxa::dma::{DmaChannel, Tcd, TransferOptions}; |
| 35 | use embassy_mcxa::{bind_interrupts, pac}; | 33 | use embassy_mcxa::pac; |
| 36 | use static_cell::ConstStaticCell; | 34 | use static_cell::ConstStaticCell; |
| 37 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | 35 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; |
| 38 | 36 | ||
| @@ -64,32 +62,6 @@ static TCD_POOL: ConstStaticCell<TcdPool> = ConstStaticCell::new(TcdPool( | |||
| 64 | }; 2], | 62 | }; 2], |
| 65 | )); | 63 | )); |
| 66 | 64 | ||
| 67 | // AtomicBool to track scatter/gather completion | ||
| 68 | // Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 69 | // so we need this flag to detect when each transfer completes | ||
| 70 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 71 | |||
| 72 | // Custom handler for scatter/gather that delegates to HAL's on_interrupt() | ||
| 73 | // This follows the "interrupts as threads" pattern - the handler does minimal work | ||
| 74 | // (delegates to HAL + sets a flag) and the main task does the actual processing | ||
| 75 | pub struct PingPongDmaHandler; | ||
| 76 | |||
| 77 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> for PingPongDmaHandler { | ||
| 78 | unsafe fn on_interrupt() { | ||
| 79 | // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers | ||
| 80 | dma::on_interrupt(0); | ||
| 81 | // Signal completion for polling (needed because ESG clears DONE bit) | ||
| 82 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | // Bind DMA channel interrupts | ||
| 87 | // CH0: Custom handler for scatter/gather (delegates to on_interrupt + sets flag) | ||
| 88 | // CH1: Standard handler for wait_half() demo | ||
| 89 | bind_interrupts!(struct Irqs { | ||
| 90 | DMA_CH0 => PingPongDmaHandler; | ||
| 91 | }); | ||
| 92 | |||
| 93 | #[embassy_executor::main] | 65 | #[embassy_executor::main] |
| 94 | async fn main(_spawner: Spawner) { | 66 | async fn main(_spawner: Spawner) { |
| 95 | // Small delay to allow probe-rs to attach after reset | 67 | // Small delay to allow probe-rs to attach after reset |
| @@ -121,12 +93,12 @@ async fn main(_spawner: Spawner) { | |||
| 121 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | 93 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. |
| 122 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. | 94 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), links to TCD1. |
| 123 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. | 95 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), links to TCD0. |
| 124 | unsafe { | 96 | let tcds = &mut TCD_POOL.take().0; |
| 125 | let tcds = &mut TCD_POOL.take().0; | ||
| 126 | 97 | ||
| 127 | let half_len = 4usize; | 98 | let half_len = 4usize; |
| 128 | let half_bytes = (half_len * 4) as u32; | 99 | let half_bytes = (half_len * 4) as u32; |
| 129 | 100 | ||
| 101 | unsafe { | ||
| 130 | let tcd0_addr = &tcds[0] as *const _ as u32; | 102 | let tcd0_addr = &tcds[0] as *const _ as u32; |
| 131 | let tcd1_addr = &tcds[1] as *const _ as u32; | 103 | let tcd1_addr = &tcds[1] as *const _ as u32; |
| 132 | 104 | ||
| @@ -171,11 +143,13 @@ async fn main(_spawner: Spawner) { | |||
| 171 | dma_ch0.trigger_start(); | 143 | dma_ch0.trigger_start(); |
| 172 | } | 144 | } |
| 173 | 145 | ||
| 146 | let tcd = dma_ch0.tcd(); | ||
| 174 | // Wait for first half | 147 | // Wait for first half |
| 175 | while !TRANSFER_DONE.load(Ordering::Acquire) { | 148 | loop { |
| 176 | cortex_m::asm::nop(); | 149 | if tcd.tcd_saddr().read().bits() != src.as_ptr() as u32 { |
| 150 | break; | ||
| 151 | } | ||
| 177 | } | 152 | } |
| 178 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 179 | 153 | ||
| 180 | defmt::info!("First half transferred."); | 154 | defmt::info!("First half transferred."); |
| 181 | defmt::info!("Triggering second half transfer..."); | 155 | defmt::info!("Triggering second half transfer..."); |
| @@ -186,10 +160,11 @@ async fn main(_spawner: Spawner) { | |||
| 186 | } | 160 | } |
| 187 | 161 | ||
| 188 | // Wait for second half | 162 | // Wait for second half |
| 189 | while !TRANSFER_DONE.load(Ordering::Acquire) { | 163 | loop { |
| 190 | cortex_m::asm::nop(); | 164 | if tcd.tcd_saddr().read().bits() != unsafe { src.as_ptr().add(half_len) } as u32 { |
| 165 | break; | ||
| 166 | } | ||
| 191 | } | 167 | } |
| 192 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 193 | 168 | ||
| 194 | defmt::info!("Second half transferred."); | 169 | defmt::info!("Second half transferred."); |
| 195 | 170 | ||
diff --git a/examples/mcxa/src/bin/raw_dma_scatter_gather.rs b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs index 057e56826..eb9960764 100644 --- a/examples/mcxa/src/bin/raw_dma_scatter_gather.rs +++ b/examples/mcxa/src/bin/raw_dma_scatter_gather.rs | |||
| @@ -16,18 +16,15 @@ | |||
| 16 | #![no_std] | 16 | #![no_std] |
| 17 | #![no_main] | 17 | #![no_main] |
| 18 | 18 | ||
| 19 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 20 | |||
| 21 | use embassy_executor::Spawner; | 19 | use embassy_executor::Spawner; |
| 22 | use embassy_mcxa::bind_interrupts; | ||
| 23 | use embassy_mcxa::clocks::config::Div8; | 20 | use embassy_mcxa::clocks::config::Div8; |
| 24 | use embassy_mcxa::dma::{self, DmaChannel, Tcd}; | 21 | use embassy_mcxa::dma::{DmaChannel, Tcd}; |
| 25 | use static_cell::ConstStaticCell; | 22 | use static_cell::ConstStaticCell; |
| 26 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; | 23 | use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; |
| 27 | 24 | ||
| 28 | // Source and destination buffers | 25 | // Source and destination buffers |
| 29 | static SRC: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8]); | 26 | static SRC: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); |
| 30 | static DST: ConstStaticCell<[u32; 8]> = ConstStaticCell::new([0; 8]); | 27 | static DST: ConstStaticCell<[u32; 12]> = ConstStaticCell::new([0; 12]); |
| 31 | 28 | ||
| 32 | // TCD pool for scatter/gather - must be 32-byte aligned | 29 | // TCD pool for scatter/gather - must be 32-byte aligned |
| 33 | #[repr(C, align(32))] | 30 | #[repr(C, align(32))] |
| @@ -49,33 +46,6 @@ static TCD_POOL: ConstStaticCell<TcdPool> = ConstStaticCell::new(TcdPool( | |||
| 49 | }; 2], | 46 | }; 2], |
| 50 | )); | 47 | )); |
| 51 | 48 | ||
| 52 | // AtomicBool to track scatter/gather completion | ||
| 53 | // Note: With ESG=1, DONE bit is cleared by hardware when next TCD loads, | ||
| 54 | // so we need this flag to detect when each transfer completes | ||
| 55 | static TRANSFER_DONE: AtomicBool = AtomicBool::new(false); | ||
| 56 | |||
| 57 | // Custom handler for scatter/gather that delegates to HAL's on_interrupt() | ||
| 58 | // This follows the "interrupts as threads" pattern - the handler does minimal work | ||
| 59 | // (delegates to HAL + sets a flag) and the main task does the actual processing | ||
| 60 | pub struct ScatterGatherDmaHandler; | ||
| 61 | |||
| 62 | impl embassy_mcxa::interrupt::typelevel::Handler<embassy_mcxa::interrupt::typelevel::DMA_CH0> | ||
| 63 | for ScatterGatherDmaHandler | ||
| 64 | { | ||
| 65 | unsafe fn on_interrupt() { | ||
| 66 | // Delegate to HAL's on_interrupt() which clears INT flag and wakes wakers | ||
| 67 | dma::on_interrupt(0); | ||
| 68 | // Signal completion for polling (needed because ESG clears DONE bit) | ||
| 69 | TRANSFER_DONE.store(true, Ordering::Release); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | // Bind DMA channel interrupt | ||
| 74 | // Custom handler for scatter/gather (delegates to on_interrupt + sets flag) | ||
| 75 | bind_interrupts!(struct Irqs { | ||
| 76 | DMA_CH0 => ScatterGatherDmaHandler; | ||
| 77 | }); | ||
| 78 | |||
| 79 | #[embassy_executor::main] | 49 | #[embassy_executor::main] |
| 80 | async fn main(_spawner: Spawner) { | 50 | async fn main(_spawner: Spawner) { |
| 81 | // Small delay to allow probe-rs to attach after reset | 51 | // Small delay to allow probe-rs to attach after reset |
| @@ -101,43 +71,46 @@ async fn main(_spawner: Spawner) { | |||
| 101 | defmt::info!("Configuring scatter-gather DMA with Embassy-style API..."); | 71 | defmt::info!("Configuring scatter-gather DMA with Embassy-style API..."); |
| 102 | 72 | ||
| 103 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); | 73 | let dma_ch0 = DmaChannel::new(p.DMA_CH0); |
| 74 | let src_ptr = src.as_ptr(); | ||
| 75 | let dst_ptr = dst.as_mut_ptr(); | ||
| 104 | 76 | ||
| 105 | // Configure scatter-gather transfer using direct TCD access: | 77 | // Configure scatter-gather transfer using direct TCD access: |
| 106 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. | 78 | // This sets up TCD0 and TCD1 in RAM, and loads TCD0 into the channel. |
| 107 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. | 79 | // TCD0 transfers first half (SRC[0..4] -> DST[0..4]), then loads TCD1. |
| 108 | // TCD1 transfers second half (SRC[4..8] -> DST[4..8]), last TCD. | 80 | // TCD1 transfers second half (SRC[4..12] -> DST[4..12]), last TCD. |
| 109 | unsafe { | 81 | unsafe { |
| 110 | let tcds = &mut TCD_POOL.take().0; | 82 | let tcds = &mut TCD_POOL.take().0; |
| 111 | let src_ptr = src.as_ptr(); | 83 | |
| 112 | let dst_ptr = dst.as_mut_ptr(); | 84 | // In the first transfer, copy |
| 113 | 85 | tcds[0] = Tcd { | |
| 114 | let num_tcds = 2usize; | 86 | saddr: src_ptr as u32, |
| 115 | let chunk_len = 4usize; // 8 / 2 | 87 | soff: 4, |
| 116 | let chunk_bytes = (chunk_len * 4) as u32; | 88 | attr: 0x0202, // 32-bit src/dst |
| 117 | 89 | nbytes: 4 * 4, | |
| 118 | for i in 0..num_tcds { | 90 | slast: 0, |
| 119 | let is_last = i == num_tcds - 1; | 91 | daddr: dst_ptr as u32, |
| 120 | let next_tcd_addr = if is_last { | 92 | doff: 4, |
| 121 | 0 // No next TCD | 93 | citer: 1, |
| 122 | } else { | 94 | dlast_sga: tcds.as_ptr().add(1) as i32, |
| 123 | &tcds[i + 1] as *const _ as u32 | 95 | // ESG (scatter/gather) for non-last, INTMAJOR for all |
| 124 | }; | 96 | csr: 0x0012, |
| 125 | 97 | biter: 1, | |
| 126 | tcds[i] = Tcd { | 98 | }; |
| 127 | saddr: src_ptr.add(i * chunk_len) as u32, | 99 | |
| 128 | soff: 4, | 100 | tcds[1] = Tcd { |
| 129 | attr: 0x0202, // 32-bit src/dst | 101 | saddr: src_ptr.add(4) as u32, |
| 130 | nbytes: chunk_bytes, | 102 | soff: 4, |
| 131 | slast: 0, | 103 | attr: 0x0202, // 32-bit src/dst |
| 132 | daddr: dst_ptr.add(i * chunk_len) as u32, | 104 | nbytes: 8 * 4, |
| 133 | doff: 4, | 105 | slast: 0, |
| 134 | citer: 1, | 106 | daddr: dst_ptr.add(4) as u32, |
| 135 | dlast_sga: next_tcd_addr as i32, | 107 | doff: 4, |
| 136 | // ESG (scatter/gather) for non-last, INTMAJOR for all | 108 | citer: 1, |
| 137 | csr: if is_last { 0x0002 } else { 0x0012 }, | 109 | dlast_sga: 0, |
| 138 | biter: 1, | 110 | // ESG (scatter/gather) for non-last, INTMAJOR for all |
| 139 | }; | 111 | csr: 0x0002, |
| 140 | } | 112 | biter: 1, |
| 113 | }; | ||
| 141 | 114 | ||
| 142 | // Load TCD0 into hardware registers | 115 | // Load TCD0 into hardware registers |
| 143 | dma_ch0.load_tcd(&tcds[0]); | 116 | dma_ch0.load_tcd(&tcds[0]); |
| @@ -145,6 +118,8 @@ async fn main(_spawner: Spawner) { | |||
| 145 | 118 | ||
| 146 | defmt::info!("Triggering first half transfer..."); | 119 | defmt::info!("Triggering first half transfer..."); |
| 147 | 120 | ||
| 121 | let tcd = dma_ch0.tcd(); | ||
| 122 | |||
| 148 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) | 123 | // Trigger first transfer (first half: SRC[0..4] -> DST[0..4]) |
| 149 | // TCD0 is currently loaded. | 124 | // TCD0 is currently loaded. |
| 150 | unsafe { | 125 | unsafe { |
| @@ -152,10 +127,13 @@ async fn main(_spawner: Spawner) { | |||
| 152 | } | 127 | } |
| 153 | 128 | ||
| 154 | // Wait for first half | 129 | // Wait for first half |
| 155 | while !TRANSFER_DONE.load(Ordering::Acquire) { | 130 | loop { |
| 156 | cortex_m::asm::nop(); | 131 | if tcd.tcd_saddr().read().bits() != src_ptr as u32 { |
| 132 | defmt::info!("saddr: {=u32}", tcd.tcd_saddr().read().bits()); | ||
| 133 | defmt::info!("srptr: {=u32}", src_ptr as u32); | ||
| 134 | break; | ||
| 135 | } | ||
| 157 | } | 136 | } |
| 158 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 159 | 137 | ||
| 160 | defmt::info!("First half transferred."); | 138 | defmt::info!("First half transferred."); |
| 161 | defmt::info!("Triggering second half transfer..."); | 139 | defmt::info!("Triggering second half transfer..."); |
| @@ -167,10 +145,9 @@ async fn main(_spawner: Spawner) { | |||
| 167 | } | 145 | } |
| 168 | 146 | ||
| 169 | // Wait for second half | 147 | // Wait for second half |
| 170 | while !TRANSFER_DONE.load(Ordering::Acquire) { | 148 | while !dma_ch0.is_done() { |
| 171 | cortex_m::asm::nop(); | 149 | cortex_m::asm::nop(); |
| 172 | } | 150 | } |
| 173 | TRANSFER_DONE.store(false, Ordering::Release); | ||
| 174 | 151 | ||
| 175 | defmt::info!("Second half transferred."); | 152 | defmt::info!("Second half transferred."); |
| 176 | 153 | ||
