Merge pull request #2572 from plaes/nrf-spim-chunked-dma
NRF: Implement chunked DMA transfers for SPIM
This commit is contained in:
commit
e9907de39d
2 changed files with 81 additions and 44 deletions
|
@ -17,7 +17,7 @@ use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
|
||||||
use crate::gpio::sealed::Pin as _;
|
use crate::gpio::sealed::Pin as _;
|
||||||
use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
|
use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
|
||||||
use crate::interrupt::typelevel::Interrupt;
|
use crate::interrupt::typelevel::Interrupt;
|
||||||
use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut};
|
use crate::util::{slice_in_ram_or, slice_ptr_len, slice_ptr_parts, slice_ptr_parts_mut};
|
||||||
use crate::{interrupt, pac, Peripheral};
|
use crate::{interrupt, pac, Peripheral};
|
||||||
|
|
||||||
/// SPIM error
|
/// SPIM error
|
||||||
|
@ -25,10 +25,6 @@ use crate::{interrupt, pac, Peripheral};
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Supplied TX buffer overflows EasyDMA transmit buffer
|
|
||||||
TxBufferTooLong,
|
|
||||||
/// Supplied RX buffer overflows EasyDMA receive buffer
|
|
||||||
RxBufferTooLong,
|
|
||||||
/// EasyDMA can only read from data memory, read only buffers in flash will fail.
|
/// EasyDMA can only read from data memory, read only buffers in flash will fail.
|
||||||
BufferNotInRAM,
|
BufferNotInRAM,
|
||||||
}
|
}
|
||||||
|
@ -74,9 +70,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
|
||||||
let s = T::state();
|
let s = T::state();
|
||||||
|
|
||||||
#[cfg(feature = "_nrf52832_anomaly_109")]
|
#[cfg(feature = "_nrf52832_anomaly_109")]
|
||||||
if r.events_started.read().bits() != 0 {
|
{
|
||||||
s.waker.wake();
|
// Ideally we should call this only during the first chunk transfer,
|
||||||
r.intenclr.write(|w| w.started().clear());
|
// but so far calling this every time doesn't seem to be causing any issues.
|
||||||
|
if r.events_started.read().bits() != 0 {
|
||||||
|
s.waker.wake();
|
||||||
|
r.intenclr.write(|w| w.started().clear());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.events_end.read().bits() != 0 {
|
if r.events_end.read().bits() != 0 {
|
||||||
|
@ -209,35 +209,39 @@ impl<'d, T: Instance> Spim<'d, T> {
|
||||||
spim
|
spim
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
|
fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
|
||||||
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
|
|
||||||
// NOTE: RAM slice check for rx is not necessary, as a mutable
|
|
||||||
// slice can only be built from data located in RAM.
|
|
||||||
|
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
|
||||||
let r = T::regs();
|
let r = T::regs();
|
||||||
|
|
||||||
// Set up the DMA write.
|
fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) {
|
||||||
let (ptr, tx_len) = slice_ptr_parts(tx);
|
if total > offset {
|
||||||
if tx_len > EASY_DMA_SIZE {
|
(ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length))
|
||||||
return Err(Error::TxBufferTooLong);
|
} else {
|
||||||
|
(ptr, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
|
|
||||||
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
|
|
||||||
|
|
||||||
// Set up the DMA read.
|
// Set up the DMA read.
|
||||||
let (ptr, rx_len) = slice_ptr_parts_mut(rx);
|
let (ptr, len) = slice_ptr_parts_mut(rx);
|
||||||
if rx_len > EASY_DMA_SIZE {
|
let (rx_ptr, rx_len) = xfer_params(ptr as _, len as _, offset, length);
|
||||||
return Err(Error::RxBufferTooLong);
|
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx_ptr) });
|
||||||
}
|
|
||||||
|
|
||||||
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
|
|
||||||
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
|
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
|
||||||
|
|
||||||
|
// Set up the DMA write.
|
||||||
|
let (ptr, len) = slice_ptr_parts(tx);
|
||||||
|
let (tx_ptr, tx_len) = xfer_params(ptr as _, len as _, offset, length);
|
||||||
|
r.txd.ptr.write(|w| unsafe { w.ptr().bits(tx_ptr) });
|
||||||
|
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
|
||||||
|
|
||||||
|
/*
|
||||||
|
trace!("XFER: offset: {}, length: {}", offset, length);
|
||||||
|
trace!("RX(len: {}, ptr: {=u32:02x})", rx_len, rx_ptr as u32);
|
||||||
|
trace!("TX(len: {}, ptr: {=u32:02x})", tx_len, tx_ptr as u32);
|
||||||
|
*/
|
||||||
|
|
||||||
#[cfg(feature = "_nrf52832_anomaly_109")]
|
#[cfg(feature = "_nrf52832_anomaly_109")]
|
||||||
{
|
if offset == 0 {
|
||||||
let s = T::state();
|
let s = T::state();
|
||||||
|
|
||||||
r.events_started.reset();
|
r.events_started.reset();
|
||||||
|
@ -260,21 +264,32 @@ impl<'d, T: Instance> Spim<'d, T> {
|
||||||
|
|
||||||
// Start SPI transaction.
|
// Start SPI transaction.
|
||||||
r.tasks_start.write(|w| unsafe { w.bits(1) });
|
r.tasks_start.write(|w| unsafe { w.bits(1) });
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
|
fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
|
||||||
self.prepare(rx, tx)?;
|
self.prepare_dma_transfer(rx, tx, offset, length);
|
||||||
|
|
||||||
#[cfg(feature = "_nrf52832_anomaly_109")]
|
#[cfg(feature = "_nrf52832_anomaly_109")]
|
||||||
while let Poll::Pending = self.nrf52832_dma_workaround_status() {}
|
if offset == 0 {
|
||||||
|
while self.nrf52832_dma_workaround_status().is_pending() {}
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for 'end' event.
|
// Wait for 'end' event.
|
||||||
while T::regs().events_end.read().bits() == 0 {}
|
while T::regs().events_end.read().bits() == 0 {}
|
||||||
|
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
|
||||||
|
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
|
||||||
|
// NOTE: RAM slice check for rx is not necessary, as a mutable
|
||||||
|
// slice can only be built from data located in RAM.
|
||||||
|
|
||||||
|
let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
|
||||||
|
for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
|
||||||
|
let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
|
||||||
|
self.blocking_inner_from_ram_chunk(rx, tx, offset, length);
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,22 +302,23 @@ impl<'d, T: Instance> Spim<'d, T> {
|
||||||
tx_ram_buf.copy_from_slice(tx);
|
tx_ram_buf.copy_from_slice(tx);
|
||||||
self.blocking_inner_from_ram(rx, tx_ram_buf)
|
self.blocking_inner_from_ram(rx, tx_ram_buf)
|
||||||
}
|
}
|
||||||
Err(error) => Err(error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
|
async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
|
||||||
self.prepare(rx, tx)?;
|
self.prepare_dma_transfer(rx, tx, offset, length);
|
||||||
|
|
||||||
#[cfg(feature = "_nrf52832_anomaly_109")]
|
#[cfg(feature = "_nrf52832_anomaly_109")]
|
||||||
poll_fn(|cx| {
|
if offset == 0 {
|
||||||
let s = T::state();
|
poll_fn(|cx| {
|
||||||
|
let s = T::state();
|
||||||
|
|
||||||
s.waker.register(cx.waker());
|
s.waker.register(cx.waker());
|
||||||
|
|
||||||
self.nrf52832_dma_workaround_status()
|
self.nrf52832_dma_workaround_status()
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for 'end' event.
|
// Wait for 'end' event.
|
||||||
poll_fn(|cx| {
|
poll_fn(|cx| {
|
||||||
|
@ -316,7 +332,18 @@ impl<'d, T: Instance> Spim<'d, T> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
compiler_fence(Ordering::SeqCst);
|
compiler_fence(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
|
||||||
|
slice_in_ram_or(tx, Error::BufferNotInRAM)?;
|
||||||
|
// NOTE: RAM slice check for rx is not necessary, as a mutable
|
||||||
|
// slice can only be built from data located in RAM.
|
||||||
|
|
||||||
|
let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
|
||||||
|
for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
|
||||||
|
let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
|
||||||
|
self.async_inner_from_ram_chunk(rx, tx, offset, length).await;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +356,6 @@ impl<'d, T: Instance> Spim<'d, T> {
|
||||||
tx_ram_buf.copy_from_slice(tx);
|
tx_ram_buf.copy_from_slice(tx);
|
||||||
self.async_inner_from_ram(rx, tx_ram_buf).await
|
self.async_inner_from_ram(rx, tx_ram_buf).await
|
||||||
}
|
}
|
||||||
Err(error) => Err(error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,8 +554,6 @@ mod eh02 {
|
||||||
impl embedded_hal_1::spi::Error for Error {
|
impl embedded_hal_1::spi::Error for Error {
|
||||||
fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
|
fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
|
||||||
match *self {
|
match *self {
|
||||||
Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
|
|
||||||
Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
|
|
||||||
Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other,
|
Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,19 @@ use core::mem;
|
||||||
const SRAM_LOWER: usize = 0x2000_0000;
|
const SRAM_LOWER: usize = 0x2000_0000;
|
||||||
const SRAM_UPPER: usize = 0x3000_0000;
|
const SRAM_UPPER: usize = 0x3000_0000;
|
||||||
|
|
||||||
|
// #![feature(const_slice_ptr_len)]
|
||||||
|
// https://github.com/rust-lang/rust/issues/71146
|
||||||
|
pub(crate) fn slice_ptr_len<T>(ptr: *const [T]) -> usize {
|
||||||
|
use core::ptr::NonNull;
|
||||||
|
let ptr = ptr.cast_mut();
|
||||||
|
if let Some(ptr) = NonNull::new(ptr) {
|
||||||
|
ptr.len()
|
||||||
|
} else {
|
||||||
|
// We know ptr is null, so we know ptr.wrapping_byte_add(1) is not null.
|
||||||
|
NonNull::new(ptr.wrapping_byte_add(1)).unwrap().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: replace transmutes with core::ptr::metadata once it's stable
|
// TODO: replace transmutes with core::ptr::metadata once it's stable
|
||||||
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) {
|
pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) {
|
||||||
unsafe { mem::transmute(slice) }
|
unsafe { mem::transmute(slice) }
|
||||||
|
|
Loading…
Add table
Reference in a new issue