From e57dffafa5723931dd529afe8e22cba0c9ea09f0 Mon Sep 17 00:00:00 2001 From: i509VCB Date: Mon, 23 Jun 2025 23:15:09 -0500 Subject: mspm0: add dma driver --- embassy-mspm0/src/dma.rs | 626 ++++++++++++++++++++++++++++++++++++++++++++++ embassy-mspm0/src/gpio.rs | 18 +- embassy-mspm0/src/lib.rs | 115 ++++++++- 3 files changed, 740 insertions(+), 19 deletions(-) create mode 100644 embassy-mspm0/src/dma.rs (limited to 'embassy-mspm0/src') diff --git a/embassy-mspm0/src/dma.rs b/embassy-mspm0/src/dma.rs new file mode 100644 index 000000000..66b79709c --- /dev/null +++ b/embassy-mspm0/src/dma.rs @@ -0,0 +1,626 @@ +//! Direct Memory Access (DMA) + +#![macro_use] + +use core::future::Future; +use core::mem; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use critical_section::CriticalSection; +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use mspm0_metapac::common::{Reg, RW}; +use mspm0_metapac::dma::regs; +use mspm0_metapac::dma::vals::{self, Autoen, Em, Incr, Preirq, Wdth}; + +use crate::{interrupt, pac, Peri}; + +/// The burst size of a DMA transfer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BurstSize { + /// The whole block transfer is completed in one transfer without interruption. + Complete, + + /// The burst size is 8, after 9 transfers the block transfer is interrupted and the priority + /// is reevaluated. + _8, + + /// The burst size is 16, after 17 transfers the block transfer is interrupted and the priority + /// is reevaluated. + _16, + + /// The burst size is 32, after 32 transfers the block transfer is interrupted and the priority + /// is reevaluated. + _32, +} + +/// DMA channel. +#[allow(private_bounds)] +pub trait Channel: Into + PeripheralType {} + +/// Full DMA channel. +#[allow(private_bounds)] +pub trait FullChannel: Channel + Into {} + +/// Type-erased DMA channel. +pub struct AnyChannel { + pub(crate) id: u8, +} +impl_peripheral!(AnyChannel); + +impl SealedChannel for AnyChannel { + fn id(&self) -> u8 { + self.id + } +} +impl Channel for AnyChannel {} + +/// Type-erased full DMA channel. +pub struct AnyFullChannel { + pub(crate) id: u8, +} +impl_peripheral!(AnyFullChannel); + +impl SealedChannel for AnyFullChannel { + fn id(&self) -> u8 { + self.id + } +} +impl Channel for AnyFullChannel {} +impl FullChannel for AnyFullChannel {} + +impl From for AnyChannel { + fn from(value: AnyFullChannel) -> Self { + Self { id: value.id } + } +} + +#[allow(private_bounds)] +pub trait Word: SealedWord { + /// Size in bytes for the width. + fn size() -> isize; +} + +impl SealedWord for u8 { + fn width() -> vals::Wdth { + vals::Wdth::BYTE + } +} +impl Word for u8 { + fn size() -> isize { + 1 + } +} + +impl SealedWord for u16 { + fn width() -> vals::Wdth { + vals::Wdth::HALF + } +} +impl Word for u16 { + fn size() -> isize { + 2 + } +} + +impl SealedWord for u32 { + fn width() -> vals::Wdth { + vals::Wdth::WORD + } +} +impl Word for u32 { + fn size() -> isize { + 4 + } +} + +impl SealedWord for u64 { + fn width() -> vals::Wdth { + vals::Wdth::LONG + } +} +impl Word for u64 { + fn size() -> isize { + 8 + } +} + +// TODO: u128 (LONGLONG) support. G350x does support it, but other parts do not such as C110x. More metadata is +// needed to properly enable this. +// impl SealedWord for u128 { +// fn width() -> vals::Wdth { +// vals::Wdth::LONGLONG +// } +// } +// impl Word for u128 { +// fn size() -> isize { +// 16 +// } +// } + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The DMA transfer is too large. + /// + /// The hardware limits the DMA to 16384 transfers per channel at a time. This means that transferring + /// 16384 `u8` and 16384 `u64` are equivalent, since the DMA must copy 16384 values. + TooManyTransfers, +} + +/// DMA transfer mode for basic channels. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TransferMode { + /// Each DMA trigger will transfer a single value. + Single, + + /// Each DMA trigger will transfer the complete block with one trigger. + Block, +} + +/// DMA transfer options. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// DMA transfer mode. + pub mode: TransferMode, + // TODO: Read and write stride. +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + mode: TransferMode::Single, + } + } +} + +/// DMA transfer. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a> { + channel: Peri<'a, AnyChannel>, +} + +impl<'a> Transfer<'a> { + /// Software trigger source. + /// + /// Using this trigger source means that a transfer will start immediately rather than waiting for + /// a hardware event. This can be useful if you want to do a DMA accelerated memcpy. + pub const SOFTWARE_TRIGGER: u8 = 0; + + /// Create a new read DMA transfer. + pub unsafe fn new_read( + channel: Peri<'a, impl Channel>, + trigger_source: u8, + src: *mut SW, + dst: &'a mut [DW], + options: TransferOptions, + ) -> Result { + Self::new_read_raw(channel, trigger_source, src, dst, options) + } + + /// Create a new read DMA transfer, using raw pointers. + pub unsafe fn new_read_raw( + channel: Peri<'a, impl Channel>, + trigger_source: u8, + src: *mut SW, + dst: *mut [DW], + options: TransferOptions, + ) -> Result { + verify_transfer::(dst)?; + + let channel = channel.into(); + channel.configure( + trigger_source, + src.cast(), + SW::width(), + dst.cast(), + DW::width(), + dst.len() as u16, + false, + true, + options, + ); + channel.start(); + + Ok(Self { channel }) + } + + /// Create a new write DMA transfer. + pub unsafe fn new_write( + channel: Peri<'a, impl Channel>, + trigger_source: u8, + src: &'a [SW], + dst: *mut DW, + options: TransferOptions, + ) -> Result { + Self::new_write_raw(channel, trigger_source, src, dst, options) + } + + /// Create a new write DMA transfer, using raw pointers. + pub unsafe fn new_write_raw( + channel: Peri<'a, impl Channel>, + trigger_source: u8, + src: *const [SW], + dst: *mut DW, + options: TransferOptions, + ) -> Result { + verify_transfer::(src)?; + + let channel = channel.into(); + channel.configure( + trigger_source, + src.cast(), + SW::width(), + dst.cast(), + DW::width(), + src.len() as u16, + true, + false, + options, + ); + channel.start(); + + Ok(Self { channel }) + } + + // TODO: Copy between slices. + + /// Request the transfer to resume. + pub fn resume(&mut self) { + self.channel.resume(); + } + + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause(); + } + + /// Return whether this transfer is still running. + /// + /// If this returns [`false`], it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`]. + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// Blocking wait until the transfer finishes. + pub fn blocking_wait(mut self) { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + // Prevent drop from being called since we ran to completion (drop will try to pause). + mem::forget(self); + } +} + +impl<'a> Unpin for Transfer<'a> {} +impl<'a> Future for Transfer<'a> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let state: &ChannelState = &STATE[self.channel.id as usize]; + + state.waker.register(cx.waker()); + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + if self.channel.is_running() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +impl<'a> Drop for Transfer<'a> { + fn drop(&mut self) { + self.channel.request_pause(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + } +} + +// impl details + +fn verify_transfer(ptr: *const [W]) -> Result<(), Error> { + if ptr.len() > (u16::MAX as usize) { + return Err(Error::TooManyTransfers); + } + + // TODO: Stride checks + + Ok(()) +} + +fn convert_burst_size(value: BurstSize) -> vals::Burstsz { + match value { + BurstSize::Complete => vals::Burstsz::INFINITI, + BurstSize::_8 => vals::Burstsz::BURST_8, + BurstSize::_16 => vals::Burstsz::BURST_16, + BurstSize::_32 => vals::Burstsz::BURST_32, + } +} + +fn convert_mode(mode: TransferMode) -> vals::Tm { + match mode { + TransferMode::Single => vals::Tm::SINGLE, + TransferMode::Block => vals::Tm::BLOCK, + } +} + +const CHANNEL_COUNT: usize = crate::_generated::DMA_CHANNELS; +static STATE: [ChannelState; CHANNEL_COUNT] = [const { ChannelState::new() }; CHANNEL_COUNT]; + +struct ChannelState { + waker: AtomicWaker, +} + +impl ChannelState { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +/// SAFETY: Must only be called once. +/// +/// Changing the burst size mid transfer may have some odd behavior. +pub(crate) unsafe fn init(_cs: CriticalSection, burst_size: BurstSize, round_robin: bool) { + pac::DMA.prio().modify(|prio| { + prio.set_burstsz(convert_burst_size(burst_size)); + prio.set_roundrobin(round_robin); + }); + pac::DMA.int_event(0).imask().modify(|w| { + w.set_dataerr(true); + w.set_addrerr(true); + }); + + interrupt::DMA.enable(); +} + +pub(crate) trait SealedWord { + fn width() -> vals::Wdth; +} + +pub(crate) trait SealedChannel { + fn id(&self) -> u8; + + #[inline] + fn tctl(&self) -> Reg { + pac::DMA.trig(self.id() as usize).tctl() + } + + #[inline] + fn ctl(&self) -> Reg { + pac::DMA.chan(self.id() as usize).ctl() + } + + #[inline] + fn sa(&self) -> Reg { + pac::DMA.chan(self.id() as usize).sa() + } + + #[inline] + fn da(&self) -> Reg { + pac::DMA.chan(self.id() as usize).da() + } + + #[inline] + fn sz(&self) -> Reg { + pac::DMA.chan(self.id() as usize).sz() + } + + #[inline] + fn mask_interrupt(&self, enable: bool) { + // Enabling interrupts is an RMW operation. + critical_section::with(|_cs| { + pac::DMA.int_event(0).imask().modify(|w| { + w.set_ch(self.id() as usize, enable); + }); + }) + } + + /// # Safety + /// + /// - `src` must be valid for the lifetime of the transfer. + /// - `dst` must be valid for the lifetime of the transfer. + unsafe fn configure( + &self, + trigger_sel: u8, + src: *const u32, + src_wdth: Wdth, + dst: *const u32, + dst_wdth: Wdth, + transfer_count: u16, + increment_src: bool, + increment_dst: bool, + options: TransferOptions, + ) { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + self.ctl().modify(|w| { + // SLAU 5.2.5: + // "The DMATSEL bits should be modified only when the DMACTLx.DMAEN bit is + // 0; otherwise, unpredictable DMA triggers can occur." + // + // We also want to stop any transfers before setup. + w.set_en(false); + w.set_req(false); + + // Not every part supports auto enable, so force its value to 0. + w.set_autoen(Autoen::NONE); + w.set_preirq(Preirq::PREIRQ_DISABLE); + w.set_srcwdth(src_wdth); + w.set_dstwdth(dst_wdth); + w.set_srcincr(if increment_src { + Incr::INCREMENT + } else { + Incr::UNCHANGED + }); + w.set_dstincr(if increment_dst { + Incr::INCREMENT + } else { + Incr::UNCHANGED + }); + + w.set_em(Em::NORMAL); + // Single and block will clear the enable bit when the transfers finish. + w.set_tm(convert_mode(options.mode)); + }); + + self.tctl().write(|w| { + w.set_tsel(trigger_sel); + // Basic channels do not implement cross triggering. + w.set_tint(vals::Tint::EXTERNAL); + }); + + self.sz().write(|w| { + w.set_size(transfer_count); + }); + + self.sa().write_value(src as u32); + self.da().write_value(dst as u32); + + // Enable the channel. + self.ctl().modify(|w| { + // FIXME: Why did putting set_req later fix some transfers + w.set_en(true); + w.set_req(true); + }); + } + + fn start(&self) { + self.mask_interrupt(true); + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + // Request the DMA transfer to start. + self.ctl().modify(|w| { + w.set_req(true); + }); + } + + fn resume(&self) { + self.mask_interrupt(true); + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + self.ctl().modify(|w| { + // w.set_en(true); + w.set_req(true); + }); + } + + fn request_pause(&self) { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + // Stop the transfer. + // + // SLAU846 5.2.6: + // "A DMA block transfer in progress can be stopped by clearing the DMAEN bit" + self.ctl().modify(|w| { + // w.set_en(false); + w.set_req(false); + }); + } + + fn is_running(&self) -> bool { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + compiler_fence(Ordering::SeqCst); + + let ctl = self.ctl().read(); + + // Is the transfer requested? + ctl.req() + // Is the channel enabled? + && ctl.en() + } +} + +macro_rules! impl_dma_channel { + ($instance: ident, $num: expr) => { + impl crate::dma::SealedChannel for crate::peripherals::$instance { + fn id(&self) -> u8 { + $num + } + } + + impl From for crate::dma::AnyChannel { + fn from(value: crate::peripherals::$instance) -> Self { + use crate::dma::SealedChannel; + + Self { id: value.id() } + } + } + + impl crate::dma::Channel for crate::peripherals::$instance {} + }; +} + +// C1104 has no full DMA channels. +#[allow(unused_macros)] +macro_rules! impl_full_dma_channel { + ($instance: ident, $num: expr) => { + impl_dma_channel!($instance, $num); + + impl From for crate::dma::AnyFullChannel { + fn from(value: crate::peripherals::$instance) -> Self { + use crate::dma::SealedChannel; + + Self { id: value.id() } + } + } + + impl crate::dma::FullChannel for crate::peripherals::$instance {} + }; +} + +#[cfg(feature = "rt")] +#[interrupt] +fn DMA() { + use crate::BitIter; + + let events = pac::DMA.int_event(0); + let mis = events.mis().read(); + + // TODO: Handle DATAERR and ADDRERR? However we do not know which channel causes an error. + if mis.dataerr() { + panic!("DMA data error"); + } else if mis.addrerr() { + panic!("DMA address error") + } + + // Ignore preirq interrupts (values greater than 16). + for i in BitIter(mis.0 & 0x0000_FFFF) { + if let Some(state) = STATE.get(i as usize) { + state.waker.wake(); + + // Notify the future that the counter size hit zero + events.imask().modify(|w| { + w.set_ch(i as usize, false); + }); + } + } +} diff --git a/embassy-mspm0/src/gpio.rs b/embassy-mspm0/src/gpio.rs index 738d51928..e6380e819 100644 --- a/embassy-mspm0/src/gpio.rs +++ b/embassy-mspm0/src/gpio.rs @@ -1090,7 +1090,9 @@ pub(crate) fn init(gpio: gpio::Gpio) { #[cfg(feature = "rt")] fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { + use crate::BitIter; // Only consider pins which have interrupts unmasked. + let bits = gpio.cpu_int().mis().read().0; for i in BitIter(bits) { @@ -1103,22 +1105,6 @@ fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { } } -struct BitIter(u32); - -impl Iterator for BitIter { - type Item = u32; - - fn next(&mut self) -> Option { - match self.0.trailing_zeros() { - 32 => None, - b => { - self.0 &= !(1 << b); - Some(b) - } - } - } -} - // C110x and L110x have a dedicated interrupts just for GPIOA. // // These chips do not have a GROUP1 interrupt. diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 7ff60e946..bb8d91403 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs @@ -13,6 +13,7 @@ pub(crate) mod fmt; // This must be declared early as well for mod macros; +pub mod dma; pub mod gpio; pub mod timer; pub mod uart; @@ -59,22 +60,106 @@ pub(crate) use mspm0_metapac as pac; pub use crate::_generated::interrupt; +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, peripherals}; +/// +/// bind_interrupts!( +/// /// Binds the SPIM3 interrupt. +/// struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// } +/// ); +/// ``` +/// +/// Example of how to bind multiple interrupts in a single macro invocation: +/// +/// ```rust,ignore +/// use embassy_nrf::{bind_interrupts, spim, twim, peripherals}; +/// +/// bind_interrupts!(struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// TWISPI0 => twim::InterruptHandler; +/// }); +/// ``` + +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + /// `embassy-mspm0` global configuration. #[non_exhaustive] #[derive(Clone, Copy)] pub struct Config { - // TODO + // TODO: OSC configuration. + /// The size of DMA block transfer burst. + /// + /// If this is set to a value + pub dma_burst_size: dma::BurstSize, + + /// Whether the DMA channels are used in a fixed priority or a round robin fashion. + /// + /// If [`false`], the DMA priorities are fixed. + /// + /// If [`true`], after a channel finishes a transfer it becomes the lowest priority. + pub dma_round_robin: bool, } impl Default for Config { fn default() -> Self { Self { - // TODO + dma_burst_size: dma::BurstSize::Complete, + dma_round_robin: false, } } } -pub fn init(_config: Config) -> Peripherals { +pub fn init(config: Config) -> Peripherals { critical_section::with(|cs| { let peripherals = Peripherals::take_with_cs(cs); @@ -112,9 +197,33 @@ pub fn init(_config: Config) -> Peripherals { crate::interrupt::typelevel::GPIOA::enable(); } + // SAFETY: Peripherals::take_with_cs will only be run once or panic. + unsafe { dma::init(cs, config.dma_burst_size, config.dma_round_robin) }; + #[cfg(feature = "_time-driver")] time_driver::init(cs); peripherals }) } + +pub(crate) mod sealed { + #[allow(dead_code)] + pub trait Sealed {} +} + +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} -- cgit