Skip to content

Commit a3522db

Browse files
bors[bot]Dirbaio
andauthored
Merge #365
365: spi: allow impls returning early, add flush. r=therealprof a=Dirbaio Fixes #264 Depends on #351 - Document that all methods may return early, before bus is idle. - Add a `flush` method to wait for bus to be idle. The alternative is to document that methods MUST wait for bus idle before returning, but that would be bad for performance IMO. Allowing early return means multiple successive writes can "overlap" so that bytes come out back-to-back without gaps, which is great for workloads with tons of small writes, like SPI displays with `embedded-graphics`. Drivers using `SpiDevice` won't have to worry about this, it'll Just Work since Device impls must flush before deasserting CS. Drivers using SpiBus will have to care if they coordinate SPI activity with other GPIO activity. Co-authored-by: Dario Nieuwenhuis <[email protected]>
2 parents 6fda0f2 + 1a94884 commit a3522db

File tree

2 files changed

+61
-5
lines changed

2 files changed

+61
-5
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- The Minimum Supported Rust Version (MSRV) is now 1.54.0
1212
- `spi`: unify all traits into `SpiReadBus`, `SpiWriteBus` and `SpiBus` (read-write).
1313
- `spi`: Add `SpiDevice` trait to represent a single device in a (possibly shared) bus, with managed chip-select (CS) pin.
14+
- `spi`: Clarify that implementations are allowed to return before operations are finished, add `flush` to wait until finished.
1415

1516
## [v1.0.0-alpha.7] - 2022-02-09
1617

src/spi/blocking.rs

+60-5
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,32 @@
140140
//!
141141
//! HALs **must not** add infrastructure for sharing at the [`SpiBus`] level. User code owning a [`SpiBus`] must have the guarantee
142142
//! of exclusive access.
143+
//!
144+
//! # Flushing
145+
//!
146+
//! To improve performance, [`SpiBus`] implementations are allowed to return before the operation is finished, i.e. when the bus is still not
147+
//! idle.
148+
//!
149+
//! When using a [`SpiBus`], call [`flush`](SpiBusFlush::flush) to wait for operations to actually finish. Examples of situations
150+
//! where this is needed are:
151+
//! - To synchronize SPI activity and GPIO activity, for example before deasserting a CS pin.
152+
//! - Before deinitializing the hardware SPI peripheral.
153+
//!
154+
//! When using a [`SpiDevice`], you can still call [`flush`](SpiBusFlush::flush) on the bus within a transaction.
155+
//! It's very rarely needed, because [`transaction`](SpiDevice::transaction) already flushes for you
156+
//! before deasserting CS. For example, you may need it to synchronize with GPIOs other than CS, such as DCX pins
157+
//! sometimes found in SPI displays.
158+
//!
159+
//! For example, for [`write`](SpiBusWrite::write) operations, it is common for hardware SPI peripherals to have a small
160+
//! FIFO buffer, usually 1-4 bytes. Software writes data to the FIFO, and the peripheral sends it on MOSI at its own pace,
161+
//! at the specified SPI frequency. It is allowed for an implementation of [`write`](SpiBusWrite::write) to return as soon
162+
//! as all the data has been written to the FIFO, before it is actually sent. Calling [`flush`](SpiBusFlush::flush) would
163+
//! wait until all the bits have actually been sent, the FIFO is empty, and the bus is idle.
164+
//!
165+
//! This still applies to other operations such as [`read`](SpiBusRead::read) or [`transfer`](SpiBus::transfer). It is less obvious
166+
//! why, because these methods can't return before receiving all the read data. However it's still technically possible
167+
//! for them to return before the bus is idle. For example, assuming SPI mode 0, the last bit is sampled on the first (rising) edge
168+
//! of SCK, at which point a method could return, but the second (falling) SCK edge still has to happen before the bus is idle.
143169
144170
use core::fmt::Debug;
145171

