1use 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 gpio_base_for_pin_numbers(pins.iter().copied())
34}
35
36fn gpio_base_for_pin_numbers(pins: impl IntoIterator<Item = u8>) -> Option<bool> {
37 let mut has_pins = false;
38 let mut all_low = true;
39 let mut all_high = true;
40 for pin in pins {
41 has_pins = true;
42 all_low &= pin < 32;
43 all_high &= (16..48).contains(&pin);
44 }
45
46 if !has_pins {
47 None
48 } else if all_low {
49 Some(false)
50 } else if all_high {
51 Some(true)
52 } else {
53 None
54 }
55}
56
57pub trait PioProgramPin {
63 #[doc(hidden)]
64 fn pio_pin_number(&self) -> u8;
65}
66
67impl<P: PioPin> PioProgramPin for Peri<'static, P> {
68 fn pio_pin_number(&self) -> u8 {
69 self.pin()
70 }
71}
72
73pub enum AnyStateMachine<'d, PIO: Instance + 'static> {
78 Sm0(StateMachine<'d, PIO, 0>),
79 Sm1(StateMachine<'d, PIO, 1>),
80 Sm2(StateMachine<'d, PIO, 2>),
81 Sm3(StateMachine<'d, PIO, 3>),
82}
83
84#[must_use = "custom PIO access reserves its block for the rest of the boot"]
94pub enum PioAccess<'a> {
95 Block0 {
96 common: &'a mut Common<'static, PIO0>,
97 sm: AnyStateMachine<'static, PIO0>,
98 },
99 Block1 {
100 common: &'a mut Common<'static, PIO1>,
101 sm: AnyStateMachine<'static, PIO1>,
102 },
103 Block2 {
104 common: &'a mut Common<'static, PIO2>,
105 sm: AnyStateMachine<'static, PIO2>,
106 },
107}
108
109embassy_rp::bind_interrupts!(struct PioIrqs {
110 PIO0_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO0>;
111 PIO1_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO1>;
112 PIO2_IRQ_0 => embassy_rp::pio::InterruptHandler<PIO2>;
113});
114
115struct PioSlot<PIO: Instance + 'static> {
119 common: Common<'static, PIO>,
120 sm0: Option<StateMachine<'static, PIO, 0>>,
121 sm1: Option<StateMachine<'static, PIO, 1>>,
122 sm2: Option<StateMachine<'static, PIO, 2>>,
123 sm3: Option<StateMachine<'static, PIO, 3>>,
124 _irq_flags: IrqFlags<'static, PIO>,
125 _irq0: Irq<'static, PIO, 0>,
126 _irq1: Irq<'static, PIO, 1>,
127 _irq2: Irq<'static, PIO, 2>,
128 _irq3: Irq<'static, PIO, 3>,
129 gpio_base_high: Option<bool>,
130 reserved_instructions: u8,
131}
132
133impl<PIO: Instance + Send + 'static> PioSlot<PIO> {
134 fn new(pio: Pio<'static, PIO>) -> Self {
135 let Pio {
136 common,
137 irq_flags,
138 irq0,
139 irq1,
140 irq2,
141 irq3,
142 sm0,
143 sm1,
144 sm2,
145 sm3,
146 ..
147 } = pio;
148 Self {
149 common,
150 sm0: Some(sm0),
151 sm1: Some(sm1),
152 sm2: Some(sm2),
153 sm3: Some(sm3),
154 _irq_flags: irq_flags,
155 _irq0: irq0,
156 _irq1: irq1,
157 _irq2: irq2,
158 _irq3: irq3,
159 gpio_base_high: None,
160 reserved_instructions: 0,
161 }
162 }
163
164 fn can_reserve(&self, gpio_base_high: bool, instructions: u8) -> bool {
165 self.gpio_base_high
166 .is_none_or(|current| current == gpio_base_high)
167 && self.reserved_instructions.saturating_add(instructions) <= 32
168 }
169
170 fn reserve(&mut self, gpio_base_high: bool, instructions: u8) {
171 debug_assert!(self.can_reserve(gpio_base_high, instructions));
172 self.gpio_base_high = Some(gpio_base_high);
173 self.reserved_instructions += instructions;
174 }
175
176 fn has_sm(&self, sm: Sm) -> bool {
177 match sm {
178 Sm::Sm0 => self.sm0.is_some(),
179 Sm::Sm1 => self.sm1.is_some(),
180 Sm::Sm2 => self.sm2.is_some(),
181 Sm::Sm3 => self.sm3.is_some(),
182 }
183 }
184
185 fn take_any_sm(&mut self, sm: Sm) -> Option<AnyStateMachine<'static, PIO>> {
186 match sm {
187 Sm::Sm0 => self.sm0.take().map(AnyStateMachine::Sm0),
188 Sm::Sm1 => self.sm1.take().map(AnyStateMachine::Sm1),
189 Sm::Sm2 => self.sm2.take().map(AnyStateMachine::Sm2),
190 Sm::Sm3 => self.sm3.take().map(AnyStateMachine::Sm3),
191 }
192 }
193
194 fn build_spi<TxDma, RxDma, Irq>(
195 &mut self,
196 sm: Sm,
197 gpio_base_high: bool,
198 instructions: u8,
199 clk: Peri<'static, impl PioPin>,
200 mosi: Peri<'static, impl PioPin>,
201 miso: Peri<'static, impl PioPin>,
202 tx_dma: Peri<'static, TxDma>,
203 rx_dma: Peri<'static, RxDma>,
204 irq: Irq,
205 config: spi::Config,
206 ) -> Option<Box<dyn DynSpiBusCombined>>
207 where
208 TxDma: ChannelInstance,
209 RxDma: ChannelInstance,
210 Irq: Binding<TxDma::Interrupt, dma::InterruptHandler<TxDma>>
211 + Binding<RxDma::Interrupt, dma::InterruptHandler<RxDma>>
212 + 'static,
213 {
214 macro_rules! take {
215 ($field:ident) => {
216 match self.$field.take() {
217 Some(sm) => {
218 self.reserve(gpio_base_high, instructions);
219 Some(Box::new(
220 PioSpiBus::new(
221 &mut self.common,
222 sm,
223 clk,
224 mosi,
225 miso,
226 tx_dma,
227 rx_dma,
228 irq,
229 config,
230 )
231 .expect("PIO SPI configuration was validated before allocation"),
232 ) as Box<dyn DynSpiBusCombined>)
233 }
234 None => None,
235 }
236 };
237 }
238
239 match sm {
240 Sm::Sm0 => take!(sm0),
241 Sm::Sm1 => take!(sm1),
242 Sm::Sm2 => take!(sm2),
243 Sm::Sm3 => take!(sm3),
244 }
245 }
246
247 fn build_uart(
248 &mut self,
249 sm_tx: Sm,
250 sm_rx: Sm,
251 gpio_base_high: bool,
252 instructions: u8,
253 tx_pin: Peri<'static, impl PioPin>,
254 rx_pin: Peri<'static, impl PioPin>,
255 baud_rate: u32,
256 ) -> Option<Box<dyn DynUartBus>> {
257 macro_rules! pair {
258 ($txf:ident, $rxf:ident) => {
259 match (self.$txf.take(), self.$rxf.take()) {
260 (Some(t), Some(r)) => {
261 self.reserve(gpio_base_high, instructions);
262 Some(Box::new(
263 PioUartBus::new(&mut self.common, t, r, tx_pin, rx_pin, baud_rate)
264 .expect("PIO UART baud was validated before allocation"),
265 ) as Box<dyn DynUartBus>)
266 }
267 (Some(t), None) => {
268 self.$txf = Some(t);
269 None
270 }
271 (None, _) => None,
272 }
273 };
274 }
275
276 match (sm_tx, sm_rx) {
277 (Sm::Sm0, Sm::Sm1) => pair!(sm0, sm1),
278 (Sm::Sm0, Sm::Sm2) => pair!(sm0, sm2),
279 (Sm::Sm0, Sm::Sm3) => pair!(sm0, sm3),
280 (Sm::Sm1, Sm::Sm0) => pair!(sm1, sm0),
281 (Sm::Sm1, Sm::Sm2) => pair!(sm1, sm2),
282 (Sm::Sm1, Sm::Sm3) => pair!(sm1, sm3),
283 (Sm::Sm2, Sm::Sm0) => pair!(sm2, sm0),
284 (Sm::Sm2, Sm::Sm1) => pair!(sm2, sm1),
285 (Sm::Sm2, Sm::Sm3) => pair!(sm2, sm3),
286 (Sm::Sm3, Sm::Sm0) => pair!(sm3, sm0),
287 (Sm::Sm3, Sm::Sm1) => pair!(sm3, sm1),
288 (Sm::Sm3, Sm::Sm2) => pair!(sm3, sm2),
289 _ => None,
292 }
293 }
294}
295
296pub struct PioManager {
297 pio0: Option<PioSlot<PIO0>>,
298 pio1: Option<PioSlot<PIO1>>,
299 pio2: Option<PioSlot<PIO2>>,
300}
301
302impl PioManager {
303 pub fn new(
304 pio0: Peri<'static, PIO0>,
305 pio1: Peri<'static, PIO1>,
306 pio2: Peri<'static, PIO2>,
307 ) -> Self {
308 Self {
309 pio0: Some(PioSlot::new(Pio::new(pio0, PioIrqs))),
310 pio1: Some(PioSlot::new(Pio::new(pio1, PioIrqs))),
311 pio2: Some(PioSlot::new(Pio::new(pio2, PioIrqs))),
312 }
313 }
314
315 pub(crate) fn build_spi_at<TxDma, RxDma, Irq>(
318 &mut self,
319 block: PioBlock,
320 sm: Sm,
321 gpio_base_high: bool,
322 instructions: u8,
323 clk: Peri<'static, impl PioPin>,
324 mosi: Peri<'static, impl PioPin>,
325 miso: Peri<'static, impl PioPin>,
326 tx_dma: Peri<'static, TxDma>,
327 rx_dma: Peri<'static, RxDma>,
328 irq: Irq,
329 config: spi::Config,
330 ) -> SpiBusHandle
331 where
332 TxDma: ChannelInstance,
333 RxDma: ChannelInstance,
334 Irq: Binding<TxDma::Interrupt, dma::InterruptHandler<TxDma>>
335 + Binding<RxDma::Interrupt, dma::InterruptHandler<RxDma>>
336 + 'static,
337 {
338 let bus = match block {
339 PioBlock::Block0 => self
340 .pio0
341 .as_mut()
342 .and_then(|slot| {
343 slot.build_spi(
344 sm,
345 gpio_base_high,
346 instructions,
347 clk,
348 mosi,
349 miso,
350 tx_dma,
351 rx_dma,
352 irq,
353 config,
354 )
355 })
356 .expect("PIO SM allocation invariant: SM was not free"),
357 PioBlock::Block1 => self
358 .pio1
359 .as_mut()
360 .and_then(|slot| {
361 slot.build_spi(
362 sm,
363 gpio_base_high,
364 instructions,
365 clk,
366 mosi,
367 miso,
368 tx_dma,
369 rx_dma,
370 irq,
371 config,
372 )
373 })
374 .expect("PIO SM allocation invariant: SM was not free"),
375 PioBlock::Block2 => self
376 .pio2
377 .as_mut()
378 .and_then(|slot| {
379 slot.build_spi(
380 sm,
381 gpio_base_high,
382 instructions,
383 clk,
384 mosi,
385 miso,
386 tx_dma,
387 rx_dma,
388 irq,
389 config,
390 )
391 })
392 .expect("PIO SM allocation invariant: SM was not free"),
393 };
394 SpiBusHandle::new(bus, SpiBusVersion::Pio)
395 }
396
397 pub(crate) fn build_uart_at(
400 &mut self,
401 block: PioBlock,
402 sm_tx: Sm,
403 sm_rx: Sm,
404 gpio_base_high: bool,
405 instructions: u8,
406 tx_pin: Peri<'static, impl PioPin>,
407 rx_pin: Peri<'static, impl PioPin>,
408 baud_rate: u32,
409 ) -> UartBusHandle {
410 let bus = match block {
411 PioBlock::Block0 => self
412 .pio0
413 .as_mut()
414 .and_then(|slot| {
415 slot.build_uart(
416 sm_tx,
417 sm_rx,
418 gpio_base_high,
419 instructions,
420 tx_pin,
421 rx_pin,
422 baud_rate,
423 )
424 })
425 .expect("PIO SM allocation invariant: SM pair was not free"),
426 PioBlock::Block1 => self
427 .pio1
428 .as_mut()
429 .and_then(|slot| {
430 slot.build_uart(
431 sm_tx,
432 sm_rx,
433 gpio_base_high,
434 instructions,
435 tx_pin,
436 rx_pin,
437 baud_rate,
438 )
439 })
440 .expect("PIO SM allocation invariant: SM pair was not free"),
441 PioBlock::Block2 => self
442 .pio2
443 .as_mut()
444 .and_then(|slot| {
445 slot.build_uart(
446 sm_tx,
447 sm_rx,
448 gpio_base_high,
449 instructions,
450 tx_pin,
451 rx_pin,
452 baud_rate,
453 )
454 })
455 .expect("PIO SM allocation invariant: SM pair was not free"),
456 };
457 UartBusHandle::new(bus, UartBusVersion::Pio)
458 }
459
460 pub fn build_uart_pio(
463 &mut self,
464 tx_pin: Peri<'static, impl PioPin>,
465 rx_pin: Peri<'static, impl PioPin>,
466 baud_rate: u32,
467 ) -> Option<UartBusHandle> {
468 let gpio_base_high = gpio_base_for_pins(&[tx_pin.pin()])?;
469 if gpio_base_for_pins(&[rx_pin.pin()])? != gpio_base_high {
470 return None;
471 }
472 let (block, sm_tx, sm_rx) =
473 self.find_free_sm_pair(gpio_base_high, PIO_UART_INSTRUCTIONS)?;
474 Some(self.build_uart_at(
475 block,
476 sm_tx,
477 sm_rx,
478 gpio_base_high,
479 PIO_UART_INSTRUCTIONS,
480 tx_pin,
481 rx_pin,
482 baud_rate,
483 ))
484 }
485
486 pub fn request_pio(&mut self, pins: &[&dyn PioProgramPin]) -> Option<PioAccess<'_>> {
499 let gpio_base_high =
500 gpio_base_for_pin_numbers(pins.iter().map(|pin| pin.pio_pin_number()))?;
501 let (block, sm) = self.find_free_sm(gpio_base_high, 32)?;
502 match block {
503 PioBlock::Block0 => {
504 let slot = self.pio0.as_mut()?;
505 let sm = slot.take_any_sm(sm)?;
506 slot.reserve(gpio_base_high, 32);
507 Some(PioAccess::Block0 {
508 common: &mut slot.common,
509 sm,
510 })
511 }
512 PioBlock::Block1 => {
513 let slot = self.pio1.as_mut()?;
514 let sm = slot.take_any_sm(sm)?;
515 slot.reserve(gpio_base_high, 32);
516 Some(PioAccess::Block1 {
517 common: &mut slot.common,
518 sm,
519 })
520 }
521 PioBlock::Block2 => {
522 let slot = self.pio2.as_mut()?;
523 let sm = slot.take_any_sm(sm)?;
524 slot.reserve(gpio_base_high, 32);
525 Some(PioAccess::Block2 {
526 common: &mut slot.common,
527 sm,
528 })
529 }
530 }
531 }
532
533 pub(crate) fn find_free_sm(
534 &self,
535 gpio_base_high: bool,
536 instructions: u8,
537 ) -> Option<(PioBlock, Sm)> {
538 if let Some(slot) = &self.pio0 {
539 for sm in Sm::ALL {
540 if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
541 return Some((PioBlock::Block0, sm));
542 }
543 }
544 }
545 if let Some(slot) = &self.pio1 {
546 for sm in Sm::ALL {
547 if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
548 return Some((PioBlock::Block1, sm));
549 }
550 }
551 }
552 if let Some(slot) = &self.pio2 {
553 for sm in Sm::ALL {
554 if slot.has_sm(sm) && slot.can_reserve(gpio_base_high, instructions) {
555 return Some((PioBlock::Block2, sm));
556 }
557 }
558 }
559 None
560 }
561
562 pub(crate) fn find_free_sm_pair(
563 &self,
564 gpio_base_high: bool,
565 instructions: u8,
566 ) -> Option<(PioBlock, Sm, Sm)> {
567 if let Some(slot) = &self.pio0
568 && slot.can_reserve(gpio_base_high, instructions)
569 && let Some((a, b)) = two_free_sms(slot)
570 {
571 return Some((PioBlock::Block0, a, b));
572 }
573 if let Some(slot) = &self.pio1
574 && slot.can_reserve(gpio_base_high, instructions)
575 && let Some((a, b)) = two_free_sms(slot)
576 {
577 return Some((PioBlock::Block1, a, b));
578 }
579 if let Some(slot) = &self.pio2
580 && slot.can_reserve(gpio_base_high, instructions)
581 && let Some((a, b)) = two_free_sms(slot)
582 {
583 return Some((PioBlock::Block2, a, b));
584 }
585 None
586 }
587}
588
589fn two_free_sms<PIO: Instance + Send + 'static>(slot: &PioSlot<PIO>) -> Option<(Sm, Sm)> {
590 let mut free = Sm::ALL.into_iter().filter(|sm| slot.has_sm(*sm));
591 match (free.next(), free.next()) {
592 (Some(a), Some(b)) => Some((a, b)),
593 _ => None,
594 }
595}
596
597#[macro_export]
621macro_rules! with_pio {
622 ($handle:expr, $common:ident, $sm:ident, $body:expr) => {
623 match $handle {
624 $crate::bus::pio::PioAccess::Block0 { common, sm } => {
625 let $common = common;
626 match sm {
627 $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
628 $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
629 $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
630 $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
631 }
632 }
633 $crate::bus::pio::PioAccess::Block1 { common, sm } => {
634 let $common = common;
635 match sm {
636 $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
637 $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
638 $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
639 $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
640 }
641 }
642 $crate::bus::pio::PioAccess::Block2 { common, sm } => {
643 let $common = common;
644 match sm {
645 $crate::bus::pio::AnyStateMachine::Sm0($sm) => $body,
646 $crate::bus::pio::AnyStateMachine::Sm1($sm) => $body,
647 $crate::bus::pio::AnyStateMachine::Sm2($sm) => $body,
648 $crate::bus::pio::AnyStateMachine::Sm3($sm) => $body,
649 }
650 }
651 }
652 };
653}