From 988d6adcdc6a79580ea578f8c945b827788bc95f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 2 Jul 2024 20:09:21 +0200 Subject: [PATCH] come on dma, do something.. --- examples/simple/examples/dma.rs | 107 +++++++++ va416xx-hal/src/dma.rs | 386 ++++++++++++++++++++++++++------ 2 files changed, 430 insertions(+), 63 deletions(-) create mode 100644 examples/simple/examples/dma.rs diff --git a/examples/simple/examples/dma.rs b/examples/simple/examples/dma.rs new file mode 100644 index 0000000..4f44797 --- /dev/null +++ b/examples/simple/examples/dma.rs @@ -0,0 +1,107 @@ +//! Simple DMA example +#![no_main] +#![no_std] + +use core::cell::Cell; + +use cortex_m::interrupt::Mutex; +use cortex_m_rt::entry; +use embedded_hal::delay::DelayNs; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use simple_examples::peb1; +use va416xx_hal::dma::{Dma, DmaCfg, DmaCtrlBlock}; +use va416xx_hal::pwm::CountdownTimer; +use va416xx_hal::{ + pac::{self, interrupt}, + prelude::*, +}; + +// Place the DMA control block in SRAM1 +const DMA_CTRL_BLOCK_ADDR: u32 = 0x2000_0000; +static DMA_DONE_FLAG: Mutex> = Mutex::new(Cell::new(false)); + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("VA416xx DMA example"); + + let mut dp = pac::Peripherals::take().unwrap(); + // Use the external clock connected to XTAL_N. + let clocks = dp + .clkgen + .constrain() + .xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ) + .freeze(&mut dp.sysconfig) + .unwrap(); + let dma_ctrl_block = + DmaCtrlBlock::new_at_addr(DMA_CTRL_BLOCK_ADDR).expect("error creating DMA control block"); + let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), dma_ctrl_block) + .expect("error creating DMA"); + let (mut dma0, _, _, _) = dma.split(); + let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks); + let mut src_buf: [u8; 64] = [0; 64]; + let mut dest_buf: [u8; 64] = [0; 64]; + let dma_ctrl_block_ptr = DMA_CTRL_BLOCK_ADDR as *const DmaCtrlBlock; + let dma_ctrl_block_ref = unsafe { &*dma_ctrl_block_ptr }; + loop { + (0..64).for_each(|i| { + src_buf[i] = i as u8; + }); + cortex_m::interrupt::free(|cs| { + DMA_DONE_FLAG.borrow(cs).set(false); + }); + dma0.prepare_mem_to_mem_transfer_8_bit(&src_buf, &mut dest_buf) + .expect("error preparing transfer"); + rprintln!("ch0 cfg: {:?}", dma_ctrl_block_ref.pri[0].cfg); + dma0.select_primary_structure(); + // Safety: Not using mask based critical sections. + unsafe { + dma0.enable_done_interrupt(); + dma0.enable_active_interrupt(); + }; + dma0.enable(); + //dma0.sw_request(); + let state = dma0.state_raw(); + rprintln!("dma state: {}", state); + // Use polling for completion status. + loop { + let mut dma_done = false; + cortex_m::interrupt::free(|cs| { + if DMA_DONE_FLAG.borrow(cs).get() { + dma_done = true; + } + }); + if dma_done { + rprintln!("DMA transfer done"); + break; + } + let state = dma0.state_raw(); + if state != 0 { + rprintln!("dma state: {}", state); + } + delay_ms.delay_ms(1); + } + (0..64).for_each(|i| { + assert_eq!(dest_buf[i], i as u8); + }); + dest_buf.fill(0); + delay_ms.delay_ms(200); + } +} + +#[interrupt] +#[allow(non_snake_case)] +fn DMA_DONE0() { + rprintln!("dma done interrupt"); + // Notify the main loop that the DMA transfer is finished. + cortex_m::interrupt::free(|cs| { + DMA_DONE_FLAG.borrow(cs).set(true); + }); +} + +#[interrupt] +#[allow(non_snake_case)] +fn DMA_ACTIVE0() { + rprintln!("dma active interrupt"); +} diff --git a/va416xx-hal/src/dma.rs b/va416xx-hal/src/dma.rs index 3bb63df..839f643 100644 --- a/va416xx-hal/src/dma.rs +++ b/va416xx-hal/src/dma.rs @@ -1,62 +1,111 @@ use crate::{ clock::{PeripheralClock, PeripheralSelect}, - pac, + enable_interrupt, pac, prelude::*, }; -/* -/** DMA channel config struct */ -typedef struct stc_dma_chnl_cfg -{ - uint32_t cycle_ctrl: 3; - uint32_t next_useburst: 1; - uint32_t n_minus_1: 10; - uint32_t r_power: 4; - uint32_t src_prot_ctrl: 3; - uint32_t dst_prot_ctrl: 3; - uint32_t src_size: 2; - uint32_t src_inc: 2; - uint32_t dst_size: 2; - uint32_t dst_inc: 2; -} stc_dma_chnl_cfg_t; +const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024; +const BASE_PTR_ADDR_MASK: u32 = 0b1111111; -/** DMA channel struct */ -typedef struct stc_dma_chnl -{ - /* Note: This DMA engine expects source, dest pointers need to point to - the LAST element, not the first */ - uint32_t src; // source *END* pointer - addr of last elememt in source - uint32_t dst; // destination *END* pointer - addr of last element in dest - union{ - uint32_t ctrl_raw; // Control register raw val - stc_dma_chnl_cfg_t ctrl; // Control register bitfield - }; - uint32_t padding; // unused -} stc_dma_chnl_t; +/// DMA cycle control values. +/// +/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details. +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CycleControl { + /// Indicates that the data structure is invalid. + Stop = 0b000, + /// The controller must receive a new request prior to entering the arbitration + /// process, to enable the DMA cycle to complete. This means that the DMA will only + /// continue to do transfers as long as a trigger signal is still active. Therefore, + /// this should not be used for momentary triggers like a timer. + Basic = 0b001, + /// The controller automatically inserts a request for the appropriate channel during the + /// arbitration process. This means that the initial request is sufficient to enable the + /// DMA cycle to complete. + Auto = 0b010, + /// This is used to support continuous data flow. Both primary and alternate data structure + /// are used. The primary data structure is used first. When the first transfer is complete, an + /// interrupt can be generated, and the DMA switches to the alternate data structure. When the + /// second transfer is complete, the primary data structure is used. This pattern continues + /// until software disables the channel. + PingPong = 0b011, + MemScatterGatherPrimary = 0b100, + MemScatterGatherAlternate = 0b101, + PeriphScatterGatherPrimary = 0b110, + PeriphScatterGatherAlternate = 0b111, +} -/** DMA control block data structure */ -typedef struct stc_dma_control_blk -{ - stc_dma_chnl_t pri[DMA_NUM_CHNLS]; // primary ch 0x00 to 0x3f - stc_dma_chnl_t alt[DMA_NUM_CHNLS]; // alternate ch 0x40 to 0x7f -} stc_dma_control_blk_t; -*/ +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AddrIncrement { + Byte = 0b00, + Halfword = 0b01, + Word = 0b10, + None = 0b11, +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DataSize { + Byte = 0b00, + Halfword = 0b01, + Word = 0b10, +} + +/// This configuration controls how many DMA transfers can occur before the controller arbitrates. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RPower { + EachTransfer = 0b0000, + Every2 = 0b0001, + Every4 = 0b0010, + Every8 = 0b0011, + Every16 = 0b0100, + Every32 = 0b0101, + Every64 = 0b0110, + Every128 = 0b0111, + Every256 = 0b1000, + Every512 = 0b1001, + Every1024Min = 0b1010, + Every1024 = 0b1111, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct InvalidCtrlBlockAddr; bitfield::bitfield! { + #[repr(transparent)] + #[derive(Clone, Copy)] pub struct ChannelConfig(u32); impl Debug; + u32; + pub raw, set_raw: 31,0; + u8; dst_inc, set_dst_inc: 31, 30; + u8; dst_size, set_dst_size: 29, 28; + u8; src_inc, set_src_inc: 27, 26; + u8; src_size, set_src_size: 25, 24; + u8; dest_prot_ctrl, set_dest_prot_ctrl: 23, 21; + u8; src_prot_ctrl, set_src_prot_ctrl: 20, 18; + u8; r_power, set_r_power: 17, 14; + u16; n_minus_1, set_n_minus_1: 13, 4; + bool; next_useburst, set_next_useburst: 3; + u8; + cycle_ctrl, set_cycle_ctr: 2, 0; } #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct DmaChannelControl { pub src_end_ptr: u32, pub dest_end_ptr: u32, @@ -64,15 +113,60 @@ pub struct DmaChannelControl { padding: u32, } +impl Default for DmaChannelControl { + fn default() -> Self { + Self { + src_end_ptr: Default::default(), + dest_end_ptr: Default::default(), + cfg: ChannelConfig(0), + padding: Default::default(), + } + } +} #[repr(C)] pub struct DmaCtrlBlock { pub pri: [DmaChannelControl; 4], pub alt: [DmaChannelControl; 4], } +impl DmaCtrlBlock { + /// This function creates a DMA control block at the specified memory address. + /// + /// The passed address must be 128-byte aligned. The user must also take care of specifying + /// a valid memory address for the DMA control block which is accessible by the system as well. + pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddr> { + if addr & BASE_PTR_ADDR_MASK > 0 { + return Err(InvalidCtrlBlockAddr); + } + let ctrl_block_ptr = addr as *mut DmaCtrlBlock; + unsafe { + core::ptr::write( + ctrl_block_ptr, + DmaCtrlBlock { + pri: [DmaChannelControl::default(); 4], + alt: [DmaChannelControl::default(); 4], + }, + ) + } + Ok(ctrl_block_ptr) + } +} + pub struct Dma { dma: pac::Dma, - ctrl_block: &'static DmaCtrlBlock, + ctrl_block: *mut DmaCtrlBlock, +} + +#[derive(Debug, Clone, Copy)] +pub enum DmaTransferInitError { + SourceDestLenMissmatch { + src_len: usize, + dest_len: usize, + }, + /// Overflow when calculating the source or destination end address. + AddrOverflow, + /// Transfer size larger than 1024 units. + TransferSizeTooLarge(usize), } #[derive(Debug, Clone, Copy, Default)] @@ -84,15 +178,173 @@ pub struct DmaCfg { pub struct DmaChannel { idx: u8, - dma: pac::Dma, - ch_ctrl_pri: &'static DmaChannelControl, - ch_ctrl_alt: &'static DmaChannelControl, + done_interrupt: pac::Interrupt, + active_interrupt: pac::Interrupt, + pub dma: pac::Dma, + pub ch_ctrl_pri: &'static mut DmaChannelControl, + pub ch_ctrl_alt: &'static mut DmaChannelControl, } -impl DmaChannel {} +impl DmaChannel { + #[inline(always)] + pub fn enable(&mut self) { + self.dma + .chnl_enable_set() + .write(|w| unsafe { w.bits(1 << self.idx) }); + } -#[derive(Debug, PartialEq, Eq)] -pub struct InvalidCtrlBlockAddr; + #[inline(always)] + pub fn is_enabled(&mut self) -> bool { + ((self.dma.chnl_enable_set().read().bits() >> self.idx) & 0b1) != 0 + } + + #[inline(always)] + pub fn disable(&mut self) { + self.dma + .chnl_enable_clr() + .write(|w| unsafe { w.bits(1 << self.idx) }); + } + + #[inline(always)] + pub fn sw_request(&mut self) { + self.dma + .chnl_sw_request() + .write(|w| unsafe { w.bits(1 << self.idx) }); + } + + #[inline(always)] + pub fn state_raw(&self) -> u8 { + self.dma.status().read().state().bits() + } + + #[inline(always)] + pub fn select_primary_structure(&self) { + self.dma + .chnl_pri_alt_clr() + .write(|w| unsafe { w.bits(1 << self.idx) }); + } + + #[inline(always)] + pub fn select_alternate_structure(&self) { + self.dma + .chnl_pri_alt_set() + .write(|w| unsafe { w.bits(1 << self.idx) }); + } + + /// Enables the DMA_DONE interrupt for the DMA channel. + /// + /// # Safety + /// + /// This function is `unsafe` because it can break mask-based critical sections. + pub unsafe fn enable_done_interrupt(&mut self) { + enable_interrupt(self.done_interrupt); + } + + /// Enables the DMA_ACTIVE interrupt for the DMA channel. + /// + /// # Safety + /// + /// This function is `unsafe` because it can break mask-based critical sections. + pub unsafe fn enable_active_interrupt(&mut self) { + enable_interrupt(self.active_interrupt); + } + + pub fn prepare_mem_to_mem_transfer_8_bit( + &mut self, + source: &[u8], + dest: &mut [u8], + ) -> Result<(), DmaTransferInitError> { + let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; + self.generic_mem_to_mem_transfer_init( + len, + (source.as_ptr() as u32) + .checked_add(len as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + (dest.as_ptr() as u32) + .checked_add(len as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + DataSize::Byte, + AddrIncrement::Byte, + ); + Ok(()) + } + + pub fn prepare_mem_to_mem_transfer_16_bit( + &mut self, + source: &[u8], + dest: &mut [u8], + ) -> Result<(), DmaTransferInitError> { + let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; + self.generic_mem_to_mem_transfer_init( + len, + (source.as_ptr() as u32) + .checked_add(len as u32 * core::mem::size_of::() as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + (dest.as_ptr() as u32) + .checked_add(len as u32 * core::mem::size_of::() as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + DataSize::Halfword, + AddrIncrement::Halfword, + ); + Ok(()) + } + + pub fn prepare_mem_to_mem_transfer_32_bit( + &mut self, + source: &[u32], + dest: &mut [u32], + ) -> Result<(), DmaTransferInitError> { + let len = Self::common_mem_transfer_checks(source.len(), dest.len())?; + self.generic_mem_to_mem_transfer_init( + len, + (source.as_ptr() as u32) + .checked_add(len as u32 * core::mem::size_of::() as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + (dest.as_ptr() as u32) + .checked_add(len as u32 * core::mem::size_of::() as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?, + DataSize::Word, + AddrIncrement::Word, + ); + Ok(()) + } + + // This function performs common checks and returns the source length minus one which is + // relevant for further configuration of the DMA. This is because the DMA API expects N minus + // 1 and the source and end pointer need to point to the last transfer address. + fn common_mem_transfer_checks( + src_len: usize, + dest_len: usize, + ) -> Result { + if src_len != dest_len { + return Err(DmaTransferInitError::SourceDestLenMissmatch { src_len, dest_len }); + } + if src_len > MAX_DMA_TRANSFERS_PER_CYCLE { + return Err(DmaTransferInitError::TransferSizeTooLarge(src_len)); + } + Ok(src_len - 1) + } + + fn generic_mem_to_mem_transfer_init( + &mut self, + n_minus_one: usize, + src_end_ptr: u32, + dest_end_ptr: u32, + data_size: DataSize, + addr_incr: AddrIncrement, + ) { + self.ch_ctrl_pri.cfg.set_raw(0); + self.ch_ctrl_pri.src_end_ptr = src_end_ptr; + self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr; + self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8); + self.ch_ctrl_pri.cfg.set_src_size(data_size as u8); + self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8); + self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8); + self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8); + self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16); + self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8); + } +} impl Dma { /// Create a new DMA instance. @@ -103,23 +355,21 @@ impl Dma { syscfg: &mut pac::Sysconfig, dma: pac::Dma, cfg: DmaCfg, - ctrl_block: &'static DmaCtrlBlock, + ctrl_block: *mut DmaCtrlBlock, ) -> Result { - syscfg.enable_peripheral_clock(PeripheralClock::Dma); - syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma); - syscfg.enable_peripheral_clock(PeripheralClock::IrqRouter); - syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::IrqRouter); - let dma = Dma { dma, ctrl_block }; - dma.set_protection_bits(&cfg); - dma.enable(); - let raw_addr = core::ptr::addr_of!(ctrl_block) as *const _ as u32; - if raw_addr % 128 != 0 { + // The conversion to u32 is safe here because we are on a 32-bit system. + let raw_addr = ctrl_block as u32; + if raw_addr & BASE_PTR_ADDR_MASK > 0 { return Err(InvalidCtrlBlockAddr); } - // The conversion to u32 is safe here because we are on a 32-bit system. + syscfg.enable_peripheral_clock(PeripheralClock::Dma); + syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma); + let dma = Dma { dma, ctrl_block }; dma.dma .ctrl_base_ptr() - .write(|w| unsafe { w.ctrl_base_ptr().bits(raw_addr) }); + .write(|w| unsafe { w.bits(raw_addr) }); + dma.set_protection_bits(&cfg); + dma.enable(); Ok(dma) } @@ -144,30 +394,40 @@ impl Dma { /// Split the DMA instance into four DMA channels which can be used individually. pub fn split(self) -> (DmaChannel, DmaChannel, DmaChannel, DmaChannel) { + //let (pri, alt) = self.ctrl_block.split(); + // Safety: The DMA channel API only operates on its respective channels. ( DmaChannel { idx: 0, + done_interrupt: pac::Interrupt::DMA_DONE0, + active_interrupt: pac::Interrupt::DMA_ACTIVE0, dma: unsafe { pac::Dma::steal() }, - ch_ctrl_pri: &self.ctrl_block.pri[0], - ch_ctrl_alt: &self.ctrl_block.alt[0], + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[0] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[0] }, }, DmaChannel { idx: 1, + done_interrupt: pac::Interrupt::DMA_DONE1, + active_interrupt: pac::Interrupt::DMA_ACTIVE1, dma: unsafe { pac::Dma::steal() }, - ch_ctrl_pri: &self.ctrl_block.pri[1], - ch_ctrl_alt: &self.ctrl_block.alt[1], + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[1] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[1] }, }, DmaChannel { idx: 2, + done_interrupt: pac::Interrupt::DMA_DONE2, + active_interrupt: pac::Interrupt::DMA_ACTIVE2, dma: unsafe { pac::Dma::steal() }, - ch_ctrl_pri: &self.ctrl_block.pri[2], - ch_ctrl_alt: &self.ctrl_block.alt[2], + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[2] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[2] }, }, DmaChannel { idx: 3, + done_interrupt: pac::Interrupt::DMA_DONE3, + active_interrupt: pac::Interrupt::DMA_ACTIVE3, dma: unsafe { pac::Dma::steal() }, - ch_ctrl_pri: &self.ctrl_block.pri[3], - ch_ctrl_alt: &self.ctrl_block.alt[3], + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[3] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[3] }, }, ) }