From d18b3b8a93ef30791a76d4188ef5afe1cd09f9a5 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Tue, 18 Mar 2025 06:23:50 +0000 Subject: [PATCH 1/6] pio: enable async.await support --- rp2040-hal/src/async_utils.rs | 22 ++-- rp2040-hal/src/pio.rs | 79 +++++++++++- rp2040-hal/src/pio/non_blocking.rs | 196 +++++++++++++++++++++++++++++ 3 files changed, 282 insertions(+), 15 deletions(-) create mode 100644 rp2040-hal/src/pio/non_blocking.rs diff --git a/rp2040-hal/src/async_utils.rs b/rp2040-hal/src/async_utils.rs index 11f82760d..d0a793093 100644 --- a/rp2040-hal/src/async_utils.rs +++ b/rp2040-hal/src/async_utils.rs @@ -64,13 +64,12 @@ pub trait AsyncPeripheral: sealed::Wakeable { pub(crate) struct CancellablePollFn<'periph, Periph, PFn, EnIrqFn, CancelFn, OutputTy> where Periph: sealed::Wakeable, - CancelFn: FnMut(&mut Periph), + CancelFn: FnOnce(&mut Periph), { periph: &'periph mut Periph, poll: PFn, enable_irq: EnIrqFn, - cancel: CancelFn, - done: bool, + cancel: Option, // captures F's return type. phantom: PhantomData, } @@ -80,7 +79,7 @@ where Periph: sealed::Wakeable, PFn: FnMut(&mut Periph) -> Poll, EnIrqFn: FnMut(&mut Periph), - CancelFn: FnMut(&mut Periph), + CancelFn: FnOnce(&mut Periph), { pub(crate) fn new( periph: &'p mut Periph, @@ -92,8 +91,7 @@ where periph, poll, enable_irq, - cancel, - done: false, + cancel: Some(cancel), phantom: PhantomData, } } @@ -105,7 +103,7 @@ where Periph: sealed::Wakeable, PFn: FnMut(&mut Periph) -> Poll, EnIrqFn: FnMut(&mut Periph), - CancelFn: FnMut(&mut Periph), + CancelFn: FnOnce(&mut Periph), { type Output = OutputTy; @@ -115,7 +113,7 @@ where ref mut periph, poll: ref mut is_ready, enable_irq: ref mut setup_flags, - ref mut done, + ref mut cancel, .. } = unsafe { self.get_unchecked_mut() }; let r = (is_ready)(periph); @@ -123,7 +121,7 @@ where Periph::waker().register(cx.waker()); (setup_flags)(periph); } else { - *done = true; + *cancel = None; } r } @@ -132,12 +130,12 @@ impl Drop for CancellablePollFn<'_, Periph, PFn, EnIrqFn, CancelFn, OutputTy> where Periph: sealed::Wakeable, - CancelFn: FnMut(&mut Periph), + CancelFn: FnOnce(&mut Periph), { fn drop(&mut self) { - if !self.done { + if let Some(cancel) = self.cancel.take() { Periph::waker().clear(); - (self.cancel)(self.periph); + cancel(self.periph); } } } diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index 980c281ee..4e6a407ea 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -14,6 +14,8 @@ use crate::{ typelevel::Sealed, }; +mod non_blocking; + const PIO_INSTRUCTION_COUNT: usize = 32; impl Sealed for PIO0 {} @@ -21,9 +23,18 @@ impl Sealed for PIO1 {} /// PIO Instance pub trait PIOExt: Deref + SubsystemReset + Sized + Send + Sealed { + /// RX FIFO depth + const RX_FIFO_DEPTH: usize; + + /// TX FIFO depth + const TX_FIFO_DEPTH: usize; + /// Associated Pin Function. type PinFunction: Function; + /// Returns a pointer to the PIO’s Register Block + fn ptr() -> *const RegisterBlock; + /// Create a new PIO wrapper and split the state machines into individual objects. #[allow(clippy::type_complexity)] // Required for symmetry with PIO::free(). fn split( @@ -76,13 +87,23 @@ pub trait PIOExt: Deref + SubsystemReset + Sized + Send } impl PIOExt for PIO0 { + const RX_FIFO_DEPTH: usize = 4; + const TX_FIFO_DEPTH: usize = 4; type PinFunction = FunctionPio0; + fn ptr() -> *const RegisterBlock { + PIO0::ptr() + } fn id() -> usize { 0 } } impl PIOExt for PIO1 { + const RX_FIFO_DEPTH: usize = 4; + const TX_FIFO_DEPTH: usize = 4; type PinFunction = FunctionPio1; + fn ptr() -> *const RegisterBlock { + PIO1::ptr() + } fn id() -> usize { 1 } @@ -576,9 +597,9 @@ pub struct Running; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PioIRQ { #[allow(missing_docs)] - Irq0, + Irq0 = 0, #[allow(missing_docs)] - Irq1, + Irq1 = 1, } impl PioIRQ { const fn to_index(self) -> usize { @@ -1380,6 +1401,32 @@ impl Rx { unsafe { self.block().fstat().read().rxfull().bits() & (1 << SM::id()) != 0 } } + /// Reads the number of word in the fifo + pub fn fifo_level(&self) -> usize { + // Safety: read-only access without side-effect + let flevel = unsafe { self.block().flevel().read() }; + (match SM::id() { + 0 => flevel.rx0().bits(), + 1 => flevel.rx1().bits(), + 2 => flevel.rx2().bits(), + 3 => flevel.rx3().bits(), + _ => unreachable!(), + }) as usize + } + + /// Returns the FIFO depth. + pub fn fifo_depth(&self) -> usize { + // Safety: read-only access without side-effect + let block = unsafe { self.block() }; + let join_rx = block.sm(SM::id()).sm_shiftctrl().read().fjoin_rx().bit(); + let depth = block.dbg_cfginfo().read().fifo_depth().bits() as usize; + if join_rx { + depth * 2 + } else { + depth + } + } + /// Enable RX FIFO not empty interrupt. /// /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. @@ -1498,7 +1545,7 @@ impl Tx { /// This is a value between 0 and 39. Each FIFO on each state machine on /// each PIO has a unique value. pub fn dreq_value(&self) -> u8 { - if self.block as usize == 0x5020_0000usize { + if self.block == PIO0::ptr() { TREQ_SEL_A::PIO0_TX0 as u8 + (SM::id() as u8) } else { TREQ_SEL_A::PIO1_TX0 as u8 + (SM::id() as u8) @@ -1588,6 +1635,32 @@ impl Tx { unsafe { self.block().fstat().read().txfull().bits() & (1 << SM::id()) != 0 } } + /// Reads the number of word in the FIFO + pub fn fifo_level(&self) -> usize { + // Safety: read-only access without side-effect + let flevel = unsafe { self.block().flevel().read() }; + (match SM::id() { + 0 => flevel.tx0().bits(), + 1 => flevel.tx1().bits(), + 2 => flevel.tx2().bits(), + 3 => flevel.tx3().bits(), + _ => unreachable!(), + }) as usize + } + + /// Returns the FIFO depth. + pub fn fifo_depth(&self) -> usize { + // Safety: read-only access without side-effect + let block = unsafe { self.block() }; + let join_tx = block.sm(SM::id()).sm_shiftctrl().read().fjoin_tx().bit(); + let depth = block.dbg_cfginfo().read().fifo_depth().bits() as usize; + if join_tx { + depth * 2 + } else { + depth + } + } + /// Enable TX FIFO not full interrupt. /// /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. diff --git a/rp2040-hal/src/pio/non_blocking.rs b/rp2040-hal/src/pio/non_blocking.rs new file mode 100644 index 000000000..321edd38a --- /dev/null +++ b/rp2040-hal/src/pio/non_blocking.rs @@ -0,0 +1,196 @@ +use core::task::Poll; + +use crate::async_utils::{ + sealed::{IrqWaker, Wakeable}, + CancellablePollFn, +}; +use crate::dma::TransferSize; + +use super::{Interrupt, PioIRQ, Rx, Tx}; +use super::{PIOExt, StateMachineIndex, ValidStateMachine}; +use super::{PIO0, PIO1, SM0, SM1, SM2, SM3}; + +macro_rules! impl_wakeable { + ($($pio:ident => ( $($sm:ident),* )),*) => { + $( + impl Wakeable for Interrupt<'_, $pio, IRQ> { + fn waker() -> &'static IrqWaker { + static WAKER: IrqWaker = IrqWaker::new(); + &WAKER + } + } + $( + impl_wakeable!(Rx, 0, "Rx Not Empty", $pio, $sm); + impl_wakeable!(Tx, 4, "Tx Not Full", $pio, $sm); + )* + )* + }; + ($t:ident, $offset:expr, $doc:expr, $pio:ident, $sm:ident) => { + impl Wakeable for $t<($pio, $sm), W> + where + W: TransferSize, + { + fn waker() -> &'static IrqWaker { + static WAKER: IrqWaker = IrqWaker::new(); + &WAKER + } + } + }; +} +impl_wakeable!( + PIO0 => ( SM0, SM1, SM2, SM3 ), + PIO1 => ( SM0, SM1, SM2, SM3 ) +); + +impl Interrupt<'_, P, IRQ> +where + Self: Wakeable, + P: PIOExt, +{ + /// Wakes a task awaiting of this interrupt. + pub fn on_interrupt() { + use crate::atomic_register_access::write_bitmask_clear; + unsafe { + let mask = 1 << (IRQ + 8); + let sm_irq = (*P::ptr()).sm_irq(IRQ); + if sm_irq.irq_ints().read().bits() & mask == mask { + write_bitmask_clear(sm_irq.irq_inte().as_ptr(), mask); + } + } + Self::waker().wake(); + } +} + +macro_rules! on_interrupts { + ($t:ident, $offset:expr, $event:expr) => { + impl $t<(P, SM), W> + where + W: TransferSize, + P: PIOExt, + SM: StateMachineIndex, + Self: Wakeable, + { + /// Wakes a task awaiting on + #[doc = $event] + pub fn on_interrupt(irq: PioIRQ) { + use crate::atomic_register_access::write_bitmask_clear; + unsafe { + let mask = 1 << ($offset + <(P, SM)>::id()); + let irq = (&*P::ptr()).sm_irq(irq as usize); + if irq.irq_ints().read().bits() & mask == mask { + write_bitmask_clear(irq.irq_inte().as_ptr(), mask); + } + } + Self::waker().wake(); + } + } + }; +} +on_interrupts!(Rx, 0, "Rx Not Empty"); +on_interrupts!(Tx, 4, "Tx Not Full"); + +impl Interrupt<'_, P, IRQ> +where + P: PIOExt, + Self: Wakeable, +{ + /// Stalls until the IRQ fires + pub async fn sm_interrupt(&mut self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + let mask = 1 << id; + + CancellablePollFn::new( + self, + |me| unsafe { + if (me.block().irq().read().irq().bits() & mask) == mask { + Poll::Ready(()) + } else { + Poll::Pending + } + }, + |me| me.enable_sm_interrupt(id), + |me| { + me.disable_sm_interrupt(id); + }, + ) + .await + } +} + +impl Rx +where + Self: Wakeable, +{ + /// Reads a u32 from the FIFO. + pub async fn async_read(&mut self, irq: PioIRQ) -> u32 { + self.rx_not_empty(irq).await; + let fifo_address = self.fifo_address(); + unsafe { core::ptr::read_volatile(fifo_address) } + } + + /// Used to `.await` until the fifo is no longer empty. + pub async fn rx_not_empty(&mut self, irq: PioIRQ) { + CancellablePollFn::new( + self, + |me| { + if me.is_empty() { + Poll::Pending + } else { + Poll::Ready(()) + } + }, + |me| me.enable_rx_not_empty_interrupt(irq), + |me| { + me.disable_rx_not_empty_interrupt(irq); + }, + ) + .await + } +} + +impl Tx +where + Self: Wakeable, +{ + /// Writes a u8 to the FIFO replicated 4 times in a 32bits word. + pub async fn async_write_u8_replicated(&mut self, irq: PioIRQ, value: u8) { + self.async_write_generic(irq, value).await + } + /// Writes a u16 to the FIFO replicated 2 times in a 32bits word. + pub async fn async_write_u16_replicated(&mut self, irq: PioIRQ, value: u16) { + self.async_write_generic(irq, value).await + } + + /// Writes a u32 to the FIFO. + pub async fn async_write(&mut self, irq: PioIRQ, value: u32) { + self.async_write_generic(irq, value).await + } + + async fn async_write_generic(&mut self, irq: PioIRQ, value: T) { + self.tx_not_full(irq).await; + // Safety: Only accessed by this instance (unless DMA is used). + unsafe { + let reg_ptr = self.fifo_address() as *mut T; + reg_ptr.write_volatile(value); + } + } + + /// Used to `.await` until the fifo is no longer full. + pub async fn tx_not_full(&mut self, irq: PioIRQ) { + CancellablePollFn::new( + self, + |me| { + if me.is_full() { + Poll::Pending + } else { + Poll::Ready(()) + } + }, + |me| me.enable_tx_not_full_interrupt(irq), + |me| { + me.disable_tx_not_full_interrupt(irq); + }, + ) + .await + } +} From bd24db55f2d9476d3024bad953c98545d3c32405 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 21 Mar 2025 06:58:10 +0000 Subject: [PATCH 2/6] i2c: allow Error to be checked for equality --- rp2040-hal/src/i2c.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rp2040-hal/src/i2c.rs b/rp2040-hal/src/i2c.rs index 11455db6b..3143cb1c0 100644 --- a/rp2040-hal/src/i2c.rs +++ b/rp2040-hal/src/i2c.rs @@ -115,6 +115,7 @@ impl ValidAddress for u16 { /// I2C error #[non_exhaustive] +#[derive(PartialEq, Eq)] pub enum Error { /// I2C abort with error Abort(u32), From 490ddc8aff2efa9df05e7341d3cee5c5ac113b0e Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 21 Mar 2025 07:00:48 +0000 Subject: [PATCH 3/6] i2c: optimise `next_event` This change saves us from reading ic_raw_intr_stat twice in the same loop. --- rp2040-hal/src/i2c/peripheral.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/rp2040-hal/src/i2c/peripheral.rs b/rp2040-hal/src/i2c/peripheral.rs index 3055786ee..3b982304e 100644 --- a/rp2040-hal/src/i2c/peripheral.rs +++ b/rp2040-hal/src/i2c/peripheral.rs @@ -210,10 +210,10 @@ impl, PINS> Iterator for I2C, PINS> I2C { - /// Returns the next i2c event if any. - pub fn next_event(&mut self) -> Option { - let stat = self.i2c.ic_raw_intr_stat().read(); - + fn internal_next_event( + &mut self, + stat: rp2040_pac::i2c0::ic_raw_intr_stat::R, + ) -> Option { match self.mode.state { State::Idle if stat.start_det().bit_is_set() => { self.i2c.ic_clr_start_det().read(); @@ -258,6 +258,13 @@ impl, PINS> I2C { _ => None, } } + + /// Returns the next i2c event if any. + pub fn next_event(&mut self) -> Option { + let stat = self.i2c.ic_raw_intr_stat().read(); + + self.internal_next_event(stat) + } } macro_rules! impl_wakeable { @@ -291,12 +298,13 @@ where { /// Asynchronously waits for an Event. pub async fn wait_next(&mut self) -> Event { + let mut stat = self.i2c.ic_raw_intr_stat().read(); loop { - if let Some(evt) = self.next_event() { + if let Some(evt) = self.internal_next_event(stat) { return evt; } - CancellablePollFn::new( + stat = CancellablePollFn::new( self, |me| { let stat = me.i2c.ic_raw_intr_stat().read(); @@ -306,7 +314,7 @@ where || stat.rd_req().bit_is_set() || stat.rx_full().bit_is_set() { - Poll::Ready(()) + Poll::Ready(stat) } else { Poll::Pending } From 8bbde2570159570524fb0fbb3ff089022199c456 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 21 Mar 2025 07:02:24 +0000 Subject: [PATCH 4/6] on-target-tests: cause a compilation error if no target feature is selected --- on-target-tests/build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/on-target-tests/build.rs b/on-target-tests/build.rs index d88d873a8..b952d7a88 100644 --- a/on-target-tests/build.rs +++ b/on-target-tests/build.rs @@ -15,6 +15,9 @@ fn main() { let memory_x = include_bytes!("memory_rp2040.x"); #[cfg(feature = "rp235x")] let memory_x = include_bytes!("memory_rp235x.x"); + #[cfg(not(any(feature = "rp2040", feature = "rp235x")))] + compile_error!("No target feature enabled"); + let mut f = File::create(out.join("memory.x")).unwrap(); f.write_all(memory_x).unwrap(); println!("cargo:rerun-if-changed=memory.x"); From 3d2e1381bb752ef5a70f01c2e73f91c8295d05b1 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 21 Mar 2025 19:51:15 +0000 Subject: [PATCH 5/6] i2c: Handle the case where a restart is issued right after the address One case is on read operations for 10bit I2C. First a write is issued with the address, then a restart and a the msb of the address is resent with this time the read flag. Because there is no data phase, the peripheral entered the active state but never switched to either Read or Write. --- rp2040-hal/src/i2c/peripheral.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal/src/i2c/peripheral.rs b/rp2040-hal/src/i2c/peripheral.rs index 3b982304e..5be8bbd32 100644 --- a/rp2040-hal/src/i2c/peripheral.rs +++ b/rp2040-hal/src/i2c/peripheral.rs @@ -241,7 +241,7 @@ impl, PINS> I2C { State::Read if stat.rd_req().bit_is_set() => Some(Event::TransferRead), State::Write if !self.rx_fifo_empty() => Some(Event::TransferWrite), - State::Read | State::Write if stat.restart_det().bit_is_set() => { + State::Read | State::Write | State::Active if stat.restart_det().bit_is_set() => { self.i2c.ic_clr_restart_det().read(); self.i2c.ic_clr_start_det().read(); self.mode.state = State::Active; From cb04212c57fc96ab364f81c15d3f1bc12a1c3a72 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 21 Mar 2025 20:01:36 +0000 Subject: [PATCH 6/6] Align I2C and PIO on rp2040 and rp2350 --- rp2040-hal/src/pio.rs | 22 ++-- rp235x-hal/src/i2c/peripheral.rs | 24 ++-- rp235x-hal/src/pio.rs | 84 +++++++++++- rp235x-hal/src/pio/non_blocking.rs | 197 +++++++++++++++++++++++++++++ 4 files changed, 305 insertions(+), 22 deletions(-) create mode 100644 rp235x-hal/src/pio/non_blocking.rs diff --git a/rp2040-hal/src/pio.rs b/rp2040-hal/src/pio.rs index 4e6a407ea..b0a70df80 100644 --- a/rp2040-hal/src/pio.rs +++ b/rp2040-hal/src/pio.rs @@ -665,8 +665,8 @@ impl StateMachine { pub fn clear_fifos(&mut self) { // Safety: all accesses to these registers are controlled by this instance unsafe { - let sm = &self.sm.sm(); - let sm_shiftctrl = &sm.sm_shiftctrl(); + let sm = self.sm.sm(); + let sm_shiftctrl = sm.sm_shiftctrl(); let mut current = false; // Toggling the FIFO join state clears the fifo sm_shiftctrl.modify(|r, w| { @@ -700,10 +700,10 @@ impl StateMachine { // Safety: all accesses to these registers are controlled by this instance unsafe { - let sm = &self.sm.sm(); - let sm_pinctrl = &sm.sm_pinctrl(); - let sm_instr = &sm.sm_instr(); - let fstat = &self.sm.pio().fstat(); + let sm = self.sm.sm(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); + let fstat = self.sm.pio().fstat(); let operands = if sm.sm_shiftctrl().read().autopull().bit_is_set() { OUT @@ -792,9 +792,9 @@ impl StateMachine { // Safety: all accesses to these registers are controlled by this instance unsafe { let sm = self.sm.sm(); - let sm_pinctrl = &sm.sm_pinctrl(); - let sm_execctrl = &sm.sm_execctrl(); - let sm_instr = &sm.sm_instr(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_execctrl = sm.sm_execctrl(); + let sm_instr = sm.sm_instr(); // sideset_count is implicitly set to 0 when the set_base/set_count are written (rather // than modified) @@ -1292,8 +1292,8 @@ impl StateMachine { // Safety: all accesses to these registers are controlled by this instance unsafe { let sm = self.sm.sm(); - let sm_pinctrl = &sm.sm_pinctrl(); - let sm_instr = &sm.sm_instr(); + let sm_pinctrl = sm.sm_pinctrl(); + let sm_instr = sm.sm_instr(); // save exec_ctrl & make side_set optional let mut saved_sideset_count = 0; diff --git a/rp235x-hal/src/i2c/peripheral.rs b/rp235x-hal/src/i2c/peripheral.rs index 27c8450d7..7f347840e 100644 --- a/rp235x-hal/src/i2c/peripheral.rs +++ b/rp235x-hal/src/i2c/peripheral.rs @@ -210,10 +210,10 @@ impl, PINS> Iterator for I2C, PINS> I2C { - /// Returns the next i2c event if any. - pub fn next_event(&mut self) -> Option { - let stat = self.i2c.ic_raw_intr_stat().read(); - + fn internal_next_event( + &mut self, + stat: rp235x_pac::i2c0::ic_raw_intr_stat::R, + ) -> Option { match self.mode.state { State::Idle if stat.start_det().bit_is_set() => { self.i2c.ic_clr_start_det().read(); @@ -241,7 +241,7 @@ impl, PINS> I2C { State::Read if stat.rd_req().bit_is_set() => Some(Event::TransferRead), State::Write if !self.rx_fifo_empty() => Some(Event::TransferWrite), - State::Read | State::Write if stat.restart_det().bit_is_set() => { + State::Read | State::Write | State::Active if stat.restart_det().bit_is_set() => { self.i2c.ic_clr_restart_det().read(); self.i2c.ic_clr_start_det().read(); self.mode.state = State::Active; @@ -258,6 +258,13 @@ impl, PINS> I2C { _ => None, } } + + /// Returns the next i2c event if any. + pub fn next_event(&mut self) -> Option { + let stat = self.i2c.ic_raw_intr_stat().read(); + + self.internal_next_event(stat) + } } macro_rules! impl_wakeable { @@ -291,12 +298,13 @@ where { /// Asynchronously waits for an Event. pub async fn wait_next(&mut self) -> Event { + let mut stat = self.i2c.ic_raw_intr_stat().read(); loop { - if let Some(evt) = self.next_event() { + if let Some(evt) = self.internal_next_event(stat) { return evt; } - CancellablePollFn::new( + stat = CancellablePollFn::new( self, |me| { let stat = me.i2c.ic_raw_intr_stat().read(); @@ -306,7 +314,7 @@ where || stat.rd_req().bit_is_set() || stat.rx_full().bit_is_set() { - Poll::Ready(()) + Poll::Ready(stat) } else { Poll::Pending } diff --git a/rp235x-hal/src/pio.rs b/rp235x-hal/src/pio.rs index deadda1d3..9dc94d885 100644 --- a/rp235x-hal/src/pio.rs +++ b/rp235x-hal/src/pio.rs @@ -15,6 +15,8 @@ use crate::{ typelevel::Sealed, }; +mod non_blocking; + const PIO_INSTRUCTION_COUNT: usize = 32; impl Sealed for PIO0 {} @@ -23,9 +25,18 @@ impl Sealed for PIO2 {} /// PIO Instance pub trait PIOExt: Deref + SubsystemReset + Sized + Send + Sealed { + /// RX FIFO depth + const RX_FIFO_DEPTH: usize; + + /// TX FIFO depth + const TX_FIFO_DEPTH: usize; + /// Associated Pin Function. type PinFunction: Function; + /// Returns a pointer to the PIO’s Register Block + fn ptr() -> *const RegisterBlock; + /// Create a new PIO wrapper and split the state machines into individual objects. #[allow(clippy::type_complexity)] // Required for symmetry with PIO::free(). fn split( @@ -78,19 +89,34 @@ pub trait PIOExt: Deref + SubsystemReset + Sized + Send } impl PIOExt for PIO0 { + const RX_FIFO_DEPTH: usize = 4; + const TX_FIFO_DEPTH: usize = 4; type PinFunction = FunctionPio0; + fn ptr() -> *const RegisterBlock { + PIO0::ptr() + } fn id() -> usize { 0 } } impl PIOExt for PIO1 { + const RX_FIFO_DEPTH: usize = 4; + const TX_FIFO_DEPTH: usize = 4; type PinFunction = FunctionPio1; + fn ptr() -> *const RegisterBlock { + PIO1::ptr() + } fn id() -> usize { 1 } } impl PIOExt for PIO2 { + const RX_FIFO_DEPTH: usize = 4; + const TX_FIFO_DEPTH: usize = 4; type PinFunction = FunctionPio2; + fn ptr() -> *const RegisterBlock { + PIO1::ptr() + } fn id() -> usize { 2 } @@ -601,9 +627,9 @@ pub struct Running; #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum PioIRQ { #[allow(missing_docs)] - Irq0, + Irq0 = 0, #[allow(missing_docs)] - Irq1, + Irq1 = 1, } impl PioIRQ { const fn to_index(self) -> usize { @@ -1408,6 +1434,32 @@ impl Rx { unsafe { self.block().fstat().read().rxfull().bits() & (1 << SM::id()) != 0 } } + /// Reads the number of word in the fifo + pub fn fifo_level(&self) -> usize { + // Safety: read-only access without side-effect + let flevel = unsafe { self.block().flevel().read() }; + (match SM::id() { + 0 => flevel.rx0().bits(), + 1 => flevel.rx1().bits(), + 2 => flevel.rx2().bits(), + 3 => flevel.rx3().bits(), + _ => unreachable!(), + }) as usize + } + + /// Returns the FIFO depth. + pub fn fifo_depth(&self) -> usize { + // Safety: read-only access without side-effect + let block = unsafe { self.block() }; + let join_rx = block.sm(SM::id()).sm_shiftctrl().read().fjoin_rx().bit(); + let depth = block.dbg_cfginfo().read().fifo_depth().bits() as usize; + if join_rx { + depth * 2 + } else { + depth + } + } + /// Enable RX FIFO not empty interrupt. /// /// This interrupt is raised when the RX FIFO is not empty, i.e. one could read more data from it. @@ -1526,7 +1578,7 @@ impl Tx { /// This is a value between 0 and 39. Each FIFO on each state machine on /// each PIO has a unique value. pub fn dreq_value(&self) -> u8 { - if self.block as usize == 0x5020_0000usize { + if self.block == PIO0::ptr() { TREQ_SEL_A::PIO0_TX0 as u8 + (SM::id() as u8) } else if self.block as usize == 0x5030_0000usize { TREQ_SEL_A::PIO1_TX0 as u8 + (SM::id() as u8) @@ -1619,6 +1671,32 @@ impl Tx { unsafe { self.block().fstat().read().txfull().bits() & (1 << SM::id()) != 0 } } + /// Reads the number of word in the FIFO + pub fn fifo_level(&self) -> usize { + // Safety: read-only access without side-effect + let flevel = unsafe { self.block().flevel().read() }; + (match SM::id() { + 0 => flevel.tx0().bits(), + 1 => flevel.tx1().bits(), + 2 => flevel.tx2().bits(), + 3 => flevel.tx3().bits(), + _ => unreachable!(), + }) as usize + } + + /// Returns the FIFO depth. + pub fn fifo_depth(&self) -> usize { + // Safety: read-only access without side-effect + let block = unsafe { self.block() }; + let join_tx = block.sm(SM::id()).sm_shiftctrl().read().fjoin_tx().bit(); + let depth = block.dbg_cfginfo().read().fifo_depth().bits() as usize; + if join_tx { + depth * 2 + } else { + depth + } + } + /// Enable TX FIFO not full interrupt. /// /// This interrupt is raised when the TX FIFO is not full, i.e. one could push more data to it. diff --git a/rp235x-hal/src/pio/non_blocking.rs b/rp235x-hal/src/pio/non_blocking.rs new file mode 100644 index 000000000..4773b42ea --- /dev/null +++ b/rp235x-hal/src/pio/non_blocking.rs @@ -0,0 +1,197 @@ +use core::task::Poll; + +use crate::async_utils::{ + sealed::{IrqWaker, Wakeable}, + CancellablePollFn, +}; +use crate::dma::TransferSize; + +use super::{Interrupt, PioIRQ, Rx, Tx}; +use super::{PIOExt, StateMachineIndex, ValidStateMachine}; +use super::{PIO0, PIO1, PIO2, SM0, SM1, SM2, SM3}; + +macro_rules! impl_wakeable { + ($($pio:ident => ( $($sm:ident),* )),*) => { + $( + impl Wakeable for Interrupt<'_, $pio, IRQ> { + fn waker() -> &'static IrqWaker { + static WAKER: IrqWaker = IrqWaker::new(); + &WAKER + } + } + $( + impl_wakeable!(Rx, 0, "Rx Not Empty", $pio, $sm); + impl_wakeable!(Tx, 4, "Tx Not Full", $pio, $sm); + )* + )* + }; + ($t:ident, $offset:expr, $doc:expr, $pio:ident, $sm:ident) => { + impl Wakeable for $t<($pio, $sm), W> + where + W: TransferSize, + { + fn waker() -> &'static IrqWaker { + static WAKER: IrqWaker = IrqWaker::new(); + &WAKER + } + } + }; +} +impl_wakeable!( + PIO0 => ( SM0, SM1, SM2, SM3 ), + PIO1 => ( SM0, SM1, SM2, SM3 ), + PIO2 => ( SM0, SM1, SM2, SM3 ) +); + +impl Interrupt<'_, P, IRQ> +where + Self: Wakeable, + P: PIOExt, +{ + /// Wakes a task awaiting of this interrupt. + pub fn on_interrupt() { + use crate::atomic_register_access::write_bitmask_clear; + unsafe { + let mask = 1 << (IRQ + 8); + let sm_irq = (*P::ptr()).sm_irq(IRQ); + if sm_irq.irq_ints().read().bits() & mask == mask { + write_bitmask_clear(sm_irq.irq_inte().as_ptr(), mask); + } + } + Self::waker().wake(); + } +} + +macro_rules! on_interrupts { + ($t:ident, $offset:expr, $event:expr) => { + impl $t<(P, SM), W> + where + W: TransferSize, + P: PIOExt, + SM: StateMachineIndex, + Self: Wakeable, + { + /// Wakes a task awaiting on + #[doc = $event] + pub fn on_interrupt(irq: PioIRQ) { + use crate::atomic_register_access::write_bitmask_clear; + unsafe { + let mask = 1 << ($offset + <(P, SM)>::id()); + let irq = (&*P::ptr()).sm_irq(irq as usize); + if irq.irq_ints().read().bits() & mask == mask { + write_bitmask_clear(irq.irq_inte().as_ptr(), mask); + } + } + Self::waker().wake(); + } + } + }; +} +on_interrupts!(Rx, 0, "Rx Not Empty"); +on_interrupts!(Tx, 4, "Tx Not Full"); + +impl Interrupt<'_, P, IRQ> +where + P: PIOExt, + Self: Wakeable, +{ + /// Stalls until the IRQ fires + pub async fn sm_interrupt(&mut self, id: u8) { + assert!(id < 4, "invalid state machine interrupt number"); + let mask = 1 << id; + + CancellablePollFn::new( + self, + |me| unsafe { + if (me.block().irq().read().irq().bits() & mask) == mask { + Poll::Ready(()) + } else { + Poll::Pending + } + }, + |me| me.enable_sm_interrupt(id), + |me| { + me.disable_sm_interrupt(id); + }, + ) + .await + } +} + +impl Rx +where + Self: Wakeable, +{ + /// Reads a u32 from the FIFO. + pub async fn async_read(&mut self, irq: PioIRQ) -> u32 { + self.rx_not_empty(irq).await; + let fifo_address = self.fifo_address(); + unsafe { core::ptr::read_volatile(fifo_address) } + } + + /// Used to `.await` until the fifo is no longer empty. + pub async fn rx_not_empty(&mut self, irq: PioIRQ) { + CancellablePollFn::new( + self, + |me| { + if me.is_empty() { + Poll::Pending + } else { + Poll::Ready(()) + } + }, + |me| me.enable_rx_not_empty_interrupt(irq), + |me| { + me.disable_rx_not_empty_interrupt(irq); + }, + ) + .await + } +} + +impl Tx +where + Self: Wakeable, +{ + /// Writes a u8 to the FIFO replicated 4 times in a 32bits word. + pub async fn async_write_u8_replicated(&mut self, irq: PioIRQ, value: u8) { + self.async_write_generic(irq, value).await + } + /// Writes a u16 to the FIFO replicated 2 times in a 32bits word. + pub async fn async_write_u16_replicated(&mut self, irq: PioIRQ, value: u16) { + self.async_write_generic(irq, value).await + } + + /// Writes a u32 to the FIFO. + pub async fn async_write(&mut self, irq: PioIRQ, value: u32) { + self.async_write_generic(irq, value).await + } + + async fn async_write_generic(&mut self, irq: PioIRQ, value: T) { + self.tx_not_full(irq).await; + // Safety: Only accessed by this instance (unless DMA is used). + unsafe { + let reg_ptr = self.fifo_address() as *mut T; + reg_ptr.write_volatile(value); + } + } + + /// Used to `.await` until the fifo is no longer full. + pub async fn tx_not_full(&mut self, irq: PioIRQ) { + CancellablePollFn::new( + self, + |me| { + if me.is_full() { + Poll::Pending + } else { + Poll::Ready(()) + } + }, + |me| me.enable_tx_not_full_interrupt(irq), + |me| { + me.disable_tx_not_full_interrupt(irq); + }, + ) + .await + } +}