rp2040/
pio_gspi.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright OxidOS Automotive 2025.
4
5//! PIO gSPI (generic SPI) support
6
7use crate::dma::{self, DmaChannel, DmaChannelClient};
8use crate::gpio::RPGpioPin;
9use crate::pio::{Pio, PioSmClient, SMNumber, StateMachineConfiguration};
10use kernel::hil::gpio::{self, Output as _};
11use kernel::hil::spi::{self, SpiMasterDevice};
12use kernel::utilities::cells::OptionalCell;
13use kernel::utilities::leasable_buffer::SubSliceMut;
14use kernel::ErrorCode;
15
16/// The PIO program
17const PROG: [u16; 11] = [
18    0x6020, //  0: out    x, 32           side 0
19    0x6040, //  1: out    y, 32           side 0
20    0xe081, //  2: set    pindirs, 1      side 0
21    //     .wrap_target
22    0x6001, //  3: out    pins, 1         side 0
23    0x1043, //  4: jmp    x--, 3          side 1
24    0xe080, //  5: set    pindirs, 0      side 0
25    0xa042, //  6: nop                    side 0
26    0x5001, //  7: in     pins, 1         side 1
27    0x0087, //  8: jmp    y--, 7          side 0
28    0x20a0, //  9: wait   1 pin, 0        side 0
29    0xc000, // 10: irq    nowait 0        side 0
30            //     .wrap
31];
32
33/// The gSPI PIO peripheral driver
34pub struct PioGSpi<'a> {
35    pio: &'a Pio,
36    dma: DmaChannel<'a>,
37    clock_pin: u32,
38    dio_pin: u32,
39    cs_pin: &'a RPGpioPin<'a>,
40    sm_number: SMNumber,
41    write_buffer: OptionalCell<SubSliceMut<'static, u8>>,
42    read_buffer: OptionalCell<SubSliceMut<'static, u8>>,
43    client: OptionalCell<&'a dyn spi::SpiMasterClient>,
44    irq_client: OptionalCell<&'a dyn gpio::Client>,
45    pending: OptionalCell<Pending>,
46}
47
48#[derive(Debug)]
49enum Pending {
50    Write,
51    Read,
52}
53
54impl<'a> PioGSpi<'a> {
55    /// Create a new `PioCyw43Spi` instance
56    pub fn new(
57        pio: &'a Pio,
58        dma: DmaChannel<'a>,
59        clock_pin: u32,
60        dio_pin: u32,
61        cs_pin: &'a RPGpioPin<'a>,
62        sm_number: SMNumber,
63    ) -> Self {
64        Self {
65            pio,
66            dma,
67            clock_pin,
68            dio_pin,
69            cs_pin,
70            sm_number,
71            pending: OptionalCell::empty(),
72            client: OptionalCell::empty(),
73            irq_client: OptionalCell::empty(),
74            write_buffer: OptionalCell::empty(),
75            read_buffer: OptionalCell::empty(),
76        }
77    }
78
79    /// Return SM number
80    pub fn sm_number(&self) -> SMNumber {
81        self.sm_number
82    }
83
84    pub fn set_irq_client(&self, client: &'a dyn gpio::Client) {
85        self.irq_client.set(client);
86    }
87
88    /// Configure the PIO peripheral as an SPI device to communicate with the CYW43439 chip
89    pub fn init(&self) {
90        self.pio.init();
91        self.cs_pin.set();
92
93        // load program
94        self.pio.add_program16(None::<usize>, &PROG).unwrap();
95
96        let config = StateMachineConfiguration {
97            out_pins_count: 1,
98            out_pins_base: self.dio_pin,
99            set_pins_count: 1,
100            set_pins_base: self.dio_pin,
101            in_pins_base: self.dio_pin,
102            side_set_base: self.clock_pin,
103            side_set_bit_count: 1,
104            in_push_threshold: 0,
105            out_pull_threshold: 0,
106            div_int: 2u32,
107            div_frac: 0u32,
108            wrap: 10,
109            wrap_to: 3,
110            in_autopush: true,
111            out_autopull: true,
112            in_shift_direction_right: false,
113            out_shift_direction_right: false,
114            ..Default::default()
115        };
116
117        self.pio
118            .cyw43_spi_program_init(self.sm_number, self.clock_pin, self.dio_pin, &config);
119
120        self.pio
121            .set_irq_source(0, crate::pio::InterruptSources::Interrupt0, true);
122    }
123}
124
125impl DmaChannelClient for PioGSpi<'_> {
126    fn transfer_done(&self) {
127        let Some(pending) = self.pending.take() else {
128            return;
129        };
130
131        if let Pending::Write = pending {
132            let read_buffer = self.read_buffer.take();
133            if let Some(read_buffer) = read_buffer {
134                self.dma_pull(read_buffer.as_ptr() as u32, read_buffer.len() as u32 / 4);
135                self.pending.set(Pending::Read);
136                self.read_buffer.set(read_buffer);
137                return;
138            }
139        }
140
141        let write_buffer = self.write_buffer.take().unwrap();
142        let len = write_buffer.len();
143
144        self.cs_pin.set();
145        self.client
146            .map(|client| client.read_write_done(write_buffer, self.read_buffer.take(), Ok(len)));
147    }
148}
149
150impl PioGSpi<'_> {
151    fn dma_pull(&self, addr: u32, len: u32) {
152        assert!(addr.is_multiple_of(4));
153        let current_sm = self.pio.sm(self.sm_number);
154        self.dma
155            .set_read_addr(current_sm.rx_fifo_addr(self.pio.number()));
156        self.dma.set_write_addr(addr);
157
158        self.dma.set_len(len);
159        self.dma.enable(
160            dma::DmaPeripheral::PioRxFifo(self.pio.number(), self.sm_number),
161            dma::DataSize::Word,
162            dma::Transfer::PeripheralToMemory,
163            false,
164        );
165    }
166
167    fn dma_push(&self, addr: u32, len: u32) {
168        assert!(addr.is_multiple_of(4));
169        let current_sm = self.pio.sm(self.sm_number);
170        self.dma.set_read_addr(addr);
171        self.dma
172            .set_write_addr(current_sm.tx_fifo_addr(self.pio.number()));
173
174        self.dma.set_len(len);
175
176        self.dma.enable(
177            dma::DmaPeripheral::PioTxFifo(self.pio.number(), self.sm_number),
178            dma::DataSize::Word,
179            dma::Transfer::MemoryToPeripheral,
180            false,
181        );
182    }
183}
184
185impl<'a> SpiMasterDevice<'a> for PioGSpi<'a> {
186    fn set_client(&self, client: &'a dyn spi::SpiMasterClient) {
187        self.client.set(client);
188    }
189
190    fn read_write_bytes(
191        &self,
192        write_buffer: SubSliceMut<'static, u8>,
193        read_buffer: Option<SubSliceMut<'static, u8>>,
194    ) -> Result<
195        (),
196        (
197            ErrorCode,
198            SubSliceMut<'static, u8>,
199            Option<SubSliceMut<'static, u8>>,
200        ),
201    > {
202        assert!(write_buffer.len().is_multiple_of(4));
203
204        let current_sm = self.pio.sm(self.sm_number);
205        self.cs_pin.clear();
206        current_sm.set_enabled(false);
207
208        // Try to push the number of bits
209        let write_bits = (write_buffer.len() as u32) * 8 - 1;
210        let read_bits = read_buffer
211            .as_ref()
212            .map(|rx| rx.len() * 8 - 1)
213            .unwrap_or_default();
214
215        current_sm.push_blocking(write_bits).unwrap();
216        current_sm.push_blocking(read_bits as _).unwrap();
217
218        current_sm.exec(0);
219        current_sm.set_enabled(true);
220
221        self.dma_push(write_buffer.as_ptr() as u32, write_buffer.len() as u32 / 4);
222
223        self.write_buffer.set(write_buffer);
224        self.read_buffer.insert(read_buffer);
225        self.pending.set(Pending::Write);
226
227        Ok(())
228    }
229
230    fn set_rate(&self, _: u32) -> Result<(), ErrorCode> {
231        Err(ErrorCode::NOSUPPORT)
232    }
233
234    fn set_polarity(&self, _: kernel::hil::spi::ClockPolarity) -> Result<(), ErrorCode> {
235        Err(ErrorCode::NOSUPPORT)
236    }
237
238    fn set_phase(&self, _: kernel::hil::spi::ClockPhase) -> Result<(), ErrorCode> {
239        Err(ErrorCode::NOSUPPORT)
240    }
241
242    fn configure(
243        &self,
244        _: kernel::hil::spi::ClockPolarity,
245        _: kernel::hil::spi::ClockPhase,
246        _: u32,
247    ) -> Result<(), ErrorCode> {
248        Err(ErrorCode::NOSUPPORT)
249    }
250
251    fn get_polarity(&self) -> kernel::hil::spi::ClockPolarity {
252        kernel::hil::spi::ClockPolarity::IdleLow
253    }
254
255    fn get_phase(&self) -> kernel::hil::spi::ClockPhase {
256        kernel::hil::spi::ClockPhase::SampleLeading
257    }
258
259    fn get_rate(&self) -> u32 {
260        0
261    }
262}
263
264impl PioSmClient for PioGSpi<'_> {
265    fn on_irq(&self) {
266        // Clear interrupt
267        self.pio.interrupt_clear(0);
268        self.irq_client.map(|client| client.fired());
269    }
270}