stm32/dma: refactor.
This commit is contained in:
@ -1,18 +1,31 @@
|
||||
#![macro_use]
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::Waker;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::{TransferOptions, Word, WordSize};
|
||||
use super::{Dir, Word, WordSize};
|
||||
use crate::_generated::BDMA_CHANNEL_COUNT;
|
||||
use crate::dma::Request;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac;
|
||||
use crate::pac::bdma::vals;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WordSize> for vals::Size {
|
||||
fn from(raw: WordSize) -> Self {
|
||||
match raw {
|
||||
@ -23,6 +36,15 @@ impl From<WordSize> for vals::Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dir> for vals::Dir {
|
||||
fn from(raw: Dir) -> Self {
|
||||
match raw {
|
||||
Dir::MemoryToPeripheral => Self::FROMMEMORY,
|
||||
Dir::PeripheralToMemory => Self::FROMPERIPHERAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT],
|
||||
}
|
||||
@ -55,228 +77,267 @@ foreach_dma_channel! {
|
||||
// BDMA1 in H7 doesn't use DMAMUX, which breaks
|
||||
};
|
||||
($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
|
||||
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
|
||||
|
||||
unsafe fn start_write<W: Word>(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMMEMORY,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
impl sealed::Channel for crate::peripherals::$channel_peri {
|
||||
fn regs(&self) -> pac::bdma::Dma {
|
||||
pac::$dma_peri
|
||||
}
|
||||
|
||||
unsafe fn start_write_repeated<W: Word>(&mut self, _request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMMEMORY,
|
||||
reg_addr as *const u32,
|
||||
repeated as *mut u32,
|
||||
count,
|
||||
false,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
)
|
||||
fn num(&self) -> usize {
|
||||
$channel_num
|
||||
}
|
||||
|
||||
unsafe fn start_read<W: Word>(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
_request,
|
||||
vals::Dir::FROMPERIPHERAL,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
fn index(&self) -> usize {
|
||||
$index
|
||||
}
|
||||
|
||||
unsafe fn start_double_buffered_read<W: super::Word>(
|
||||
&mut self,
|
||||
_request: Request,
|
||||
_reg_addr: *const W,
|
||||
_buffer0: *mut W,
|
||||
_buffer1: *mut W,
|
||||
_buffer_len: usize,
|
||||
_options: TransferOptions,
|
||||
) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer0<W: super::Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer1<W: super::Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool {
|
||||
panic!("Unsafe double buffered mode is unavailable on BDMA");
|
||||
}
|
||||
|
||||
fn request_stop(&mut self){
|
||||
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
fn remaining_transfers(&mut self) -> u16 {
|
||||
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
unsafe { low_level_api::set_waker($index, waker) }
|
||||
}
|
||||
|
||||
fn on_irq() {
|
||||
unsafe {
|
||||
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
|
||||
}
|
||||
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::dma::Channel for crate::peripherals::$channel_peri {}
|
||||
impl Channel for crate::peripherals::$channel_peri {}
|
||||
};
|
||||
}
|
||||
|
||||
mod low_level_api {
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) {
|
||||
let isr = dma.isr().read();
|
||||
let cr = dma.ch(channel_num).cr();
|
||||
|
||||
if isr.teif(channel_num) {
|
||||
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
if isr.tcif(channel_num) && cr.read().tcie() {
|
||||
cr.write(|_| ()); // Disable channel interrupts with the default value.
|
||||
STATE.ch_wakers[index].wake();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(bdma_v2, dmamux))]
|
||||
pub type Request = u8;
|
||||
#[cfg(not(any(bdma_v2, dmamux)))]
|
||||
pub type Request = ();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
|
||||
#[cfg(not(dmamux))]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub unsafe fn start_transfer(
|
||||
dma: pac::bdma::Dma,
|
||||
channel_number: u8,
|
||||
#[cfg(any(bdma_v2, dmamux))] request: Request,
|
||||
dir: vals::Dir,
|
||||
pub trait Channel {
|
||||
fn regs(&self) -> pac::bdma::Dma;
|
||||
fn num(&self) -> usize;
|
||||
fn index(&self) -> usize;
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Transfer<'a, C: Channel> {
|
||||
channel: PeripheralRef<'a, C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Transfer<'a, C> {
|
||||
pub unsafe fn new_read<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_read_raw(channel, request, peri_addr, buf, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_read_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::PeripheralToMemory,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_write_raw(channel, request, buf, peri_addr, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_repeated<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
repeated: &'a W,
|
||||
count: usize,
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
repeated as *const W as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn new_inner(
|
||||
channel: PeripheralRef<'a, C>,
|
||||
_request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *const u32,
|
||||
mem_addr: *mut u32,
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: vals::Size,
|
||||
options: TransferOptions,
|
||||
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
|
||||
#[cfg(dmamux)] dmamux_ch_num: u8,
|
||||
) {
|
||||
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(
|
||||
options.flow_ctrl == crate::dma::FlowControl::Dma,
|
||||
"Peripheral flow control not supported"
|
||||
);
|
||||
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
|
||||
reset_status(dma, channel_number);
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
|
||||
|
||||
#[cfg(bdma_v2)]
|
||||
critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request)));
|
||||
data_size: WordSize,
|
||||
_options: TransferOptions,
|
||||
) -> Self {
|
||||
let ch = channel.regs().ch(channel.num());
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
#[cfg(bdma_v2)]
|
||||
critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request)));
|
||||
|
||||
let mut this = Self { channel };
|
||||
this.clear_irqs();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, _request);
|
||||
|
||||
ch.par().write_value(peri_addr as u32);
|
||||
ch.mar().write_value(mem_addr as u32);
|
||||
ch.ndtr().write(|w| w.set_ndt(mem_len as u16));
|
||||
ch.cr().write(|w| {
|
||||
w.set_psize(data_size);
|
||||
w.set_msize(data_size);
|
||||
w.set_psize(data_size.into());
|
||||
w.set_msize(data_size.into());
|
||||
if incr_mem {
|
||||
w.set_minc(vals::Inc::ENABLED);
|
||||
} else {
|
||||
w.set_minc(vals::Inc::DISABLED);
|
||||
}
|
||||
w.set_dir(dir);
|
||||
w.set_dir(dir.into());
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
w.set_en(true);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) {
|
||||
reset_status(dma, channel_number);
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
|
||||
// Disable the channel and interrupts with the default value.
|
||||
ch.cr().write(|_| ());
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
fn clear_irqs(&mut self) {
|
||||
unsafe {
|
||||
self.channel.regs().ifcr().write(|w| {
|
||||
w.set_tcif(self.channel.num(), true);
|
||||
w.set_teif(self.channel.num(), true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool {
|
||||
let ch = dma.ch(ch as _);
|
||||
ch.cr().read().en()
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.cr().read() }.en()
|
||||
}
|
||||
|
||||
/// Gets the total remaining transfers for the channel
|
||||
/// Note: this will be zero for transfers that completed without cancellation.
|
||||
pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(ch as _);
|
||||
// read the remaining transfer count. If this is zero, the transfer completed fully.
|
||||
ch.ndtr().read().ndt() as u16
|
||||
pub fn get_remaining_transfers(&self) -> u16 {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.ndtr().read() }.ndt()
|
||||
}
|
||||
|
||||
/// Sets the waker for the specified DMA channel
|
||||
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
|
||||
STATE.ch_wakers[state_number].register(waker);
|
||||
pub fn blocking_wait(mut self) {
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) {
|
||||
dma.ifcr().write(|w| {
|
||||
w.set_tcif(channel_number as _, true);
|
||||
w.set_teif(channel_number as _, true);
|
||||
});
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) {
|
||||
let channel_num = channel_num as usize;
|
||||
let index = index as usize;
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
STATE.ch_wakers[self.channel.index()].register(cx.waker());
|
||||
|
||||
let isr = dma.isr().read();
|
||||
let cr = dma.ch(channel_num).cr();
|
||||
|
||||
if isr.teif(channel_num) {
|
||||
panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
if isr.tcif(channel_num) && cr.read().tcie() {
|
||||
cr.write(|_| ()); // Disable channel interrupts with the default value.
|
||||
STATE.ch_wakers[index].wake();
|
||||
if self.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,44 @@
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::Waker;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use pac::dma::regs;
|
||||
|
||||
use super::{Burst, FifoThreshold, FlowControl, Request, TransferOptions, Word, WordSize};
|
||||
use super::{Dir, Word, WordSize};
|
||||
use crate::_generated::DMA_CHANNEL_COUNT;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac::dma::{regs, vals};
|
||||
use crate::pac::dma::vals;
|
||||
use crate::{interrupt, pac};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {
|
||||
/// Peripheral burst transfer configuration
|
||||
pub pburst: Burst,
|
||||
/// Memory burst transfer configuration
|
||||
pub mburst: Burst,
|
||||
/// Flow control configuration
|
||||
pub flow_ctrl: FlowControl,
|
||||
/// FIFO threshold for DMA FIFO mode. If none, direct mode is used.
|
||||
pub fifo_threshold: Option<FifoThreshold>,
|
||||
}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pburst: Burst::Single,
|
||||
mburst: Burst::Single,
|
||||
flow_ctrl: FlowControl::Dma,
|
||||
fifo_threshold: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WordSize> for vals::Size {
|
||||
fn from(raw: WordSize) -> Self {
|
||||
match raw {
|
||||
@ -20,6 +49,28 @@ impl From<WordSize> for vals::Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dir> for vals::Dir {
|
||||
fn from(raw: Dir) -> Self {
|
||||
match raw {
|
||||
Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL,
|
||||
Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Burst {
|
||||
/// Single transfer
|
||||
Single,
|
||||
/// Incremental burst of 4 beats
|
||||
Incr4,
|
||||
/// Incremental burst of 8 beats
|
||||
Incr8,
|
||||
/// Incremental burst of 16 beats
|
||||
Incr16,
|
||||
}
|
||||
|
||||
impl From<Burst> for vals::Burst {
|
||||
fn from(burst: Burst) -> Self {
|
||||
match burst {
|
||||
@ -31,6 +82,15 @@ impl From<Burst> for vals::Burst {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FlowControl {
|
||||
/// Flow control by DMA
|
||||
Dma,
|
||||
/// Flow control by peripheral
|
||||
Peripheral,
|
||||
}
|
||||
|
||||
impl From<FlowControl> for vals::Pfctrl {
|
||||
fn from(flow: FlowControl) -> Self {
|
||||
match flow {
|
||||
@ -40,6 +100,19 @@ impl From<FlowControl> for vals::Pfctrl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FifoThreshold {
|
||||
/// 1/4 full FIFO
|
||||
Quarter,
|
||||
/// 1/2 full FIFO
|
||||
Half,
|
||||
/// 3/4 full FIFO
|
||||
ThreeQuarters,
|
||||
/// Full FIFO
|
||||
Full,
|
||||
}
|
||||
|
||||
impl From<FifoThreshold> for vals::Fth {
|
||||
fn from(value: FifoThreshold) -> Self {
|
||||
match value {
|
||||
@ -51,27 +124,15 @@ impl From<FifoThreshold> for vals::Fth {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelState {
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
channels: [ChannelState; DMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const CH: ChannelState = ChannelState::new();
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
channels: [CH; DMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AW; DMA_CHANNEL_COUNT],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,158 +153,183 @@ pub(crate) unsafe fn init(irq_priority: Priority) {
|
||||
|
||||
foreach_dma_channel! {
|
||||
($channel_peri:ident, $dma_peri:ident, dma, $channel_num:expr, $index:expr, $dmamux:tt) => {
|
||||
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
|
||||
unsafe fn start_write<W: Word>(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
vals::Dir::MEMORYTOPERIPHERAL,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
)
|
||||
impl sealed::Channel for crate::peripherals::$channel_peri {
|
||||
fn regs(&self) -> pac::dma::Dma {
|
||||
pac::$dma_peri
|
||||
}
|
||||
|
||||
unsafe fn start_write_repeated<W: Word>(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
vals::Dir::MEMORYTOPERIPHERAL,
|
||||
reg_addr as *const u32,
|
||||
repeated as *mut u32,
|
||||
count,
|
||||
false,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
)
|
||||
fn num(&self) -> usize {
|
||||
$channel_num
|
||||
}
|
||||
|
||||
unsafe fn start_read<W: Word>(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
vals::Dir::PERIPHERALTOMEMORY,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
fn index(&self) -> usize {
|
||||
$index
|
||||
}
|
||||
|
||||
unsafe fn start_double_buffered_read<W: Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
reg_addr: *const W,
|
||||
buffer0: *mut W,
|
||||
buffer1: *mut W,
|
||||
buffer_len: usize,
|
||||
options: TransferOptions,
|
||||
) {
|
||||
low_level_api::start_dbm_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
vals::Dir::PERIPHERALTOMEMORY,
|
||||
reg_addr as *const u32,
|
||||
buffer0 as *mut u32,
|
||||
buffer1 as *mut u32,
|
||||
buffer_len,
|
||||
true,
|
||||
vals::Size::from(W::bits()),
|
||||
options,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_REGS,
|
||||
#[cfg(dmamux)]
|
||||
<Self as super::dmamux::sealed::MuxChannel>::DMAMUX_CH_NUM,
|
||||
);
|
||||
}
|
||||
|
||||
unsafe fn set_buffer0<W: Word>(&mut self, buffer: *mut W) {
|
||||
low_level_api::set_dbm_buffer0(pac::$dma_peri, $channel_num, buffer as *mut u32);
|
||||
}
|
||||
|
||||
unsafe fn set_buffer1<W: Word>(&mut self, buffer: *mut W) {
|
||||
low_level_api::set_dbm_buffer1(pac::$dma_peri, $channel_num, buffer as *mut u32);
|
||||
}
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool {
|
||||
low_level_api::is_buffer0_accessible(pac::$dma_peri, $channel_num)
|
||||
}
|
||||
|
||||
fn request_stop(&mut self) {
|
||||
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn remaining_transfers(&mut self) -> u16 {
|
||||
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
unsafe {low_level_api::set_waker($index, waker )}
|
||||
}
|
||||
|
||||
fn on_irq() {
|
||||
unsafe {
|
||||
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
|
||||
}
|
||||
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
|
||||
}
|
||||
}
|
||||
impl crate::dma::Channel for crate::peripherals::$channel_peri { }
|
||||
|
||||
impl Channel for crate::peripherals::$channel_peri {}
|
||||
};
|
||||
}
|
||||
|
||||
mod low_level_api {
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index: usize) {
|
||||
let cr = dma.st(channel_num).cr();
|
||||
let isr = dma.isr(channel_num / 4).read();
|
||||
|
||||
if isr.teif(channel_num % 4) {
|
||||
panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
|
||||
if isr.tcif(channel_num % 4) && cr.read().tcie() {
|
||||
/* acknowledge transfer complete interrupt */
|
||||
dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true));
|
||||
STATE.ch_wakers[index].wake();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(dma_v2, dmamux))]
|
||||
pub type Request = u8;
|
||||
#[cfg(not(any(dma_v2, dmamux)))]
|
||||
pub type Request = ();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
|
||||
#[cfg(not(dmamux))]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub unsafe fn start_transfer(
|
||||
dma: pac::dma::Dma,
|
||||
channel_number: u8,
|
||||
pub trait Channel {
|
||||
fn regs(&self) -> pac::dma::Dma;
|
||||
fn num(&self) -> usize;
|
||||
fn index(&self) -> usize;
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Transfer<'a, C: Channel> {
|
||||
channel: PeripheralRef<'a, C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Transfer<'a, C> {
|
||||
pub unsafe fn new_read<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
dir: vals::Dir,
|
||||
peri_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_read_raw(channel, request, peri_addr, buf, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_read_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::PeripheralToMemory,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_write_raw(channel, request, buf, peri_addr, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_repeated<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
repeated: &'a W,
|
||||
count: usize,
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
repeated as *const W as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn new_inner(
|
||||
channel: PeripheralRef<'a, C>,
|
||||
_request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *const u32,
|
||||
mem_addr: *mut u32,
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: vals::Size,
|
||||
data_size: WordSize,
|
||||
options: TransferOptions,
|
||||
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
|
||||
#[cfg(dmamux)] dmamux_ch_num: u8,
|
||||
) {
|
||||
#[cfg(dmamux)]
|
||||
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
|
||||
) -> Self {
|
||||
let ch = channel.regs().st(channel.num());
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
reset_status(dma, channel_number);
|
||||
let mut this = Self { channel };
|
||||
this.clear_irqs();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, _request);
|
||||
|
||||
let ch = dma.st(channel_number as _);
|
||||
ch.par().write_value(peri_addr as u32);
|
||||
ch.m0ar().write_value(mem_addr as u32);
|
||||
ch.ndtr().write_value(regs::Ndtr(mem_len as _));
|
||||
@ -258,15 +344,14 @@ mod low_level_api {
|
||||
}
|
||||
});
|
||||
ch.cr().write(|w| {
|
||||
w.set_dir(dir);
|
||||
w.set_msize(data_size);
|
||||
w.set_psize(data_size);
|
||||
w.set_dir(dir.into());
|
||||
w.set_msize(data_size.into());
|
||||
w.set_psize(data_size.into());
|
||||
w.set_pl(vals::Pl::VERYHIGH);
|
||||
if incr_mem {
|
||||
w.set_minc(vals::Inc::INCREMENTED);
|
||||
} else {
|
||||
w.set_minc(vals::Inc::FIXED);
|
||||
}
|
||||
w.set_minc(match incr_mem {
|
||||
true => vals::Inc::INCREMENTED,
|
||||
false => vals::Inc::FIXED,
|
||||
});
|
||||
w.set_pinc(vals::Inc::FIXED);
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
@ -274,7 +359,7 @@ mod low_level_api {
|
||||
w.set_trbuff(true);
|
||||
|
||||
#[cfg(dma_v2)]
|
||||
w.set_chsel(request);
|
||||
w.set_chsel(_request);
|
||||
|
||||
w.set_pburst(options.pburst.into());
|
||||
w.set_mburst(options.mburst.into());
|
||||
@ -282,159 +367,76 @@ mod low_level_api {
|
||||
|
||||
w.set_en(true);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
pub unsafe fn start_dbm_transfer(
|
||||
dma: pac::dma::Dma,
|
||||
channel_number: u8,
|
||||
request: Request,
|
||||
dir: vals::Dir,
|
||||
peri_addr: *const u32,
|
||||
mem0_addr: *mut u32,
|
||||
mem1_addr: *mut u32,
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: vals::Size,
|
||||
options: TransferOptions,
|
||||
#[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux,
|
||||
#[cfg(dmamux)] dmamux_ch_num: u8,
|
||||
) {
|
||||
#[cfg(dmamux)]
|
||||
super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request);
|
||||
fn clear_irqs(&mut self) {
|
||||
let isrn = self.channel.num() / 4;
|
||||
let isrbit = self.channel.num() % 4;
|
||||
|
||||
trace!(
|
||||
"Starting DBM transfer with 0: 0x{:x}, 1: 0x{:x}, len: 0x{:x}",
|
||||
mem0_addr as u32,
|
||||
mem1_addr as u32,
|
||||
mem_len
|
||||
);
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
reset_status(dma, channel_number);
|
||||
|
||||
let ch = dma.st(channel_number as _);
|
||||
ch.par().write_value(peri_addr as u32);
|
||||
ch.m0ar().write_value(mem0_addr as u32);
|
||||
// configures the second buffer for DBM
|
||||
ch.m1ar().write_value(mem1_addr as u32);
|
||||
ch.ndtr().write_value(regs::Ndtr(mem_len as _));
|
||||
ch.cr().write(|w| {
|
||||
w.set_dir(dir);
|
||||
w.set_msize(data_size);
|
||||
w.set_psize(data_size);
|
||||
w.set_pl(vals::Pl::VERYHIGH);
|
||||
if incr_mem {
|
||||
w.set_minc(vals::Inc::INCREMENTED);
|
||||
} else {
|
||||
w.set_minc(vals::Inc::FIXED);
|
||||
}
|
||||
w.set_pinc(vals::Inc::FIXED);
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
|
||||
#[cfg(dma_v1)]
|
||||
w.set_trbuff(true);
|
||||
|
||||
#[cfg(dma_v2)]
|
||||
w.set_chsel(request);
|
||||
|
||||
// enable double buffered mode
|
||||
w.set_dbm(vals::Dbm::ENABLED);
|
||||
|
||||
w.set_pburst(options.pburst.into());
|
||||
w.set_mburst(options.mburst.into());
|
||||
w.set_pfctrl(options.flow_ctrl.into());
|
||||
|
||||
w.set_en(true);
|
||||
});
|
||||
unsafe {
|
||||
self.channel.regs().ifcr(isrn).write(|w| {
|
||||
w.set_tcif(isrbit, true);
|
||||
w.set_teif(isrbit, true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_dbm_buffer0(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(channel_number as _);
|
||||
// change M0AR to the new address
|
||||
ch.m0ar().write_value(mem_addr as _);
|
||||
}
|
||||
|
||||
pub unsafe fn set_dbm_buffer1(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(channel_number as _);
|
||||
// change M1AR to the new address
|
||||
ch.m1ar().write_value(mem_addr as _);
|
||||
}
|
||||
|
||||
pub unsafe fn is_buffer0_accessible(dma: pac::dma::Dma, channel_number: u8) -> bool {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(channel_number as _);
|
||||
// check the current target register value
|
||||
ch.cr().read().ct() == vals::Ct::MEMORY1
|
||||
}
|
||||
|
||||
/// Stops the DMA channel.
|
||||
pub unsafe fn request_stop(dma: pac::dma::Dma, channel_number: u8) {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(channel_number as _);
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().st(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
ch.cr().write(|w| {
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
});
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_teie(true);
|
||||
w.set_tcie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the running status of the channel
|
||||
pub unsafe fn is_running(dma: pac::dma::Dma, ch: u8) -> bool {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(ch as _);
|
||||
// Get whether it's enabled (running)
|
||||
ch.cr().read().en()
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().st(self.channel.num());
|
||||
unsafe { ch.cr().read() }.en()
|
||||
}
|
||||
|
||||
/// Gets the total remaining transfers for the channel
|
||||
/// Note: this will be zero for transfers that completed without cancellation.
|
||||
pub unsafe fn get_remaining_transfers(dma: pac::dma::Dma, ch: u8) -> u16 {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.st(ch as _);
|
||||
// read the remaining transfer count. If this is zero, the transfer completed fully.
|
||||
ch.ndtr().read().ndt()
|
||||
pub fn get_remaining_transfers(&self) -> u16 {
|
||||
let ch = self.channel.regs().st(self.channel.num());
|
||||
unsafe { ch.ndtr().read() }.ndt()
|
||||
}
|
||||
|
||||
/// Sets the waker for the specified DMA channel
|
||||
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
|
||||
STATE.channels[state_number].waker.register(waker);
|
||||
pub fn blocking_wait(mut self) {
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn reset_status(dma: pac::dma::Dma, channel_number: u8) {
|
||||
let isrn = channel_number as usize / 4;
|
||||
let isrbit = channel_number as usize % 4;
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
dma.ifcr(isrn).write(|w| {
|
||||
w.set_tcif(isrbit, true);
|
||||
w.set_teif(isrbit, true);
|
||||
});
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: u8, state_index: u8) {
|
||||
let channel_num = channel_num as usize;
|
||||
let state_index = state_index as usize;
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
STATE.ch_wakers[self.channel.index()].register(cx.waker());
|
||||
|
||||
let cr = dma.st(channel_num).cr();
|
||||
let isr = dma.isr(channel_num / 4).read();
|
||||
|
||||
if isr.teif(channel_num % 4) {
|
||||
panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num);
|
||||
}
|
||||
|
||||
if isr.tcif(channel_num % 4) && cr.read().tcie() {
|
||||
/* acknowledge transfer complete interrupt */
|
||||
dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true));
|
||||
STATE.channels[state_index].waker.wake();
|
||||
if self.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
use crate::{pac, peripherals};
|
||||
|
||||
pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_ch_num: u8, request: u8) {
|
||||
let ch_mux_regs = dmamux_regs.ccr(dmamux_ch_num as _);
|
||||
pub(crate) unsafe fn configure_dmamux<M: MuxChannel>(channel: &mut M, request: u8) {
|
||||
let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num());
|
||||
ch_mux_regs.write(|reg| {
|
||||
reg.set_nbreq(0);
|
||||
reg.set_dmareq_id(request);
|
||||
@ -14,11 +14,11 @@ pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_c
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
pub(crate) mod dmamux_sealed {
|
||||
use super::*;
|
||||
pub trait MuxChannel {
|
||||
const DMAMUX_CH_NUM: u8;
|
||||
const DMAMUX_REGS: pac::dmamux::Dmamux;
|
||||
fn mux_regs(&self) -> pac::dmamux::Dmamux;
|
||||
fn mux_num(&self) -> usize;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,15 +26,19 @@ pub struct DMAMUX1;
|
||||
#[cfg(stm32h7)]
|
||||
pub struct DMAMUX2;
|
||||
|
||||
pub trait MuxChannel: sealed::MuxChannel + super::Channel {
|
||||
pub trait MuxChannel: dmamux_sealed::MuxChannel {
|
||||
type Mux;
|
||||
}
|
||||
|
||||
foreach_dma_channel! {
|
||||
($channel_peri:ident, $dma_peri:ident, $version:ident, $channel_num:expr, $index:expr, {dmamux: $dmamux:ident, dmamux_channel: $dmamux_channel:expr}) => {
|
||||
impl sealed::MuxChannel for peripherals::$channel_peri {
|
||||
const DMAMUX_CH_NUM: u8 = $dmamux_channel;
|
||||
const DMAMUX_REGS: pac::dmamux::Dmamux = pac::$dmamux;
|
||||
impl dmamux_sealed::MuxChannel for peripherals::$channel_peri {
|
||||
fn mux_regs(&self) -> pac::dmamux::Dmamux {
|
||||
pac::$dmamux
|
||||
}
|
||||
fn mux_num(&self) -> usize {
|
||||
$dmamux_channel
|
||||
}
|
||||
}
|
||||
impl MuxChannel for peripherals::$channel_peri {
|
||||
type Mux = $dmamux;
|
||||
|
@ -1,13 +1,30 @@
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::Waker;
|
||||
#![macro_use]
|
||||
|
||||
use core::future::Future;
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::{Request, TransferOptions, Word, WordSize};
|
||||
use super::{Dir, Word, WordSize};
|
||||
use crate::_generated::GPDMA_CHANNEL_COUNT;
|
||||
use crate::interrupt::{Interrupt, InterruptExt};
|
||||
use crate::pac::gpdma::{vals, Gpdma};
|
||||
use crate::{interrupt, pac};
|
||||
use crate::pac;
|
||||
use crate::pac::gpdma::vals;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WordSize> for vals::ChTr1Dw {
|
||||
fn from(raw: WordSize) -> Self {
|
||||
@ -19,27 +36,15 @@ impl From<WordSize> for vals::ChTr1Dw {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelState {
|
||||
waker: AtomicWaker,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
waker: AtomicWaker::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
channels: [ChannelState; GPDMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT],
|
||||
}
|
||||
|
||||
impl State {
|
||||
const fn new() -> Self {
|
||||
const CH: ChannelState = ChannelState::new();
|
||||
const AW: AtomicWaker = AtomicWaker::new();
|
||||
Self {
|
||||
channels: [CH; GPDMA_CHANNEL_COUNT],
|
||||
ch_wakers: [AW; GPDMA_CHANNEL_COUNT],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,10 +52,12 @@ impl State {
|
||||
static STATE: State = State::new();
|
||||
|
||||
/// safety: must be called only once
|
||||
pub(crate) unsafe fn init() {
|
||||
pub(crate) unsafe fn init(irq_priority: Priority) {
|
||||
foreach_interrupt! {
|
||||
($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => {
|
||||
interrupt::$irq::steal().enable();
|
||||
let irq = crate::interrupt::$irq::steal();
|
||||
irq.set_priority(irq_priority);
|
||||
irq.enable();
|
||||
};
|
||||
}
|
||||
crate::_generated::init_gpdma();
|
||||
@ -58,117 +65,171 @@ pub(crate) unsafe fn init() {
|
||||
|
||||
foreach_dma_channel! {
|
||||
($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => {
|
||||
impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri {
|
||||
unsafe fn start_write<W: Word>(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::MemoryToPeripheral,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
impl sealed::Channel for crate::peripherals::$channel_peri {
|
||||
fn regs(&self) -> pac::gpdma::Gpdma {
|
||||
pac::$dma_peri
|
||||
}
|
||||
|
||||
unsafe fn start_write_repeated<W: Word>(&mut self, request: Request, repeated: *const W, count: usize, reg_addr: *mut W, options: TransferOptions) {
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::MemoryToPeripheral,
|
||||
reg_addr as *const u32,
|
||||
repeated as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
fn num(&self) -> usize {
|
||||
$channel_num
|
||||
}
|
||||
|
||||
unsafe fn start_read<W: Word>(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) {
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
low_level_api::start_transfer(
|
||||
pac::$dma_peri,
|
||||
$channel_num,
|
||||
request,
|
||||
low_level_api::Dir::PeripheralToMemory,
|
||||
reg_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
);
|
||||
fn index(&self) -> usize {
|
||||
$index
|
||||
}
|
||||
|
||||
unsafe fn start_double_buffered_read<W: Word>(
|
||||
&mut self,
|
||||
_request: Request,
|
||||
_reg_addr: *const W,
|
||||
_buffer0: *mut W,
|
||||
_buffer1: *mut W,
|
||||
_buffer_len: usize,
|
||||
_options: TransferOptions,
|
||||
) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer0<W: Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn set_buffer1<W: Word>(&mut self, _buffer: *mut W) {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool {
|
||||
panic!("Unsafe double buffered mode is unavailable on GPBDMA");
|
||||
}
|
||||
|
||||
fn request_stop(&mut self) {
|
||||
unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn remaining_transfers(&mut self) -> u16 {
|
||||
unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)}
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
unsafe {low_level_api::set_waker($index, waker )}
|
||||
}
|
||||
|
||||
fn on_irq() {
|
||||
unsafe {
|
||||
low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index);
|
||||
}
|
||||
unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) }
|
||||
}
|
||||
}
|
||||
impl crate::dma::Channel for crate::peripherals::$channel_peri { }
|
||||
|
||||
impl Channel for crate::peripherals::$channel_peri {}
|
||||
};
|
||||
}
|
||||
|
||||
mod low_level_api {
|
||||
use super::*;
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub(crate) unsafe fn on_irq_inner(dma: pac::gpdma::Gpdma, channel_num: usize, index: usize) {
|
||||
let ch = dma.ch(channel_num);
|
||||
let sr = ch.sr().read();
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Dir {
|
||||
MemoryToPeripheral,
|
||||
PeripheralToMemory,
|
||||
if sr.dtef() {
|
||||
panic!(
|
||||
"DMA: data transfer error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
if sr.usef() {
|
||||
panic!(
|
||||
"DMA: user settings error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
|
||||
pub unsafe fn start_transfer(
|
||||
dma: Gpdma,
|
||||
channel_number: u8,
|
||||
if sr.suspf() || sr.tcf() {
|
||||
// disable all xxIEs to prevent the irq from firing again.
|
||||
ch.cr().write(|_| {});
|
||||
|
||||
// Wake the future. It'll look at tcf and see it's set.
|
||||
STATE.ch_wakers[index].wake();
|
||||
}
|
||||
}
|
||||
|
||||
pub type Request = u8;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {}
|
||||
#[cfg(not(dmamux))]
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub trait Channel {
|
||||
fn regs(&self) -> pac::gpdma::Gpdma;
|
||||
fn num(&self) -> usize;
|
||||
fn index(&self) -> usize;
|
||||
fn on_irq();
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub struct Transfer<'a, C: Channel> {
|
||||
channel: PeripheralRef<'a, C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Transfer<'a, C> {
|
||||
pub unsafe fn new_read<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_read_raw(channel, request, peri_addr, buf, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_read_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts_mut(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::PeripheralToMemory,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
Self::new_write_raw(channel, request, buf, peri_addr, options)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_raw<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
let (ptr, len) = super::slice_ptr_parts(buf);
|
||||
assert!(len > 0 && len <= 0xFFFF);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
ptr as *mut u32,
|
||||
len,
|
||||
true,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn new_write_repeated<W: Word>(
|
||||
channel: impl Peripheral<P = C> + 'a,
|
||||
request: Request,
|
||||
repeated: &'a W,
|
||||
count: usize,
|
||||
peri_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
into_ref!(channel);
|
||||
|
||||
Self::new_inner(
|
||||
channel,
|
||||
request,
|
||||
Dir::MemoryToPeripheral,
|
||||
peri_addr as *const u32,
|
||||
repeated as *const W as *mut u32,
|
||||
count,
|
||||
false,
|
||||
W::bits(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
unsafe fn new_inner(
|
||||
channel: PeripheralRef<'a, C>,
|
||||
request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *const u32,
|
||||
@ -176,24 +237,19 @@ mod low_level_api {
|
||||
mem_len: usize,
|
||||
incr_mem: bool,
|
||||
data_size: WordSize,
|
||||
options: TransferOptions,
|
||||
) {
|
||||
assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported");
|
||||
assert!(
|
||||
options.flow_ctrl == crate::dma::FlowControl::Dma,
|
||||
"Peripheral flow control not supported"
|
||||
);
|
||||
assert!(options.fifo_threshold.is_none(), "FIFO mode not supported");
|
||||
_options: TransferOptions,
|
||||
) -> Self {
|
||||
let ch = channel.regs().ch(channel.num());
|
||||
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let ch = dma.ch(channel_number as _);
|
||||
let this = Self { channel };
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut *this.channel, request);
|
||||
|
||||
// Reset ch
|
||||
ch.cr().write(|w| w.set_reset(true));
|
||||
|
||||
ch.llr().write(|_| {}); // no linked list
|
||||
ch.tr1().write(|w| {
|
||||
w.set_sdw(data_size.into());
|
||||
@ -234,72 +290,66 @@ mod low_level_api {
|
||||
// Start it
|
||||
w.set_en(true);
|
||||
});
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
/// Stops the DMA channel.
|
||||
pub unsafe fn request_stop(dma: Gpdma, channel_number: u8) {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(channel_number as _);
|
||||
pub fn request_stop(&mut self) {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
|
||||
// Disable the channel. Keep the IEs enabled so the irqs still fire.
|
||||
ch.cr().write(|w| {
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
});
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
unsafe {
|
||||
ch.cr().write(|w| {
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the running status of the channel
|
||||
pub unsafe fn is_running(dma: Gpdma, ch: u8) -> bool {
|
||||
let ch = dma.ch(ch as _);
|
||||
!ch.sr().read().tcf()
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
!unsafe { ch.sr().read() }.tcf()
|
||||
}
|
||||
|
||||
/// Gets the total remaining transfers for the channel
|
||||
/// Note: this will be zero for transfers that completed without cancellation.
|
||||
pub unsafe fn get_remaining_transfers(dma: Gpdma, ch: u8) -> u16 {
|
||||
// get a handle on the channel itself
|
||||
let ch = dma.ch(ch as _);
|
||||
// read the remaining transfer count. If this is zero, the transfer completed fully.
|
||||
ch.br1().read().bndt()
|
||||
pub fn get_remaining_transfers(&self) -> u16 {
|
||||
let ch = self.channel.regs().ch(self.channel.num());
|
||||
unsafe { ch.br1().read() }.bndt()
|
||||
}
|
||||
|
||||
/// Sets the waker for the specified DMA channel
|
||||
pub unsafe fn set_waker(state_number: usize, waker: &Waker) {
|
||||
STATE.channels[state_number].waker.register(waker);
|
||||
pub fn blocking_wait(mut self) {
|
||||
while self.is_running() {}
|
||||
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
core::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Safety: Must be called with a matching set of parameters for a valid dma channel
|
||||
pub unsafe fn on_irq_inner(dma: Gpdma, channel_num: u8, state_index: u8) {
|
||||
let channel_num = channel_num as usize;
|
||||
let state_index = state_index as usize;
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.request_stop();
|
||||
while self.is_running() {}
|
||||
|
||||
let ch = dma.ch(channel_num);
|
||||
let sr = ch.sr().read();
|
||||
// "Subsequent reads and writes cannot be moved ahead of preceding reads."
|
||||
fence(Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
if sr.dtef() {
|
||||
panic!(
|
||||
"DMA: data transfer error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
if sr.usef() {
|
||||
panic!(
|
||||
"DMA: user settings error on DMA@{:08x} channel {}",
|
||||
dma.0 as u32, channel_num
|
||||
);
|
||||
}
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
STATE.ch_wakers[self.channel.index()].register(cx.waker());
|
||||
|
||||
if sr.suspf() || sr.tcf() {
|
||||
// disable all xxIEs to prevent the irq from firing again.
|
||||
ch.cr().write(|_| {});
|
||||
|
||||
// Wake the future. It'll look at tcf and see it's set.
|
||||
STATE.channels[state_index].waker.wake();
|
||||
if self.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,124 +1,39 @@
|
||||
#[cfg(bdma)]
|
||||
pub(crate) mod bdma;
|
||||
#[cfg(dma)]
|
||||
pub(crate) mod dma;
|
||||
#[cfg(dma)]
|
||||
pub use dma::*;
|
||||
|
||||
// stm32h7 has both dma and bdma. In that case, we export dma as "main" dma,
|
||||
// and bdma as "secondary", under `embassy_stm32::dma::bdma`.
|
||||
#[cfg(all(bdma, dma))]
|
||||
pub mod bdma;
|
||||
|
||||
#[cfg(all(bdma, not(dma)))]
|
||||
pub(crate) mod bdma;
|
||||
#[cfg(all(bdma, not(dma)))]
|
||||
pub use bdma::*;
|
||||
|
||||
#[cfg(gpdma)]
|
||||
pub(crate) mod gpdma;
|
||||
#[cfg(gpdma)]
|
||||
pub use gpdma::*;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
mod dmamux;
|
||||
#[cfg(gpdma)]
|
||||
mod gpdma;
|
||||
|
||||
use core::future::Future;
|
||||
use core::mem;
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
#[cfg(any(dma, bdma))]
|
||||
use embassy_cortex_m::interrupt::Priority;
|
||||
use embassy_hal_common::{impl_peripheral, into_ref};
|
||||
use embassy_hal_common::impl_peripheral;
|
||||
|
||||
#[cfg(dmamux)]
|
||||
pub use self::dmamux::*;
|
||||
use crate::Peripheral;
|
||||
|
||||
#[cfg(feature = "unstable-pac")]
|
||||
pub mod low_level {
|
||||
pub use super::transfers::*;
|
||||
}
|
||||
|
||||
pub(crate) use transfers::*;
|
||||
|
||||
#[cfg(any(bdma_v2, dma_v2, dmamux, gpdma))]
|
||||
pub type Request = u8;
|
||||
#[cfg(not(any(bdma_v2, dma_v2, dmamux, gpdma)))]
|
||||
pub type Request = ();
|
||||
|
||||
pub(crate) mod sealed {
|
||||
use super::*;
|
||||
|
||||
pub trait Word {}
|
||||
|
||||
pub trait Channel {
|
||||
/// Starts this channel for writing a stream of words.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `buf` must point to a valid buffer for DMA reading.
|
||||
/// - `buf` must be alive for the entire duration of the DMA transfer.
|
||||
/// - `reg_addr` must be a valid peripheral register address to write to.
|
||||
unsafe fn start_write<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
buf: *const [W],
|
||||
reg_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// Starts this channel for writing a word repeatedly.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `reg_addr` must be a valid peripheral register address to write to.
|
||||
unsafe fn start_write_repeated<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
repeated: *const W,
|
||||
count: usize,
|
||||
reg_addr: *mut W,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// Starts this channel for reading a stream of words.
|
||||
///
|
||||
/// Safety:
|
||||
/// - `buf` must point to a valid buffer for DMA writing.
|
||||
/// - `buf` must be alive for the entire duration of the DMA transfer.
|
||||
/// - `reg_addr` must be a valid peripheral register address to read from.
|
||||
unsafe fn start_read<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
reg_addr: *const W,
|
||||
buf: *mut [W],
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
/// DMA double-buffered mode is unsafe as UB can happen when the hardware writes to a buffer currently owned by the software
|
||||
/// more information can be found here: https://github.com/embassy-rs/embassy/issues/702
|
||||
/// This feature is now used solely for the purposes of implementing giant DMA transfers required for DCMI
|
||||
unsafe fn start_double_buffered_read<W: super::Word>(
|
||||
&mut self,
|
||||
request: Request,
|
||||
reg_addr: *const W,
|
||||
buffer0: *mut W,
|
||||
buffer1: *mut W,
|
||||
buffer_len: usize,
|
||||
options: TransferOptions,
|
||||
);
|
||||
|
||||
unsafe fn set_buffer0<W: super::Word>(&mut self, buffer: *mut W);
|
||||
|
||||
unsafe fn set_buffer1<W: super::Word>(&mut self, buffer: *mut W);
|
||||
|
||||
unsafe fn is_buffer0_accessible(&mut self) -> bool;
|
||||
|
||||
/// Requests the channel to stop.
|
||||
/// NOTE: The channel does not immediately stop, you have to wait
|
||||
/// for `is_running() = false`.
|
||||
fn request_stop(&mut self);
|
||||
|
||||
/// Returns whether this channel is running or stopped.
|
||||
///
|
||||
/// The channel stops running when it either completes or is manually stopped.
|
||||
fn is_running(&self) -> bool;
|
||||
|
||||
/// Returns the total number of remaining transfers.
|
||||
fn remaining_transfers(&mut self) -> u16;
|
||||
|
||||
/// Sets the waker that is called when this channel stops (either completed or manually stopped)
|
||||
fn set_waker(&mut self, waker: &Waker);
|
||||
|
||||
/// This is called when this channel triggers an interrupt.
|
||||
/// Note: Because some channels share an interrupt, this function might be
|
||||
/// called for a channel that didn't trigger an interrupt.
|
||||
fn on_irq();
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
enum Dir {
|
||||
MemoryToPeripheral,
|
||||
PeripheralToMemory,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
@ -139,191 +54,39 @@ impl WordSize {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Word: sealed::Word {
|
||||
mod word_sealed {
|
||||
pub trait Word {}
|
||||
}
|
||||
|
||||
pub trait Word: word_sealed::Word {
|
||||
fn bits() -> WordSize;
|
||||
}
|
||||
|
||||
impl sealed::Word for u8 {}
|
||||
impl word_sealed::Word for u8 {}
|
||||
impl Word for u8 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::OneByte
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Word for u16 {}
|
||||
impl word_sealed::Word for u16 {}
|
||||
impl Word for u16 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::TwoBytes
|
||||
}
|
||||
}
|
||||
|
||||
impl sealed::Word for u32 {}
|
||||
impl word_sealed::Word for u32 {}
|
||||
impl Word for u32 {
|
||||
fn bits() -> WordSize {
|
||||
WordSize::FourBytes
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Burst {
|
||||
/// Single transfer
|
||||
Single,
|
||||
/// Incremental burst of 4 beats
|
||||
Incr4,
|
||||
/// Incremental burst of 8 beats
|
||||
Incr8,
|
||||
/// Incremental burst of 16 beats
|
||||
Incr16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FlowControl {
|
||||
/// Flow control by DMA
|
||||
Dma,
|
||||
/// Flow control by peripheral
|
||||
Peripheral,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FifoThreshold {
|
||||
/// 1/4 full FIFO
|
||||
Quarter,
|
||||
/// 1/2 full FIFO
|
||||
Half,
|
||||
/// 3/4 full FIFO
|
||||
ThreeQuarters,
|
||||
/// Full FIFO
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TransferOptions {
|
||||
/// Peripheral burst transfer configuration
|
||||
pub pburst: Burst,
|
||||
/// Memory burst transfer configuration
|
||||
pub mburst: Burst,
|
||||
/// Flow control configuration
|
||||
pub flow_ctrl: FlowControl,
|
||||
/// FIFO threshold for DMA FIFO mode. If none, direct mode is used.
|
||||
pub fifo_threshold: Option<FifoThreshold>,
|
||||
}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pburst: Burst::Single,
|
||||
mburst: Burst::Single,
|
||||
flow_ctrl: FlowControl::Dma,
|
||||
fifo_threshold: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod transfers {
|
||||
use embassy_hal_common::PeripheralRef;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn read<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
reg_addr: *mut W,
|
||||
buf: &'a mut [W],
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_read::<W>(request, reg_addr, buf, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn write<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
buf: &'a [W],
|
||||
reg_addr: *mut W,
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
assert!(buf.len() > 0 && buf.len() <= 0xFFFF);
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_write::<W>(request, buf, reg_addr, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn write_repeated<'a, W: Word>(
|
||||
channel: impl Peripheral<P = impl Channel> + 'a,
|
||||
request: Request,
|
||||
repeated: *const W,
|
||||
count: usize,
|
||||
reg_addr: *mut W,
|
||||
) -> impl Future<Output = ()> + 'a {
|
||||
into_ref!(channel);
|
||||
|
||||
unsafe { channel.start_write_repeated::<W>(request, repeated, count, reg_addr, Default::default()) };
|
||||
|
||||
Transfer::new(channel)
|
||||
}
|
||||
|
||||
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
||||
pub(crate) struct Transfer<'a, C: Channel> {
|
||||
channel: PeripheralRef<'a, C>,
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Transfer<'a, C> {
|
||||
pub(crate) fn new(channel: impl Peripheral<P = C> + 'a) -> Self {
|
||||
into_ref!(channel);
|
||||
Self { channel }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Drop for Transfer<'a, C> {
|
||||
fn drop(&mut self) {
|
||||
self.channel.request_stop();
|
||||
while self.channel.is_running() {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: Channel> Unpin for Transfer<'a, C> {}
|
||||
impl<'a, C: Channel> Future for Transfer<'a, C> {
|
||||
type Output = ();
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.channel.set_waker(cx.waker());
|
||||
if self.channel.is_running() {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {}
|
||||
|
||||
pub struct NoDma;
|
||||
|
||||
impl_peripheral!(NoDma);
|
||||
|
||||
// safety: must be called only once at startup
|
||||
pub(crate) unsafe fn init(#[cfg(bdma)] bdma_priority: Priority, #[cfg(dma)] dma_priority: Priority) {
|
||||
#[cfg(bdma)]
|
||||
bdma::init(bdma_priority);
|
||||
#[cfg(dma)]
|
||||
dma::init(dma_priority);
|
||||
#[cfg(dmamux)]
|
||||
dmamux::init();
|
||||
#[cfg(gpdma)]
|
||||
gpdma::init();
|
||||
}
|
||||
|
||||
// TODO: replace transmutes with core::ptr::metadata once it's stable
|
||||
#[allow(unused)]
|
||||
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
|
||||
@ -334,3 +97,19 @@ pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (usize, usize) {
|
||||
pub(crate) fn slice_ptr_parts_mut<T>(slice: *mut [T]) -> (usize, usize) {
|
||||
unsafe { mem::transmute(slice) }
|
||||
}
|
||||
|
||||
// safety: must be called only once at startup
|
||||
pub(crate) unsafe fn init(
|
||||
#[cfg(bdma)] bdma_priority: Priority,
|
||||
#[cfg(dma)] dma_priority: Priority,
|
||||
#[cfg(gpdma)] gpdma_priority: Priority,
|
||||
) {
|
||||
#[cfg(bdma)]
|
||||
bdma::init(bdma_priority);
|
||||
#[cfg(dma)]
|
||||
dma::init(dma_priority);
|
||||
#[cfg(gpdma)]
|
||||
gpdma::init(gpdma_priority);
|
||||
#[cfg(dmamux)]
|
||||
dmamux::init();
|
||||
}
|
||||
|
Reference in New Issue
Block a user