From f263a90f9d390a04f880d2671999a5aa30e22698 Mon Sep 17 00:00:00 2001 From: bjoernQ Date: Mon, 10 Oct 2022 15:00:55 +0200 Subject: [PATCH] Basic DMA for ESP32-C3 --- esp-hal-common/Cargo.toml | 1 + esp-hal-common/src/dma/gdma.rs | 684 +++++++++++++++++++++++ esp-hal-common/src/dma/mod.rs | 2 + esp-hal-common/src/lib.rs | 2 + esp-hal-common/src/spi.rs | 604 +++++++++++++++++++- esp-hal-common/src/system.rs | 10 + esp32c3-hal/examples/spi_loopback_dma.rs | 124 ++++ esp32c3-hal/src/lib.rs | 1 + 8 files changed, 1414 insertions(+), 14 deletions(-) create mode 100644 esp-hal-common/src/dma/gdma.rs create mode 100644 esp-hal-common/src/dma/mod.rs create mode 100644 esp32c3-hal/examples/spi_loopback_dma.rs diff --git a/esp-hal-common/Cargo.toml b/esp-hal-common/Cargo.toml index 62258e2a7da..80ad2fbdece 100644 --- a/esp-hal-common/Cargo.toml +++ b/esp-hal-common/Cargo.toml @@ -23,6 +23,7 @@ nb = "1.0.0" paste = "=1.0.8" procmacros = { version = "0.1.0", package = "esp-hal-procmacros", path = "../esp-hal-procmacros" } void = { version = "1.0.2", default-features = false } +embedded-dma = "0.2.0" # RISC-V riscv = { version = "0.8.0", optional = true } diff --git a/esp-hal-common/src/dma/gdma.rs b/esp-hal-common/src/dma/gdma.rs new file mode 100644 index 00000000000..6ef63d2a5dc --- /dev/null +++ b/esp-hal-common/src/dma/gdma.rs @@ -0,0 +1,684 @@ +//! Direct Memory Access + +use core::{marker::PhantomData, sync::atomic::compiler_fence}; + +use crate::system::{Peripheral, PeripheralClockControl}; + +/// DMA Errors +#[derive(Debug, Clone, Copy)] +pub enum DmaError { + InvalidAlignment, + OutOfDescriptors, + InvalidDescriptorSize, + DescriptorError, +} + +/// DMA Priorities +pub enum DmaPriority { + Priority0 = 0, + Priority1 = 1, + Priority2 = 2, + Priority3 = 3, + Priority4 = 4, + Priority5 = 5, + Priority6 = 6, + Priority7 = 7, + Priority8 = 8, + Priority9 = 9, + Priority10 = 10, + Priority11 = 11, + Priority12 = 12, + Priority13 = 13, + Priority14 = 14, + Priority15 = 15, +} + +enum Owner { + Cpu = 0, + Dma = 1, +} + +impl From for Owner { + fn from(value: u32) -> Self { + match value { + 0 => Owner::Cpu, + _ => Owner::Dma, + } + } +} + +trait DmaLinkedListDw0 { + fn set_size(&mut self, len: u16); + fn get_size(&mut self) -> u16; + fn set_length(&mut self, len: u16); + fn get_length(&mut self) -> u16; + fn set_err_eof(&mut self, err_eof: bool); + fn get_err_eof(&mut self) -> bool; + fn set_suc_eof(&mut self, suc_eof: bool); + fn get_suc_eof(&mut self) -> bool; + fn set_owner(&mut self, owner: Owner); + fn get_owner(&mut self) -> Owner; +} + +impl DmaLinkedListDw0 for &mut u32 { + fn set_size(&mut self, len: u16) { + let mask = 0b111111111111; + let bit_s = 0; + **self = (**self & !(mask << bit_s)) | (len as u32) << bit_s; + } + + fn get_size(&mut self) -> u16 { + let mask = 0b111111111111; + let bit_s = 0; + ((**self & (mask << bit_s)) >> bit_s) as u16 + } + + fn set_length(&mut self, len: u16) { + let mask = 0b111111111111; + let bit_s = 12; + **self = (**self & !(mask << bit_s)) | (len as u32) << bit_s; + } + + fn get_length(&mut self) -> u16 { + let mask = 0b111111111111; + let bit_s = 12; + ((**self & (mask << bit_s)) >> bit_s) as u16 + } + + fn set_err_eof(&mut self, err_eof: bool) { + let mask = 0b1; + let bit_s = 28; + **self = (**self & !(mask << bit_s)) | (err_eof as u32) << bit_s; + } + + fn get_err_eof(&mut self) -> bool { + let mask = 0b1; + let bit_s = 28; + ((**self & (mask << bit_s)) >> bit_s) != 0 + } + + fn set_suc_eof(&mut self, suc_eof: bool) { + let mask = 0b1; + let bit_s = 30; + **self = (**self & !(mask << bit_s)) | (suc_eof as u32) << bit_s; + } + + fn get_suc_eof(&mut self) -> bool { + let mask = 0b1; + let bit_s = 30; + ((**self & (mask << bit_s)) >> bit_s) != 0 + } + + fn set_owner(&mut self, owner: Owner) { + let mask = 0b1; + let bit_s = 31; + **self = (**self & !(mask << bit_s)) | (owner as u32) << bit_s; + } + + fn get_owner(&mut self) -> Owner { + let mask = 0b1; + let bit_s = 31; + ((**self & (mask << bit_s)) >> bit_s).into() + } +} + +/// DMA capable peripherals +pub enum DmaPeripheral { + Spi2 = 0, + Uhci0 = 2, + I2s = 3, + Aes = 6, + Sha = 7, + Adc = 8, +} + +/// DMA Rx +/// +/// The functions here are not meant to be used outside the HAL and will be +/// hidden/inaccessible in future. +pub trait Rx { + fn init(&mut self, burst_mode: bool, priority: DmaPriority); + fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + data: *mut u8, + len: usize, + ) -> Result<(), DmaError>; + fn is_done(&mut self) -> bool; +} + +pub(crate) trait RxChannel +where + R: RegisterAccess, +{ + fn init(&mut self, burst_mode: bool, priority: DmaPriority) { + R::set_in_burstmode(burst_mode); + R::set_in_priority(priority); + } + + fn prepare_transfer( + &mut self, + descriptors: &mut [u32], + peri: DmaPeripheral, + data: *mut u8, + len: usize, + ) -> Result<(), DmaError> { + for descr in descriptors.iter_mut() { + *descr = 0; + } + + compiler_fence(core::sync::atomic::Ordering::SeqCst); + + let mut processed = 0; + let mut descr = 0; + loop { + let chunk_size = usize::min(4092, len - processed); + let last = processed + chunk_size >= len; + + descriptors[descr + 1] = data as u32 + processed as u32; + + let mut dw0 = &mut descriptors[descr]; + + dw0.set_suc_eof(last); + dw0.set_owner(Owner::Dma); + dw0.set_size(chunk_size as u16); // align to 32 bits? + dw0.set_length(0); // actual size of the data!? + + if !last { + descriptors[descr + 2] = (&descriptors[descr + 3]) as *const _ as *const () as u32; + } else { + descriptors[descr + 2] = 0; + } + + processed += chunk_size; + descr += 3; + + if processed >= len { + break; + } + } + + R::clear_in_interrupts(); + R::reset_in(); + R::set_in_descriptors(descriptors.as_ptr() as u32); + R::set_in_peripheral(peri as u8); + R::start_in(); + + if R::has_in_descriptor_error() { + return Err(DmaError::DescriptorError); + } + + Ok(()) + } + + fn is_done(&mut self) -> bool { + R::is_in_done() + } +} + +pub(crate) struct ChannelRx<'a, T, R> +where + T: RxChannel, + R: RegisterAccess, +{ + descriptors: &'a mut [u32], + burst_mode: bool, + rx_impl: T, + _phantom: PhantomData, +} + +impl<'a, T, R> Rx for ChannelRx<'a, T, R> +where + T: RxChannel, + R: RegisterAccess, +{ + fn init(&mut self, burst_mode: bool, priority: DmaPriority) { + self.rx_impl.init(burst_mode, priority); + } + + fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + data: *mut u8, + len: usize, + ) -> Result<(), DmaError> { + if self.descriptors.len() % 3 != 0 { + return Err(DmaError::InvalidDescriptorSize); + } + + if self.descriptors.len() / 3 < len / 4092 { + return Err(DmaError::OutOfDescriptors); + } + + if self.burst_mode && (len % 4 != 0 || data as u32 % 4 != 0) { + return Err(DmaError::InvalidAlignment); + } + + self.rx_impl + .prepare_transfer(self.descriptors, peri, data, len)?; + Ok(()) + } + + fn is_done(&mut self) -> bool { + self.rx_impl.is_done() + } +} + +/// DMA Tx +/// +/// The functions here are not meant to be used outside the HAL and will be +/// hidden/inaccessible in future. +pub trait Tx { + fn init(&mut self, burst_mode: bool, priority: DmaPriority); + fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + data: *const u8, + len: usize, + ) -> Result<(), DmaError>; + fn is_done(&mut self) -> bool; +} + +pub(crate) trait TxChannel +where + R: RegisterAccess, +{ + fn init(&mut self, burst_mode: bool, priority: DmaPriority) { + R::set_out_burstmode(burst_mode); + R::set_out_priority(priority); + } + + fn prepare_transfer( + &mut self, + descriptors: &mut [u32], + peri: DmaPeripheral, + data: *const u8, + len: usize, + ) -> Result<(), DmaError> { + for descr in descriptors.iter_mut() { + *descr = 0; + } + + compiler_fence(core::sync::atomic::Ordering::SeqCst); + + let mut processed = 0; + let mut descr = 0; + loop { + let chunk_size = usize::min(4092, len - processed); + let last = processed + chunk_size >= len; + + descriptors[descr + 1] = data as u32 + processed as u32; + + let mut dw0 = &mut descriptors[descr]; + + dw0.set_suc_eof(last); + dw0.set_owner(Owner::Dma); + dw0.set_size(chunk_size as u16); // align to 32 bits? + dw0.set_length(chunk_size as u16); // actual size of the data!? + + if !last { + descriptors[descr + 2] = (&descriptors[descr + 3]) as *const _ as *const () as u32; + } else { + descriptors[descr + 2] = 0; + } + + processed += chunk_size; + descr += 3; + + if processed >= len { + break; + } + } + + R::clear_out_interrupts(); + R::reset_out(); + R::set_out_descriptors(descriptors.as_ptr() as u32); + R::set_out_peripheral(peri as u8); + R::start_out(); + + if R::has_out_descriptor_error() { + return Err(DmaError::DescriptorError); + } + + Ok(()) + } + + fn is_done(&mut self) -> bool { + R::is_out_done() + } +} + +pub(crate) struct ChannelTx<'a, T, R> +where + T: TxChannel, + R: RegisterAccess, +{ + descriptors: &'a mut [u32], + #[allow(unused)] + burst_mode: bool, + tx_impl: T, + _phantom: PhantomData, +} + +impl<'a, T, R> Tx for ChannelTx<'a, T, R> +where + T: TxChannel, + R: RegisterAccess, +{ + fn init(&mut self, burst_mode: bool, priority: DmaPriority) { + self.tx_impl.init(burst_mode, priority); + } + + fn prepare_transfer( + &mut self, + peri: DmaPeripheral, + data: *const u8, + len: usize, + ) -> Result<(), DmaError> { + if self.descriptors.len() % 3 != 0 { + return Err(DmaError::InvalidDescriptorSize); + } + + if self.descriptors.len() / 3 < len / 4092 { + return Err(DmaError::OutOfDescriptors); + } + + self.tx_impl + .prepare_transfer(self.descriptors, peri, data, len)?; + + Ok(()) + } + + fn is_done(&mut self) -> bool { + self.tx_impl.is_done() + } +} + +pub(crate) trait RegisterAccess { + fn set_out_burstmode(burst_mode: bool); + fn set_out_priority(priority: DmaPriority); + fn clear_out_interrupts(); + fn reset_out(); + fn set_out_descriptors(address: u32); + fn has_out_descriptor_error() -> bool; + fn set_out_peripheral(peripheral: u8); + fn start_out(); + fn is_out_done() -> bool; + fn set_in_burstmode(burst_mode: bool); + fn set_in_priority(priority: DmaPriority); + fn clear_in_interrupts(); + fn reset_in(); + fn set_in_descriptors(address: u32); + fn has_in_descriptor_error() -> bool; + fn set_in_peripheral(peripheral: u8); + fn start_in(); + fn is_in_done() -> bool; +} + +macro_rules! ImplChannel { + ($num: literal) => { + paste::paste! { + pub(crate) struct [] {} + + impl RegisterAccess for [] { + fn set_out_burstmode(burst_mode: bool) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].modify(|_,w| { + w.[]().bit(burst_mode) + .[]().bit(burst_mode) + }); + } + + fn set_out_priority(priority: DmaPriority) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].write(|w| { + w.[]().variant(priority as u8) + }); + } + + fn clear_out_interrupts() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].write(|w| { + w.[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + }); + } + + fn reset_out() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].modify(|_, w| w.[]().set_bit()); + dma.[].modify(|_, w| w.[]().clear_bit()); + } + + fn set_out_descriptors(address: u32) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| unsafe { w.[]().bits(address) }); + } + + fn has_out_descriptor_error() -> bool { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].read().[]().bit() + } + + fn set_out_peripheral(peripheral: u8) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| w.[]().variant(peripheral)); + } + + fn start_out() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| w.[]().set_bit()); + } + + fn is_out_done() -> bool { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].read().[]().bit() + } + + fn set_in_burstmode(burst_mode: bool) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].modify(|_,w| { + w.[]().bit(burst_mode) + .[]().bit(burst_mode) + }); + } + + fn set_in_priority(priority: DmaPriority) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].write(|w| { + w.[]().variant(priority as u8) + }); + } + + fn clear_in_interrupts() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + + dma.[].write(|w| { + w.[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + .[]() + .set_bit() + }); + } + + fn reset_in() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].modify(|_, w| w.[]().set_bit()); + dma.[].modify(|_, w| w.[]().clear_bit()); + } + + fn set_in_descriptors(address: u32) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| unsafe { w.[]().bits(address) }); + } + + fn has_in_descriptor_error() -> bool { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].read().[]().bit() + } + + fn set_in_peripheral(peripheral: u8) { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| w.[]().variant(peripheral)); + } + + fn start_in() { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[] + .modify(|_, w| w.[]().set_bit()); + } + + fn is_in_done() -> bool { + let dma = unsafe { &*crate::pac::DMA::PTR }; + dma.[].read().[]().bit() + } + } + + pub(crate) struct [] {} + + impl<'a> TxChannel<[]> for [] {} + + pub(crate) struct [] {} + + impl<'a> RxChannel<[]> for [] {} + + /// Create the Tx half of the DMA channel + pub struct [] { + } + + impl [] { + pub fn get<'a>( + self, + descriptors: &'a mut [u32], + burst_mode: bool, + priority: DmaPriority, + ) -> impl Tx + 'a { + let mut tx_impl = [] {}; + tx_impl.init(burst_mode, priority); + + ChannelTx { + descriptors, + burst_mode, + tx_impl: tx_impl, + _phantom: PhantomData::default(), + } + } + } + + /// Create the Rx half of the DMA channel + pub struct [] { + } + + impl [] { + pub fn get<'a>( + self, + descriptors: &'a mut [u32], + burst_mode: bool, + priority: DmaPriority, + ) -> impl Rx + 'a { + let mut rx_impl = [] {}; + rx_impl.init(burst_mode, priority); + + ChannelRx { + descriptors, + burst_mode, + rx_impl: rx_impl, + _phantom: PhantomData::default(), + } + } + } + } + }; +} + +ImplChannel!(0); +ImplChannel!(1); +ImplChannel!(2); + +/// DMA Channel +pub struct Channel { + pub tx: TX, + pub rx: RX, +} + +/// GDMA Peripheral +/// This offers the available DMA channels. +pub struct Gdma { + _inner: crate::pac::DMA, + pub channel0: Channel, + pub channel1: Channel, + pub channel2: Channel, +} + +impl Gdma { + /// Create a DMA instance. + pub fn new( + dma: crate::pac::DMA, + peripheral_clock_control: &mut PeripheralClockControl, + ) -> Gdma { + peripheral_clock_control.enable(Peripheral::Gdma); + dma.misc_conf.modify(|_, w| w.ahbm_rst_inter().set_bit()); + dma.misc_conf.modify(|_, w| w.ahbm_rst_inter().clear_bit()); + dma.misc_conf.modify(|_, w| w.clk_en().set_bit()); + + Gdma { + _inner: dma, + channel0: Channel { + rx: RxCreator0 {}, + tx: TxCreator0 {}, + }, + channel1: Channel { + rx: RxCreator1 {}, + tx: TxCreator1 {}, + }, + channel2: Channel { + rx: RxCreator2 {}, + tx: TxCreator2 {}, + }, + } + } +} + +/// Trait to be implemented for an in progress dma transfer. +#[allow(drop_bounds)] +pub trait DmaTransfer: Drop { + /// Wait for the transfer to finish. + fn wait(self) -> (B, T); +} + +/// Trait to be implemented for an in progress dma transfer. +#[allow(drop_bounds)] +pub trait DmaTransferRxTx: Drop { + /// Wait for the transfer to finish. + fn wait(self) -> (BR, BT, T); +} diff --git a/esp-hal-common/src/dma/mod.rs b/esp-hal-common/src/dma/mod.rs new file mode 100644 index 00000000000..763cef6e36f --- /dev/null +++ b/esp-hal-common/src/dma/mod.rs @@ -0,0 +1,2 @@ +#[cfg(esp32c3)] +pub mod gdma; diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index bee963ecafb..a05a37d609c 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -79,6 +79,8 @@ pub mod efuse; #[cfg_attr(xtensa, path = "interrupt/xtensa.rs")] pub mod interrupt; +pub mod dma; + /// Enumeration of CPU cores /// The actual number of available cores depends on the target. pub enum Cpu { diff --git a/esp-hal-common/src/spi.rs b/esp-hal-common/src/spi.rs index f982aa36928..b7adea983a1 100644 --- a/esp-hal-common/src/spi.rs +++ b/esp-hal-common/src/spi.rs @@ -48,10 +48,10 @@ //! underlying SPI bus by means of a Mutex. This ensures that device //! transactions do not interfere with each other. -use core::convert::Infallible; - use fugit::HertzU32; +#[cfg(any(esp32c3))] +use crate::dma::gdma::{DmaError, Rx, Tx}; use crate::{ clock::Clocks, pac::spi2::RegisterBlock, @@ -69,6 +69,31 @@ const FIFO_SIZE: usize = 72; /// Padding byte for empty write transfers const EMPTY_WRITE_PAD: u8 = 0x00u8; +#[allow(unused)] +const MAX_DMA_SIZE: usize = 32736; + +#[derive(Debug, Clone, Copy)] +pub enum Error { + #[cfg(esp32c3)] + DmaError(DmaError), + MaxDmaTransferSizeExceeded, + Unknown, +} + +#[cfg(esp32c3)] +impl From for Error { + fn from(value: DmaError) -> Self { + Error::DmaError(value) + } +} + +#[cfg(feature = "eh1")] +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + embedded_hal_1::spi::ErrorKind::Other + } +} + #[derive(Debug, Clone, Copy)] pub enum SpiMode { Mode0, @@ -173,7 +198,7 @@ where Self::new_internal(spi, frequency, mode, peripheral_clock_control, clocks) } - pub fn new_internal( + pub(crate) fn new_internal( spi: T, frequency: HertzU32, mode: SpiMode, @@ -200,7 +225,7 @@ impl embedded_hal::spi::FullDuplex for Spi where T: Instance, { - type Error = Infallible; + type Error = Error; fn read(&mut self) -> nb::Result { self.spi.read_byte() @@ -215,7 +240,7 @@ impl embedded_hal::blocking::spi::Transfer for Spi where T: Instance, { - type Error = Infallible; + type Error = Error; fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { self.spi.transfer(words) @@ -226,7 +251,7 @@ impl embedded_hal::blocking::spi::Write for Spi where T: Instance, { - type Error = Infallible; + type Error = Error; fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.spi.write_bytes(words)?; @@ -235,6 +260,358 @@ where } } +#[cfg(any(esp32c3))] +pub mod dma { + use core::mem; + + use embedded_dma::{ReadBuffer, WriteBuffer}; + + use super::{Instance, InstanceDma, Spi, MAX_DMA_SIZE}; + use crate::dma::gdma::{DmaTransfer, DmaTransferRxTx, Rx, Tx}; + + impl Spi + where + T: Instance, + { + /// Make this SPI instance into a DMA capable instance. + /// Pass the Rx and Tx half of the DMA channel. + pub fn with_tx_rx_dma(self, tx: TX, rx: RX) -> SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + SpiDma { + spi: self.spi, + tx, + rx, + } + } + } + + /// An in-progress DMA transfer + pub struct SpiDmaTransferRxTx + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + spi_dma: SpiDma, + rbuffer: RBUFFER, + tbuffer: TBUFFER, + } + + impl DmaTransferRxTx> + for SpiDmaTransferRxTx + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + /// Wait for the DMA transfer to complete and return the buffers and the + /// SPI instance. + fn wait(mut self) -> (RXBUF, TXBUF, SpiDma) { + self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough + + // `DmaTransfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let rbuffer = core::ptr::read(&self.rbuffer); + let tbuffer = core::ptr::read(&self.tbuffer); + let payload = core::ptr::read(&self.spi_dma); + mem::forget(self); + (rbuffer, tbuffer, payload) + } + } + } + + impl Drop for SpiDmaTransferRxTx + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + fn drop(&mut self) { + self.spi_dma.spi.flush().ok(); + } + } + + /// An in-progress DMA transfer. + pub struct SpiDmaTransfer + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + spi_dma: SpiDma, + buffer: BUFFER, + } + + impl DmaTransfer> for SpiDmaTransfer + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + /// Wait for the DMA transfer to complete and return the buffers and the + /// SPI instance. + fn wait(mut self) -> (BUFFER, SpiDma) { + self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough + + // `DmaTransfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = core::ptr::read(&self.buffer); + let payload = core::ptr::read(&self.spi_dma); + mem::forget(self); + (buffer, payload) + } + } + } + + impl Drop for SpiDmaTransfer + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + fn drop(&mut self) { + self.spi_dma.spi.flush().ok(); + } + } + + /// A DMA capable SPI instance. + pub struct SpiDma { + spi: T, + tx: TX, + rx: RX, + } + + impl SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + /// Return the raw interface to the underlying peripheral instance + pub fn free(self) -> T { + self.spi + } + + /// Perform a DMA write. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be sent is 32736 + /// bytes. + pub fn dma_write( + mut self, + words: TXBUF, + ) -> Result, super::Error> + where + TXBUF: ReadBuffer, + { + let (ptr, len) = unsafe { words.read_buffer() }; + + if len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi.start_write_bytes_dma(ptr, len, &mut self.tx)?; + Ok(SpiDmaTransfer { + spi_dma: self, + buffer: words, + }) + } + + /// Perform a DMA read. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be received is 32736 + /// bytes. + pub fn dma_read( + mut self, + mut words: RXBUF, + ) -> Result, super::Error> + where + RXBUF: WriteBuffer, + { + let (ptr, len) = unsafe { words.write_buffer() }; + + if len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi.start_read_bytes_dma(ptr, len, &mut self.rx)?; + Ok(SpiDmaTransfer { + spi_dma: self, + buffer: words, + }) + } + + /// Perform a DMA transfer. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be sent/received is + /// 32736 bytes. + pub fn dma_transfer( + mut self, + words: TXBUF, + mut read_buffer: RXBUF, + ) -> Result, super::Error> + where + TXBUF: ReadBuffer, + RXBUF: WriteBuffer, + { + let (write_ptr, write_len) = unsafe { words.read_buffer() }; + let (read_ptr, read_len) = unsafe { read_buffer.write_buffer() }; + + if write_len > MAX_DMA_SIZE || read_len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi.start_transfer_dma( + write_ptr, + write_len, + read_ptr, + read_len, + &mut self.tx, + &mut self.rx, + )?; + Ok(SpiDmaTransferRxTx { + spi_dma: self, + rbuffer: read_buffer, + tbuffer: words, + }) + } + } + + impl embedded_hal::blocking::spi::Transfer for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + type Error = super::Error; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.spi + .transfer_in_place_dma(words, &mut self.tx, &mut self.rx) + } + } + + impl embedded_hal::blocking::spi::Write for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + type Error = super::Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.spi.write_bytes_dma(words, &mut self.tx)?; + self.spi.flush()?; + Ok(()) + } + } + + #[cfg(feature = "eh1")] + mod ehal1 { + use embedded_hal_1::spi::{SpiBus, SpiBusFlush, SpiBusRead, SpiBusWrite}; + + use super::{super::InstanceDma, SpiDma}; + use crate::dma::gdma::{Rx, Tx}; + + impl embedded_hal_1::spi::ErrorType for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + type Error = super::super::Error; + } + + impl SpiBusWrite for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + /// See also: [`write_bytes`]. + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.spi.write_bytes_dma(words, &mut self.tx)?; + self.flush() + } + } + + impl SpiBusRead for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.spi + .transfer_dma(&[], words, &mut self.tx, &mut self.rx)?; + self.flush() + } + } + + impl SpiBus for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + /// Write out data from `write`, read response into `read`. + /// + /// `read` and `write` are allowed to have different lengths. If + /// `write` is longer, all other bytes received are + /// discarded. If `read` is longer, [`EMPTY_WRITE_PAD`] + /// is written out as necessary until enough bytes have + /// been read. Reading and writing happens + /// simultaneously. + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.spi + .transfer_dma(write, read, &mut self.tx, &mut self.rx)?; + self.flush() + } + + /// Transfer data in place. + /// + /// Writes data from `words` out on the bus and stores the reply + /// into `words`. A convenient wrapper around + /// [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and + /// [`read`](SpiBusRead::read). + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.spi + .transfer_in_place_dma(words, &mut self.tx, &mut self.rx)?; + self.flush() + } + } + + impl SpiBusFlush for SpiDma + where + T: InstanceDma, + TX: Tx, + RX: Rx, + { + fn flush(&mut self) -> Result<(), Self::Error> { + self.spi.flush() + } + } + } +} + #[cfg(feature = "eh1")] pub use ehal1::*; @@ -257,7 +634,7 @@ mod ehal1 { use crate::OutputPin; impl embedded_hal_1::spi::ErrorType for Spi { - type Error = Infallible; + type Error = super::Error; } impl FullDuplex for Spi @@ -469,6 +846,197 @@ mod ehal1 { } } +#[cfg(any(esp32c3))] +pub trait InstanceDma: Instance +where + TX: Tx, + RX: Rx, +{ + fn transfer_in_place_dma<'w>( + &mut self, + words: &'w mut [u8], + tx: &mut TX, + rx: &mut RX, + ) -> Result<&'w [u8], Error> { + for chunk in words.chunks_mut(MAX_DMA_SIZE) { + self.start_transfer_dma( + chunk.as_ptr(), + chunk.len(), + chunk.as_mut_ptr(), + chunk.len(), + tx, + rx, + )?; + + while !tx.is_done() && !rx.is_done() {} + self.flush().unwrap(); + } + + return Ok(words); + } + + fn transfer_dma<'w>( + &mut self, + write_buffer: &'w [u8], + read_buffer: &'w mut [u8], + tx: &mut TX, + rx: &mut RX, + ) -> Result<&'w [u8], Error> { + let mut idx = 0; + loop { + let write_idx = isize::min(idx, write_buffer.len() as isize); + let write_len = usize::min(write_buffer.len() - idx as usize, MAX_DMA_SIZE); + + let read_idx = isize::min(idx, read_buffer.len() as isize); + let read_len = usize::min(read_buffer.len() - idx as usize, MAX_DMA_SIZE); + + self.start_transfer_dma( + unsafe { write_buffer.as_ptr().offset(write_idx) }, + write_len, + unsafe { read_buffer.as_mut_ptr().offset(read_idx) }, + read_len, + tx, + rx, + )?; + + while !tx.is_done() && !rx.is_done() {} + self.flush().unwrap(); + + idx += MAX_DMA_SIZE as isize; + if idx >= write_buffer.len() as isize && idx >= read_buffer.len() as isize { + break; + } + } + + return Ok(read_buffer); + } + + fn start_transfer_dma<'w>( + &mut self, + write_buffer_ptr: *const u8, + write_buffer_len: usize, + read_buffer_ptr: *mut u8, + read_buffer_len: usize, + tx: &mut TX, + rx: &mut RX, + ) -> Result<(), Error> { + let reg_block = self.register_block(); + self.configure_datalen(usize::max(read_buffer_len, write_buffer_len) as u32 * 8); + + tx.is_done(); + rx.is_done(); + + reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit()); + reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit()); + self.update(); + + tx.prepare_transfer( + crate::dma::gdma::DmaPeripheral::Spi2, + write_buffer_ptr, + write_buffer_len, + )?; + rx.prepare_transfer( + crate::dma::gdma::DmaPeripheral::Spi2, + read_buffer_ptr, + read_buffer_len, + )?; + + reg_block.dma_int_clr.write(|w| { + w.dma_infifo_full_err_int_clr() + .set_bit() + .dma_outfifo_empty_err_int_clr() + .set_bit() + .trans_done_int_clr() + .set_bit() + .mst_rx_afifo_wfull_err_int_clr() + .set_bit() + .mst_tx_afifo_rempty_err_int_clr() + .set_bit() + }); + + reg_block.cmd.modify(|_, w| w.usr().set_bit()); + + Ok(()) + } + + fn write_bytes_dma<'w>(&mut self, words: &'w [u8], tx: &mut TX) -> Result<&'w [u8], Error> { + for chunk in words.chunks(MAX_DMA_SIZE) { + self.start_write_bytes_dma(chunk.as_ptr(), chunk.len(), tx)?; + + while !tx.is_done() {} + self.flush().unwrap(); // seems "is_done" doesn't work as intended? + } + + return Ok(words); + } + + fn start_write_bytes_dma<'w>( + &mut self, + ptr: *const u8, + len: usize, + tx: &mut TX, + ) -> Result<(), Error> { + let reg_block = self.register_block(); + self.configure_datalen(len as u32 * 8); + + reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit()); + reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit()); + self.update(); + + tx.prepare_transfer(crate::dma::gdma::DmaPeripheral::Spi2, ptr, len)?; + + reg_block.dma_int_clr.write(|w| { + w.dma_infifo_full_err_int_clr() + .set_bit() + .dma_outfifo_empty_err_int_clr() + .set_bit() + .trans_done_int_clr() + .set_bit() + .mst_rx_afifo_wfull_err_int_clr() + .set_bit() + .mst_tx_afifo_rempty_err_int_clr() + .set_bit() + }); + + reg_block.cmd.modify(|_, w| w.usr().set_bit()); + + return Ok(()); + } + + fn start_read_bytes_dma<'w>( + &mut self, + ptr: *mut u8, + len: usize, + rx: &mut RX, + ) -> Result<(), Error> { + let reg_block = self.register_block(); + self.configure_datalen(len as u32 * 8); + + reg_block.dma_conf.modify(|_, w| w.dma_tx_ena().set_bit()); + reg_block.dma_conf.modify(|_, w| w.dma_rx_ena().set_bit()); + self.update(); + + rx.prepare_transfer(crate::dma::gdma::DmaPeripheral::Spi2, ptr, len)?; + + reg_block.dma_int_clr.write(|w| { + w.dma_infifo_full_err_int_clr() + .set_bit() + .dma_outfifo_empty_err_int_clr() + .set_bit() + .trans_done_int_clr() + .set_bit() + .mst_rx_afifo_wfull_err_int_clr() + .set_bit() + .mst_tx_afifo_rempty_err_int_clr() + .set_bit() + }); + + reg_block.cmd.modify(|_, w| w.usr().set_bit()); + + return Ok(()); + } +} + pub trait Instance { fn register_block(&self) -> &RegisterBlock; @@ -656,7 +1224,7 @@ pub trait Instance { self } - fn read_byte(&mut self) -> nb::Result { + fn read_byte(&mut self) -> nb::Result { let reg_block = self.register_block(); if reg_block.cmd.read().usr().bit_is_set() { @@ -666,7 +1234,7 @@ pub trait Instance { Ok(u32::try_into(reg_block.w0.read().bits()).unwrap_or_default()) } - fn write_byte(&mut self, word: u8) -> nb::Result<(), Infallible> { + fn write_byte(&mut self, word: u8) -> nb::Result<(), Error> { let reg_block = self.register_block(); if reg_block.cmd.read().usr().bit_is_set() { @@ -693,7 +1261,7 @@ pub trait Instance { /// you must ensure that the whole messages was written correctly, use /// [`flush`]. // FIXME: See below. - fn write_bytes(&mut self, words: &[u8]) -> Result<(), Infallible> { + fn write_bytes(&mut self, words: &[u8]) -> Result<(), Error> { let reg_block = self.register_block(); let num_chunks = words.len() / FIFO_SIZE; @@ -739,7 +1307,7 @@ pub trait Instance { /// Sends out a stuffing byte for every byte to read. This function doesn't /// perform flushing. If you want to read the response to something you /// have written before, consider using [`transfer`] instead. - fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Infallible> { + fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Error> { let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; for chunk in words.chunks_mut(FIFO_SIZE) { @@ -759,7 +1327,7 @@ pub trait Instance { // FIXME: Using something like `core::slice::from_raw_parts` and // `copy_from_slice` on the receive registers works only for the esp32 and // esp32c3 varaints. The reason for this is unknown. - fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Infallible> { + fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Error> { let reg_block = self.register_block(); for chunk in words.chunks_mut(FIFO_SIZE) { @@ -783,7 +1351,7 @@ pub trait Instance { } // Check if the bus is busy and if it is wait for it to be idle - fn flush(&mut self) -> Result<(), Infallible> { + fn flush(&mut self) -> Result<(), Error> { let reg_block = self.register_block(); while reg_block.cmd.read().usr().bit_is_set() { @@ -792,7 +1360,7 @@ pub trait Instance { Ok(()) } - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Infallible> { + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Error> { for chunk in words.chunks_mut(FIFO_SIZE) { self.write_bytes(chunk)?; self.flush()?; @@ -872,6 +1440,14 @@ impl Instance for crate::pac::SPI2 { } } +#[cfg(any(esp32c3))] +impl InstanceDma for crate::pac::SPI2 +where + TX: Tx, + RX: Rx, +{ +} + #[cfg(any(esp32))] impl Instance for crate::pac::SPI2 { #[inline(always)] diff --git a/esp-hal-common/src/system.rs b/esp-hal-common/src/system.rs index 9dbb0efcd05..bc29e53ba57 100644 --- a/esp-hal-common/src/system.rs +++ b/esp-hal-common/src/system.rs @@ -24,6 +24,8 @@ pub enum Peripheral { Ledc, #[cfg(esp32c3)] ApbSarAdc, + #[cfg(esp32c3)] + Gdma, } /// Controls the enablement of peripheral clocks. @@ -41,6 +43,9 @@ impl PeripheralClockControl { #[cfg(esp32)] let (perip_clk_en0, perip_rst_en0) = { (&system.perip_clk_en, &system.perip_rst_en) }; + #[cfg(esp32c3)] + let (perip_clk_en1, perip_rst_en1) = { (&system.perip_clk_en1, &system.perip_rst_en1) }; + match peripheral { Peripheral::Spi2 => { perip_clk_en0.modify(|_, w| w.spi2_clk_en().set_bit()); @@ -78,6 +83,11 @@ impl PeripheralClockControl { perip_clk_en0.modify(|_, w| w.apb_saradc_clk_en().set_bit()); perip_rst_en0.modify(|_, w| w.apb_saradc_rst().clear_bit()); } + #[cfg(esp32c3)] + Peripheral::Gdma => { + perip_clk_en1.modify(|_, w| w.dma_clk_en().set_bit()); + perip_rst_en1.modify(|_, w| w.dma_rst().clear_bit()); + } } } } diff --git a/esp32c3-hal/examples/spi_loopback_dma.rs b/esp32c3-hal/examples/spi_loopback_dma.rs new file mode 100644 index 00000000000..fb0520b1475 --- /dev/null +++ b/esp32c3-hal/examples/spi_loopback_dma.rs @@ -0,0 +1,124 @@ +//! SPI loopback test using DMA +//! +//! Folowing pins are used: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect MISO and MOSI pins to see the outgoing data is read as incoming +//! data. + +#![no_std] +#![no_main] + +use esp32c3_hal::{ + clock::ClockControl, + gdma::{DmaPriority, DmaTransferRxTx, Gdma}, + gpio::IO, + pac::Peripherals, + prelude::*, + spi::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; +use riscv_rt::entry; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let sclk = io.pins.gpio6; + let miso = io.pins.gpio2; + let mosi = io.pins.gpio7; + let cs = io.pins.gpio10; + + let dma = Gdma::new(peripherals.DMA, &mut system.peripheral_clock_control); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + sclk, + mosi, + miso, + cs, + 100u32.kHz(), + SpiMode::Mode0, + &mut system.peripheral_clock_control, + &clocks, + ) + .with_tx_rx_dma( + dma_channel + .tx + .get(&mut descriptors, false, DmaPriority::Priority0), + dma_channel + .rx + .get(&mut rx_descriptors, false, DmaPriority::Priority0), + ); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let mut send = buffer1(); + let mut receive = buffer2(); + let mut i = 0; + + for (i, v) in send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + + loop { + send[0] = i; + send[send.len() - 1] = i; + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(send, receive).unwrap(); + // here we could do something else while DMA transfer is in progress + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (receive, send, spi) = transfer.wait(); + println!( + "{:x?} .. {:x?}", + &receive[..10], + &receive[receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} diff --git a/esp32c3-hal/src/lib.rs b/esp32c3-hal/src/lib.rs index 8ff801afdeb..3bbbd1d25c6 100644 --- a/esp32c3-hal/src/lib.rs +++ b/esp32c3-hal/src/lib.rs @@ -5,6 +5,7 @@ use core::arch::global_asm; pub use embedded_hal as ehal; pub use esp_hal_common::{ clock, + dma::gdma, efuse, gpio as gpio_types, i2c,