use crate::peripherals::{Dma, Sdma};
use core::marker::PhantomData;
mod sealed {
pub trait DmaInstanceSealed {}
}
pub trait DmaInstance: sealed::DmaInstanceSealed {
fn ptr() -> *const crate::soc::pac::dma::RegisterBlock;
const CHANNEL_BASE: u8;
fn bypass_auto_clock_gate() {}
}
pub struct Dma0;
impl sealed::DmaInstanceSealed for Dma0 {}
impl DmaInstance for Dma0 {
fn ptr() -> *const crate::soc::pac::dma::RegisterBlock {
Dma::ptr()
}
const CHANNEL_BASE: u8 = 0;
fn bypass_auto_clock_gate() {
#[cfg(feature = "chip-ws63")]
{
let r = unsafe { &*crate::peripherals::SysCtl1::ptr() };
r.ip_auto_cg_bypass().modify(|_, w| w.dma_clk_on().set_bit());
}
}
}
#[instability::unstable]
pub struct Sdma0;
impl sealed::DmaInstanceSealed for Sdma0 {}
impl DmaInstance for Sdma0 {
fn ptr() -> *const crate::soc::pac::dma::RegisterBlock {
Sdma::ptr()
}
const CHANNEL_BASE: u8 = 8;
}
#[inline]
fn physical_channel_index(base: u8, channel: u8) -> usize {
assert!(channel >= base && channel < base + 4, "DMA channel out of range for this controller");
(channel - base) as usize
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[instability::unstable]
pub struct DmaTransferSize(u16);
impl DmaTransferSize {
pub const MAX_BEATS: usize = 0x0fff;
pub const fn from_beats(beats: usize) -> Option<Self> {
if beats <= Self::MAX_BEATS { Some(Self(beats as u16)) } else { None }
}
#[inline]
const fn get(self) -> u16 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[instability::unstable]
pub struct DmaSyncMask(u8);
impl DmaSyncMask {
pub const fn from_bits(bits: u16) -> Option<Self> {
if bits & !0x000f == 0 { Some(Self(bits as u8)) } else { None }
}
pub const NONE: Self = Self(0);
pub const ALL: Self = Self(0x0f);
#[inline]
const fn bits(self) -> u16 {
self.0 as u16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransferWidth {
Width8 = 0,
Width16 = 1,
Width32 = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BurstSize {
Beats1 = 0,
Beats4 = 1,
Beats8 = 2,
Beats16 = 3,
Beats32 = 4,
Beats64 = 5,
Beats128 = 6,
Beats256 = 7,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowControl {
MemToMem = 0,
MemToPeripheral = 1,
PeripheralToMem = 2,
PeripheralToPeripheral = 3,
}
#[derive(Debug, Clone, Copy)]
pub struct DmaChannelConfig {
src_peripheral: u8,
dst_peripheral: u8,
flow_control: FlowControl,
src_width: TransferWidth,
dst_width: TransferWidth,
src_burst: BurstSize,
dst_burst: BurstSize,
src_inc: bool,
dst_inc: bool,
transfer_int: bool,
error_int: bool,
bus_lock: bool,
}
impl Default for DmaChannelConfig {
fn default() -> Self {
Self::mem_to_mem()
}
}
impl DmaChannelConfig {
pub const fn mem_to_mem() -> Self {
Self {
src_peripheral: 0,
dst_peripheral: 0,
flow_control: FlowControl::MemToMem,
src_width: TransferWidth::Width32,
dst_width: TransferWidth::Width32,
src_burst: BurstSize::Beats1,
dst_burst: BurstSize::Beats1,
src_inc: true,
dst_inc: true,
transfer_int: false,
error_int: false,
bus_lock: false,
}
}
#[inline]
fn control_word(self, transfer_size: DmaTransferSize) -> u32 {
let mut control: u32 = 0;
control |= (transfer_size.get() as u32) & 0x0fff; control |= ((self.src_burst as u32) & 0x07) << 12; control |= ((self.dst_burst as u32) & 0x07) << 15; control |= ((self.src_width as u32) & 0x07) << 18; control |= ((self.dst_width as u32) & 0x07) << 21; if self.src_inc {
control |= 1 << 26;
}
if self.dst_inc {
control |= 1 << 27;
}
if self.transfer_int {
control |= 1 << 31;
}
control
}
#[inline]
fn channel_config_word(self) -> u32 {
let mut ch_cfg: u32 = 0;
ch_cfg |= 0x01; ch_cfg |= ((self.src_peripheral as u32) & 0x0f) << 1; ch_cfg |= ((self.dst_peripheral as u32) & 0x0f) << 5; ch_cfg |= ((self.flow_control as u32) & 0x07) << 9; if self.error_int {
ch_cfg |= 1 << 12;
}
if self.transfer_int {
ch_cfg |= 1 << 13;
}
if self.bus_lock {
ch_cfg |= 1 << 14;
}
ch_cfg
}
}
pub struct DmaDriver<'d, T: DmaInstance> {
_instance: PhantomData<&'d T>,
}
impl<'d, T: DmaInstance> DmaDriver<'d, T> {
#[cfg(all(test, not(target_arch = "riscv32")))]
fn new_for_test() -> Self {
Self { _instance: PhantomData }
}
fn regs() -> &'static crate::soc::pac::dma::RegisterBlock {
unsafe { &*T::ptr() }
}
#[inline]
fn physical_channel(channel: u8) -> usize {
physical_channel_index(T::CHANNEL_BASE, channel)
}
pub fn enable_controller(&mut self) {
T::bypass_auto_clock_gate();
let r = Self::regs();
unsafe {
r.dmac_config().write(|w| w.bits(0x01));
}
}
pub fn disable_controller(&mut self) {
unsafe {
Self::regs().dmac_config().write(|w| w.bits(0));
}
}
pub(crate) fn configure_channel_raw(
&mut self,
channel: u8,
src_addr: u32,
dst_addr: u32,
transfer_size: DmaTransferSize,
config: &DmaChannelConfig,
) {
let ch = Self::physical_channel(channel);
let r = Self::regs();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(0));
}
unsafe {
r.dmac_s_addr_0(ch).write(|w| w.bits(src_addr));
}
unsafe {
r.dmac_d_addr_0(ch).write(|w| w.bits(dst_addr));
}
unsafe {
r.dmac_lli_0(ch).write(|w| w.bits(0));
}
let control = config.control_word(transfer_size);
unsafe {
r.dmac_chn_control_0(ch).write(|w| w.bits(control));
}
let ch_cfg = config.channel_config_word();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(ch_cfg));
}
let en = r.dmac_en_chns().read().bits();
unsafe {
r.dmac_en_chns().write(|w| w.bits(en | (1 << ch)));
}
}
pub(crate) fn enable_channel_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
let r = Self::regs();
let cfg = r.dmac_chn_config_0(ch).read().bits();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(cfg | 0x01));
}
}
pub(crate) fn disable_channel_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
let r = Self::regs();
let cfg = r.dmac_chn_config_0(ch).read().bits();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(cfg & !0x01));
}
}
pub(crate) fn channel_enabled_raw(&self, channel: u8) -> bool {
let ch = Self::physical_channel(channel);
let mask = 1u32 << ch;
Self::regs().dmac_en_chns().read().bits() & mask != 0
}
pub(crate) fn channel_active_raw(&self, channel: u8) -> bool {
let ch = Self::physical_channel(channel);
Self::regs().dmac_chn_config_0(ch).read().bits() & (1 << 15) != 0
}
pub(crate) fn halt_channel_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
let r = Self::regs();
let cfg = r.dmac_chn_config_0(ch).read().bits();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(cfg | (1 << 16)));
}
}
pub(crate) fn resume_channel_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
let r = Self::regs();
let cfg = r.dmac_chn_config_0(ch).read().bits();
unsafe {
r.dmac_chn_config_0(ch).write(|w| w.bits(cfg & !(1 << 16)));
}
}
pub(crate) fn burst_request_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
unsafe {
Self::regs().dmac_burst_req().write(|w| w.bits(1 << ch));
}
}
pub(crate) fn single_request_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
unsafe {
Self::regs().dmac_single_req().write(|w| w.bits(1 << ch));
}
}
pub fn raw_interrupt_status(&self) -> (u8, u8) {
let sts = Self::regs().dmac_ori_int_st().read().bits();
((sts & 0xFF) as u8, ((sts >> 8) & 0xFF) as u8)
}
pub fn interrupt_status(&self) -> (u8, u8) {
let sts = Self::regs().dmac_int_st().read().bits();
((sts & 0xFF) as u8, ((sts >> 16) & 0xFF) as u8)
}
pub(crate) fn clear_transfer_interrupt_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
unsafe {
Self::regs().dmac_int_clr().write(|w| w.bits(1 << ch));
}
}
pub(crate) fn clear_error_interrupt_raw(&mut self, channel: u8) {
let ch = Self::physical_channel(channel);
unsafe {
Self::regs().dmac_int_clr().write(|w| w.bits(1 << (ch + 8)));
}
}
#[instability::unstable]
pub fn set_sync(&mut self, sync_mask: DmaSyncMask) {
unsafe {
Self::regs().dmac_sync().write(|w| w.bits(sync_mask.bits() as u32));
}
}
}
impl<'d> DmaDriver<'d, Dma0> {
pub fn new_dma(_dma: Dma<'d>) -> Self {
Self { _instance: PhantomData }
}
}
#[cfg(feature = "chip-ws63")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DmaPeripheral {
Tie0 = 0,
Uart0Tx = 1,
Uart0Rx = 2,
Uart1Tx = 3,
Uart1Rx = 4,
Uart2Tx = 5,
Uart2Rx = 6,
Spi0Tx = 7,
Spi0Rx = 8,
I2sTx = 11,
I2sRx = 12,
Spi1Tx = 13,
Spi1Rx = 14,
}
#[cfg(feature = "chip-ws63")]
impl DmaPeripheral {
pub(crate) const fn request_id(self) -> u8 {
self as u8
}
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DmaDirection {
Tx,
Rx,
}
impl DmaChannelConfig {
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
pub fn mem_to_peripheral(mut self, peri: DmaPeripheral) -> Self {
self.flow_control = FlowControl::MemToPeripheral;
self.dst_peripheral = peri.request_id();
self.dst_inc = false;
self
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
pub fn peripheral_to_mem(mut self, peri: DmaPeripheral) -> Self {
self.flow_control = FlowControl::PeripheralToMem;
self.src_peripheral = peri.request_id();
self.src_inc = false;
self
}
}
impl<'d> DmaDriver<'d, Sdma0> {
#[instability::unstable]
pub fn new_sdma(_sdma: Sdma<'d>) -> Self {
Self { _instance: PhantomData }
}
}
use core::mem::ManuallyDrop;
use embedded_dma::{ReadBuffer, WriteBuffer};
pub const DMA_ALIGN: usize = 32;
const DMA_WAIT_LOOPS: u32 = 5_000_000;
impl<'d> DmaDriver<'d, Dma0> {
#[instability::unstable]
pub fn start_mem_to_mem<SRC, DST>(
mut self,
channel: DmaChannel,
src: SRC,
mut dst: DST,
) -> Result<Transfer<'d, SRC, DST>, DmaStartError<'d, SRC, DST>>
where
SRC: ReadBuffer<Word = u32>,
DST: WriteBuffer<Word = u32>,
{
let (src_ptr, src_len) = unsafe { src.read_buffer() };
let (dst_ptr, dst_len) = unsafe { dst.write_buffer() };
let words = src_len.min(dst_len);
let transfer_size = match DmaTransferSize::from_beats(words) {
Some(size) => size,
None => return Err(DmaStartError { error: DmaError::TransferTooLarge, driver: self, channel, src, dst }),
};
let bytes = words * core::mem::size_of::<u32>();
self.enable_controller();
#[cfg(feature = "chip-ws63")]
unsafe {
crate::cache::clean_range(src_ptr as usize, bytes);
}
let cfg = DmaChannelConfig::default();
let ch = channel.logical;
self.configure_channel_raw(ch, src_ptr as u32, dst_ptr as u32, transfer_size, &cfg);
Ok(Transfer { driver: self, channel, src, dst, dst_addr: dst_ptr as usize, bytes })
}
}
#[instability::unstable]
pub struct Transfer<'d, SRC, DST> {
driver: DmaDriver<'d, Dma0>,
channel: DmaChannel,
src: SRC,
dst: DST,
#[cfg_attr(not(feature = "chip-ws63"), allow(dead_code))]
dst_addr: usize,
#[cfg_attr(not(feature = "chip-ws63"), allow(dead_code))]
bytes: usize,
}
#[instability::unstable]
pub type DmaTransferParts<'d, SRC, DST> = (DmaDriver<'d, Dma0>, DmaChannel, SRC, DST);
#[instability::unstable]
pub type DmaWaitResult<'d, SRC, DST> = Result<DmaTransferParts<'d, SRC, DST>, DmaWaitError<'d, SRC, DST>>;
impl<'d, SRC, DST> Transfer<'d, SRC, DST> {
pub fn is_done(&self) -> bool {
!self.driver.channel_enabled_raw(self.channel.logical)
}
pub fn wait(self) -> DmaWaitResult<'d, SRC, DST> {
let mut this = ManuallyDrop::new(self);
let mut n = DMA_WAIT_LOOPS;
while this.driver.channel_enabled_raw(this.channel.logical) {
n -= 1;
if n == 0 {
break;
}
core::hint::spin_loop();
}
let ch = this.channel.logical;
let timed_out = this.driver.channel_enabled_raw(ch);
if timed_out {
quiesce_channel(&mut this.driver, ch);
}
#[cfg(feature = "chip-ws63")]
unsafe {
crate::cache::invalidate_range(this.dst_addr, this.bytes);
}
unsafe {
let driver = core::ptr::read(&this.driver);
let channel = core::ptr::read(&this.channel);
let src = core::ptr::read(&this.src);
let dst = core::ptr::read(&this.dst);
if timed_out {
Err(DmaWaitError { error: DmaError::Timeout, driver, channel, src, dst })
} else {
Ok((driver, channel, src, dst))
}
}
}
}
#[instability::unstable]
pub struct DmaStartError<'d, SRC, DST> {
error: DmaError,
driver: DmaDriver<'d, Dma0>,
channel: DmaChannel,
src: SRC,
dst: DST,
}
impl<SRC, DST> core::fmt::Debug for DmaStartError<'_, SRC, DST> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DmaStartError").field("error", &self.error).finish_non_exhaustive()
}
}
impl<'d, SRC, DST> DmaStartError<'d, SRC, DST> {
pub const fn error(&self) -> DmaError {
self.error
}
pub fn into_parts(self) -> (DmaError, DmaDriver<'d, Dma0>, DmaChannel, SRC, DST) {
(self.error, self.driver, self.channel, self.src, self.dst)
}
}
#[instability::unstable]
pub struct DmaWaitError<'d, SRC, DST> {
error: DmaError,
driver: DmaDriver<'d, Dma0>,
channel: DmaChannel,
src: SRC,
dst: DST,
}
impl<SRC, DST> core::fmt::Debug for DmaWaitError<'_, SRC, DST> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DmaWaitError").field("error", &self.error).finish_non_exhaustive()
}
}
impl<'d, SRC, DST> DmaWaitError<'d, SRC, DST> {
pub const fn error(&self) -> DmaError {
self.error
}
pub fn into_parts(self) -> (DmaError, DmaDriver<'d, Dma0>, DmaChannel, SRC, DST) {
(self.error, self.driver, self.channel, self.src, self.dst)
}
}
impl<SRC, DST> Drop for Transfer<'_, SRC, DST> {
fn drop(&mut self) {
quiesce_channel(&mut self.driver, self.channel.logical);
}
}
fn quiesce_channel<T: DmaInstance>(driver: &mut DmaDriver<'_, T>, channel: u8) {
driver.halt_channel_raw(channel);
let mut n = DMA_WAIT_LOOPS;
while driver.channel_active_raw(channel) {
if n == 0 {
break;
}
n -= 1;
core::hint::spin_loop();
}
driver.disable_channel_raw(channel);
}
#[cfg(feature = "chip-ws63")]
fn cancel_then_quiesce(peri_dis: &mut PeriDmaCtl, driver: &mut DmaDriver<'_, Dma0>, channel: u8) {
peri_dis.disable();
quiesce_channel(driver, channel);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DmaError {
TransferTooLarge,
Timeout,
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DmaFrame {
Byte,
HalfWord,
}
#[cfg(feature = "chip-ws63")]
impl DmaFrame {
pub const fn width(self) -> TransferWidth {
match self {
DmaFrame::Byte => TransferWidth::Width8,
DmaFrame::HalfWord => TransferWidth::Width16,
}
}
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeriKind {
Spi,
Uart,
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
#[derive(Debug, Clone, Copy)]
#[cfg_attr(not(target_arch = "riscv32"), allow(dead_code))]
pub struct PeriDmaCtl {
base: usize,
kind: PeriKind,
dir: DmaDirection,
}
#[cfg(feature = "chip-ws63")]
impl PeriDmaCtl {
#[instability::unstable]
pub const fn new(base: usize, kind: PeriKind, dir: DmaDirection) -> Self {
Self { base, kind, dir }
}
#[cfg(target_arch = "riscv32")]
fn disable(&mut self) {
match self.kind {
PeriKind::Spi => {
if let Some(r) = spi_regs_from_base(self.base) {
match self.dir {
DmaDirection::Tx => r.spi_dcr().modify(|_, w| w.tdmae().clear_bit()),
DmaDirection::Rx => r.spi_dcr().modify(|_, w| w.rdmae().clear_bit()),
};
}
}
PeriKind::Uart => {}
}
}
#[cfg(not(target_arch = "riscv32"))]
fn disable(&mut self) {}
}
#[cfg(all(feature = "chip-ws63", target_arch = "riscv32"))]
fn spi_regs_from_base(base: usize) -> Option<&'static crate::soc::pac::spi0::RegisterBlock> {
if base == crate::peripherals::Spi0::ptr() as usize {
Some(unsafe { &*crate::peripherals::Spi0::ptr() })
} else if base == crate::peripherals::Spi1::ptr() as usize {
Some(unsafe { &*crate::peripherals::Spi1::ptr() })
} else {
None
}
}
pub struct DmaChannel {
logical: u8,
}
impl DmaChannel {
pub const fn logical(&self) -> u8 {
self.logical
}
}
impl Drop for DmaChannel {
fn drop(&mut self) {
DMA0_CHANNELS_CLAIMED.fetch_and(!(1 << self.logical), portable_atomic::Ordering::AcqRel);
}
}
pub struct DmaChannels {
pub ch0: DmaChannel,
pub ch1: DmaChannel,
pub ch2: DmaChannel,
pub ch3: DmaChannel,
}
static DMA0_CHANNELS_CLAIMED: portable_atomic::AtomicU8 = portable_atomic::AtomicU8::new(0);
impl DmaDriver<'_, Dma0> {
pub fn split_channels(&self) -> Option<DmaChannels> {
match DMA0_CHANNELS_CLAIMED.compare_exchange(
0,
0b1111,
portable_atomic::Ordering::AcqRel,
portable_atomic::Ordering::Acquire,
) {
Ok(_) => Some(DmaChannels {
ch0: DmaChannel { logical: 0 },
ch1: DmaChannel { logical: 1 },
ch2: DmaChannel { logical: 2 },
ch3: DmaChannel { logical: 3 },
}),
Err(_) => None,
}
}
pub fn enable_channel(&mut self, channel: &DmaChannel) {
self.enable_channel_raw(channel.logical);
}
pub fn disable_channel(&mut self, channel: &DmaChannel) {
self.disable_channel_raw(channel.logical);
}
pub fn channel_enabled(&self, channel: &DmaChannel) -> bool {
self.channel_enabled_raw(channel.logical)
}
pub fn channel_active(&self, channel: &DmaChannel) -> bool {
self.channel_active_raw(channel.logical)
}
pub fn halt_channel(&mut self, channel: &DmaChannel) {
self.halt_channel_raw(channel.logical);
}
pub fn resume_channel(&mut self, channel: &DmaChannel) {
self.resume_channel_raw(channel.logical);
}
pub fn burst_request(&mut self, channel: &DmaChannel) {
self.burst_request_raw(channel.logical);
}
pub fn single_request(&mut self, channel: &DmaChannel) {
self.single_request_raw(channel.logical);
}
pub fn clear_transfer_interrupt(&mut self, channel: &DmaChannel) {
self.clear_transfer_interrupt_raw(channel.logical);
}
pub fn clear_error_interrupt(&mut self, channel: &DmaChannel) {
self.clear_error_interrupt_raw(channel.logical);
}
}
#[cfg(feature = "chip-ws63")]
impl<'d> DmaDriver<'d, Dma0> {
#[instability::unstable]
pub fn start_mem_to_peripheral<SRC>(
mut self,
channel: DmaChannel,
src: SRC,
peri_data_addr: usize,
peri: DmaPeripheral,
frame: DmaFrame,
peri_dis: PeriDmaCtl,
) -> Result<PeripheralTransfer<'d, SRC>, DmaError>
where
SRC: ReadBuffer<Word = u8>,
{
let ch = channel.logical;
let (src_ptr, beats) = unsafe { src.read_buffer() };
if beats > 0xFFF {
drop(channel);
return Err(DmaError::TransferTooLarge);
}
let bytes = beats;
unsafe {
crate::cache::clean_range(src_ptr as usize, bytes);
}
let size = DmaTransferSize::from_beats(beats).expect("beats checked above");
let cfg = DmaChannelConfig::default().mem_to_peripheral(peri).with_width(frame).with_transfer_int(true);
self.configure_channel_raw(ch, src_ptr as u32, peri_data_addr as u32, size, &cfg);
Ok(PeripheralTransfer {
driver: self,
channel,
buf: src,
dir: DmaDirection::Tx,
mem_addr: src_ptr as usize,
bytes,
peri_dis,
})
}
#[instability::unstable]
pub fn start_peripheral_to_mem<DST>(
mut self,
channel: DmaChannel,
mut dst: DST,
peri_data_addr: usize,
peri: DmaPeripheral,
frame: DmaFrame,
peri_dis: PeriDmaCtl,
) -> Result<PeripheralTransfer<'d, DST>, DmaError>
where
DST: WriteBuffer<Word = u8>,
{
let ch = channel.logical;
let (dst_ptr, beats) = unsafe { dst.write_buffer() };
if beats > 0xFFF {
drop(channel);
return Err(DmaError::TransferTooLarge);
}
let bytes = beats;
let size = DmaTransferSize::from_beats(beats).expect("beats checked above");
let cfg = DmaChannelConfig::default().peripheral_to_mem(peri).with_width(frame).with_transfer_int(true);
self.configure_channel_raw(ch, peri_data_addr as u32, dst_ptr as u32, size, &cfg);
Ok(PeripheralTransfer {
driver: self,
channel,
buf: dst,
dir: DmaDirection::Rx,
mem_addr: dst_ptr as usize,
bytes,
peri_dis,
})
}
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
pub struct PeripheralTransfer<'d, BUF> {
driver: DmaDriver<'d, Dma0>,
channel: DmaChannel,
buf: BUF,
dir: DmaDirection,
mem_addr: usize,
bytes: usize,
peri_dis: PeriDmaCtl,
}
#[cfg(feature = "chip-ws63")]
impl<'d, BUF> PeripheralTransfer<'d, BUF> {
#[instability::unstable]
pub fn is_done(&self) -> bool {
!self.driver.channel_enabled_raw(self.channel.logical)
}
#[instability::unstable]
pub fn wait(self) -> Result<(DmaDriver<'d, Dma0>, DmaChannel, BUF), DmaError> {
let mut this = ManuallyDrop::new(self);
let ch = this.channel.logical;
let mut n = DMA_WAIT_LOOPS;
while this.driver.channel_enabled_raw(ch) {
n -= 1;
if n == 0 {
break;
}
core::hint::spin_loop();
}
if this.driver.channel_enabled_raw(ch) {
this.peri_dis.disable();
quiesce_channel(&mut this.driver, ch);
if this.dir == DmaDirection::Rx {
unsafe {
crate::cache::invalidate_range(this.mem_addr, this.bytes);
}
}
drop(ManuallyDrop::into_inner(this));
return Err(DmaError::Timeout);
}
if this.dir == DmaDirection::Rx {
unsafe {
crate::cache::invalidate_range(this.mem_addr, this.bytes);
}
}
unsafe {
let driver = core::ptr::read(&this.driver);
let channel = core::ptr::read(&this.channel);
let buf = core::ptr::read(&this.buf);
Ok((driver, channel, buf))
}
}
}
#[cfg(feature = "chip-ws63")]
impl<BUF> Drop for PeripheralTransfer<'_, BUF> {
fn drop(&mut self) {
cancel_then_quiesce(&mut self.peri_dis, &mut self.driver, self.channel.logical);
}
}
#[cfg(feature = "chip-ws63")]
impl DmaChannelConfig {
#[instability::unstable]
pub fn with_width(mut self, frame: DmaFrame) -> Self {
let w = frame.width();
self.src_width = w;
self.dst_width = w;
self
}
#[instability::unstable]
pub fn with_transfer_int(mut self, on: bool) -> Self {
self.transfer_int = on;
self
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::*;
#[test]
fn test_dma_direction_tx_rx_distinct() {
assert_ne!(DmaDirection::Tx as u8, DmaDirection::Rx as u8);
}
#[test]
fn test_dma_peripheral_spi_handshaking_ids() {
assert_eq!(DmaPeripheral::Spi0Tx.request_id(), 7);
assert_eq!(DmaPeripheral::Spi0Rx.request_id(), 8);
assert_eq!(DmaPeripheral::Spi1Tx.request_id(), 13);
assert_eq!(DmaPeripheral::Spi1Rx.request_id(), 14);
}
#[test]
fn test_dma_peripheral_uart_handshaking_ids() {
assert_eq!(DmaPeripheral::Uart0Tx.request_id(), 1);
assert_eq!(DmaPeripheral::Uart0Rx.request_id(), 2);
assert_eq!(DmaPeripheral::Uart1Tx.request_id(), 3);
assert_eq!(DmaPeripheral::Uart1Rx.request_id(), 4);
assert_eq!(DmaPeripheral::Uart2Tx.request_id(), 5);
assert_eq!(DmaPeripheral::Uart2Rx.request_id(), 6);
}
#[test]
fn test_dma_peripheral_i2s_handshaking_ids() {
assert_eq!(DmaPeripheral::I2sTx.request_id(), 11);
assert_eq!(DmaPeripheral::I2sRx.request_id(), 12);
}
#[test]
fn test_dma_peripheral_ids_fit_4bit_field() {
for p in [
DmaPeripheral::Uart0Tx,
DmaPeripheral::Uart2Rx,
DmaPeripheral::Spi0Tx,
DmaPeripheral::Spi1Rx,
DmaPeripheral::I2sTx,
DmaPeripheral::I2sRx,
] {
assert!(p.request_id() <= 0x0F, "{:?} id {} > 4 bits", p, p.request_id());
}
}
#[test]
fn test_dma_channel_config_peripheral_wiring() {
let tx = DmaChannelConfig::default().mem_to_peripheral(DmaPeripheral::Spi0Tx);
assert_eq!(tx.flow_control, FlowControl::MemToPeripheral);
assert_eq!(tx.dst_peripheral, 7);
assert!(!tx.dst_inc);
let rx = DmaChannelConfig::default().peripheral_to_mem(DmaPeripheral::Uart1Rx);
assert_eq!(rx.flow_control, FlowControl::PeripheralToMem);
assert_eq!(rx.src_peripheral, 4);
assert!(!rx.src_inc);
}
#[test]
fn test_channel_base_consts() {
assert_eq!(Dma0::CHANNEL_BASE, 0);
assert_eq!(Sdma0::CHANNEL_BASE, 8);
}
#[test]
fn test_mdma_logical_to_physical() {
for ch in 0u8..4 {
assert_eq!(physical_channel_index(Dma0::CHANNEL_BASE, ch), ch as usize);
}
}
#[test]
fn test_sdma_logical_to_physical() {
assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 8), 0);
assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 9), 1);
assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 10), 2);
assert_eq!(physical_channel_index(Sdma0::CHANNEL_BASE, 11), 3);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_mdma_channel_4_panics() {
physical_channel_index(Dma0::CHANNEL_BASE, 4);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_sdma_channel_7_panics() {
physical_channel_index(Sdma0::CHANNEL_BASE, 7);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_sdma_channel_12_panics() {
physical_channel_index(Sdma0::CHANNEL_BASE, 12);
}
#[cfg(feature = "chip-ws63")]
#[test]
fn test_peripheral_config_tx_full() {
let cfg = DmaChannelConfig::default()
.mem_to_peripheral(DmaPeripheral::Spi0Tx)
.with_width(DmaFrame::Byte)
.with_transfer_int(true);
assert_eq!(cfg.flow_control, FlowControl::MemToPeripheral);
assert_eq!(cfg.dst_peripheral, 7);
assert!(!cfg.dst_inc);
assert!(cfg.src_inc, "memory side still increments");
assert_eq!(cfg.src_width, TransferWidth::Width8);
assert_eq!(cfg.dst_width, TransferWidth::Width8);
assert!(cfg.transfer_int);
}
#[cfg(feature = "chip-ws63")]
#[test]
fn test_peripheral_config_rx_full() {
let cfg = DmaChannelConfig::default().peripheral_to_mem(DmaPeripheral::Spi0Rx).with_width(DmaFrame::HalfWord);
assert_eq!(cfg.flow_control, FlowControl::PeripheralToMem);
assert_eq!(cfg.src_peripheral, 8);
assert!(!cfg.src_inc);
assert!(cfg.dst_inc);
assert_eq!(cfg.src_width, TransferWidth::Width16);
assert_eq!(cfg.dst_width, TransferWidth::Width16);
}
#[cfg(feature = "chip-ws63")]
#[test]
fn test_dma_frame_width_mapping() {
assert_eq!(DmaFrame::Byte.width(), TransferWidth::Width8);
assert_eq!(DmaFrame::HalfWord.width(), TransferWidth::Width16);
}
#[cfg(feature = "chip-ws63")]
#[test]
fn test_start_mem_to_peripheral_rejects_too_large() {
let dma: DmaDriver<'static, Dma0> = DmaDriver::new_for_test();
let ch = DmaChannel { logical: 0 };
static BUF: [u8; 5000] = [0; 5000]; let peri_dis = PeriDmaCtl::new(0x4402_0000, PeriKind::Spi, DmaDirection::Tx);
let r = dma.start_mem_to_peripheral(ch, &BUF[..], 0x4402_0060, DmaPeripheral::Spi0Tx, DmaFrame::Byte, peri_dis);
assert!(matches!(r, Err(DmaError::TransferTooLarge)), "expected TransferTooLarge");
}
#[cfg(feature = "chip-ws63")]
#[test]
fn test_split_channels_once() {
use std::sync::Mutex;
static LOCK: Mutex<()> = Mutex::new(());
let _g = LOCK.lock().unwrap();
DMA0_CHANNELS_CLAIMED.store(0, portable_atomic::Ordering::Release);
let dma: DmaDriver<'static, Dma0> = DmaDriver::new_for_test();
let chs = dma.split_channels();
assert!(chs.is_some(), "first split must succeed");
assert!(dma.split_channels().is_none(), "second split must fail");
drop(chs); assert!(dma.split_channels().is_some(), "split after drop must succeed");
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::{BurstSize, FlowControl, TransferWidth, physical_channel_index};
use proptest::prelude::*;
fn control_word(
transfer_size: u16,
src_burst: BurstSize,
dst_burst: BurstSize,
src_width: TransferWidth,
dst_width: TransferWidth,
src_inc: bool,
dst_inc: bool,
transfer_int: bool,
) -> u32 {
let mut control: u32 = 0;
control |= (transfer_size as u32) & 0xFFF; control |= ((src_burst as u32) & 0x07) << 12; control |= ((dst_burst as u32) & 0x07) << 15; control |= ((src_width as u32) & 0x07) << 18; control |= ((dst_width as u32) & 0x07) << 21; if src_inc {
control |= 1 << 26;
}
if dst_inc {
control |= 1 << 27;
}
if transfer_int {
control |= 1 << 31;
}
control
}
fn ch_cfg_word(
src_peripheral: u8,
dst_peripheral: u8,
flow_control: FlowControl,
error_int: bool,
transfer_int: bool,
bus_lock: bool,
) -> u32 {
let mut ch_cfg: u32 = 0;
ch_cfg |= 0x01; ch_cfg |= ((src_peripheral as u32) & 0x0F) << 1; ch_cfg |= ((dst_peripheral as u32) & 0x0F) << 5; ch_cfg |= ((flow_control as u32) & 0x07) << 9; if error_int {
ch_cfg |= 1 << 12; }
if transfer_int {
ch_cfg |= 1 << 13; }
if bus_lock {
ch_cfg |= 1 << 14; }
ch_cfg
}
fn burst(v: u8) -> BurstSize {
match v & 0x07 {
0 => BurstSize::Beats1,
1 => BurstSize::Beats4,
2 => BurstSize::Beats8,
3 => BurstSize::Beats16,
4 => BurstSize::Beats32,
5 => BurstSize::Beats64,
6 => BurstSize::Beats128,
_ => BurstSize::Beats256,
}
}
fn width(v: u8) -> TransferWidth {
match v % 3 {
0 => TransferWidth::Width8,
1 => TransferWidth::Width16,
_ => TransferWidth::Width32,
}
}
fn flow(v: u8) -> FlowControl {
match v & 0x03 {
0 => FlowControl::MemToMem,
1 => FlowControl::MemToPeripheral,
2 => FlowControl::PeripheralToMem,
_ => FlowControl::PeripheralToPeripheral,
}
}
proptest! {
#[test]
fn physical_index_in_range(base in 0u8..=200, off in 0u8..4) {
let idx = physical_channel_index(base, base + off);
prop_assert_eq!(idx, off as usize);
prop_assert!(idx < 4);
}
#[test]
fn physical_index_monotonic(base in 0u8..=200) {
let mut prev = None;
for off in 0u8..4 {
let idx = physical_channel_index(base, base + off);
if let Some(p) = prev {
prop_assert!(idx > p);
}
prev = Some(idx);
}
}
#[test]
fn physical_index_below_base_panics(base in 1u8..=200, under in 1u8..=100) {
let under = under.min(base);
let ch = base - under;
let r = std::panic::catch_unwind(|| physical_channel_index(base, ch));
prop_assert!(r.is_err());
}
#[test]
fn physical_index_above_range_panics(base in 0u8..=200, over in 0u8..=50) {
let ch = (base as u16 + 4 + over as u16).min(255) as u8;
prop_assume!(ch >= base.saturating_add(4) || ch < base);
let r = std::panic::catch_unwind(|| physical_channel_index(base, ch));
prop_assert!(r.is_err());
}
#[test]
fn control_word_no_stray_bits(
ts in any::<u16>(),
sb in any::<u8>(), db in any::<u8>(),
sw in any::<u8>(), dw in any::<u8>(),
si in any::<bool>(), di in any::<bool>(), ti in any::<bool>(),
) {
let c = control_word(ts, burst(sb), burst(db), width(sw), width(dw), si, di, ti);
const DEFINED: u32 = 0x00FF_FFFF | (1 << 26) | (1 << 27) | (1 << 31);
prop_assert_eq!(c & !DEFINED, 0, "stray bits in control word {:#010x}", c);
}
#[test]
fn control_word_trans_size_field(ts in any::<u16>()) {
let c = control_word(ts, BurstSize::Beats1, BurstSize::Beats1,
TransferWidth::Width8, TransferWidth::Width8, false, false, false);
prop_assert_eq!(c & 0xFFF, (ts as u32) & 0xFFF);
prop_assert!((c & 0xFFF) <= 0xFFF);
}
#[test]
fn control_word_fields_round_trip(
sb in 0u8..8, db in 0u8..8, sw in 0u8..3, dw in 0u8..3,
) {
let (sbe, dbe, swe, dwe) = (burst(sb), burst(db), width(sw), width(dw));
let c = control_word(0, sbe, dbe, swe, dwe, false, false, false);
prop_assert_eq!((c >> 12) & 0x07, sbe as u32);
prop_assert_eq!((c >> 15) & 0x07, dbe as u32);
prop_assert_eq!((c >> 18) & 0x07, swe as u32);
prop_assert_eq!((c >> 21) & 0x07, dwe as u32);
}
#[test]
fn control_word_flag_bits(si in any::<bool>(), di in any::<bool>(), ti in any::<bool>()) {
let c = control_word(0, BurstSize::Beats1, BurstSize::Beats1,
TransferWidth::Width8, TransferWidth::Width8, si, di, ti);
prop_assert_eq!((c >> 26) & 1 == 1, si);
prop_assert_eq!((c >> 27) & 1 == 1, di);
prop_assert_eq!((c >> 31) & 1 == 1, ti);
}
#[test]
fn ch_cfg_word_no_stray_bits(
sp in any::<u8>(), dp in any::<u8>(), fc in any::<u8>(),
ei in any::<bool>(), ti in any::<bool>(), bl in any::<bool>(),
) {
let w = ch_cfg_word(sp, dp, flow(fc), ei, ti, bl);
prop_assert_eq!(w & 0x01, 1, "chn_en must always be set");
prop_assert_eq!(w & !0x7FFF, 0, "stray bits in ch_cfg {:#010x}", w);
}
#[test]
fn ch_cfg_word_peripheral_fields(sp in any::<u8>(), dp in any::<u8>()) {
let w = ch_cfg_word(sp, dp, FlowControl::MemToMem, false, false, false);
prop_assert_eq!((w >> 1) & 0x0F, (sp as u32) & 0x0F);
prop_assert_eq!((w >> 5) & 0x0F, (dp as u32) & 0x0F);
}
#[test]
fn ch_cfg_word_flow_field(fc in 0u8..4) {
let f = flow(fc);
let w = ch_cfg_word(0, 0, f, false, false, false);
prop_assert_eq!((w >> 9) & 0x07, f as u32);
}
#[test]
fn ch_cfg_word_flag_bits(ei in any::<bool>(), ti in any::<bool>(), bl in any::<bool>()) {
let w = ch_cfg_word(0, 0, FlowControl::MemToMem, ei, ti, bl);
prop_assert_eq!((w >> 12) & 1 == 1, ei);
prop_assert_eq!((w >> 13) & 1 == 1, ti);
prop_assert_eq!((w >> 14) & 1 == 1, bl);
}
}
}
#[cfg(all(feature = "chip-ws63", feature = "async", feature = "unstable"))]
mod asynch_impl {
use super::{Dma0, DmaDriver, DmaInstance};
use crate::asynch::IrqSignal;
use crate::interrupt::{self, Interrupt};
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
static DMA_SIGNAL: [IrqSignal; 4] = [IrqSignal::new(), IrqSignal::new(), IrqSignal::new(), IrqSignal::new()];
pub fn on_interrupt() {
let r = unsafe { &*Dma0::ptr() };
let done = (r.dmac_ori_int_st().read().bits() & 0xFF) as u8;
let mut clr = 0u32;
for ch in 0..4u8 {
if done & (1 << ch) != 0 {
DMA_SIGNAL[ch as usize].signal();
clr |= 1 << ch;
}
}
if clr != 0 {
unsafe { r.dmac_int_clr().write(|w| w.bits(clr)) };
}
interrupt::clear_pending(Interrupt::DMA_INT);
}
struct DmaDoneFuture {
ch: u8,
}
impl Future for DmaDoneFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let sig = &DMA_SIGNAL[(self.ch as usize).min(3)];
if sig.take_fired() {
Poll::Ready(())
} else {
sig.register(cx.waker());
Poll::Pending
}
}
}
impl DmaDriver<'_, Dma0> {
pub async fn wait_transfer_done(&mut self, channel: &super::DmaChannel) {
let channel = channel.logical;
let bit = 1u8 << channel; if self.raw_interrupt_status().0 & bit == 0 {
DMA_SIGNAL[(channel as usize).min(3)].reset();
unsafe { interrupt::enable(Interrupt::DMA_INT) };
if self.raw_interrupt_status().0 & bit == 0 {
DmaDoneFuture { ch: channel }.await;
}
}
self.clear_transfer_interrupt_raw(channel);
}
}
}
#[cfg(all(feature = "chip-ws63", feature = "async", feature = "unstable"))]
pub use asynch_impl::on_interrupt;