Skip to main content

xpanse_api/bus/
pio.rs

1//! PIO resource manager.
2//!
3//! Owns the three PIO blocks and hands out state machines on demand. Unlike the
4//! previous design, a `PioSlot` keeps the `Common` handle, all four state
5//! machines and the IRQ handles alive for the lifetime of the slot, so SMs are
6//! never orphaned and can be allocated independently up to the full 12 SMs.
7
8use alloc::boxed::Box;
9
10use embassy_rp::Peri;
11use embassy_rp::dma::{self, ChannelInstance};
12use embassy_rp::interrupt::typelevel::Binding;
13use embassy_rp::peripherals::{PIO0, PIO1, PIO2};
14use embassy_rp::pio::{Common, Instance, Irq, IrqFlags, Pio, PioPin, StateMachine};
15use embassy_rp::spi;
16
17use crate::bus::allocator::{PioBlock, Sm};
18use crate::bus::spi::{DynSpiBusCombined, SpiBusHandle, SpiBusVersion};
19use crate::bus::spi_pio::PioSpiBus;
20use crate::bus::uart::{DynUartBus, UartBusHandle, UartBusVersion};
21use crate::bus::uart_pio::PioUartBus;
22
23pub(crate) const PIO_UART_INSTRUCTIONS: u8 = 14;
24
25pub(crate) fn spi_program_instructions(config: &spi::Config) -> u8 {
26    match config.phase {
27        spi::Phase::CaptureOnFirstTransition => 2,
28        spi::Phase::CaptureOnSecondTransition => 3,
29    }
30}
31
32pub(crate) fn gpio_base_for_pins(pins: &[u8]) -> Option<bool> {
33    if pins.iter().all(|pin| *pin < 32) {
34        Some(false)
35    } else if pins.iter().all(|pin| *pin >= 16) {
36        Some(true)
37    } else {
38        None
39    }
40}
41
42/// A `StateMachine` with the SM number erased at runtime.
43///
44/// Drivers that load custom PIO programs match on this enum to recover the
45/// const-generic `StateMachine<'static, PIO, N>` they need.
46pub enum AnyStateMachine<'d, PIO: Instance + 'static> {
47    Sm0(StateMachine<'d, PIO, 0>),
48    Sm1(StateMachine<'d, PIO, 1>),
49    Sm2(StateMachine<'d, PIO, 2>),
50    Sm3(StateMachine<'d, PIO, 3>),
51}
52
53/// A handle returned by [`BusAllocator::request_pio`](crate::bus::allocator::BusAllocator::request_pio)
54/// that gives a driver temporary access to one block's `Common` handle plus a
55/// free `StateMachine`.
56///
57/// The `Common` borrow is only valid for the lifetime of the handle (which is
58/// tied to `&mut BusAllocator`).  Programs loaded via `Common` return handles
59/// with `'static` lifetime (because the `Common` inside `BusAllocator` is
60/// `Common<'static, PIO>`), so a driver can load a program, configure the SM,
61/// and then keep the `LoadedProgram` + `StateMachine` after the borrow ends.
62#[must_use = "custom PIO access reserves its block for the rest of the boot"]
63pub enum PioAccess<'a> {
64    Block0 {
65        common: &'a mut Common<'static, PIO0>,
66        sm: AnyStateMachine<'static, PIO0>,
67    },
68    Block1 {
69        common: &'a mut Common<'static, PIO1>,
70        sm: AnyStateMachine<'static, PIO1>,
71    },
72    Block2 {
73        common: &'a mut Common<'static, PIO2>,
74        sm: AnyStateMachine<'static, PIO2>,
75    },
76}
77
78embassy_rp::bind_interrupts!(struct PioIrqs {
79    PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO0>;
80    PIO1_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO1>;
81    PIO2_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO2>;
82});
83
84/// A single PIO block: the shared `Common` handle plus all four state machines
85/// and IRQ handles, kept alive together so dropping one piece never decrements
86/// the PIO's user counter prematurely.
87struct PioSlot<PIO: Instance + 'static> {
88    common: Common<'static, PIO>,
89    sm0: Option<StateMachine<'static, PIO, 0>>,
90    sm1: Option<StateMachine<'static, PIO, 1>>,
91    sm2: Option<StateMachine<'static, PIO, 2>>,
92    sm3: Option<StateMachine<'static, PIO, 3>>,
93    _irq_flags: IrqFlags<'static, PIO>,
94    _irq0: Irq<'static, PIO, 0>,
95    _irq1: Irq<'static, PIO, 1>,
96    _irq2: Irq<'static, PIO, 2>,
97    _irq3: Irq<'static, PIO, 3>,
98    gpio_base_high: Option<bool>,
99    reserved_instructions: u8,
100}
101
102impl<PIO: Instance + Send + 'static> PioSlot<PIO> {
103    fn new(pio: Pio<'static, PIO>) -> Self {
104        let Pio {
105            common,
106            irq_flags,
107            irq0,
108            irq1,
109            irq2,
110            irq3,
111            sm0,
112            sm1,
113            sm2,
114            sm3,
115            ..
116        } = pio;
117        Self {
118            common,
119            sm0: Some(sm0),
120            sm1: Some(sm1),
121            sm2: Some(sm2),
122            sm3: Some(sm3),
123            _irq_flags: irq_flags,
124            _irq0: irq0,
125            _irq1: irq1,
126            _irq2: irq2,
127            _irq3: irq3,
128            gpio_base_high: None,
129            reserved_instructions: 0,
130        }
131    }
132
133    fn can_reserve(&self, gpio_base_high: bool, instructions: u8) -> bool {
134        self.gpio_base_high
135            .is_none_or(|current| current == gpio_base_high)
136            && self.reserved_instructions.saturating_add(instructions) <= 32
137    }
138
139    fn reserve(&mut self, gpio_base_high: bool, instructions: u8) {
140        debug_assert!(self.can_reserve(gpio_base_high, instructions));
141        self.gpio_base_high = Some(gpio_base_high);
142        self.reserved_instructions += instructions;
143    }
144
145    fn has_sm(&self, sm: Sm) -> bool {
146        match sm {
147            Sm::Sm0 => self.sm0.is_some(),
148            Sm::Sm1 => self.sm1.is_some(),
149            Sm::Sm2 => self.sm2.is_some(),
150            Sm::Sm3 => self.sm3.is_some(),
151        }
152    }
153
154    fn take_any_sm(&mut self, sm: Sm) -> Option<AnyStateMachine<'static, PIO>> {
155        match sm {
156            Sm::Sm0 => self.sm0.take().map(AnyStateMachine::Sm0),
157            Sm::Sm1 => self.sm1.take().map(AnyStateMachine::Sm1),
158            Sm::Sm2 => self.sm2.take().map(AnyStateMachine::Sm2),
159            Sm::Sm3 => self.sm3.take().map(AnyStateMachine::Sm3),
160        }
161    }
162
163    fn build_spi<TxDma, RxDma, Irq>(
164        &mut self,
165        sm: Sm,
166        gpio_base_high: bool,
167        instructions: u8,
168        clk: Peri<'static, impl PioPin>,
169        mosi: Peri<'static, impl PioPin>,
170        miso: Peri<'static, impl PioPin>,
171        tx_dma: Peri<'static, TxDma>,
172        rx_dma: Peri<'static, RxDma>,
173        irq: Irq,
174        config: spi::Config,
175    ) -> Option<Box<dyn DynSpiBusCombined>>
176    where
177        TxDma: ChannelInstance,
178        RxDma: ChannelInstance,
179        Irq: Binding<TxDma::Interrupt, dma::InterruptHandler<TxDma>>
180            + Binding<RxDma::Interrupt, dma::InterruptHandler<RxDma>>
181            + 'static,
182    {
183        macro_rules! take {
184            ($field:ident) => {
185                match self.$field.take() {
186                    Some(sm) => {
187                        self.reserve(gpio_base_high, instructions);
188                        Some(Box::new(
189                            PioSpiBus::new(
190                                &mut self.common,
191                                sm,
192                                clk,
193                                mosi,
194                                miso,
195                                tx_dma,
196                                rx_dma,
197                                irq,
198                                config,
199                            )
200                            .expect("PIO SPI configuration was validated before allocation"),
201                        ) as Box<dyn DynSpiBusCombined>)
202                    }
203                    None => None,
204                }
205            };
206        }
207
208        match sm {
209            Sm::Sm0 => take!(sm0),
210            Sm::Sm1 => take!(sm1),
211            Sm::Sm2 => take!(sm2),
212            Sm::Sm3 => take!(sm3),
213        }
214    }
215
216    fn build_uart(
217        &mut self,
218        sm_tx: Sm,
219        sm_rx: Sm,
220        gpio_base_high: bool,
221        instructions: u8,
222        tx_pin: Peri<'static, impl PioPin>,
223        rx_pin: Peri<'static, impl PioPin>,
224        baud_rate: u32,
225    ) -> Option<Box<dyn DynUartBus>> {
226        macro_rules! pair {
227            ($txf:ident, $rxf:ident) => {
228                match (self.$txf.take(), self.$rxf.take()) {
229                    (Some(t), Some(r)) => {
230                        self.reserve(gpio_base_high, instructions);
231                        Some(Box::new(
232                            PioUartBus::new(&mut self.common, t, r, tx_pin, rx_pin, baud_rate)
233                                .expect("PIO UART baud was validated before allocation"),
234                        ) as Box<dyn DynUartBus>)
235                    }
236                    (Some(t), None) => {
237                        self.$txf = Some(t);
238                        None
239                    }
240                    (None, _) => None,
241                }
242            };
243        }
244
245        match (sm_tx, sm_rx) {
246            (Sm::Sm0, Sm::Sm1) => pair!(sm0, sm1),
247            (Sm::Sm0, Sm::Sm2) => pair!(sm0, sm2),
248            (Sm::Sm0, Sm::Sm3) => pair!(sm0, sm3),
249            (Sm::Sm1, Sm::Sm0) => pair!(sm1, sm0),
250            (Sm::Sm1, Sm::Sm2) => pair!(sm1, sm2),
251            (Sm::Sm1, Sm::Sm3) => pair!(sm1, sm3),
252            (Sm::Sm2, Sm::Sm0) => pair!(sm2, sm0),
253            (Sm::Sm2, Sm::Sm1) => pair!(sm2, sm1),
254            (Sm::Sm2, Sm::Sm3) => pair!(sm2, sm3),
255            (Sm::Sm3, Sm::Sm0) => pair!(sm3, sm0),
256            (Sm::Sm3, Sm::Sm1) => pair!(sm3, sm1),
257            (Sm::Sm3, Sm::Sm2) => pair!(sm3, sm2),
258            // Equal pairs are never produced by the allocator (it picks two
259            // *distinct* free SMs), so this arm is unreachable in correct use.
260            _ => None,
261        }
262    }
263}
264
265pub struct PioManager {
266    pio0: Option<PioSlot<PIO0>>,
267    pio1: Option<PioSlot<PIO1>>,
268    pio2: Option<PioSlot<PIO2>>,
269}
270
271impl PioManager {
272    pub fn new(
273        pio0: Peri<'static, PIO0>,
274        pio1: Peri<'static, PIO1>,
275        pio2: Peri<'static, PIO2>,
276    ) -> Self {
277        Self {
278            pio0: Some(PioSlot::new(Pio::new(pio0, PioIrqs))),
279            pio1: Some(PioSlot::new(Pio::new(pio1, PioIrqs))),
280            pio2: Some(PioSlot::new(Pio::new(pio2, PioIrqs))),
281        }
282    }
283
284    /// Build a PIO SPI bus on a specific (block, sm). The SM must be free —
285    /// i.e. obtained from [`find_free_sm`] in the same synchronous sequence.
286    pub(crate) fn build_spi_at<TxDma, RxDma, Irq>(
287        &mut self,
288        block: PioBlock,
289        sm: Sm,
290        gpio_base_high: bool,
291        instructions: u8,
292        clk: Peri<'static, impl PioPin>,
293        mosi: Peri<'static, impl PioPin>,
294        miso: Peri<'static, impl PioPin>,
295        tx_dma: Peri<'static, TxDma>,
296        rx_dma: Peri<'static, RxDma>,
297        irq: Irq,
298        config: spi::Config,
299    ) -> SpiBusHandle
300    where
301        TxDma: ChannelInstance,
302        RxDma: ChannelInstance,
303        Irq: Binding<TxDma::Interrupt, dma::InterruptHandler<TxDma>>
304            + Binding<RxDma::Interrupt, dma::InterruptHandler<RxDma>>
305            + 'static,
306    {
307        let bus = match block {
308            PioBlock::Block0 => self
309                .pio0
310                .as_mut()
311                .and_then(|slot| {
312                    slot.build_spi(
313                        sm,
314                        gpio_base_high,
315                        instructions,
316                        clk,
317                        mosi,
318                        miso,
319                        tx_dma,
320                        rx_dma,
321                        irq,
322                        config,
323                    )
324                })
325                .expect("PIO SM allocation invariant: SM was not free"),
326            PioBlock::Block1 => self
327                .pio1
328                .as_mut()
329                .and_then(|slot| {
330                    slot.build_spi(
331                        sm,
332                        gpio_base_high,
333                        instructions,
334                        clk,
335                        mosi,
336                        miso,
337                        tx_dma,
338                        rx_dma,
339                        irq,
340                        config,
341                    )
342                })
343                .expect("PIO SM allocation invariant: SM was not free"),
344            PioBlock::Block2 => self
345                .pio2
346                .as_mut()
347                .and_then(|slot| {
348                    slot.build_spi(
349                        sm,
350                        gpio_base_high,
351                        instructions,
352                        clk,
353                        mosi,
354                        miso,
355                        tx_dma,
356                        rx_dma,
357                        irq,
358                        config,
359                    )
360                })
361                .expect("PIO SM allocation invariant: SM was not free"),
362        };
363        SpiBusHandle::new(bus, SpiBusVersion::Pio)
364    }
365
366    /// Build a PIO UART bus on a specific (block, sm_tx, sm_rx). The SMs must be
367    /// free — obtained from [`find_free_sm_pair`] in the same sequence.
368    pub(crate) fn build_uart_at(
369        &mut self,
370        block: PioBlock,
371        sm_tx: Sm,
372        sm_rx: Sm,
373        gpio_base_high: bool,
374        instructions: u8,
375        tx_pin: Peri<'static, impl PioPin>,
376        rx_pin: Peri<'static, impl PioPin>,
377        baud_rate: u32,
378    ) -> UartBusHandle {
379        let bus = match block {
380            PioBlock::Block0 => self
381                .pio0
382                .as_mut()
383                .and_then(|slot| {
384                    slot.build_uart(
385                        sm_tx,
386                        sm_rx,
387                        gpio_base_high,
388                        instructions,
389                        tx_pin,
390                        rx_pin,
391                        baud_rate,
392                    )
393                })
394                .expect("PIO SM allocation invariant: SM pair was not free"),
395            PioBlock::Block1 => self
396                .pio1
397                .as_mut()
398                .and_then(|slot| {
399                    slot.build_uart(
400                        sm_tx,
401                        sm_rx,
402                        gpio_base_high,
403                        instructions,
404                        tx_pin,
405                        rx_pin,
406                        baud_rate,
407                    )
408                })
409                .expect("PIO SM allocation invariant: SM pair was not free"),
410            PioBlock::Block2 => self
411                .pio2
412                .as_mut()
413                .and_then(|slot| {
414                    slot.build_uart(
415                        sm_tx,
416                        sm_rx,
417                        gpio_base_high,
418                        instructions,
419                        tx_pin,
420                        rx_pin,
421                        baud_rate,
422                    )
423                })
424                .expect("PIO SM allocation invariant: SM pair was not free"),
425        };
426        UartBusHandle::new(bus, UartBusVersion::Pio)
427    }
428
429    /// Build a PIO-backed UART bus on any two free state machines of a single
430    /// block (one for TX, one for RX).
431    pub fn build_uart_pio(
432        &mut self,
433        tx_pin: Peri<'static, impl PioPin>,
434        rx_pin: Peri<'static, impl PioPin>,
435        baud_rate: u32,
436    ) -> Option<UartBusHandle> {
437        let gpio_base_high = gpio_base_for_pins(&[tx_pin.pin()])?;
438        if gpio_base_for_pins(&[rx_pin.pin()])? != gpio_base_high {
439            return None;
440        }
441        let (block, sm_tx, sm_rx) =
442            self.find_free_sm_pair(gpio_base_high, PIO_UART_INSTRUCTIONS)?;
443        Some(self.build_uart_at(
444            block,
445            sm_tx,
446            sm_rx,
447            gpio_base_high,
448            PIO_UART_INSTRUCTIONS,
449            tx_pin,
450            rx_pin,
451            baud_rate,
452        ))
453    }
454
455    /// Hand out one free state machine on any block, together with the block's
456    /// `Common` handle.  Drivers use this to load custom PIO programs.
457    pub fn request_pio(&mut self, pin: &Peri<'static, impl PioPin>) -> Option<PioAccess<'_>> {
458        let gpio_base_high = gpio_base_for_pins(&[pin.pin()])?;
459        let (block, sm) = self.find_free_sm(gpio_base_high, 32)?;
460        match block {
461            PioBlock::Block0 => {
462                let slot = self.pio0.as_mut()?;
463                let sm = slot.take_any_sm(sm)?;
464                slot.reserve(gpio_base_high, 32);
465                Some(PioAccess::Block0 {
466                    common: &mut slot.common,
467                    sm,
468                })
469            }
470            PioBlock::Block1 => {
471                let slot = self.pio1.as_mut()?;
472                let sm = slot.take_any_sm(sm)?;
473                slot.reserve(gpio_base_high, 32);
474                Some(PioAccess::Block1 {
475                    common: &mut slot.common,
476                    sm,
477                })
478            }
479            PioBlock::Block2 => {
480                let slot = self.pio2.as_mut()?;
481                let sm = slot.take_any_sm(sm)?;
482                slot.reserve(gpio_base_high, 32);
483                Some(PioAccess::Block2 {
484                    common: &mut slot.common,
485                    sm,
486                })
487            }
488        }
489    }
490
491    pub(crate) fn find_free_sm(
492        &self,
493        gpio_base_high: bool,
494        instructions: u8,
495    ) -> Option<(PioBlock, Sm)> {
496        if let Some(slot) = &self.pio0 {
497            for sm in Sm::ALL {
498                if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
499                    return Some((PioBlock::Block0, sm));
500                }
501            }
502        }
503        if let Some(slot) = &self.pio1 {
504            for sm in Sm::ALL {
505                if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
506                    return Some((PioBlock::Block1, sm));
507                }
508            }
509        }
510        if let Some(slot) = &self.pio2 {
511            for sm in Sm::ALL {
512                if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
513                    return Some((PioBlock::Block2, sm));
514                }
515            }
516        }
517        None
518    }
519
520    pub(crate) fn find_free_sm_pair(
521        &self,
522        gpio_base_high: bool,
523        instructions: u8,
524    ) -> Option<(PioBlock, Sm, Sm)> {
525        if let Some(slot) = &self.pio0
526            && slot.can_reserve(gpio_base_high, instructions)
527            && let Some((a, b)) = two_free_sms(slot)
528        {
529            return Some((PioBlock::Block0, a, b));
530        }
531        if let Some(slot) = &self.pio1
532            && slot.can_reserve(gpio_base_high, instructions)
533            && let Some((a, b)) = two_free_sms(slot)
534        {
535            return Some((PioBlock::Block1, a, b));
536        }
537        if let Some(slot) = &self.pio2
538            && slot.can_reserve(gpio_base_high, instructions)
539            && let Some((a, b)) = two_free_sms(slot)
540        {
541            return Some((PioBlock::Block2, a, b));
542        }
543        None
544    }
545}
546
547fn two_free_sms<PIO: Instance + Send + 'static>(slot: &PioSlot<PIO>) -> Option<(Sm, Sm)> {
548    let mut free = Sm::ALL.into_iter().filter(|sm| slot.has_sm(*sm));
549    match (free.next(), free.next()) {
550        (Some(a), Some(b)) => Some((a, b)),
551        _ => None,
552    }
553}
554
555/// Dispatch over a `PioAccess` handle, recovering the typed `Common` and
556/// `StateMachine` so a driver can load a custom PIO program.
557///
558/// The driver provides a **generic** function/closure that works for any
559/// `PIO: Instance` and `const N: usize`.  The macro matches all 12
560/// (block × SM) arms and calls the function in the one that matches.
561///
562/// # Example
563///
564/// ```ignore
565/// use embassy_rp::pio::{Common, Instance, StateMachine};
566///
567/// fn my_init<PIO: Instance, const N: usize>(
568///     common: &mut Common<'static, PIO>,
569///     sm: StateMachine<'static, PIO, N>,
570/// ) -> MyDriver<PIO, N> {
571///     let program = MyProgram::new(common);
572///     MyDriver::new(common, sm, &program)
573/// }
574///
575/// let handle = allocator.request_pio(&pin)?;
576/// let driver = with_pio!(handle, common, sm, my_init(common, sm));
577/// ```
578#[macro_export]
579macro_rules! with_pio {
580    ($handle:expr, $common:ident, $sm:ident, $body:expr) => {
581        match $handle {
582            $crate::bus::pio::PioAccess::Block0 { common, sm } => {
583                let $common = common;
584                match sm {
585                    $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
586                    $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
587                    $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
588                    $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
589                }
590            }
591            $crate::bus::pio::PioAccess::Block1 { common, sm } => {
592                let $common = common;
593                match sm {
594                    $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
595                    $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
596                    $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
597                    $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
598                }
599            }
600            $crate::bus::pio::PioAccess::Block2 { common, sm } => {
601                let $common = common;
602                match sm {
603                    $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
604                    $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
605                    $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
606                    $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
607                }
608            }
609        }
610    };
611}