mirror of
https://github.com/embassy-rs/embassy.git
synced 2025-05-11 17:58:42 +00:00
Merge a73732eeb9
into dd3ae39d42
This commit is contained in:
commit
0ad5903c64
@ -1,13 +1,14 @@
|
||||
#![macro_use]
|
||||
|
||||
use core::future::Future;
|
||||
use core::future::{poll_fn, Future};
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{fence, Ordering};
|
||||
use core::task::{Context, Poll};
|
||||
use core::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering};
|
||||
use core::task::{Context, Poll, Waker};
|
||||
|
||||
use embassy_hal_internal::Peri;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer};
|
||||
use super::word::{Word, WordSize};
|
||||
use super::{AnyChannel, Channel, Dir, Request, STATE};
|
||||
use crate::interrupt::typelevel::Interrupt;
|
||||
@ -26,11 +27,16 @@ pub(crate) struct ChannelInfo {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub struct TransferOptions {}
|
||||
pub struct TransferOptions {
|
||||
/// If true, the half transfer interrupt will call the waker.
|
||||
pub half_transfer_ir: bool,
|
||||
}
|
||||
|
||||
impl Default for TransferOptions {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
Self {
|
||||
half_transfer_ir: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,11 +52,17 @@ impl From<WordSize> for vals::Dw {
|
||||
|
||||
pub(crate) struct ChannelState {
|
||||
waker: AtomicWaker,
|
||||
circular_address: AtomicUsize,
|
||||
complete_count: AtomicUsize,
|
||||
is_wake_on_half_transfer: AtomicBool,
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
pub(crate) const NEW: Self = Self {
|
||||
waker: AtomicWaker::new(),
|
||||
circular_address: AtomicUsize::new(0),
|
||||
complete_count: AtomicUsize::new(0),
|
||||
is_wake_on_half_transfer: AtomicBool::new(false),
|
||||
};
|
||||
}
|
||||
|
||||
@ -88,6 +100,7 @@ impl AnyChannel {
|
||||
info.num
|
||||
);
|
||||
}
|
||||
|
||||
if sr.usef() {
|
||||
panic!(
|
||||
"DMA: user settings error on DMA@{:08x} channel {}",
|
||||
@ -96,7 +109,23 @@ impl AnyChannel {
|
||||
);
|
||||
}
|
||||
|
||||
if sr.suspf() || sr.tcf() {
|
||||
if sr.htf() {
|
||||
//clear the flag for the half transfer complete
|
||||
ch.fcr().modify(|w| w.set_htf(true));
|
||||
if state.is_wake_on_half_transfer.load(Ordering::Relaxed) {
|
||||
state.waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
if sr.tcf() {
|
||||
//clear the flag for the transfer complete so circular transfers can resume
|
||||
ch.fcr().modify(|w| w.set_tcf(true));
|
||||
state.complete_count.fetch_add(1, Ordering::Relaxed);
|
||||
state.waker.wake();
|
||||
return;
|
||||
}
|
||||
|
||||
if sr.suspf() {
|
||||
// disable all xxIEs to prevent the irq from firing again.
|
||||
ch.cr().write(|_| {});
|
||||
|
||||
@ -337,3 +366,381 @@ impl<'a> Future for Transfer<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DmaCtrlImpl<'a>(Peri<'a, AnyChannel>, WordSize);
|
||||
|
||||
impl<'a> DmaCtrl for DmaCtrlImpl<'a> {
|
||||
fn get_remaining_transfers(&self) -> usize {
|
||||
let info = self.0.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
(ch.br1().read().bndt() / self.1.bytes() as u16) as usize
|
||||
}
|
||||
|
||||
fn reset_complete_count(&mut self) -> usize {
|
||||
let state = &STATE[self.0.id as usize];
|
||||
#[cfg(not(armv6m))]
|
||||
return state.complete_count.swap(0, Ordering::AcqRel);
|
||||
#[cfg(armv6m)]
|
||||
return critical_section::with(|_| {
|
||||
let x = state.complete_count.load(Ordering::Acquire);
|
||||
state.complete_count.store(0, Ordering::Release);
|
||||
x
|
||||
});
|
||||
}
|
||||
|
||||
fn set_waker(&mut self, waker: &Waker) {
|
||||
STATE[self.0.id as usize].waker.register(waker);
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyChannel {
|
||||
fn configure<'a, W: Word>(
|
||||
&self,
|
||||
request: Request,
|
||||
dir: Dir,
|
||||
peri_addr: *mut W,
|
||||
buffer: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) {
|
||||
// "Preceding reads and writes cannot be moved past subsequent writes."
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
|
||||
let (mem_addr, mem_len): (usize, usize) = unsafe { core::mem::transmute(buffer) };
|
||||
|
||||
ch.cr().write(|w| w.set_reset(true));
|
||||
ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs
|
||||
|
||||
if mem_addr & 0b11 != 0 {
|
||||
panic!("circular address must be 4-byte aligned");
|
||||
}
|
||||
let state = &STATE[self.id as usize];
|
||||
defmt::info!("Circular address: {:#x}", mem_addr);
|
||||
state.circular_address.store(mem_addr, Ordering::Release);
|
||||
state
|
||||
.is_wake_on_half_transfer
|
||||
.store(options.half_transfer_ir, Ordering::Release);
|
||||
|
||||
let lli = state.circular_address.as_ptr() as u32;
|
||||
ch.llr().write(|w| {
|
||||
match dir {
|
||||
Dir::MemoryToPeripheral => w.set_usa(true),
|
||||
Dir::PeripheralToMemory => w.set_uda(true),
|
||||
}
|
||||
// lower 16 bits of the memory address
|
||||
w.set_la(((lli >> 2usize) & 0x3fff) as u16);
|
||||
});
|
||||
ch.lbar().write(|w| {
|
||||
// upper 16 bits of the address of lli1
|
||||
w.set_lba((lli >> 16usize) as u16);
|
||||
});
|
||||
|
||||
let data_size = W::size();
|
||||
ch.tr1().write(|w| {
|
||||
w.set_sdw(data_size.into());
|
||||
w.set_ddw(data_size.into());
|
||||
w.set_sinc(dir == Dir::MemoryToPeripheral);
|
||||
w.set_dinc(dir == Dir::PeripheralToMemory);
|
||||
});
|
||||
ch.tr2().write(|w| {
|
||||
w.set_dreq(match dir {
|
||||
Dir::MemoryToPeripheral => vals::Dreq::DESTINATION_PERIPHERAL,
|
||||
Dir::PeripheralToMemory => vals::Dreq::SOURCE_PERIPHERAL,
|
||||
});
|
||||
w.set_reqsel(request);
|
||||
});
|
||||
ch.br1().write(|w| {
|
||||
// BNDT is specified as bytes, not as number of transfers.
|
||||
w.set_bndt((mem_len * data_size.bytes()) as u16)
|
||||
});
|
||||
|
||||
match dir {
|
||||
Dir::MemoryToPeripheral => {
|
||||
ch.sar().write_value(mem_addr as _);
|
||||
ch.dar().write_value(peri_addr as _);
|
||||
}
|
||||
Dir::PeripheralToMemory => {
|
||||
ch.sar().write_value(peri_addr as _);
|
||||
ch.dar().write_value(mem_addr as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
let sr = ch.sr().read();
|
||||
!sr.idlef() && !sr.suspf()
|
||||
}
|
||||
|
||||
async fn stop(ch: stm32_metapac::gpdma::Channel, set_waker: &mut dyn FnMut(&Waker)) {
|
||||
//wait until cr.susp reads as true
|
||||
poll_fn(|cx| {
|
||||
set_waker(cx.waker());
|
||||
|
||||
core::sync::atomic::compiler_fence(Ordering::SeqCst);
|
||||
|
||||
let cr = ch.cr().read();
|
||||
if cr.susp() {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn clear_irqs(&self) {
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
ch.fcr().modify(|w| {
|
||||
w.set_htf(true);
|
||||
w.set_tcf(true);
|
||||
w.set_suspf(true);
|
||||
});
|
||||
}
|
||||
|
||||
fn request_suspend(&self) {
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
ch.cr().modify(|w| {
|
||||
w.set_susp(true);
|
||||
});
|
||||
}
|
||||
|
||||
fn request_stop(&self) {
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
ch.cr().modify(|w| w.set_en(false));
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
self.clear_irqs();
|
||||
let info = self.info();
|
||||
let ch = info.dma.ch(info.num);
|
||||
ch.cr().modify(|w| {
|
||||
w.set_susp(false);
|
||||
w.set_en(true);
|
||||
w.set_tcie(true);
|
||||
w.set_useie(true);
|
||||
w.set_dteie(true);
|
||||
w.set_suspie(true);
|
||||
w.set_htie(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a Readable ring buffer. It reads data from a peripheral into a buffer. The reads happen in circular mode.
|
||||
/// There are interrupts on complete and half complete. You should read half the buffer on every read.
|
||||
pub struct ReadableRingBuffer<'a, W: Word> {
|
||||
channel: Peri<'a, AnyChannel>,
|
||||
ringbuf: ReadableDmaRingBuffer<'a, W>,
|
||||
}
|
||||
|
||||
impl<'a, W: Word> ReadableRingBuffer<'a, W> {
|
||||
/// Create a new Readable ring buffer.
|
||||
pub unsafe fn new(
|
||||
channel: Peri<'a, impl Channel>,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buffer: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
let channel: Peri<'a, AnyChannel> = channel.into();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut channel, request);
|
||||
|
||||
channel.configure(request, Dir::PeripheralToMemory, peri_addr, buffer, options);
|
||||
|
||||
Self {
|
||||
channel,
|
||||
ringbuf: ReadableDmaRingBuffer::new(buffer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start reading the peripheral in ciccular mode.
|
||||
pub fn start(&mut self) {
|
||||
self.channel.start();
|
||||
}
|
||||
|
||||
/// Request the transfer to stop. Use is_running() to see when the transfer is complete.
|
||||
pub fn request_stop(&mut self) {
|
||||
self.channel.request_suspend();
|
||||
}
|
||||
|
||||
/// Await until the stop completes. This is not used with request_stop(). Just call and await.
|
||||
/// It will stop when the current transfer is complete.
|
||||
pub async fn stop(&mut self) {
|
||||
let ch: stm32_metapac::gpdma::Channel = {
|
||||
let info = self.channel.info();
|
||||
info.dma.ch(info.num)
|
||||
};
|
||||
AnyChannel::stop(ch, &mut |waker| self.set_waker(waker)).await
|
||||
}
|
||||
|
||||
/// Clear the buffers internal pointers.
|
||||
pub fn clear(&mut self) {
|
||||
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()));
|
||||
}
|
||||
|
||||
/// Read elements from the ring buffer
|
||||
/// Return a tuple of the length read and the length remaining in the buffer
|
||||
/// If not all of the elements were read, then there will be some elements in the buffer remaining
|
||||
/// The length remaining is the capacity, ring_buf.len(), less the elements remaining after the read
|
||||
/// Error::Overrun is returned if the portion to be read was overwritten by the DMA controller.
|
||||
pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf
|
||||
.read(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()), buf)
|
||||
}
|
||||
|
||||
/// Read an exact number of elements from the ringbuffer.
|
||||
///
|
||||
/// Returns the remaining number of elements available for immediate reading.
|
||||
/// OverrunError is returned if the portion to be read was overwritten by the DMA controller.
|
||||
///
|
||||
/// Async/Wake Behavior:
|
||||
/// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point,
|
||||
/// and when it wraps around. This means that when called with a buffer of length 'M', when this
|
||||
/// ring buffer was created with a buffer of size 'N':
|
||||
/// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source.
|
||||
/// - Otherwise, this function may need up to N/2 extra elements to arrive before returning.
|
||||
pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, Error> {
|
||||
self.ringbuf
|
||||
.read_exact(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()), buffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn cap(&self) -> usize {
|
||||
self.ringbuf.cap()
|
||||
}
|
||||
|
||||
/// Set the waker for the DMA controller.
|
||||
pub fn set_waker(&mut self, waker: &Waker) {
|
||||
DmaCtrlImpl(self.channel.reborrow(), W::size()).set_waker(waker);
|
||||
}
|
||||
|
||||
/// Return whether this transfer is still running.
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
self.channel.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a Writable ring buffer. It writes data from a buffer to a peripheral. The writes happen in circular mode.
|
||||
pub struct WritableRingBuffer<'a, W: Word> {
|
||||
#[allow(dead_code)] //this is only read by the DMA controller
|
||||
channel: Peri<'a, AnyChannel>,
|
||||
ringbuf: WritableDmaRingBuffer<'a, W>,
|
||||
}
|
||||
|
||||
impl<'a, W: Word> WritableRingBuffer<'a, W> {
|
||||
/// Create a new Writable ring buffer.
|
||||
pub unsafe fn new(
|
||||
channel: Peri<'a, impl Channel>,
|
||||
request: Request,
|
||||
peri_addr: *mut W,
|
||||
buffer: &'a mut [W],
|
||||
options: TransferOptions,
|
||||
) -> Self {
|
||||
let channel: Peri<'a, AnyChannel> = channel.into();
|
||||
|
||||
#[cfg(dmamux)]
|
||||
super::dmamux::configure_dmamux(&mut channel, request);
|
||||
|
||||
channel.configure(request, Dir::MemoryToPeripheral, peri_addr, buffer, options);
|
||||
|
||||
Self {
|
||||
channel,
|
||||
ringbuf: WritableDmaRingBuffer::new(buffer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Start writing to the peripheral in circular mode.
|
||||
pub fn start(&mut self) {
|
||||
self.channel.start();
|
||||
}
|
||||
|
||||
/// Await until the stop completes. This is not used with request_stop(). Just call and await.
|
||||
pub async fn stop(&mut self) {
|
||||
let ch: stm32_metapac::gpdma::Channel = {
|
||||
let info = self.channel.info();
|
||||
info.dma.ch(info.num)
|
||||
};
|
||||
AnyChannel::stop(ch, &mut |waker| self.set_waker(waker)).await
|
||||
}
|
||||
|
||||
/// Request the transfer to stop. Use is_running() to see when the transfer is complete.
|
||||
pub fn request_stop(&mut self) {
|
||||
self.channel.request_stop();
|
||||
}
|
||||
|
||||
/// Write elements directly to the raw buffer.
|
||||
/// This can be used to fill the buffer before starting the DMA transfer.
|
||||
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf.write_immediate(buf)
|
||||
}
|
||||
|
||||
/// Wait for any ring buffer write error.
|
||||
pub async fn wait_write_error(&mut self) -> Result<usize, Error> {
|
||||
self.ringbuf
|
||||
.wait_write_error(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Clear the buffers internal pointers.
|
||||
pub fn clear(&mut self) {
|
||||
self.ringbuf.reset(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()));
|
||||
}
|
||||
|
||||
/// Write elements from the ring buffer
|
||||
/// Return a tuple of the length written and the length remaining in the buffer
|
||||
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> {
|
||||
self.ringbuf
|
||||
.write(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()), buf)
|
||||
}
|
||||
|
||||
/// Write an exact number of elements to the ringbuffer.
|
||||
pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, Error> {
|
||||
self.ringbuf
|
||||
.write_exact(&mut DmaCtrlImpl(self.channel.reborrow(), W::size()), buffer)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The capacity of the ringbuffer
|
||||
pub const fn cap(&self) -> usize {
|
||||
self.ringbuf.cap()
|
||||
}
|
||||
|
||||
/// Set the waker for the DMA controller.
|
||||
pub fn set_waker(&mut self, waker: &Waker) {
|
||||
DmaCtrlImpl(self.channel.reborrow(), W::size()).set_waker(waker);
|
||||
}
|
||||
|
||||
/// Return whether this transfer is still running.
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
self.channel.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Word> Drop for WritableRingBuffer<'a, W> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ use core::marker::PhantomData;
|
||||
use embassy_hal_internal::PeripheralType;
|
||||
|
||||
pub use crate::dma::word;
|
||||
#[cfg(not(gpdma))]
|
||||
use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, Request, TransferOptions, WritableRingBuffer};
|
||||
use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed};
|
||||
use crate::pac::sai::{vals, Sai as Regs};
|
||||
@ -26,7 +25,6 @@ pub enum Error {
|
||||
Overrun,
|
||||
}
|
||||
|
||||
#[cfg(not(gpdma))]
|
||||
impl From<ringbuffer::Error> for Error {
|
||||
fn from(#[allow(unused)] err: ringbuffer::Error) -> Self {
|
||||
#[cfg(feature = "defmt")]
|
||||
@ -598,7 +596,7 @@ pub struct Config {
|
||||
pub frame_sync_polarity: FrameSyncPolarity,
|
||||
pub frame_sync_active_level_length: word::U7,
|
||||
pub frame_sync_definition: FrameSyncDefinition,
|
||||
pub frame_length: u8,
|
||||
pub frame_length: u16,
|
||||
pub clock_strobe: ClockStrobe,
|
||||
pub output_drive: OutputDrive,
|
||||
pub master_clock_divider: MasterClockDivider,
|
||||
@ -650,7 +648,6 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(gpdma))]
|
||||
enum RingBuffer<'d, W: word::Word> {
|
||||
Writable(WritableRingBuffer<'d, W>),
|
||||
Readable(ReadableRingBuffer<'d, W>),
|
||||
@ -677,7 +674,6 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) {
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(gpdma))]
|
||||
fn get_ring_buffer<'d, T: Instance, W: word::Word>(
|
||||
dma: Peri<'d, impl Channel>,
|
||||
dma_buf: &'d mut [W],
|
||||
@ -748,14 +744,10 @@ pub struct Sai<'d, T: Instance, W: word::Word> {
|
||||
fs: Option<Peri<'d, AnyPin>>,
|
||||
sck: Option<Peri<'d, AnyPin>>,
|
||||
mclk: Option<Peri<'d, AnyPin>>,
|
||||
#[cfg(gpdma)]
|
||||
ring_buffer: PhantomData<W>,
|
||||
#[cfg(not(gpdma))]
|
||||
ring_buffer: RingBuffer<'d, W>,
|
||||
sub_block: WhichSubBlock,
|
||||
}
|
||||
|
||||
#[cfg(not(gpdma))]
|
||||
impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> {
|
||||
/// Create a new SAI driver in asynchronous mode with MCLK.
|
||||
///
|
||||
@ -919,12 +911,16 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> {
|
||||
w.set_tris(config.is_high_impedance_on_inactive_slot);
|
||||
});
|
||||
|
||||
if config.frame_length > 256 {
|
||||
panic!("Frame length must be <= 256");
|
||||
}
|
||||
|
||||
ch.frcr().modify(|w| {
|
||||
w.set_fsoff(config.frame_sync_offset.fsoff());
|
||||
w.set_fspol(config.frame_sync_polarity.fspol());
|
||||
w.set_fsdef(config.frame_sync_definition.fsdef());
|
||||
w.set_fsall(config.frame_sync_active_level_length.0 as u8 - 1);
|
||||
w.set_frl(config.frame_length - 1);
|
||||
w.set_frl((config.frame_length - 1) as u8);
|
||||
});
|
||||
|
||||
ch.slotr().modify(|w| {
|
||||
|
Loading…
Reference in New Issue
Block a user