mirror of
https://github.com/italicsjenga/rp-hal-boards.git
synced 2024-12-24 13:11:30 +11:00
pio: Split PIO into multiple objects that can be moved around separately.
One PIO block often implements multiple functions that are used in different parts of the codebase. Previously, that would be impossible, as PIO contained all StateMachine instances. Now, StateMachine instances use atomic operations whenever accessing shared registers, so they can be used concurrently.
This commit is contained in:
parent
da89888ccb
commit
e5a7c325b0
|
@ -6,23 +6,82 @@ use pio::{Program, SideSet, Wrap};
|
||||||
const PIO_INSTRUCTION_COUNT: usize = 32;
|
const PIO_INSTRUCTION_COUNT: usize = 32;
|
||||||
|
|
||||||
/// PIO Instance
|
/// PIO Instance
|
||||||
pub trait Instance:
|
pub trait PIOExt:
|
||||||
core::ops::Deref<Target = rp2040_pac::pio0::RegisterBlock> + SubsystemReset
|
core::ops::Deref<Target = rp2040_pac::pio0::RegisterBlock> + SubsystemReset + Sized
|
||||||
{
|
{
|
||||||
|
/// Create a new PIO wrapper and split the state machines into individual objects.
|
||||||
|
fn split(
|
||||||
|
self,
|
||||||
|
resets: &mut pac::RESETS,
|
||||||
|
) -> (
|
||||||
|
PIO<Self>,
|
||||||
|
StateMachine<Self>,
|
||||||
|
StateMachine<Self>,
|
||||||
|
StateMachine<Self>,
|
||||||
|
StateMachine<Self>,
|
||||||
|
) {
|
||||||
|
self.reset_bring_up(resets);
|
||||||
|
|
||||||
|
let sm0 = StateMachine {
|
||||||
|
id: 0,
|
||||||
|
block: self.deref(),
|
||||||
|
sm: &self.deref().sm[0],
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
};
|
||||||
|
let sm1 = StateMachine {
|
||||||
|
id: 0,
|
||||||
|
block: self.deref(),
|
||||||
|
sm: &self.deref().sm[0],
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
};
|
||||||
|
let sm2 = StateMachine {
|
||||||
|
id: 0,
|
||||||
|
block: self.deref(),
|
||||||
|
sm: &self.deref().sm[0],
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
};
|
||||||
|
let sm3 = StateMachine {
|
||||||
|
id: 0,
|
||||||
|
block: self.deref(),
|
||||||
|
sm: &self.deref().sm[0],
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
};
|
||||||
|
(
|
||||||
|
PIO {
|
||||||
|
used_instruction_space: 0,
|
||||||
|
interrupts: [
|
||||||
|
Interrupt {
|
||||||
|
id: 0,
|
||||||
|
block: self.deref(),
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
},
|
||||||
|
Interrupt {
|
||||||
|
id: 1,
|
||||||
|
block: self.deref(),
|
||||||
|
_phantom: core::marker::PhantomData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pio: self,
|
||||||
|
},
|
||||||
|
sm0,
|
||||||
|
sm1,
|
||||||
|
sm2,
|
||||||
|
sm3,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance for rp2040_pac::PIO0 {}
|
impl PIOExt for rp2040_pac::PIO0 {}
|
||||||
impl Instance for rp2040_pac::PIO1 {}
|
impl PIOExt for rp2040_pac::PIO1 {}
|
||||||
|
|
||||||
/// Programmable IO Block
|
/// Programmable IO Block
|
||||||
pub struct PIO<P: Instance> {
|
pub struct PIO<P: PIOExt> {
|
||||||
used_instruction_space: core::cell::Cell<u32>, // bit for each PIO_INSTRUCTION_COUNT
|
used_instruction_space: u32, // bit for each PIO_INSTRUCTION_COUNT
|
||||||
pio: P,
|
pio: P,
|
||||||
state_machines: [StateMachine<P>; 4],
|
|
||||||
interrupts: [Interrupt<P>; 2],
|
interrupts: [Interrupt<P>; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Instance> core::fmt::Debug for PIO<P> {
|
impl<P: PIOExt> core::fmt::Debug for PIO<P> {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
f.debug_struct("PIO")
|
f.debug_struct("PIO")
|
||||||
.field("used_instruction_space", &self.used_instruction_space)
|
.field("used_instruction_space", &self.used_instruction_space)
|
||||||
|
@ -31,12 +90,12 @@ impl<P: Instance> core::fmt::Debug for PIO<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: `PIO` provides exclusive access to PIO registers.
|
// Safety: `PIO` only provides access to those registers which are not directly used by
|
||||||
unsafe impl<P: Instance + Send> Send for PIO<P> {}
|
// `StateMachine`.
|
||||||
|
unsafe impl<P: PIOExt + Send> Send for PIO<P> {}
|
||||||
|
|
||||||
impl<P: Instance> PIO<P> {
|
impl<P: PIOExt> PIO<P> {
|
||||||
/// Create a new PIO wrapper.
|
/*pub fn new(pio: P, resets: &mut pac::RESETS) -> Self {
|
||||||
pub fn new(pio: P, resets: &mut pac::RESETS) -> Self {
|
|
||||||
pio.reset_bring_up(resets);
|
pio.reset_bring_up(resets);
|
||||||
|
|
||||||
PIO {
|
PIO {
|
||||||
|
@ -77,18 +136,20 @@ impl<P: Instance> PIO<P> {
|
||||||
],
|
],
|
||||||
pio,
|
pio,
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// Free this instance.
|
/// Free this instance.
|
||||||
pub fn free(self) -> P {
|
pub fn free(
|
||||||
|
self,
|
||||||
|
_sm0: StateMachine<P>,
|
||||||
|
_sm1: StateMachine<P>,
|
||||||
|
_sm2: StateMachine<P>,
|
||||||
|
_sm3: StateMachine<P>,
|
||||||
|
) -> P {
|
||||||
|
// TODO: Disable the PIO block.
|
||||||
self.pio
|
self.pio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This PIO's state machines.
|
|
||||||
pub fn state_machines(&self) -> &[StateMachine<P>; 4] {
|
|
||||||
&self.state_machines
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This PIO's interrupts.
|
/// This PIO's interrupts.
|
||||||
pub fn interrupts(&self) -> &[Interrupt<P>; 2] {
|
pub fn interrupts(&self) -> &[Interrupt<P>; 2] {
|
||||||
&self.interrupts
|
&self.interrupts
|
||||||
|
@ -99,10 +160,7 @@ impl<P: Instance> PIO<P> {
|
||||||
/// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of
|
/// The PIO has 8 IRQ flags, of which 4 are visible to the host processor. Each bit of `flags` corresponds to one of
|
||||||
/// the IRQ flags.
|
/// the IRQ flags.
|
||||||
pub fn clear_irq(&self, flags: u8) {
|
pub fn clear_irq(&self, flags: u8) {
|
||||||
self.pio
|
self.pio.irq.write(|w| unsafe { w.irq().bits(flags) });
|
||||||
.deref()
|
|
||||||
.irq
|
|
||||||
.write(|w| unsafe { w.irq().bits(flags) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force PIO's IRQ flags indicated by the bits.
|
/// Force PIO's IRQ flags indicated by the bits.
|
||||||
|
@ -111,7 +169,6 @@ impl<P: Instance> PIO<P> {
|
||||||
/// the IRQ flags.
|
/// the IRQ flags.
|
||||||
pub fn force_irq(&self, flags: u8) {
|
pub fn force_irq(&self, flags: u8) {
|
||||||
self.pio
|
self.pio
|
||||||
.deref()
|
|
||||||
.irq_force
|
.irq_force
|
||||||
.write(|w| unsafe { w.irq_force().bits(flags) });
|
.write(|w| unsafe { w.irq_force().bits(flags) });
|
||||||
}
|
}
|
||||||
|
@ -123,7 +180,7 @@ impl<P: Instance> PIO<P> {
|
||||||
let mask = (1 << i.len()) - 1;
|
let mask = (1 << i.len()) - 1;
|
||||||
if let Some(origin) = origin {
|
if let Some(origin) = origin {
|
||||||
if origin as usize > PIO_INSTRUCTION_COUNT - i.len()
|
if origin as usize > PIO_INSTRUCTION_COUNT - i.len()
|
||||||
|| self.used_instruction_space.get() & (mask << origin) != 0
|
|| self.used_instruction_space & (mask << origin) != 0
|
||||||
{
|
{
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,7 +188,7 @@ impl<P: Instance> PIO<P> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i in (0..=32 - i.len()).rev() {
|
for i in (0..=32 - i.len()).rev() {
|
||||||
if self.used_instruction_space.get() & (mask << i) == 0 {
|
if self.used_instruction_space & (mask << i) == 0 {
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +198,7 @@ impl<P: Instance> PIO<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_program(
|
fn add_program(
|
||||||
&self,
|
&mut self,
|
||||||
instructions: &[u16],
|
instructions: &[u16],
|
||||||
origin: Option<u8>,
|
origin: Option<u8>,
|
||||||
side_set: pio::SideSet,
|
side_set: pio::SideSet,
|
||||||
|
@ -171,8 +228,8 @@ impl<P: Instance> PIO<P> {
|
||||||
{
|
{
|
||||||
self.pio.instr_mem[i + offset].write(|w| unsafe { w.bits(instr as u32) })
|
self.pio.instr_mem[i + offset].write(|w| unsafe { w.bits(instr as u32) })
|
||||||
}
|
}
|
||||||
self.used_instruction_space
|
self.used_instruction_space =
|
||||||
.set(self.used_instruction_space.get() | ((1 << instructions.len()) - 1));
|
self.used_instruction_space | ((1 << instructions.len()) - 1);
|
||||||
Some(offset)
|
Some(offset)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -182,40 +239,52 @@ impl<P: Instance> PIO<P> {
|
||||||
|
|
||||||
/// PIO State Machine.
|
/// PIO State Machine.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StateMachine<P: Instance> {
|
pub struct StateMachine<P: PIOExt> {
|
||||||
id: u8,
|
id: u8,
|
||||||
block: *const rp2040_pac::pio0::RegisterBlock,
|
block: *const rp2040_pac::pio0::RegisterBlock,
|
||||||
|
sm: *const rp2040_pac::pio0::SM,
|
||||||
_phantom: core::marker::PhantomData<P>,
|
_phantom: core::marker::PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// `StateMachine` doesn't implement `Send` because it sometimes accesses shared registers, e.g. `sm_enable`.
|
// `StateMachine` doesn't implement `Send` because it sometimes accesses shared registers, e.g. `sm_enable`.
|
||||||
// unsafe impl<P: Instance + Send> Send for StateMachine<P> {}
|
// unsafe impl<P: Instance + Send> Send for StateMachine<P> {}
|
||||||
|
|
||||||
impl<P: Instance> StateMachine<P> {
|
impl<P: PIOExt> StateMachine<P> {
|
||||||
/// Start and stop the state machine.
|
/// Start and stop the state machine.
|
||||||
pub fn set_enabled(&self, enabled: bool) {
|
pub fn set_enabled(&mut self, enabled: bool) {
|
||||||
|
// Bits 3:0 are SM_ENABLE.
|
||||||
let mask = 1 << self.id;
|
let mask = 1 << self.id;
|
||||||
if enabled {
|
if enabled {
|
||||||
self.block()
|
self.set_ctrl_bits(mask);
|
||||||
.ctrl
|
|
||||||
.modify(|r, w| unsafe { w.sm_enable().bits(r.sm_enable().bits() | mask) })
|
|
||||||
} else {
|
} else {
|
||||||
self.block()
|
self.clear_ctrl_bits(mask);
|
||||||
.ctrl
|
|
||||||
.modify(|r, w| unsafe { w.sm_enable().bits(r.sm_enable().bits() & !mask) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restart(&self) {
|
fn restart(&mut self) {
|
||||||
self.block()
|
// Bits 7:4 are SM_RESTART.
|
||||||
.ctrl
|
self.set_ctrl_bits(1 << (self.id + 4));
|
||||||
.write(|w| unsafe { w.sm_restart().bits(1 << self.id) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_clock(&self) {
|
fn reset_clock(&mut self) {
|
||||||
self.block()
|
// Bits 11:8 are CLKDIV_RESTART.
|
||||||
.ctrl
|
self.set_ctrl_bits(1 << (self.id + 8));
|
||||||
.write(|w| unsafe { w.clkdiv_restart().bits(1 << self.id) });
|
}
|
||||||
|
|
||||||
|
fn set_ctrl_bits(&mut self, bits: u32) {
|
||||||
|
const ATOMIC_SET_OFFSET: usize = 0x2000;
|
||||||
|
// Safety: We only use the atomic alias of the register.
|
||||||
|
unsafe {
|
||||||
|
*(*self.block).ctrl.as_ptr().add(ATOMIC_SET_OFFSET / 4) = bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_ctrl_bits(&mut self, bits: u32) {
|
||||||
|
const ATOMIC_CLEAR_OFFSET: usize = 0x3000;
|
||||||
|
// Safety: We only use the atomic alias of the register.
|
||||||
|
unsafe {
|
||||||
|
*(*self.block).ctrl.as_ptr().add(ATOMIC_CLEAR_OFFSET / 4) = bits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_clock_divisor(&self, divisor: f32) {
|
fn set_clock_divisor(&self, divisor: f32) {
|
||||||
|
@ -239,7 +308,7 @@ impl<P: Instance> StateMachine<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the current instruction.
|
/// Set the current instruction.
|
||||||
pub fn set_instruction(&self, instruction: u16) {
|
pub fn set_instruction(&mut self, instruction: u16) {
|
||||||
self.sm()
|
self.sm()
|
||||||
.sm_instr
|
.sm_instr
|
||||||
.write(|w| unsafe { w.sm0_instr().bits(instruction) })
|
.write(|w| unsafe { w.sm0_instr().bits(instruction) })
|
||||||
|
@ -250,55 +319,78 @@ impl<P: Instance> StateMachine<P> {
|
||||||
self.sm().sm_execctrl.read().exec_stalled().bits()
|
self.sm().sm_execctrl.read().exec_stalled().bits()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block(&self) -> &rp2040_pac::pio0::RegisterBlock {
|
|
||||||
unsafe { &*self.block }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sm(&self) -> &rp2040_pac::pio0::SM {
|
fn sm(&self) -> &rp2040_pac::pio0::SM {
|
||||||
&self.block().sm[self.id as usize]
|
unsafe { &*self.sm }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the next element from RX FIFO.
|
/// Get the next element from RX FIFO.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the FIFO is empty.
|
/// Returns `None` if the FIFO is empty.
|
||||||
pub fn read_rx(&self) -> Option<u32> {
|
pub fn read_rx(&mut self) -> Option<u32> {
|
||||||
let is_empty = self.block().fstat.read().rxempty().bits() & (1 << self.id) != 0;
|
// Safety: The register is never written by software.
|
||||||
|
let is_empty = unsafe { &*self.block }.fstat.read().rxempty().bits() & (1 << self.id) != 0;
|
||||||
|
|
||||||
if is_empty {
|
if is_empty {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(self.block().rxf[self.id as usize].read().bits())
|
// Safety: The register is unique to this state machine.
|
||||||
|
Some(unsafe { &*self.block }.rxf[self.id as usize].read().bits())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write an element to TX FIFO.
|
/// Write an element to TX FIFO.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the value was written to FIFO, `false` otherwise.
|
/// Returns `true` if the value was written to FIFO, `false` otherwise.
|
||||||
pub fn write_tx(&self, value: u32) -> bool {
|
pub fn write_tx(&mut self, value: u32) -> bool {
|
||||||
let is_full = self.block().fstat.read().txfull().bits() & (1 << self.id) != 0;
|
// Safety: The register is never written by software.
|
||||||
|
let is_full = unsafe { &*self.block }.fstat.read().txfull().bits() & (1 << self.id) != 0;
|
||||||
|
|
||||||
if is_full {
|
if is_full {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.block().txf[self.id as usize].write(|w| unsafe { w.bits(value) });
|
// Safety: The register is unique to this state machine.
|
||||||
|
unsafe { &*self.block }.txf[self.id as usize].write(|w| unsafe { w.bits(value) });
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_pindirs_with_mask(&mut self, mut pins: u32, pindir: u32) {
|
||||||
|
let mut pin = 0;
|
||||||
|
while pins != 0 {
|
||||||
|
if (pins & 1) != 0 {
|
||||||
|
self.sm().sm_pinctrl.write(|w| {
|
||||||
|
unsafe {
|
||||||
|
w.set_count().bits(1);
|
||||||
|
w.set_base().bits(pin as u8);
|
||||||
|
}
|
||||||
|
w
|
||||||
|
});
|
||||||
|
self.sm().sm_instr.write(|w| {
|
||||||
|
unsafe {
|
||||||
|
w.sm0_instr().bits(0xe080 | ((pindir >> pin) & 0x1) as u16);
|
||||||
|
}
|
||||||
|
w
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pin += 1;
|
||||||
|
pins = pins >> 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PIO Interrupt controller.
|
/// PIO Interrupt controller.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Interrupt<P: Instance> {
|
pub struct Interrupt<P: PIOExt> {
|
||||||
id: u8,
|
id: u8,
|
||||||
block: *const rp2040_pac::pio0::RegisterBlock,
|
block: *const rp2040_pac::pio0::RegisterBlock,
|
||||||
_phantom: core::marker::PhantomData<P>,
|
_phantom: core::marker::PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: `Interrupt` provides exclusive access to interrupt registers.
|
// Safety: `Interrupt` provides exclusive access to interrupt registers.
|
||||||
unsafe impl<P: Instance + Send> Send for Interrupt<P> {}
|
unsafe impl<P: PIOExt + Send> Send for Interrupt<P> {}
|
||||||
|
|
||||||
impl<P: Instance> Interrupt<P> {
|
impl<P: PIOExt> Interrupt<P> {
|
||||||
/// Enable interrupts raised by state machines.
|
/// Enable interrupts raised by state machines.
|
||||||
///
|
///
|
||||||
/// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this
|
/// The PIO peripheral has 4 outside visible interrupts that can be raised by the state machines. Note that this
|
||||||
|
@ -813,7 +905,11 @@ impl<'a> PIOBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the config and deploy it to a StateMachine.
|
/// Build the config and deploy it to a StateMachine.
|
||||||
pub fn build<P: Instance>(self, pio: &PIO<P>, sm: &StateMachine<P>) -> Result<(), BuildError> {
|
pub fn build<P: PIOExt>(
|
||||||
|
self,
|
||||||
|
pio: &mut PIO<P>,
|
||||||
|
sm: &mut StateMachine<P>,
|
||||||
|
) -> Result<(), BuildError> {
|
||||||
let offset =
|
let offset =
|
||||||
match pio.add_program(self.instructions, self.instruction_origin, self.side_set) {
|
match pio.add_program(self.instructions, self.instruction_origin, self.side_set) {
|
||||||
Some(o) => o,
|
Some(o) => o,
|
||||||
|
|
Loading…
Reference in a new issue