@@ -162,6 +188,7 @@ pub trait SpiDevice: ErrorType {
162188
/// - Locks the bus
163189
/// - Asserts the CS (Chip Select) pin.
164190
/// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device.
191+
/// - [Flushes](SpiBusFlush::flush) the bus.
165192
/// - Deasserts the CS pin.
166193
/// - Unlocks the bus.
167194
///
@@ -236,12 +263,29 @@ impl<T: SpiDevice> SpiDevice for &mut T {
236263
}
237264
}
238265

266+
/// Flush support for SPI bus
267+
pub trait SpiBusFlush: ErrorType {
268+
/// Blocks until all operations have completed and the bus is idle.
269+
///
270+
/// See the [module-level documentation](self) for important usage information.
271+
fn flush(&mut self) -> Result<(), Self::Error>;
272+
}
273+
274+
impl<T: SpiBusFlush> SpiBusFlush for &mut T {
275+
fn flush(&mut self) -> Result<(), Self::Error> {
276+
T::flush(self)
277+
}
278+
}
279+
239280
/// Read-only SPI bus
240-
pub trait SpiBusRead<Word: Copy = u8>: ErrorType {
281+
pub trait SpiBusRead<Word: Copy = u8>: SpiBusFlush {
241282
/// Reads `words` from the slave.
242283
///
243284
/// The word value sent on MOSI during reading is implementation-defined,
244285
/// typically `0x00`, `0xFF`, or configurable.
286+
///
287+
/// Implementations are allowed to return before the operation is
288+
/// complete. See the [module-level documentation](self) for detials.
245289
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
246290
}
247291

@@ -252,8 +296,11 @@ impl<T: SpiBusRead<Word>, Word: Copy> SpiBusRead<Word> for &mut T {
252296
}
253297

254298
/// Write-only SPI bus
255-
pub trait SpiBusWrite<Word: Copy = u8>: ErrorType {
299+
pub trait SpiBusWrite<Word: Copy = u8>: SpiBusFlush {
256300
/// Writes `words` to the slave, ignoring all the incoming words
301+
///
302+
/// Implementations are allowed to return before the operation is
303+
/// complete. See the [module-level documentation](self) for detials.
257304
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
258305
}
259306

@@ -277,11 +324,17 @@ pub trait SpiBus<Word: Copy = u8>: SpiBusRead<Word> + SpiBusWrite<Word> {
277324
/// incoming words after `read` has been filled will be discarded. If `write` is shorter,
278325
/// the value of words sent in MOSI after all `write` has been sent is implementation-defined,
279326
/// typically `0x00`, `0xFF`, or configurable.
327+
///
328+
/// Implementations are allowed to return before the operation is
329+
/// complete. See the [module-level documentation](self) for detials.
280330
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
281331

282332
/// Writes and reads simultaneously. The contents of `words` are
283333
/// written to the slave, and the received words are stored into the same
284334
/// `words` buffer, overwriting it.
335+
///
336+
/// Implementations are allowed to return before the operation is
337+
/// complete. See the [module-level documentation](self) for detials.
285338
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
286339
}
287340

@@ -335,15 +388,15 @@ impl<BUS, CS> ExclusiveDevice<BUS, CS> {
335388

336389
impl<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
337390
where
338-
BUS: ErrorType,
391+
BUS: SpiBusFlush,
339392
CS: OutputPin,
340393
{
341394
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
342395
}
343396

344397
impl<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS>
345398
where
346-
BUS: ErrorType,
399+
BUS: SpiBusFlush,
347400
CS: OutputPin,
348401
{
349402
type Bus = BUS;
@@ -356,10 +409,12 @@ where
356409

357410
let f_res = f(&mut self.bus);
358411

359-
// If the closure fails, it's important to still deassert CS.
412+
// On failure, it's important to still flush and deassert CS.
413+
let flush_res = self.bus.flush();
360414
let cs_res = self.cs.set_high();
361415

362416
let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?;
417+
flush_res.map_err(ExclusiveDeviceError::Spi)?;
363418
cs_res.map_err(ExclusiveDeviceError::Cs)?;
364419

365420
Ok(f_res)

0 commit comments

Comments
 (0)