use crate::peripherals::{Spi0, Spi1};
use core::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpiMode {
Mode0,
Mode1,
Mode2,
Mode3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SpiHz(u32);
impl SpiHz {
pub const ONE_MHZ: SpiHz = SpiHz(1_000_000);
pub const fn try_from_hz(hz: u32) -> Option<Self> {
if hz == 0 {
return None;
}
let div = crate::soc::chip::SPI_CLOCK_HZ / hz;
if div < 2 || div > 0xFFFE {
return None;
}
Some(SpiHz(hz))
}
pub const fn hz(self) -> u32 {
self.0
}
const fn to_sckdv(self) -> u32 {
sckdv(crate::soc::chip::SPI_CLOCK_HZ, self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct DataBits(u8);
impl DataBits {
pub const EIGHT: DataBits = DataBits(8);
pub const SIXTEEN: DataBits = DataBits(16);
pub const fn new(bits: u8) -> Option<Self> {
if bits >= 4 && bits <= 16 { Some(DataBits(bits)) } else { None }
}
pub const fn bits(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub frequency: SpiHz,
pub mode: SpiMode,
pub data_bits: DataBits,
}
impl Default for Config {
fn default() -> Self {
Self { frequency: SpiHz::ONE_MHZ, mode: SpiMode::Mode0, data_bits: DataBits::EIGHT }
}
}
pub struct Spi<'d, T> {
idx: u8,
_peripheral: PhantomData<&'d T>,
}
fn spi_regs(idx: u8) -> &'static crate::soc::pac::spi0::RegisterBlock {
unsafe {
match idx {
0 => &*Spi0::ptr(),
1 => &*Spi1::ptr(),
_ => unreachable!(),
}
}
}
const fn sckdv(pclk: u32, freq: u32) -> u32 {
let freq = if freq == 0 { 1 } else { freq };
let div = match pclk / freq {
d if d < 2 => 2,
d if d > 0xFFFF => 0xFFFF,
d => d,
};
div & !1 }
const SPI_WAIT_LOOPS: u32 = 1_000_000;
#[inline]
fn wait_until(mut ready: impl FnMut() -> bool) -> Result<(), SpiError> {
let mut n = SPI_WAIT_LOOPS;
while !ready() {
n -= 1;
if n == 0 {
return Err(SpiError::Timeout);
}
}
Ok(())
}
impl<'d> Spi<'d, Spi0<'d>> {
pub fn new_spi0(_spi: Spi0<'d>, config: Config) -> Self {
configure_spi(0, &config);
Self { idx: 0, _peripheral: PhantomData }
}
}
impl<'d> Spi<'d, Spi1<'d>> {
#[instability::unstable]
pub fn new_spi1(_spi: Spi1<'d>, config: Config) -> Self {
configure_spi(1, &config);
Self { idx: 1, _peripheral: PhantomData }
}
}
#[cfg(feature = "chip-ws63")]
const SPI_PLL_ROOT_MHZ: u32 = 480;
#[cfg(feature = "chip-ws63")]
fn configure_spi_source_clock() {
let ssi_mhz = (crate::soc::chip::SPI_CLOCK_HZ / 1_000_000).max(1);
let div = (SPI_PLL_ROOT_MHZ / ssi_mhz).clamp(1, 0x1F);
let cldo = unsafe { &*crate::peripherals::CldoCrg::ptr() };
cldo.div_ctl3().modify(|_, w| w.spi_load_div_en().clear_bit());
cldo.div_ctl3().modify(|_, w| unsafe { w.spi_div2_cfg().bits((div & 0x1F) as u8).spi_div1_cfg().bits(1) });
cldo.div_ctl3().modify(|_, w| w.spi_load_div_en().set_bit());
cldo.cken_ctl1().modify(|_, w| w.spi_cken().clear_bit());
cldo.clk_sel().modify(|_, w| w.spi_clk_sel().set_bit());
cldo.cken_ctl1().modify(|_, w| w.spi_cken().set_bit());
}
fn configure_spi(idx: u8, config: &Config) {
#[cfg(feature = "chip-ws63")]
configure_spi_source_clock();
let r = spi_regs(idx);
r.spi_er().write(|w| unsafe { w.bits(0) });
r.spi_brs().write(|w| unsafe { w.bits(config.frequency.to_sckdv()) });
let mut ctra = 0u32;
match config.mode {
SpiMode::Mode0 => {}
SpiMode::Mode1 => ctra |= 1 << 3,
SpiMode::Mode2 => ctra |= 1 << 4,
SpiMode::Mode3 => ctra |= (1 << 3) | (1 << 4),
}
ctra |= ((config.data_bits.bits() - 1) as u32) << 13;
r.spi_ctra().write(|w| unsafe { w.bits(ctra) });
r.spi_slenr().write(|w| unsafe { w.bits(0x1) });
r.spi_er().write(|w| unsafe { w.bits(0x1) });
}
impl<'d, T> Spi<'d, T> {
pub fn transfer(&mut self, write: &[u8], read: &mut [u8]) -> Result<(), SpiError> {
let r = spi_regs(self.idx);
let len = write.len().max(read.len());
for i in 0..len {
let tx = if i < write.len() { write[i] as u32 } else { 0 };
wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
unsafe { r.spi_dr().write(|w| w.bits(tx)) };
wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
let rx = r.spi_dr().read().bits();
if i < read.len() {
read[i] = rx as u8;
}
}
Ok(())
}
pub fn write(&mut self, data: &[u8]) -> Result<(), SpiError> {
let r = spi_regs(self.idx);
for &byte in data {
wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
unsafe { r.spi_dr().write(|w| w.bits(byte as u32)) };
}
Ok(())
}
#[instability::unstable]
pub unsafe fn register_block(&self) -> &'static crate::soc::pac::spi0::RegisterBlock {
spi_regs(self.idx)
}
pub fn wait_idle(&self) -> Result<(), SpiError> {
let r = spi_regs(self.idx);
wait_until(|| r.spi_wsr().read().txfe().bit_is_set())?;
wait_until(|| !r.spi_wsr().read().busy().bit_is_set())?;
Ok(())
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
pub fn with_dma(self, dma: crate::dma::DmaDriver<'d, crate::dma::Dma0>) -> SpiDma<'d, T> {
SpiDma { idx: self.idx, dma, _p: PhantomData }
}
}
#[cfg(feature = "chip-ws63")]
#[instability::unstable]
pub struct SpiDma<'d, T> {
idx: u8,
dma: crate::dma::DmaDriver<'d, crate::dma::Dma0>,
_p: PhantomData<&'d T>,
}
#[cfg(feature = "chip-ws63")]
impl<'d, T> SpiDma<'d, T> {
fn dr_addr(&self) -> u32 {
let r = spi_regs(self.idx);
r.spi_dr() as *const _ as u32
}
fn tx_peri(&self) -> crate::dma::DmaPeripheral {
match self.idx {
0 => crate::dma::DmaPeripheral::Spi0Tx,
1 => crate::dma::DmaPeripheral::Spi1Tx,
_ => unreachable!(),
}
}
fn rx_peri(&self) -> crate::dma::DmaPeripheral {
match self.idx {
0 => crate::dma::DmaPeripheral::Spi0Rx,
1 => crate::dma::DmaPeripheral::Spi1Rx,
_ => unreachable!(),
}
}
#[instability::unstable]
pub fn write_dma<B: embedded_dma::ReadBuffer<Word = u8>>(
&mut self,
ch: crate::dma::DmaChannel,
buf: B,
) -> Result<B, SpiError> {
use crate::dma::{DmaChannelConfig, DmaFrame, DmaTransferSize};
let r = spi_regs(self.idx);
let (ptr, beats) = unsafe { buf.read_buffer() };
if beats > 0xFFF {
return Err(SpiError::BufferTooLong);
}
let size = DmaTransferSize::from_beats(beats).ok_or(SpiError::BufferTooLong)?;
let bytes = beats;
let dr = self.dr_addr();
let _ = ();
unsafe {
r.spi_dtdl().write(|w| w.bits(4));
r.spi_drdl().write(|w| w.bits(0));
}
unsafe { crate::cache::clean_range(ptr as usize, bytes) };
let cfg = DmaChannelConfig::default().mem_to_peripheral(self.tx_peri()).with_width(DmaFrame::Byte);
let chn = ch.logical();
self.dma.configure_channel_raw(chn, ptr as u32, dr, size, &cfg);
r.spi_dcr().modify(|_, w| w.tdmae().set_bit());
let mut n = SPI_WAIT_LOOPS;
while self.dma.channel_enabled_raw(chn) {
n -= 1;
if n == 0 {
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit());
self.dma.halt_channel_raw(chn);
let mut m = SPI_WAIT_LOOPS;
while self.dma.channel_active_raw(chn) {
m -= 1;
if m == 0 {
break;
}
core::hint::spin_loop();
}
self.dma.disable_channel_raw(chn);
return Err(SpiError::Timeout);
}
core::hint::spin_loop();
}
while r.spi_wsr().read().rxfne().bit_is_set() {
let _ = r.spi_dr().read().bits();
}
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit());
Ok(buf)
}
#[cfg(all(feature = "async", feature = "unstable"))]
#[instability::unstable]
pub async fn write_dma_async<B: embedded_dma::ReadBuffer<Word = u8>>(
&mut self,
ch: crate::dma::DmaChannel,
buf: B,
) -> Result<B, SpiError> {
use crate::dma::{DmaChannelConfig, DmaFrame, DmaTransferSize};
let r = spi_regs(self.idx);
let (ptr, beats) = unsafe { buf.read_buffer() };
if beats > 0xFFF {
return Err(SpiError::BufferTooLong);
}
let size = DmaTransferSize::from_beats(beats).ok_or(SpiError::BufferTooLong)?;
let bytes = beats;
let dr = self.dr_addr();
unsafe {
r.spi_dtdl().write(|w| w.bits(4));
r.spi_drdl().write(|w| w.bits(0));
}
unsafe { crate::cache::clean_range(ptr as usize, bytes) };
let cfg = DmaChannelConfig::default()
.mem_to_peripheral(self.tx_peri())
.with_width(DmaFrame::Byte)
.with_transfer_int(true);
let chn = ch.logical();
self.dma.configure_channel_raw(chn, ptr as u32, dr, size, &cfg);
r.spi_dcr().modify(|_, w| w.tdmae().set_bit());
self.dma.wait_transfer_done(&ch).await;
while r.spi_wsr().read().rxfne().bit_is_set() {
let _ = r.spi_dr().read().bits();
}
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit());
Ok(buf)
}
#[instability::unstable]
pub fn transfer_dma<RB: embedded_dma::WriteBuffer<Word = u8>, TB: embedded_dma::ReadBuffer<Word = u8>>(
&mut self,
tx_ch: crate::dma::DmaChannel,
rx_ch: crate::dma::DmaChannel,
mut read: RB,
write: TB,
) -> Result<(RB, TB), SpiError> {
use crate::dma::{DmaChannelConfig, DmaFrame, DmaTransferSize};
let r = spi_regs(self.idx);
let (tx_ptr, tx_beats) = unsafe { write.read_buffer() };
let (rx_ptr, rx_beats) = unsafe { read.write_buffer() };
let beats = tx_beats.min(rx_beats);
if tx_beats > 0xFFF || rx_beats > 0xFFF {
return Err(SpiError::BufferTooLong);
}
let size = DmaTransferSize::from_beats(beats).ok_or(SpiError::BufferTooLong)?;
let bytes = beats;
let dr = self.dr_addr();
unsafe {
r.spi_dtdl().write(|w| w.bits(4));
r.spi_drdl().write(|w| w.bits(0));
}
unsafe { crate::cache::clean_range(tx_ptr as usize, bytes) };
let tx_cfg = DmaChannelConfig::default().mem_to_peripheral(self.tx_peri()).with_width(DmaFrame::Byte);
let rx_cfg = DmaChannelConfig::default().peripheral_to_mem(self.rx_peri()).with_width(DmaFrame::Byte);
let tx_chn = tx_ch.logical();
let rx_chn = rx_ch.logical();
self.dma.configure_channel_raw(tx_chn, tx_ptr as u32, dr, size, &tx_cfg);
self.dma.configure_channel_raw(rx_chn, dr, rx_ptr as u32, size, &rx_cfg);
r.spi_dcr().modify(|_, w| w.tdmae().set_bit().rdmae().set_bit());
let mut n = SPI_WAIT_LOOPS;
while self.dma.channel_enabled_raw(tx_chn) || self.dma.channel_enabled_raw(rx_chn) {
n -= 1;
if n == 0 {
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit().rdmae().clear_bit());
return Err(SpiError::Timeout);
}
core::hint::spin_loop();
}
unsafe { crate::cache::invalidate_range(rx_ptr as usize, bytes) };
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit().rdmae().clear_bit());
Ok((read, write))
}
#[instability::unstable]
#[cfg(all(feature = "async", feature = "unstable"))]
pub async fn transfer_dma_async<
RB: embedded_dma::WriteBuffer<Word = u8>,
TB: embedded_dma::ReadBuffer<Word = u8>,
>(
&mut self,
tx_ch: crate::dma::DmaChannel,
rx_ch: crate::dma::DmaChannel,
mut read: RB,
write: TB,
) -> Result<(RB, TB), SpiError> {
use crate::dma::{DmaChannelConfig, DmaFrame, DmaTransferSize};
let r = spi_regs(self.idx);
let (tx_ptr, tx_beats) = unsafe { write.read_buffer() };
let (rx_ptr, rx_beats) = unsafe { read.write_buffer() };
let beats = tx_beats.min(rx_beats);
if tx_beats > 0xFFF || rx_beats > 0xFFF {
return Err(SpiError::BufferTooLong);
}
let size = DmaTransferSize::from_beats(beats).ok_or(SpiError::BufferTooLong)?;
let bytes = beats;
let dr = self.dr_addr();
unsafe {
r.spi_dtdl().write(|w| w.bits(4));
r.spi_drdl().write(|w| w.bits(0));
}
unsafe { crate::cache::clean_range(tx_ptr as usize, bytes) };
let tx_cfg = DmaChannelConfig::default()
.mem_to_peripheral(self.tx_peri())
.with_width(DmaFrame::Byte)
.with_transfer_int(true);
let rx_cfg = DmaChannelConfig::default()
.peripheral_to_mem(self.rx_peri())
.with_width(DmaFrame::Byte)
.with_transfer_int(true);
let tx_chn = tx_ch.logical();
let rx_chn = rx_ch.logical();
self.dma.configure_channel_raw(tx_chn, tx_ptr as u32, dr, size, &tx_cfg);
self.dma.configure_channel_raw(rx_chn, dr, rx_ptr as u32, size, &rx_cfg);
r.spi_dcr().modify(|_, w| w.tdmae().set_bit().rdmae().set_bit());
self.dma.wait_transfer_done(&tx_ch).await;
self.dma.wait_transfer_done(&rx_ch).await;
unsafe { crate::cache::invalidate_range(rx_ptr as usize, bytes) };
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit().rdmae().clear_bit());
Ok((read, write))
}
#[instability::unstable]
pub fn release(self) -> (Spi<'d, T>, crate::dma::DmaDriver<'d, crate::dma::Dma0>) {
let r = spi_regs(self.idx);
r.spi_dcr().modify(|_, w| w.tdmae().clear_bit().rdmae().clear_bit());
(Spi { idx: self.idx, _peripheral: PhantomData }, self.dma)
}
}
fn transfer_in_place_on(idx: u8, buf: &mut [u8]) -> Result<(), SpiError> {
let r = spi_regs(idx);
for byte in buf.iter_mut() {
let tx = *byte as u32;
wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
unsafe { r.spi_dr().write(|w| w.bits(tx)) };
wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
*byte = r.spi_dr().read().bits() as u8;
}
Ok(())
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum SpiError {
Overflow,
Timeout,
BufferTooLong,
}
impl embedded_hal::spi::Error for SpiError {
fn kind(&self) -> embedded_hal::spi::ErrorKind {
match self {
SpiError::Overflow => embedded_hal::spi::ErrorKind::Overrun,
_ => embedded_hal::spi::ErrorKind::Other,
}
}
}
impl embedded_hal::spi::ErrorType for Spi<'_, Spi0<'_>> {
type Error = SpiError;
}
impl embedded_hal::spi::SpiBus for Spi<'_, Spi0<'_>> {
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.transfer(write, read)
}
fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
self.transfer(&[], buf)
}
fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
Spi::write(self, buf)
}
fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
transfer_in_place_on(self.idx, buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.wait_idle()
}
}
impl embedded_hal::spi::ErrorType for Spi<'_, Spi1<'_>> {
type Error = SpiError;
}
impl embedded_hal::spi::SpiBus for Spi<'_, Spi1<'_>> {
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
self.transfer(write, read)
}
fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
self.transfer(&[], buf)
}
fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
Spi::write(self, buf)
}
fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
transfer_in_place_on(self.idx, buf)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.wait_idle()
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod tests {
use super::sckdv;
use crate::soc::chip::SPI_CLOCK_HZ;
#[test]
fn test_sckdv_basic() {
assert_eq!(sckdv(SPI_CLOCK_HZ, 1_000_000), 160);
}
#[test]
fn test_sckdv_is_even_and_min_two() {
assert_eq!(sckdv(SPI_CLOCK_HZ, 1_000_000) & 1, 0);
assert_eq!(sckdv(SPI_CLOCK_HZ, SPI_CLOCK_HZ), 2);
assert_eq!(sckdv(SPI_CLOCK_HZ, u32::MAX), 2);
}
#[test]
fn test_sckdv_zero_freq_guard() {
assert_eq!(sckdv(SPI_CLOCK_HZ, 0), 0xFFFE);
}
#[test]
fn test_sckdv_clamps_at_max() {
assert_eq!(sckdv(SPI_CLOCK_HZ, 1000), 0xFFFE);
}
#[test]
fn spi_hz_rejects_out_of_range() {
use super::SpiHz;
assert!(SpiHz::try_from_hz(0).is_none());
assert!(SpiHz::try_from_hz(SPI_CLOCK_HZ / 2 + 1).is_none());
assert!(SpiHz::try_from_hz(SPI_CLOCK_HZ).is_none());
assert!(SpiHz::try_from_hz(1).is_none());
assert_eq!(SpiHz::try_from_hz(1_000_000).unwrap().hz(), 1_000_000);
assert_eq!(SpiHz::ONE_MHZ.to_sckdv(), 160);
assert_eq!(SpiHz::try_from_hz(SPI_CLOCK_HZ / 2).unwrap().to_sckdv(), 2);
}
#[test]
fn data_bits_validates_4_to_16() {
use super::DataBits;
assert!(DataBits::new(3).is_none());
assert!(DataBits::new(17).is_none());
assert_eq!(DataBits::new(4).unwrap().bits(), 4);
assert_eq!(DataBits::new(16).unwrap().bits(), 16);
assert_eq!(DataBits::EIGHT.bits(), 8);
}
}
#[cfg(all(test, not(target_arch = "riscv32")))]
mod proptests {
use super::sckdv;
use proptest::prelude::*;
proptest! {
#[test]
fn sckdv_in_valid_range(freq in any::<u32>()) {
let d = sckdv(crate::soc::chip::SPI_CLOCK_HZ, freq);
prop_assert!((2..=0xFFFE).contains(&d), "divisor {} out of range for freq={}", d, freq);
prop_assert_eq!(d & 1, 0, "divisor {} not even for freq={}", d, freq);
}
#[test]
fn sckdv_monotonic(freq1 in 1u32.., freq2 in 1u32..) {
let pclk = crate::soc::chip::SPI_CLOCK_HZ;
let d1 = sckdv(pclk, freq1);
let d2 = sckdv(pclk, freq2);
if freq1 > freq2 {
prop_assert!(d1 <= d2, "freq1={}(d={}) freq2={}(d={})", freq1, d1, freq2, d2);
}
}
}
}
#[cfg(feature = "async")]
mod asynch_impl {
use super::{Spi, Spi0, Spi1};
macro_rules! async_spi {
($inst:ty) => {
impl embedded_hal_async::spi::SpiBus<u8> for Spi<'_, $inst> {
async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
embedded_hal::spi::SpiBus::read(self, buf)
}
async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
embedded_hal::spi::SpiBus::write(self, buf)
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
embedded_hal::spi::SpiBus::transfer(self, read, write)
}
async fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
embedded_hal::spi::SpiBus::transfer_in_place(self, buf)
}
async fn flush(&mut self) -> Result<(), Self::Error> {
embedded_hal::spi::SpiBus::flush(self)
}
}
};
}
async_spi!(Spi0<'_>);
async_spi!(Spi1<'_>);
}