From 1c7affc4c5ad09627948389d9fe63afde83d0305 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 2 Jul 2024 16:03:54 +0200 Subject: [PATCH] Basic DMA HAL --- examples/simple/examples/dma.rs | 274 +++++++++++++++ va416xx-hal/Cargo.toml | 1 + va416xx-hal/memory.x | 14 - va416xx-hal/src/dma.rs | 571 ++++++++++++++++++++++++++++++++ va416xx-hal/src/lib.rs | 1 + 5 files changed, 847 insertions(+), 14 deletions(-) create mode 100644 examples/simple/examples/dma.rs delete mode 100644 va416xx-hal/memory.x create mode 100644 va416xx-hal/src/dma.rs diff --git a/examples/simple/examples/dma.rs b/examples/simple/examples/dma.rs new file mode 100644 index 0000000..6f89fa8 --- /dev/null +++ b/examples/simple/examples/dma.rs @@ -0,0 +1,274 @@ +//! 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, DmaChannel, DmaCtrlBlock}; +use va416xx_hal::pwm::CountdownTimer; +use va416xx_hal::{ + pac::{self, interrupt}, + prelude::*, +}; + +static DMA_DONE_FLAG: Mutex> = Mutex::new(Cell::new(false)); +static DMA_ACTIVE_FLAG: Mutex> = Mutex::new(Cell::new(false)); + +// Place the DMA control block into SRAM1 statically. This section needs to be defined in +// memory.x +#[link_section = ".sram1"] +static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new(); + +// We can use statically allocated buffers for DMA transfers as well. +#[link_section = ".sram1"] +static mut DMA_SRC_BUF: [u16; 36] = [0; 36]; +#[link_section = ".sram1"] +static mut DMA_DEST_BUF: [u16; 36] = [0; 36]; + +#[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(); + // Safety: The DMA control block has an alignment rule of 128 and we constructed it directly + // statically. + let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe { + core::ptr::addr_of_mut!(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_8_bit: [u8; 65] = [0; 65]; + let mut dest_buf_8_bit: [u8; 65] = [0; 65]; + let mut src_buf_32_bit: [u32; 17] = [0; 17]; + let mut dest_buf_32_bit: [u32; 17] = [0; 17]; + loop { + // This example uses stack-allocated buffers. + transfer_example_8_bit( + &mut src_buf_8_bit, + &mut dest_buf_8_bit, + &mut dma0, + &mut delay_ms, + ); + delay_ms.delay_ms(500); + // This example uses statically allocated buffers. + transfer_example_16_bit(&mut dma0, &mut delay_ms); + delay_ms.delay_ms(500); + transfer_example_32_bit( + &mut src_buf_32_bit, + &mut dest_buf_32_bit, + &mut dma0, + &mut delay_ms, + ); + delay_ms.delay_ms(500); + } +} + +fn transfer_example_8_bit( + src_buf: &mut [u8; 65], + dest_buf: &mut [u8; 65], + dma0: &mut DmaChannel, + delay_ms: &mut CountdownTimer, +) { + (0..64).for_each(|i| { + src_buf[i] = i as u8; + }); + cortex_m::interrupt::free(|cs| { + DMA_DONE_FLAG.borrow(cs).set(false); + }); + cortex_m::interrupt::free(|cs| { + DMA_ACTIVE_FLAG.borrow(cs).set(false); + }); + // Safety: The source and destination buffer are valid for the duration of the DMA transfer. + unsafe { + dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf) + .expect("error preparing transfer"); + } + // Enable all interrupts. + // Safety: Not using mask based critical sections. + unsafe { + dma0.enable_done_interrupt(); + dma0.enable_active_interrupt(); + }; + // Enable the individual channel. + dma0.enable(); + // We still need to manually trigger the DMA request. + dma0.trigger_with_sw_request(); + // Use polling for completion status. + loop { + let mut dma_done = false; + cortex_m::interrupt::free(|cs| { + if DMA_ACTIVE_FLAG.borrow(cs).get() { + rprintln!("DMA0 is active with 8 bit transfer"); + DMA_ACTIVE_FLAG.borrow(cs).set(false); + } + if DMA_DONE_FLAG.borrow(cs).get() { + dma_done = true; + } + }); + if dma_done { + rprintln!("8-bit transfer done"); + break; + } + delay_ms.delay_ms(1); + } + (0..64).for_each(|i| { + assert_eq!(dest_buf[i], i as u8); + }); + // Sentinel value, should be 0. + assert_eq!(dest_buf[64], 0); + dest_buf.fill(0); +} + +fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer) { + unsafe { + // Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer. + (0..32).for_each(|i| { + DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (DMA_SRC_BUF.len() - 1) as u32) as u16; + }); + } + cortex_m::interrupt::free(|cs| { + DMA_DONE_FLAG.borrow(cs).set(false); + }); + cortex_m::interrupt::free(|cs| { + DMA_ACTIVE_FLAG.borrow(cs).set(false); + }); + let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..32]) }; + // Safety: The source and destination buffer are valid for the duration of the DMA transfer. + unsafe { + dma0.prepare_mem_to_mem_transfer_16_bit( + &*core::ptr::addr_of!(DMA_SRC_BUF[0..32]), + dest_buf_ref, + ) + .expect("error preparing transfer"); + } + // Enable all interrupts. + // Safety: Not using mask based critical sections. + unsafe { + dma0.enable_done_interrupt(); + dma0.enable_active_interrupt(); + }; + // Enable the individual channel. + dma0.enable(); + // We still need to manually trigger the DMA request. + dma0.trigger_with_sw_request(); + // Use polling for completion status. + loop { + let mut dma_done = false; + cortex_m::interrupt::free(|cs| { + if DMA_ACTIVE_FLAG.borrow(cs).get() { + rprintln!("DMA0 is active with 16-bit transfer"); + DMA_ACTIVE_FLAG.borrow(cs).set(false); + } + if DMA_DONE_FLAG.borrow(cs).get() { + dma_done = true; + } + }); + if dma_done { + rprintln!("16-bit transfer done"); + break; + } + delay_ms.delay_ms(1); + } + (0..32).for_each(|i| { + assert_eq!( + dest_buf_ref[i], + (i as u32 * u16::MAX as u32 / (dest_buf_ref.len() - 1) as u32) as u16 + ); + }); + // Sentinel value, should be 0. + assert_eq!(dest_buf_ref[32], 0); + dest_buf_ref.fill(0); +} + +fn transfer_example_32_bit( + src_buf: &mut [u32; 17], + dest_buf: &mut [u32; 17], + dma0: &mut DmaChannel, + delay_ms: &mut CountdownTimer, +) { + // Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer. + (0..16).for_each(|i| { + src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32; + }); + cortex_m::interrupt::free(|cs| { + DMA_DONE_FLAG.borrow(cs).set(false); + }); + cortex_m::interrupt::free(|cs| { + DMA_ACTIVE_FLAG.borrow(cs).set(false); + }); + // Safety: The source and destination buffer are valid for the duration of the DMA transfer. + unsafe { + dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf) + .expect("error preparing transfer"); + } + // Enable all interrupts. + // Safety: Not using mask based critical sections. + unsafe { + dma0.enable_done_interrupt(); + dma0.enable_active_interrupt(); + }; + // Enable the individual channel. + dma0.enable(); + // We still need to manually trigger the DMA request. + dma0.trigger_with_sw_request(); + // Use polling for completion status. + loop { + let mut dma_done = false; + cortex_m::interrupt::free(|cs| { + if DMA_ACTIVE_FLAG.borrow(cs).get() { + rprintln!("DMA0 is active with 32-bit transfer"); + DMA_ACTIVE_FLAG.borrow(cs).set(false); + } + if DMA_DONE_FLAG.borrow(cs).get() { + dma_done = true; + } + }); + if dma_done { + rprintln!("32-bit transfer done"); + break; + } + delay_ms.delay_ms(1); + } + (0..16).for_each(|i| { + assert_eq!( + dest_buf[i], + (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32 + ); + }); + // Sentinel value, should be 0. + assert_eq!(dest_buf[16], 0); + dest_buf.fill(0); +} + +#[interrupt] +#[allow(non_snake_case)] +fn DMA_DONE0() { + // 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() { + // Notify the main loop that the DMA 0 is active now. + cortex_m::interrupt::free(|cs| { + DMA_ACTIVE_FLAG.borrow(cs).set(true); + }); +} diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 8644adc..b76fa40 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -20,6 +20,7 @@ embedded-io = "0.6" num_enum = { version = "0.7", default-features = false } typenum = "1" bitflags = "2" +bitfield = "0.15" defmt = { version = "0.3", optional = true } fugit = "0.3" delegate = "0.12" diff --git a/va416xx-hal/memory.x b/va416xx-hal/memory.x deleted file mode 100644 index cf01aa3..0000000 --- a/va416xx-hal/memory.x +++ /dev/null @@ -1,14 +0,0 @@ -MEMORY -{ - FLASH : ORIGIN = 0x00000000, LENGTH = 256K - /* RAM is a mandatory region. This RAM refers to the SRAM_0 */ - RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K - SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K -} - -/* This is where the call stack will be allocated. */ -/* The stack is of the full descending type. */ -/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */ -/* SRAM_0 can be used for all busses: Instruction, Data and System */ -/* SRAM_1 only supports the system bus */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 4; diff --git a/va416xx-hal/src/dma.rs b/va416xx-hal/src/dma.rs new file mode 100644 index 0000000..e26bfe3 --- /dev/null +++ b/va416xx-hal/src/dma.rs @@ -0,0 +1,571 @@ +//! API for the DMA peripheral +//! +//! ## Examples +//! +//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs) +use crate::{ + clock::{PeripheralClock, PeripheralSelect}, + enable_interrupt, pac, + prelude::*, +}; + +const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024; +const BASE_PTR_ADDR_MASK: u32 = 0b1111111; + +/// 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, +} + +#[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; + pub dst_inc, set_dst_inc: 31, 30; + u8; + pub dst_size, set_dst_size: 29, 28; + u8; + pub src_inc, set_src_inc: 27, 26; + u8; + pub src_size, set_src_size: 25, 24; + u8; + pub dest_prot_ctrl, set_dest_prot_ctrl: 23, 21; + u8; + pub src_prot_ctrl, set_src_prot_ctrl: 20, 18; + u8; + pub r_power, set_r_power: 17, 14; + u16; + pub n_minus_1, set_n_minus_1: 13, 4; + bool; + pub next_useburst, set_next_useburst: 3; + u8; + pub 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, + pub cfg: ChannelConfig, + padding: u32, +} + +impl DmaChannelControl { + const fn new() -> Self { + Self { + src_end_ptr: 0, + dest_end_ptr: 0, + cfg: ChannelConfig(0), + padding: 0, + } + } +} +impl Default for DmaChannelControl { + fn default() -> Self { + Self::new() + } +} +#[repr(C)] +#[repr(align(128))] +pub struct DmaCtrlBlock { + pub pri: [DmaChannelControl; 4], + pub alt: [DmaChannelControl; 4], +} + +impl DmaCtrlBlock { + pub const fn new() -> Self { + Self { + pri: [DmaChannelControl::new(); 4], + alt: [DmaChannelControl::new(); 4], + } + } +} +impl Default for DmaCtrlBlock { + fn default() -> Self { + Self::new() + } +} + +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. + /// For example, the control block can be placed in the SRAM1. + 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::default()) } + Ok(ctrl_block_ptr) + } +} + +pub struct Dma { + dma: pac::Dma, + 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)] +pub struct DmaCfg { + pub bufferable: bool, + pub cacheable: bool, + pub privileged: bool, +} + +pub struct DmaChannel { + channel: u8, + 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 { + #[inline(always)] + pub fn channel(&self) -> u8 { + self.channel + } + + #[inline(always)] + pub fn enable(&mut self) { + self.dma + .chnl_enable_set() + .write(|w| unsafe { w.bits(1 << self.channel) }); + } + + #[inline(always)] + pub fn is_enabled(&mut self) -> bool { + ((self.dma.chnl_enable_set().read().bits() >> self.channel) & 0b1) != 0 + } + + #[inline(always)] + pub fn disable(&mut self) { + self.dma + .chnl_enable_clr() + .write(|w| unsafe { w.bits(1 << self.channel) }); + } + + #[inline(always)] + pub fn trigger_with_sw_request(&mut self) { + self.dma + .chnl_sw_request() + .write(|w| unsafe { w.bits(1 << self.channel) }); + } + + #[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.channel) }); + } + + #[inline(always)] + pub fn select_alternate_structure(&self) { + self.dma + .chnl_pri_alt_set() + .write(|w| unsafe { w.bits(1 << self.channel) }); + } + + /// 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); + } + + /// Prepares a 8-bit DMA transfer from memory to memory. + /// + /// This function does not enable the DMA channel and interrupts and only prepares + /// the DMA control block parameters for the transfer. It configures the primary channel control + /// structure to perform the transfer. + /// + /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] + /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to + /// start the DMA transfer. + /// + /// # Safety + /// + /// You must ensure that the destination buffer is safe for DMA writes and the source buffer + /// is safe for DMA reads. The specific requirements can be read here: + /// + /// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html) + /// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html) + /// + /// More specifically, you must ensure that the passed slice remains valid while the DMA is + /// active or until the DMA is stopped. + pub unsafe 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(()) + } + + /// Prepares a 16-bit DMA transfer from memory to memory. + /// + /// This function does not enable the DMA channel and interrupts and only prepares + /// the DMA control block parameters for the transfer. It configures the primary channel control + /// structure to perform the transfer. + /// + /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] + /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to + /// start the DMA transfer. + /// + /// # Safety + /// + /// You must ensure that the destination buffer is safe for DMA writes and the source buffer + /// is safe for DMA reads. The specific requirements can be read here: + /// + /// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html) + /// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html) + /// + /// More specifically, you must ensure that the passed slice remains valid while the DMA is + /// active or until the DMA is stopped. + pub unsafe fn prepare_mem_to_mem_transfer_16_bit( + &mut self, + source: &[u16], + dest: &mut [u16], + ) -> 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(()) + } + + /// Prepares a 32-bit DMA transfer from memory to memory. + /// + /// This function does not enable the DMA channel and interrupts and only prepares + /// the DMA control block parameters for the transfer. It configures the primary channel control + /// structure to perform the transfer. + /// + /// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt] + /// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to + /// start the DMA transfer. + /// + /// # Safety + /// + /// You must ensure that the destination buffer is safe for DMA writes and the source buffer + /// is safe for DMA reads. The specific requirements can be read here: + /// + /// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html) + /// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html) + /// + /// More specifically, you must ensure that the passed slice remains valid while the DMA is + /// active or until the DMA is stopped. + pub unsafe 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(()) + } + + /// Prepares a 8-bit DMA transfer from memory to a peripheral. + /// + /// It is assumed that a peripheral with a 16-byte FIFO is used here and that the + /// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral + /// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with + /// rearbitration happening every 8 DMA cycles. It also configures the primary channel control + /// structure to perform the transfer. + /// + /// # Safety + /// + /// You must ensure that the source buffer is safe for DMA reads. The specific requirements + /// can be read here: + /// + /// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html) + /// + /// More specifically, you must ensure that the passed slice remains valid while the DMA is + /// active or until the DMA is stopped. + /// + /// The destination address must be the pointer address of a peripheral FIFO register address. + /// You must also ensure that the regular synchronous transfer API of the peripheral is NOT + /// used to perform transfers. + pub unsafe fn prepare_mem_to_periph_transfer_8_bit( + &mut self, + source: &[u8], + dest: *mut u32, + ) -> Result<(), DmaTransferInitError> { + if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE { + return Err(DmaTransferInitError::TransferSizeTooLarge(source.len())); + } + let len = source.len() - 1; + self.ch_ctrl_pri.cfg.set_raw(0); + self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32) + .checked_add(len as u32) + .ok_or(DmaTransferInitError::AddrOverflow)?; + self.ch_ctrl_pri.dest_end_ptr = dest as u32; + self.ch_ctrl_pri + .cfg + .set_cycle_ctr(CycleControl::Basic as u8); + self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8); + self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8); + self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8); + self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8); + self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16); + self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8); + self.select_primary_structure(); + 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); + self.select_primary_structure(); + } +} + +impl Dma { + /// Create a new DMA instance. + /// + /// You can also place the [DmaCtrlBlock] statically using a global static mutable + /// instance and the [DmaCtrlBlock::new] const constructor This also allows to place the control + /// block in a memory section using the [link_section](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute) + /// attribute and then creating a mutable pointer to it using [core::ptr::addr_of_mut]. + /// + /// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA + /// control block at a specific address. + pub fn new( + syscfg: &mut pac::Sysconfig, + dma: pac::Dma, + cfg: DmaCfg, + ctrl_block: *mut DmaCtrlBlock, + ) -> Result { + // 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); + } + 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.bits(raw_addr) }); + dma.set_protection_bits(&cfg); + dma.enable(); + Ok(dma) + } + + #[inline(always)] + pub fn enable(&self) { + self.dma.cfg().write(|w| w.master_enable().set_bit()); + } + + #[inline(always)] + pub fn disable(&self) { + self.dma.cfg().write(|w| w.master_enable().clear_bit()); + } + + #[inline(always)] + pub fn set_protection_bits(&self, cfg: &DmaCfg) { + self.dma.cfg().write(|w| unsafe { + w.chnl_prot_ctrl().bits( + cfg.privileged as u8 | ((cfg.bufferable as u8) << 1) | ((cfg.cacheable as u8) << 2), + ) + }); + } + + /// Split the DMA instance into four DMA channels which can be used individually. This allows + /// using the inidividual DMA channels in separate tasks. + pub fn split(self) -> (DmaChannel, DmaChannel, DmaChannel, DmaChannel) { + // Safety: The DMA channel API only operates on its respective channels. + ( + DmaChannel { + channel: 0, + done_interrupt: pac::Interrupt::DMA_DONE0, + active_interrupt: pac::Interrupt::DMA_ACTIVE0, + dma: unsafe { pac::Dma::steal() }, + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[0] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[0] }, + }, + DmaChannel { + channel: 1, + done_interrupt: pac::Interrupt::DMA_DONE1, + active_interrupt: pac::Interrupt::DMA_ACTIVE1, + dma: unsafe { pac::Dma::steal() }, + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[1] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[1] }, + }, + DmaChannel { + channel: 2, + done_interrupt: pac::Interrupt::DMA_DONE2, + active_interrupt: pac::Interrupt::DMA_ACTIVE2, + dma: unsafe { pac::Dma::steal() }, + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[2] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[2] }, + }, + DmaChannel { + channel: 3, + done_interrupt: pac::Interrupt::DMA_DONE3, + active_interrupt: pac::Interrupt::DMA_ACTIVE3, + dma: unsafe { pac::Dma::steal() }, + ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[3] }, + ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[3] }, + }, + ) + } +} diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index b515503..8a61e50 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -11,6 +11,7 @@ pub mod prelude; pub mod adc; pub mod clock; pub mod dac; +pub mod dma; pub mod gpio; pub mod i2c; pub mod pwm;