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. Two-stage clock: a CLDO_CRG divider sets
3//! SSI_CLK = 160MHz off the 480MHz PLL (`configure_spi_source_clock`, mirrors the
4//! vendor `spi_porting_clock_init`), then SCKDV divides to SCK. SSI_CLK =
5//! [`crate::soc::ws63::SPI_CLOCK_HZ`] (NOT the 240MHz CPU clock; SCKDV is even, >= 2).
6
7use crate::peripherals::{Spi0, Spi1};
8use core::marker::PhantomData;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SpiMode {
12    Mode0,
13    Mode1,
14    Mode2,
15    Mode3,
16}
17
18#[derive(Debug, Clone, Copy)]
19pub struct Config {
20    pub frequency: u32,
21    pub mode: SpiMode,
22    pub data_bits: u8,
23}
24
25impl Default for Config {
26    fn default() -> Self {
27        Self { frequency: 1_000_000, mode: SpiMode::Mode0, data_bits: 8 }
28    }
29}
30
31pub struct Spi<'d, T> {
32    idx: u8,
33    _peripheral: PhantomData<&'d T>,
34}
35
36fn spi_regs(idx: u8) -> &'static ws63_pac::spi0::RegisterBlock {
37    unsafe {
38        match idx {
39            0 => &*Spi0::ptr(),
40            1 => &*Spi1::ptr(),
41            _ => unreachable!(),
42        }
43    }
44}
45
46/// SCKDV clock divider for the DesignWare SSI: `SCK = SSI_CLK / SCKDV`.
47///
48/// Matches the WS63 C SDK (`hal_spi_v151.c`: `clk_div = bus_clk / freq`,
49/// clamped to `SPI_MINUMUM_CLK_DIV` = 2). SCKDV bit 0 is read-only 0, so the
50/// value must be even. There is NO `/2` and NO `-1` (an earlier version of this
51/// driver had both, producing ~2x the requested SCK).
52fn sckdv(pclk: u32, freq: u32) -> u32 {
53    let freq = if freq == 0 { 1 } else { freq };
54    let div = (pclk / freq).clamp(2, 0xFFFF);
55    div & !1 // SCKDV LSB is read-only 0 (must be even)
56}
57
58/// Bounded busy-wait. Returns [`SpiError::Timeout`] instead of hanging the CPU
59/// forever if a status bit never asserts (no slave, stuck CS, wrong mode) — the
60/// C SDK guards every equivalent wait with `hal_spi_check_timeout_by_count`.
61const SPI_WAIT_LOOPS: u32 = 1_000_000;
62
63#[inline]
64fn wait_until(mut ready: impl FnMut() -> bool) -> Result<(), SpiError> {
65    let mut n = SPI_WAIT_LOOPS;
66    while !ready() {
67        n -= 1;
68        if n == 0 {
69            return Err(SpiError::Timeout);
70        }
71    }
72    Ok(())
73}
74
75impl<'d> Spi<'d, Spi0<'d>> {
76    pub fn new_spi0(_spi: Spi0<'d>, config: Config) -> Self {
77        configure_spi(0, &config);
78        Self { idx: 0, _peripheral: PhantomData }
79    }
80}
81impl<'d> Spi<'d, Spi1<'d>> {
82    pub fn new_spi1(_spi: Spi1<'d>, config: Config) -> Self {
83        configure_spi(1, &config);
84        Self { idx: 1, _peripheral: PhantomData }
85    }
86}
87
88// ── SPI clock source (CLDO_CRG two-stage divider) ───────────────────────────
89// The SPI controller input clock (SSI_CLK) is derived from the 480 MHz FNPLL tap
90// by a CLDO_CRG divider, then divided again by the in-controller SCKDV to form
91// SCK (SCK = SSI_CLK / SCKDV). The HAL targets SSI_CLK = SPI_CLOCK_HZ (160 MHz);
92// `configure_spi_source_clock` programs the CRG divider to establish it and
93// switches the SPI clock source TCXO→PLL. Mirrors fbb_ws63 `spi_porting_clock_init`
94// (480/bus_clk_MHz into DIV_CTL3[9:5]); see ws63-guide ch8 "时钟树".
95const CLDO_CRG_DIV_CTL3: usize = 0x4400_1114; // SPI source divider
96const CLDO_CRG_CLK_SEL: usize = 0x4400_1134; // bit 6 = SPI source: 1=PLL, 0=TCXO
97const CLDO_SUB_CRG_CKEN_CTL1: usize = 0x4400_1104; // bit 25 = SPI clock gate
98const SPI_PLL_ROOT_MHZ: u32 = 480; // FNPLL SPI/QSPI tap (2880 / 6)
99
100/// Establish the two-stage SPI clock: program the CLDO_CRG divider so the SPI
101/// controller input clock (SSI_CLK) = [`crate::soc::ws63::SPI_CLOCK_HZ`] off the
102/// 480 MHz PLL, then switch the SPI clock source from TCXO to PLL
103/// (gate-close → switch → gate-open). Bus-agnostic (one divider/select/gate for
104/// the whole SPI domain) and idempotent. Requires the PLL to already be locked
105/// (the app's `clock_init` does this before any driver init).
106fn configure_spi_source_clock() {
107    // CRG divider output = 480 MHz / div → div = 480 / SSI_CLK_MHz (e.g. 3 for 160 MHz).
108    let ssi_mhz = (crate::soc::ws63::SPI_CLOCK_HZ / 1_000_000).max(1);
109    let div = (SPI_PLL_ROOT_MHZ / ssi_mhz).clamp(1, 0x1F);
110    // SAFETY: fixed CLDO_CRG MMIO registers (0x4400_11xx, within the SYS_CTL1
111    // range). Word-aligned 32-bit RMW; the load-bit handshake matches the SDK.
112    unsafe {
113        let div_ctl3 = CLDO_CRG_DIV_CTL3 as *mut u32;
114        // load-disable (bit10) → set [9:5]=div, [4:0]=1 → load-enable, per the SDK.
115        let v = core::ptr::read_volatile(div_ctl3) & !(1 << 10);
116        core::ptr::write_volatile(div_ctl3, v);
117        let v = (core::ptr::read_volatile(div_ctl3) & !(0x1F << 5) & !0x1F) | ((div & 0x1F) << 5) | 0x1;
118        core::ptr::write_volatile(div_ctl3, v);
119        core::ptr::write_volatile(div_ctl3, core::ptr::read_volatile(div_ctl3) | (1 << 10));
120
121        // Gate-close → switch SPI source to PLL (CLK_SEL bit 6) → gate-open (CKEN1 bit 25).
122        let cken1 = CLDO_SUB_CRG_CKEN_CTL1 as *mut u32;
123        let clk_sel = CLDO_CRG_CLK_SEL as *mut u32;
124        core::ptr::write_volatile(cken1, core::ptr::read_volatile(cken1) & !(1 << 25));
125        core::ptr::write_volatile(clk_sel, core::ptr::read_volatile(clk_sel) | (1 << 6));
126        core::ptr::write_volatile(cken1, core::ptr::read_volatile(cken1) | (1 << 25));
127    }
128}
129
130fn configure_spi(idx: u8, config: &Config) {
131    configure_spi_source_clock();
132    let r = spi_regs(idx);
133    r.spi_er().write(|w| unsafe { w.bits(0) });
134    let pclk = crate::soc::ws63::SPI_CLOCK_HZ;
135    r.spi_brs().write(|w| unsafe { w.bits(sckdv(pclk, config.frequency)) });
136
137    let mut ctra = 0u32;
138    match config.mode {
139        SpiMode::Mode0 => {}
140        SpiMode::Mode1 => ctra |= 1 << 3,
141        SpiMode::Mode2 => ctra |= 1 << 4,
142        SpiMode::Mode3 => ctra |= (1 << 3) | (1 << 4),
143    }
144    ctra |= ((config.data_bits.saturating_sub(1)) as u32) << 13;
145    // CTRA.trsm (bits 18:19): 0b00 = transmit-and-receive (full duplex).
146    // (0b11 is EEPROM-read, NOT TX+RX — leaving trsm = 0 is correct.)
147    r.spi_ctra().write(|w| unsafe { w.bits(ctra) });
148    r.spi_slenr().write(|w| unsafe { w.bits(0x1) });
149    r.spi_er().write(|w| unsafe { w.bits(0x1) });
150}
151
152impl<T> Spi<'_, T> {
153    pub fn transfer(&mut self, write: &[u8], read: &mut [u8]) -> Result<(), SpiError> {
154        let r = spi_regs(self.idx);
155        let len = write.len().max(read.len());
156        for i in 0..len {
157            let tx = if i < write.len() { write[i] as u32 } else { 0 };
158            wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
159            unsafe { r.spi_dr().write(|w| w.bits(tx)) };
160            wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
161            let rx = r.spi_dr().read().bits();
162            if i < read.len() {
163                read[i] = rx as u8;
164            }
165        }
166        Ok(())
167    }
168
169    pub fn write(&mut self, data: &[u8]) -> Result<(), SpiError> {
170        let r = spi_regs(self.idx);
171        for &byte in data {
172            wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
173            unsafe { r.spi_dr().write(|w| w.bits(byte as u32)) };
174        }
175        Ok(())
176    }
177
178    pub fn register_block(&self) -> &'static ws63_pac::spi0::RegisterBlock {
179        spi_regs(self.idx)
180    }
181
182    /// Wait for all pending TX data to be transmitted and the SPI bus to become idle.
183    pub fn wait_idle(&self) -> Result<(), SpiError> {
184        let r = spi_regs(self.idx);
185        // Wait for TX FIFO to drain, then for the bus to leave the busy state.
186        wait_until(|| r.spi_wsr().read().txfe().bit_is_set())?;
187        wait_until(|| !r.spi_wsr().read().busy().bit_is_set())?;
188        Ok(())
189    }
190}
191
192fn transfer_in_place_on(idx: u8, buf: &mut [u8]) -> Result<(), SpiError> {
193    let r = spi_regs(idx);
194    for byte in buf.iter_mut() {
195        let tx = *byte as u32;
196        wait_until(|| r.spi_wsr().read().txfnf().bit_is_set())?;
197        unsafe { r.spi_dr().write(|w| w.bits(tx)) };
198        wait_until(|| r.spi_wsr().read().rxfne().bit_is_set())?;
199        *byte = r.spi_dr().read().bits() as u8;
200    }
201    Ok(())
202}
203
204#[derive(Debug)]
205pub enum SpiError {
206    Overflow,
207    Timeout,
208}
209
210impl embedded_hal::spi::Error for SpiError {
211    fn kind(&self) -> embedded_hal::spi::ErrorKind {
212        embedded_hal::spi::ErrorKind::Other
213    }
214}
215impl embedded_hal::spi::ErrorType for Spi<'_, Spi0<'_>> {
216    type Error = SpiError;
217}
218impl embedded_hal::spi::SpiBus for Spi<'_, Spi0<'_>> {
219    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
220        self.transfer(write, read)
221    }
222    fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
223        self.transfer(&[], buf)
224    }
225    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
226        Spi::write(self, buf)
227    }
228    fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
229        transfer_in_place_on(self.idx, buf)
230    }
231    fn flush(&mut self) -> Result<(), Self::Error> {
232        self.wait_idle()
233    }
234}
235
236// SPI1 also implements the same traits
237impl embedded_hal::spi::ErrorType for Spi<'_, Spi1<'_>> {
238    type Error = SpiError;
239}
240impl embedded_hal::spi::SpiBus for Spi<'_, Spi1<'_>> {
241    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
242        self.transfer(write, read)
243    }
244    fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
245        self.transfer(&[], buf)
246    }
247    fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
248        Spi::write(self, buf)
249    }
250    fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
251        transfer_in_place_on(self.idx, buf)
252    }
253    fn flush(&mut self) -> Result<(), Self::Error> {
254        self.wait_idle()
255    }
256}
257
258// ── Tests ──────────────────────────────────────────────────────
259
260#[cfg(test)]
261mod tests {
262    use super::sckdv;
263    use crate::soc::ws63::SPI_CLOCK_HZ;
264
265    #[test]
266    fn test_sckdv_basic() {
267        // 160 MHz / 1 MHz = 160 (SDK writes the divisor directly, no /2, no -1).
268        assert_eq!(sckdv(SPI_CLOCK_HZ, 1_000_000), 160);
269    }
270
271    #[test]
272    fn test_sckdv_is_even_and_min_two() {
273        // SCKDV bit0 is read-only 0 → result always even.
274        assert_eq!(sckdv(SPI_CLOCK_HZ, 1_000_000) & 1, 0);
275        // freq >= pclk clamps to the minimum divisor of 2.
276        assert_eq!(sckdv(SPI_CLOCK_HZ, SPI_CLOCK_HZ), 2);
277        assert_eq!(sckdv(SPI_CLOCK_HZ, u32::MAX), 2);
278    }
279
280    #[test]
281    fn test_sckdv_zero_freq_guard() {
282        // freq == 0 is treated as 1 Hz → very large divisor, clamped to even max.
283        assert_eq!(sckdv(SPI_CLOCK_HZ, 0), 0xFFFE);
284    }
285
286    #[test]
287    fn test_sckdv_clamps_at_max() {
288        assert_eq!(sckdv(SPI_CLOCK_HZ, 1000), 0xFFFE);
289    }
290}
291
292// ── Property-based fuzz tests ──────────────────────────────────
293
294#[cfg(test)]
295mod proptests {
296    use super::sckdv;
297    use proptest::prelude::*;
298
299    proptest! {
300        /// Fuzz: sckdv never panics and is always a valid even divisor in [2, 0xFFFE].
301        #[test]
302        fn sckdv_in_valid_range(freq in any::<u32>()) {
303            let d = sckdv(crate::soc::ws63::SPI_CLOCK_HZ, freq);
304            prop_assert!((2..=0xFFFE).contains(&d), "divisor {} out of range for freq={}", d, freq);
305            prop_assert_eq!(d & 1, 0, "divisor {} not even for freq={}", d, freq);
306        }
307
308        /// Fuzz: higher frequency → lower-or-equal divisor (monotonic non-increasing).
309        #[test]
310        fn sckdv_monotonic(freq1 in 1u32.., freq2 in 1u32..) {
311            let pclk = crate::soc::ws63::SPI_CLOCK_HZ;
312            let d1 = sckdv(pclk, freq1);
313            let d2 = sckdv(pclk, freq2);
314            if freq1 > freq2 {
315                prop_assert!(d1 <= d2, "freq1={}(d={}) freq2={}(d={})", freq1, d1, freq2, d2);
316            }
317        }
318    }
319}
320
321// ── Async SPI (embedded-hal-async) ──────────────────────────────────────────
322// WS63 SPI transfers are FIFO-paced (and the ws63-qemu model loops back
323// synchronously), so the async SpiBus methods complete promptly by reusing the
324// blocking transfer logic — valid `async fn`s usable from embassy/async tasks. A
325// genuinely IRQ-parking variant would add an on_interrupt + IrqSignal once the
326// SPI completion IRQ (43/52) is modelled.
327#[cfg(feature = "async")]
328mod asynch_impl {
329    use super::{Spi, Spi0, Spi1};
330
331    macro_rules! async_spi {
332        ($inst:ty) => {
333            impl embedded_hal_async::spi::SpiBus<u8> for Spi<'_, $inst> {
334                async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
335                    embedded_hal::spi::SpiBus::read(self, buf)
336                }
337                async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
338                    embedded_hal::spi::SpiBus::write(self, buf)
339                }
340                async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
341                    embedded_hal::spi::SpiBus::transfer(self, read, write)
342                }
343                async fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
344                    embedded_hal::spi::SpiBus::transfer_in_place(self, buf)
345                }
346                async fn flush(&mut self) -> Result<(), Self::Error> {
347                    embedded_hal::spi::SpiBus::flush(self)
348                }
349            }
350        };
351    }
352    async_spi!(Spi0<'_>);
353    async_spi!(Spi1<'_>);
354}