Skip to main content

ws63_hal/
spi.rs

1//! SPI master driver for WS63 (SPI0/1, SSI v151).
2//! DesignWare SSI: SCK = SSI_CLK / SCKDV, default SSI_CLK = 240MHz (SCKDV is even, >= 2).
3
4use crate::peripherals::{Spi0, Spi1};
5use core::marker::PhantomData;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum SpiMode {
9    Mode0,
10    Mode1,
11    Mode2,
12    Mode3,
13}
14
15#[derive(Debug, Clone, Copy)]
16pub struct Config {
17    pub frequency: u32,
18    pub mode: SpiMode,
19    pub data_bits: u8,
20}
21
22impl Default for Config {
23    fn default() -> Self {
24        Self { frequency: 1_000_000, mode: SpiMode::Mode0, data_bits: 8 }
25    }
26}
27
28pub struct Spi<'d, T> {
29    idx: u8,
30    _peripheral: PhantomData<&'d T>,
31}
32
33fn spi_regs(idx: u8) -> &'static ws63_pac::spi0::RegisterBlock {
34    unsafe {
35        match idx {
36            0 => &*Spi0::ptr(),
37            1 => &*Spi1::ptr(),
38            _ => unreachable!(),
39        }
40    }
41}
42
43/// SCKDV clock divider for the DesignWare SSI: `SCK = SSI_CLK / SCKDV`.
44///
45/// Matches the WS63 C SDK (`hal_spi_v151.c`: `clk_div = bus_clk / freq`,
46/// clamped to `SPI_MINUMUM_CLK_DIV` = 2). SCKDV bit 0 is read-only 0, so the
47/// value must be even. There is NO `/2` and NO `-1` (an earlier version of this
48/// driver had both, producing ~2x the requested SCK).
49fn sckdv(pclk: u32, freq: u32) -> u32 {
50    let freq = if freq == 0 { 1 } else { freq };
51    let div = (pclk / freq).clamp(2, 0xFFFF);
52    div & !1 // SCKDV LSB is read-only 0 (must be even)
53}
54
55/// Bounded busy-wait. Returns [`SpiError::Timeout`] instead of hanging the CPU
56/// forever if a status bit never asserts (no slave, stuck CS, wrong mode) — the
57/// C SDK guards every equivalent wait with `hal_spi_check_timeout_by_count`.
58const SPI_WAIT_LOOPS: u32 = 1_000_000;
59
60#[inline]
61fn wait_until(mut ready: impl FnMut() -> bool) -> Result<(), SpiError> {
62    let mut n = SPI_WAIT_LOOPS;
63    while !ready() {
64        n -= 1;
65        if n == 0 {
66            return Err(SpiError::Timeout);
67        }
68    }
69    Ok(())
70}
71
72impl<'d> Spi<'d, Spi0<'d>> {
73    pub fn new_spi0(_spi: Spi0<'d>, config: Config) -> Self {
74        configure_spi(0, &config);
75        Self { idx: 0, _peripheral: PhantomData }
76    }
77}
78impl<'d> Spi<'d, Spi1<'d>> {
79    pub fn new_spi1(_spi: Spi1<'d>, config: Config) -> Self {
80        configure_spi(1, &config);
81        Self { idx: 1, _peripheral: PhantomData }
82    }
83}
84
85fn configure_spi(idx: u8, config: &Config) {
86    let r = spi_regs(idx);
87    r.spi_er().write(|w| unsafe { w.bits(0) });
88    let pclk = crate::soc::ws63::SYSTEM_CLOCK_HZ;
89    r.spi_brs().write(|w| unsafe { w.bits(sckdv(pclk, config.frequency)) });
90
91    let mut ctra = 0u32;
92    match config.mode {
93        SpiMode::Mode0 => {}
94        SpiMode::Mode1 => ctra |= 1 << 3,
95        SpiMode::Mode2 => ctra |= 1 << 4,
96        SpiMode::Mode3 => ctra |= (1 << 3) | (1 << 4),
97    }
98    ctra |= ((config.data_bits.saturating_sub(1)) as u32) << 13;
99    // CTRA.trsm (bits 18:19): 0b00 = transmit-and-receive (full duplex).
100    // (0b11 is EEPROM-read, NOT TX+RX — leaving trsm = 0 is correct.)
101    r.spi_ctra().write(|w| unsafe { w.bits(ctra) });
102    r.spi_slenr().write(|w| unsafe { w.bits(0x1) });
103    r.spi_er().write(|w| unsafe { w.bits(0x1) });
104}
105
106impl<T> Spi<'_, T> {
107    pub fn transfer(&mut self, write: &[u8], read: &mut [u8]) -> Result<(), SpiError> {
108        let r = spi_regs(self.idx);
109        let len = write.len().max(read.len());
110        for i in 0..len {
111            let tx = if i < write.len() { write[i] as u32 } else { 0 };
112            wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
113            unsafe { r.spi_dr().write(|w| w.bits(tx)) };
114            wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
115            let rx = r.spi_dr().read().bits();
116            if i < read.len() {
117                read[i] = rx as u8;
118            }
119        }
120        Ok(())
121    }
122
123    pub fn write(&mut self, data: &[u8]) -> Result<(), SpiError> {
124        let r = spi_regs(self.idx);
125        for &byte in data {
126            wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
127            unsafe { r.spi_dr().write(|w| w.bits(byte as u32)) };
128        }
129        Ok(())
130    }
131
132    pub fn register_block(&self) -> &'static ws63_pac::spi0::RegisterBlock {
133        spi_regs(self.idx)
134    }
135
136    /// Wait for all pending TX data to be transmitted and the SPI bus to become idle.
137    pub fn wait_idle(&self) -> Result<(), SpiError> {
138        let r = spi_regs(self.idx);
139        // Wait for TX FIFO to drain, then for the bus to leave the busy state.
140        wait_until(|| r.spi_wsr().read().txfe().bit_is_set())?;
141        wait_until(|| !r.spi_wsr().read().busy().bit_is_set())?;
142        Ok(())
143    }
144}
145
146fn transfer_in_place_on(idx: u8, buf: &mut [u8]) -> Result<(), SpiError> {
147    let r = spi_regs(idx);
148    for byte in buf.iter_mut() {
149        let tx = *byte as u32;
150        wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
151        unsafe { r.spi_dr().write(|w| w.bits(tx)) };
152        wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
153        *byte = r.spi_dr().read().bits() as u8;
154    }
155    Ok(())
156}
157
158#[derive(Debug)]
159pub enum SpiError {
160    Overflow,
161    Timeout,
162}
163
164impl embedded_hal::spi::Error for SpiError {
165    fn kind(&self) -> embedded_hal::spi::ErrorKind {
166        embedded_hal::spi::ErrorKind::Other
167    }
168}
169impl embedded_hal::spi::ErrorType for Spi<'_, Spi0<'_>> {
170    type Error = SpiError;
171}
172impl embedded_hal::spi::SpiBus for Spi<'_, Spi0<'_>> {
173    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
174        self.transfer(write, read)
175    }
176    fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
177        self.transfer(&[], buf)
178    }
179    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
180        Spi::write(self, buf)
181    }
182    fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
183        transfer_in_place_on(self.idx, buf)
184    }
185    fn flush(&mut self) -> Result<(), Self::Error> {
186        self.wait_idle()
187    }
188}
189
190// SPI1 also implements the same traits
191impl embedded_hal::spi::ErrorType for Spi<'_, Spi1<'_>> {
192    type Error = SpiError;
193}
194impl embedded_hal::spi::SpiBus for Spi<'_, Spi1<'_>> {
195    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
196        self.transfer(write, read)
197    }
198    fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
199        self.transfer(&[], buf)
200    }
201    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
202        Spi::write(self, buf)
203    }
204    fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
205        transfer_in_place_on(self.idx, buf)
206    }
207    fn flush(&mut self) -> Result<(), Self::Error> {
208        self.wait_idle()
209    }
210}
211
212// ── Tests ──────────────────────────────────────────────────────
213
214#[cfg(test)]
215mod tests {
216    use super::sckdv;
217    use crate::soc::ws63::SYSTEM_CLOCK_HZ;
218
219    #[test]
220    fn test_sckdv_basic() {
221        // 240 MHz / 1 MHz = 240 (SDK writes the divisor directly, no /2, no -1).
222        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, 1_000_000), 240);
223    }
224
225    #[test]
226    fn test_sckdv_is_even_and_min_two() {
227        // SCKDV bit0 is read-only 0 → result always even.
228        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, 1_000_000) & 1, 0);
229        // freq >= pclk clamps to the minimum divisor of 2.
230        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, SYSTEM_CLOCK_HZ), 2);
231        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, u32::MAX), 2);
232    }
233
234    #[test]
235    fn test_sckdv_zero_freq_guard() {
236        // freq == 0 is treated as 1 Hz → very large divisor, clamped to even max.
237        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, 0), 0xFFFE);
238    }
239
240    #[test]
241    fn test_sckdv_clamps_at_max() {
242        assert_eq!(sckdv(SYSTEM_CLOCK_HZ, 1000), 0xFFFE);
243    }
244}
245
246// ── Property-based fuzz tests ──────────────────────────────────
247
248#[cfg(test)]
249mod proptests {
250    use super::sckdv;
251    use proptest::prelude::*;
252
253    proptest! {
254        /// Fuzz: sckdv never panics and is always a valid even divisor in [2, 0xFFFE].
255        #[test]
256        fn sckdv_in_valid_range(freq in any::<u32>()) {
257            let d = sckdv(crate::soc::ws63::SYSTEM_CLOCK_HZ, freq);
258            prop_assert!((2..=0xFFFE).contains(&d), "divisor {} out of range for freq={}", d, freq);
259            prop_assert_eq!(d & 1, 0, "divisor {} not even for freq={}", d, freq);
260        }
261
262        /// Fuzz: higher frequency → lower-or-equal divisor (monotonic non-increasing).
263        #[test]
264        fn sckdv_monotonic(freq1 in 1u32.., freq2 in 1u32..) {
265            let pclk = crate::soc::ws63::SYSTEM_CLOCK_HZ;
266            let d1 = sckdv(pclk, freq1);
267            let d2 = sckdv(pclk, freq2);
268            if freq1 > freq2 {
269                prop_assert!(d1 <= d2, "freq1={}(d={}) freq2={}(d={})", freq1, d1, freq2, d2);
270            }
271        }
272    }
273}