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 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
42pub 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#[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
84struct 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 _ => 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 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 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 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 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#[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}