use core::future::poll_fn;
use core::task::Poll;
use embassy_hal_internal::{Peri, PeripheralType};
use embassy_sync::waitqueue::AtomicWaker;
use embedded_hal::spi::{ErrorKind, ErrorType, MODE_0, Mode, Phase, Polarity};
use pac::peripherals::spi::SPI;
use crate::hal::timeout::Timeout;
use crate::pac;
const DSIZE_8BIT: u8 = 7;
const MAX_TSIZE: usize = u16::MAX as usize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Overrun,
ModeFault,
Frame,
Timeout,
}
impl embedded_hal::spi::Error for Error {
fn kind(&self) -> ErrorKind {
match self {
Error::Overrun => ErrorKind::Overrun,
Error::ModeFault => ErrorKind::ModeFault,
Error::Frame => ErrorKind::FrameFormat,
Error::Timeout => ErrorKind::Other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub frequency: u32,
pub mode: Mode,
pub lsb_first: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
frequency: 1_000_000,
mode: MODE_0,
lsb_first: false,
}
}
}
const fn mbr(clock_hz: u32, frequency: u32) -> u8 {
assert!(frequency > 0);
let mut mbr = 0u8;
while mbr < 7 && clock_hz / (1 << (mbr + 1)) > frequency {
mbr += 1;
}
mbr
}
pub struct State {
waker: AtomicWaker,
}
impl State {
pub(crate) const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
fn on_interrupt(&self, r: SPI) {
let sr = r.SR().read();
let ier = r.IER().read();
let fire = (ier.TXPIE() && sr.TXP())
|| (ier.RXPIE() && sr.RXP())
|| (ier.EOTIE() && sr.EOT())
|| (ier.OVRIE() && sr.OVR())
|| (ier.MODFIE() && sr.MODF())
|| (ier.TIFREIE() && sr.TIFRE());
if fire {
r.IER().modify(|w| {
w.set_TXPIE(false);
w.set_RXPIE(false);
w.set_EOTIE(false);
w.set_OVRIE(false);
w.set_MODFIE(false);
w.set_TIFREIE(false);
});
self.waker.wake();
}
}
}
pub(crate) trait SealedInstance {
fn regs() -> SPI;
fn state() -> &'static State;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {
const IRQ: Option<u16>;
}
macro_rules! impl_spi {
($instance:ident, $base:expr, $irq:expr) => {
impl crate::spi::SealedInstance for crate::peripherals::$instance {
#[inline]
fn regs() -> crate::pac::peripherals::spi::SPI {
unsafe { crate::pac::peripherals::spi::SPI::from_ptr($base as *mut ()) }
}
#[inline]
fn state() -> &'static crate::spi::State {
static STATE: crate::spi::State = crate::spi::State::new();
&STATE
}
}
impl crate::spi::Instance for crate::peripherals::$instance {
const IRQ: ::core::option::Option<u16> = $irq;
}
};
}
pub(crate) use impl_spi;
pub fn on_interrupt<T: Instance>() {
T::state().on_interrupt(T::regs());
}
pub struct Spi {
regs: SPI,
state: &'static State,
timeout: Timeout,
}
impl Spi {
pub fn new<T: Instance>(peri: Peri<'static, T>, clock_hz: u32, config: Config) -> Self {
let _ = peri;
let r = T::regs();
r.CR1().modify(|w| w.set_SPE(false));
let (cpol, cpha) = mode_bits(config.mode);
r.CFG1().modify(|w| {
w.set_DSIZE(DSIZE_8BIT);
w.set_FTHLV(0);
w.set_MBR(mbr(clock_hz, config.frequency));
});
r.CFG2().modify(|w| {
w.set_MASTER(true);
w.set_CPOL(cpol);
w.set_CPHA(cpha);
w.set_LSBFRST(config.lsb_first);
w.set_SSM(true);
w.set_COMM(0);
w.set_AFCNTR(true);
});
r.CR1().modify(|w| w.set_SSI(true));
Self {
regs: r,
state: T::state(),
timeout: Timeout::DEFAULT,
}
}
pub fn set_timeout(&mut self, timeout: Timeout) {
self.timeout = timeout;
}
fn wait_blocking(&self, ready: impl Fn(&SPI) -> bool) -> Result<(), Error> {
let mut deadline = self.timeout.start();
loop {
self.check_error()?;
if ready(&self.regs) {
return Ok(());
}
deadline.tick().map_err(|_| Error::Timeout)?;
}
}
fn txdr(&self) -> *mut u8 {
self.regs.TXDR().as_ptr() as *mut u8
}
fn rxdr(&self) -> *const u8 {
self.regs.RXDR().as_ptr() as *const u8
}
fn check_error(&self) -> Result<(), Error> {
let sr = self.regs.SR().read();
if sr.OVR() {
self.regs.IFCR().write(|w| w.set_OVRC(true));
return Err(Error::Overrun);
}
if sr.MODF() {
self.regs.IFCR().write(|w| w.set_MODFC(true));
return Err(Error::ModeFault);
}
if sr.TIFRE() {
self.regs.IFCR().write(|w| w.set_TIFREC(true));
return Err(Error::Frame);
}
Ok(())
}
fn start(&self, len: usize) {
self.regs.CR1().modify(|w| w.set_SPE(false));
self.regs.CR2().modify(|w| w.set_TSIZE(len as u16));
self.regs.CR1().modify(|w| w.set_SPE(true));
self.regs.CR1().modify(|w| w.set_CSTART(true));
}
fn finish(&self) {
self.regs.IFCR().write(|w| {
w.set_EOTC(true);
w.set_TXTFC(true);
});
self.regs.CR1().modify(|w| w.set_SPE(false));
}
fn chunk_blocking(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
let len = read.len().max(write.len());
self.start(len);
let guard = AbortGuard::new(self.regs);
for i in 0..len {
let tx = write.get(i).copied().unwrap_or(0);
self.wait_blocking(|r| r.SR().read().TXP())?;
unsafe { self.txdr().write_volatile(tx) };
self.wait_blocking(|r| r.SR().read().RXP())?;
let rx = unsafe { self.rxdr().read_volatile() };
if let Some(slot) = read.get_mut(i) {
*slot = rx;
}
}
self.finish();
guard.defuse();
Ok(())
}
fn transfer_blocking(&mut self, mut read: &mut [u8], mut write: &[u8]) -> Result<(), Error> {
loop {
let len = read.len().max(write.len());
if len == 0 {
return Ok(());
}
let chunk = len.min(MAX_TSIZE);
let (r_head, r_tail) = read.split_at_mut(chunk.min(read.len()));
let (w_head, w_tail) = write.split_at(chunk.min(write.len()));
self.chunk_blocking(r_head, w_head)?;
read = r_tail;
write = w_tail;
}
}
async fn wait_flag(
&self,
arm: fn(&SPI),
ready: impl Fn(&Self) -> Option<Result<(), Error>>,
) -> Result<(), Error> {
poll_fn(|cx| {
if let Some(res) = ready(self) {
return Poll::Ready(res);
}
self.state.waker.register(cx.waker());
critical_section::with(|_| arm(&self.regs));
if let Some(res) = ready(self) {
return Poll::Ready(res);
}
Poll::Pending
})
.await
}
fn arm_errors(r: &SPI) {
r.IER().modify(|w| {
w.set_OVRIE(true);
w.set_MODFIE(true);
w.set_TIFREIE(true);
});
}
async fn wait_txp(&self) -> Result<(), Error> {
self.wait_flag(
|r| {
Self::arm_errors(r);
r.IER().modify(|w| w.set_TXPIE(true));
},
|s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.SR().read().TXP() => Some(Ok(())),
Ok(()) => None,
},
)
.await
}
async fn wait_rxp(&self) -> Result<(), Error> {
self.wait_flag(
|r| {
Self::arm_errors(r);
r.IER().modify(|w| w.set_RXPIE(true));
},
|s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.SR().read().RXP() => Some(Ok(())),
Ok(()) => None,
},
)
.await
}
async fn chunk_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
let len = read.len().max(write.len());
self.start(len);
let guard = AbortGuard::new(self.regs);
for i in 0..len {
let tx = write.get(i).copied().unwrap_or(0);
self.wait_txp().await?;
unsafe { self.txdr().write_volatile(tx) };
self.wait_rxp().await?;
let rx = unsafe { self.rxdr().read_volatile() };
if let Some(slot) = read.get_mut(i) {
*slot = rx;
}
}
self.finish();
guard.defuse();
Ok(())
}
async fn transfer_async(&mut self, mut read: &mut [u8], mut write: &[u8]) -> Result<(), Error> {
loop {
let len = read.len().max(write.len());
if len == 0 {
return Ok(());
}
let chunk = len.min(MAX_TSIZE);
let (r_head, r_tail) = read.split_at_mut(chunk.min(read.len()));
let (w_head, w_tail) = write.split_at(chunk.min(write.len()));
self.chunk_async(r_head, w_head).await?;
read = r_tail;
write = w_tail;
}
}
}
const ABORT_SPINS: u32 = 200_000;
fn abort(r: SPI) {
critical_section::with(|_| {
r.IER().write(|w| {
w.set_TXPIE(false);
w.set_RXPIE(false);
w.set_EOTIE(false);
w.set_OVRIE(false);
w.set_MODFIE(false);
w.set_TIFREIE(false);
})
});
if r.CR1().read().CSTART() {
r.CR1().modify(|w| w.set_CSUSP(true));
let mut spins = ABORT_SPINS;
while spins > 0 && !r.SR().read().SUSP() && !r.SR().read().EOT() {
spins -= 1;
}
}
r.IFCR().write(|w| {
w.set_EOTC(true);
w.set_TXTFC(true);
w.set_UDRC(true);
w.set_OVRC(true);
w.set_TIFREC(true);
w.set_MODFC(true);
w.set_SUSPC(true);
});
r.CR1().modify(|w| w.set_SPE(false));
}
struct AbortGuard {
regs: SPI,
armed: bool,
}
impl AbortGuard {
fn new(regs: SPI) -> Self {
Self { regs, armed: true }
}
fn defuse(mut self) {
self.armed = false;
}
}
impl Drop for AbortGuard {
fn drop(&mut self) {
if self.armed {
abort(self.regs);
}
}
}
fn mode_bits(mode: Mode) -> (bool, bool) {
(
matches!(mode.polarity, Polarity::IdleHigh),
matches!(mode.phase, Phase::CaptureOnSecondTransition),
)
}
impl ErrorType for Spi {
type Error = Error;
}
impl embedded_hal::spi::SpiBus<u8> for Spi {
fn read(&mut self, words: &mut [u8]) -> Result<(), Error> {
self.transfer_blocking(words, &[])
}
fn write(&mut self, words: &[u8]) -> Result<(), Error> {
self.transfer_blocking(&mut [], words)
}
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
self.transfer_blocking(read, write)
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> {
let len = words.len();
let mut offset = 0;
while offset < len {
let chunk = (len - offset).min(MAX_TSIZE);
let end = offset + chunk;
self.chunk_in_place_blocking(&mut words[offset..end])?;
offset = end;
}
Ok(())
}
fn flush(&mut self) -> Result<(), Error> {
Ok(())
}
}
impl Spi {
fn chunk_in_place_blocking(&mut self, words: &mut [u8]) -> Result<(), Error> {
self.start(words.len());
let guard = AbortGuard::new(self.regs);
for word in words.iter_mut() {
self.wait_blocking(|r| r.SR().read().TXP())?;
unsafe { self.txdr().write_volatile(*word) };
self.wait_blocking(|r| r.SR().read().RXP())?;
*word = unsafe { self.rxdr().read_volatile() };
}
self.finish();
guard.defuse();
Ok(())
}
async fn chunk_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
self.start(words.len());
let guard = AbortGuard::new(self.regs);
for word in words.iter_mut() {
self.wait_txp().await?;
unsafe { self.txdr().write_volatile(*word) };
self.wait_rxp().await?;
*word = unsafe { self.rxdr().read_volatile() };
}
self.finish();
guard.defuse();
Ok(())
}
}
impl embedded_hal_async::spi::SpiBus<u8> for Spi {
async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> {
self.transfer_async(words, &[]).await
}
async fn write(&mut self, words: &[u8]) -> Result<(), Error> {
self.transfer_async(&mut [], words).await
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> {
self.transfer_async(read, write).await
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> {
let len = words.len();
let mut offset = 0;
while offset < len {
let chunk = (len - offset).min(MAX_TSIZE);
let end = offset + chunk;
self.chunk_in_place_async(&mut words[offset..end]).await?;
offset = end;
}
Ok(())
}
async fn flush(&mut self) -> Result<(), Error> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::mbr;
#[test]
fn mbr_picks_divider_within_frequency() {
assert_eq!(mbr(100_000_000, 1_000_000), 6);
}
#[test]
fn mbr_saturates_and_floors() {
assert_eq!(mbr(1_000_000, 100_000_000), 0);
assert_eq!(mbr(u32::MAX, 1), 7);
}
}