Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- C61: Add SHA and ECC support (#5354)
- A `PsramMode` option has been introduced for ESP32-S3. The default mode is `Auto` which will try to detect if PSRAM works via Octal or Quad SPI and configure it accordingly. (#5334)
- Add I2S loopback logic to the peripheral driver. (#5349)
- SPI master: added `min_async_transfer_size` config option to force small transfers to use blocking/CPU driven mode. (#5350)
Comment thread
bugadani marked this conversation as resolved.

### Changed

Expand Down
75 changes: 75 additions & 0 deletions esp-hal/src/spi/master/dma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ impl<'d> SpiDma<'d, Async> {
self.wait_for_idle_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
return self.driver().read(words);
}

let mut descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_buffer = match DmaOperationKind::for_read(words) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -454,6 +459,12 @@ impl<'d> SpiDma<'d, Async> {
self.wait_for_idle_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
self.driver().write(words)?;
return self.driver().flush();
}

let mut descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_buffer = match DmaOperationKind::for_write(words) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -489,6 +500,18 @@ impl<'d> SpiDma<'d, Async> {
self.wait_for_idle_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(read.len().max(write.len())) {
self.dma_driver().disable_dma();
if read.is_empty() {
self.driver().write(write)?;
return self.driver().flush();
} else if write.is_empty() {
return self.driver().read(read);
} else {
return self.driver().transfer(read, write);
}
}

let mut rx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_rx_buffer = match DmaOperationKind::for_read(read) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -557,6 +580,11 @@ impl<'d> SpiDma<'d, Async> {
self.wait_for_idle_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
return self.driver().transfer_in_place(words);
}

let mut rx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut tx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let (mut maybe_copy_rx_buffer, mut maybe_copy_tx_buffer) =
Expand Down Expand Up @@ -768,6 +796,15 @@ impl<'d, Dm> SpiDma<'d, Dm>
where
Dm: DriverMode,
{
fn use_blocking_transfer(&self, transfer_size: usize) -> bool {
let threshold = self
.spi
.state()
.min_async_transfer_size
.load(Ordering::Relaxed);
threshold > 0 && transfer_size < threshold
}

fn spi(&self) -> &SpiWrapper<'_> {
&self.spi
}
Expand Down Expand Up @@ -1222,6 +1259,11 @@ where
self.wait_for_idle();
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
return self.driver().read(words);
}

let mut descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_buffer = match DmaOperationKind::for_read(words) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -1257,6 +1299,12 @@ where
self.wait_for_idle();
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
self.driver().write(words)?;
return self.driver().flush();
}

let mut descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_buffer = match DmaOperationKind::for_write(words) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -1286,6 +1334,18 @@ where
self.wait_for_idle();
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(read.len().max(write.len())) {
self.dma_driver().disable_dma();
if read.is_empty() {
self.driver().write(write)?;
return self.driver().flush();
} else if write.is_empty() {
return self.driver().read(read);
} else {
return self.driver().transfer(read, write);
}
}

let mut rx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut maybe_copy_rx_buffer = match DmaOperationKind::for_read(read) {
DmaOperationKind::Copied => {
Expand Down Expand Up @@ -1348,6 +1408,11 @@ where
self.wait_for_idle();
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.dma_driver().disable_dma();
return self.driver().transfer_in_place(words);
}

let mut rx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let mut tx_descriptors = [DmaDescriptor::EMPTY; LINK_DESCRIPTOR_COUNT];
let (mut maybe_copy_rx_buffer, mut maybe_copy_tx_buffer) =
Expand Down Expand Up @@ -1563,6 +1628,16 @@ impl DmaDriver {
self.driver.update();
}

fn disable_dma(&self) {
#[cfg(dma_kind = "gdma")]
self.regs().dma_conf().modify(|_, w| {
w.dma_tx_ena().clear_bit();
w.dma_rx_ena().clear_bit()
});
Comment thread
bugadani marked this conversation as resolved.

// PDMA: nothing to do
}

fn regs(&self) -> &RegisterBlock {
self.driver.regs()
}
Expand Down
54 changes: 54 additions & 0 deletions esp-hal/src/spi/master/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ use core::{
marker::PhantomData,
mem::MaybeUninit,
pin::Pin,
sync::atomic::{AtomicUsize, Ordering},
task::{Context, Poll},
};

Expand Down Expand Up @@ -472,6 +473,20 @@ pub struct Config {

/// Bit order of the written data.
write_bit_order: BitOrder,

/// Minimum transfer size in bytes below which CPU-driven (blocking) I/O
/// is used instead of async or DMA transfers.
///
/// This can reduce overhead for small transfers where DMA setup or
/// async context-switch cost exceeds the benefit. For
/// [`SpiDma`][crate::spi::master::dma::SpiDma], the threshold applies in
/// both blocking and async DMA modes: when met, DMA is disabled and the
/// transfer is performed by the CPU.
///
/// A value of `0` (the default) disables the threshold — all transfers use
/// the driver's default method.
#[builder_lite(unstable)]
min_async_transfer_size: usize,
Comment on lines +477 to +489
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have a mile-long field name, or just accept this...

}

impl Default for Config {
Expand All @@ -483,6 +498,7 @@ impl Default for Config {
mode: Mode::_0,
read_bit_order: BitOrder::MsbFirst,
write_bit_order: BitOrder::MsbFirst,
min_async_transfer_size: 0,
};

this.reg = this.recalculate();
Expand Down Expand Up @@ -921,6 +937,10 @@ impl<'d> Spi<'d, Async> {
self.driver().flush_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
return self.driver().transfer_in_place(words);
}

self.driver().transfer_in_place_async(words).await
}

Expand All @@ -932,6 +952,10 @@ impl<'d> Spi<'d, Async> {
self.driver().flush_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
return self.driver().read(words);
}

self.driver().read_async(words).await
}

Expand All @@ -941,6 +965,11 @@ impl<'d> Spi<'d, Async> {
self.driver().flush_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(words.len()) {
self.driver().write(words)?;
return self.driver().flush();
}

self.driver().write_async(words).await
}
}
Expand Down Expand Up @@ -1393,6 +1422,15 @@ where
self.driver().flush()
}

fn use_blocking_transfer(&self, transfer_size: usize) -> bool {
let threshold = self
.spi
.state()
.min_async_transfer_size
.load(Ordering::Relaxed);
threshold > 0 && transfer_size < threshold
}

fn driver(&self) -> Driver {
Driver {
info: self.spi.info(),
Expand Down Expand Up @@ -1478,6 +1516,17 @@ impl SpiBusAsync for Spi<'_, Async> {
self.driver().flush_async().await;
self.driver().setup_full_duplex()?;

if self.use_blocking_transfer(read.len().max(write.len())) {
if read.is_empty() {
self.driver().write(write)?;
return self.driver().flush();
} else if write.is_empty() {
return self.driver().read(read);
} else {
return self.driver().transfer(read, write);
}
}

if read.is_empty() {
self.driver().write_async(write).await
} else if write.is_empty() {
Expand Down Expand Up @@ -1867,6 +1916,9 @@ impl Driver {
self.ch_bus_freq(config)?;
self.set_bit_order(config.read_bit_order, config.write_bit_order);
self.set_data_mode(config.mode);
self.state
.min_async_transfer_size
.store(config.min_async_transfer_size, Ordering::Relaxed);

#[cfg(esp32)]
self.calculate_half_duplex_values(config);
Expand Down Expand Up @@ -2482,6 +2534,7 @@ for_each_spi_master! {
static STATE: State = State {
waker: AtomicWaker::new(),
pins: UnsafeCell::new(MaybeUninit::uninit()),
min_async_transfer_size: AtomicUsize::new(0),

#[cfg(esp32)]
esp32_hack: Esp32Hack {
Expand All @@ -2508,6 +2561,7 @@ impl QspiInstance for AnySpi<'_> {}
pub struct State {
waker: AtomicWaker,
pins: UnsafeCell<MaybeUninit<SpiPinGuard>>,
min_async_transfer_size: AtomicUsize,

#[cfg(esp32)]
esp32_hack: Esp32Hack,
Expand Down
Loading