aboutsummaryrefslogtreecommitdiff
path: root/drivers/lpspi/src
diff options
context:
space:
mode:
authorIan McIntyre <me@mciantyre.dev>2025-11-30 18:52:34 -0500
committerIan McIntyre <me@mciantyre.dev>2025-11-30 19:10:51 -0500
commit76199f21616ad86cf68f3b063c1ce23c6fc5a52f (patch)
tree4c076d0afd649803a2bd9a5ed5cbb1f1c74fb459 /drivers/lpspi/src
First commit
Diffstat (limited to 'drivers/lpspi/src')
-rw-r--r--drivers/lpspi/src/lib.rs877
1 files changed, 877 insertions, 0 deletions
diff --git a/drivers/lpspi/src/lib.rs b/drivers/lpspi/src/lib.rs
new file mode 100644
index 0000000..dcfaf0e
--- /dev/null
+++ b/drivers/lpspi/src/lib.rs
@@ -0,0 +1,877 @@
+#![no_std]
+
+use core::{marker::PhantomData, ops::Range};
+
+pub type LPSPI = ral_registers::Instance<RegisterBlock>;
+
+#[repr(C)]
+#[allow(non_snake_case)]
+pub struct RegisterBlock {
+ pub VERID: u32,
+ pub PARAM: u32,
+ reserved_0: [u8; 8],
+ pub CR: u32,
+ pub SR: u32,
+ pub IER: u32,
+ pub DER: u32,
+ pub CFGR0: u32,
+ pub CFGR1: u32,
+ reserved_1: [u8; 8],
+ pub DMR0: u32,
+ pub DMR1: u32,
+ reserved_2: [u8; 8],
+ pub CCR: u32,
+ reserved_3: [u8; 20],
+ pub FCR: u32,
+ pub FSR: u32,
+ pub TCR: u32,
+ pub TDR: u32,
+ reserved_4: [u8; 8],
+ pub RSR: u32,
+ pub RDR: u32,
+}
+
+ral_registers::register! {
+ #[doc = "Version ID Register"]
+ pub VERID<u32> RO [
+ #[doc = "Module Identification Number"]
+ FEATURE start(0) width(16) RO {
+ #[doc = "Standard feature set supporting a 32-bit shift register."]
+ FEATURE_4 = 0x4,
+ }
+ #[doc = "Minor Version Number"]
+ MINOR start(16) width(8) RO {}
+ #[doc = "Major Version Number"]
+ MAJOR start(24) width(8) RO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Parameter Register"]
+ pub PARAM<u32> RO [
+ #[doc = "Transmit FIFO Size"]
+ TXFIFO start(0) width(8) RO {}
+ #[doc = "Receive FIFO Size"]
+ RXFIFO start(8) width(8) RO {}
+ #[doc = "PCS Number"]
+ PCSNUM start(16) width(8) RO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Control Register"]
+ pub CR<u32> RW [
+ #[doc = "Module Enable"]
+ MEN start(0) width(1) RW {}
+ #[doc = "Software Reset"]
+ RST start(1) width(1) RW {}
+ #[doc = "Doze mode enable"]
+ DOZEN start(2) width(1) RW {}
+ #[doc = "Debug Enable"]
+ DBGEN start(3) width(1) RW {}
+ #[doc = "Reset Transmit FIFO"]
+ RTF start(8) width(1) WO {}
+ #[doc = "Reset Receive FIFO"]
+ RRF start(9) width(1) WO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Status Register"]
+ pub SR<u32> RW [
+ #[doc = "Transmit Data Flag"]
+ TDF start(0) width(1) RO {}
+ #[doc = "Receive Data Flag"]
+ RDF start(1) width(1) RO {}
+ #[doc = "Word Complete Flag"]
+ WCF start(8) width(1) RW {}
+ #[doc = "Frame Complete Flag"]
+ FCF start(9) width(1) RW {}
+ #[doc = "Transfer Complete Flag"]
+ TCF start(10) width(1) RW {}
+ #[doc = "Transmit Error Flag"]
+ TEF start(11) width(1) RW {}
+ #[doc = "Receive Error Flag"]
+ REF start(12) width(1) RW {}
+ #[doc = "Data Match Flag"]
+ DMF start(13) width(1) RW {}
+ #[doc = "Module Busy Flag"]
+ MBF start(24) width(1) RO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Interrupt Enable Register"]
+ pub IER<u32> RW [
+ #[doc = "Transmit Data Interrupt Enable"]
+ TDIE start(0) width(1) RW {}
+ #[doc = "Receive Data Interrupt Enable"]
+ RDIE start(1) width(1) RW {}
+ #[doc = "Word Complete Interrupt Enable"]
+ WCIE start(8) width(1) RW {}
+ #[doc = "Frame Complete Interrupt Enable"]
+ FCIE start(9) width(1) RW {}
+ #[doc = "Transfer Complete Interrupt Enable"]
+ TCIE start(10) width(1) RW {}
+ #[doc = "Transmit Error Interrupt Enable"]
+ TEIE start(11) width(1) RW {}
+ #[doc = "Receive Error Interrupt Enable"]
+ REIE start(12) width(1) RW {}
+ #[doc = "Data Match Interrupt Enable"]
+ DMIE start(13) width(1) RW {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "DMA Enable Register"]
+ pub DER<u32> RW [
+ #[doc = "Transmit Data DMA Enable"]
+ TDDE start(0) width(1) RW {}
+ #[doc = "Receive Data DMA Enable"]
+ RDDE start(1) width(1) RW {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Configuration Register 0"]
+ pub CFGR0<u32> RW [
+ #[doc = "Host Request Enable"]
+ HREN start(0) width(1) RW {}
+ #[doc = "Host Request Polarity"]
+ HRPOL start(1) width(1) RW {
+ #[doc = "Active low"]
+ ACTIVE_LOW = 0,
+ #[doc = "Active high"]
+ ACTIVE_HIGH = 0x1,
+ }
+ #[doc = "Host Request Select"]
+ HRSEL start(2) width(1) RW {
+ #[doc = "Host request input is the LPSPI_HREQ pin"]
+ LPSPI_HREQ = 0,
+ #[doc = "Host request input is the input trigger"]
+ INPUT_TRIGGER = 0x1,
+ }
+ #[doc = "Circular FIFO Enable"]
+ CIRFIFO start(8) width(1) RW {}
+ #[doc = "Receive Data Match Only"]
+ RDMO start(9) width(1) RW {
+ #[doc = "Received data is stored in the receive FIFO as in normal operations"]
+ STORE = 0,
+ #[doc = "Received data is discarded unless the Data Match Flag (DMF) is set"]
+ DISCARD = 0x1,
+ }
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Configuration Register 1"]
+ pub CFGR1<u32> RW [
+ #[doc = "Controller Mode"]
+ CONTROLLER start(0) width(1) RW {
+ #[doc = "Peripheral mode"]
+ PERIPHERAL = 0,
+ #[doc = "Controller mode"]
+ CONTROLLER = 0x1,
+ }
+ #[doc = "Sample Point"]
+ SAMPLE start(1) width(1) RW {
+ #[doc = "Input data is sampled on SCK edge"]
+ ON_EDGE = 0,
+ #[doc = "Input data is sampled on delayed SCK edge"]
+ DELAYED_EDGE = 0x1,
+ }
+ #[doc = "Automatic PCS"]
+ AUTOPCS start(2) width(1) RW {}
+ #[doc = "No Stall"]
+ NOSTALL start(3) width(1) RW {
+ #[doc = "Transfers will stall when the transmit FIFO is empty or the receive FIFO is full"]
+ STALL = 0,
+ #[doc = "Transfers will not stall, allowing transmit FIFO underruns or receive FIFO overruns to occur"]
+ NOSTALL = 0x1,
+ }
+ #[doc = "Peripheral Chip Select Polarity"]
+ PCSPOL start(8) width(4) RW {}
+ #[doc = "Match Configuration"]
+ #[doc = ""]
+ #[doc = "`+` is boolean OR. `*` is boolean AND`. The `*_SINGLE` variants never match unless you set the MATCH registers to the same value."]
+ MATCFG start(16) width(3) RW {
+ #[doc = "Match is disabled"]
+ DISABLE = 0,
+ #[doc = "010b - Match is enabled, if 1st data word equals MATCH0 OR MATCH1, i.e., (1st data word = MATCH0 + MATCH1)"]
+ FIRST_WORD_EITHER = 0x2,
+ #[doc = "011b - Match is enabled, if any data word equals MATCH0 OR MATCH1, i.e., (any data word = MATCH0 + MATCH1)"]
+ ANY_WORD_EITHER = 0x3,
+ #[doc = "100b - Match is enabled, if 1st data word equals MATCH0 AND 2nd data word equals MATCH1, i.e., [(1st data word = MATCH0) * (2nd data word = MATCH1)]"]
+ FIRST_WORD_BOTH = 0x4,
+ #[doc = "101b - Match is enabled, if any data word equals MATCH0 AND the next data word equals MATCH1, i.e., [(any data word = MATCH0) * (next data word = MATCH1)]"]
+ ANY_WORD_BOTH = 0x5,
+ #[doc = "110b - Match is enabled, if (1st data word AND MATCH1) equals (MATCH0 AND MATCH1), i.e., [(1st data word * MATCH1) = (MATCH0 * MATCH1)]"]
+ FIRST_WORD_SINGLE = 0x6,
+ #[doc = "111b - Match is enabled, if (any data word AND MATCH1) equals (MATCH0 AND MATCH1), i.e., [(any data word * MATCH1) = (MATCH0 * MATCH1)]"]
+ ANY_WORD_SINGLE = 0x7,
+ }
+ #[doc = "Pin Configuration"]
+ PINCFG start(24) width(2) RW {
+ #[doc = "SIN is used for input data and SOUT is used for output data"]
+ FULL_DUPLEX = 0,
+ #[doc = "SIN is used for both input and output data"]
+ HALF_DUPLEX_SIN = 0x1,
+ #[doc = "SOUT is used for both input and output data"]
+ HALF_DUPLEX_SOUT = 0x2,
+ #[doc = "SOUT is used for input data and SIN is used for output data"]
+ INVERSE = 0x3,
+ }
+ #[doc = "Output Config"]
+ OUTCFG start(26) width(1) RW {
+ #[doc = "Output data retains last value when chip select is negated"]
+ HOLD = 0,
+ #[doc = "Output data is tristated when chip select is negated"]
+ TRISTATE = 0x1,
+ }
+ #[doc = "Peripheral Chip Select Configuration"]
+ PCSCFG start(27) width(1) RW {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Data Match Register 0"]
+ pub DMR0<u32> RW []
+}
+
+ral_registers::register! {
+ #[doc = "Data Match Register 1"]
+ pub DMR1<u32> RW []
+}
+
+ral_registers::register! {
+ #[doc = "Clock Configuration Register"]
+ pub CCR<u32> RW [
+ #[doc = "SCK Divider"]
+ SCKDIV start(0) width(8) RW {}
+ #[doc = "Delay Between Transfers"]
+ DBT start(8) width(8) RW {}
+ #[doc = "PCS-to-SCK Delay"]
+ PCSSCK start(16) width(8) RW {}
+ #[doc = "SCK-to-PCS Delay"]
+ SCKPCS start(24) width(8) RW {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "FIFO Control Register"]
+ pub FCR<u32> RW [
+ #[doc = "Transmit FIFO Watermark"]
+ TXWATER start(0) width(4) RW {}
+ #[doc = "Receive FIFO Watermark"]
+ RXWATER start(16) width(4) RW {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "FIFO Status Register"]
+ pub FSR<u32> RO [
+ #[doc = "Transmit FIFO Count"]
+ TXCOUNT start(0) width(5) RO {}
+ #[doc = "Receive FIFO Count"]
+ RXCOUNT start(16) width(5) RO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Transmit Command Register"]
+ pub TCR<u32> RW [
+ #[doc = "Frame Size"]
+ FRAMESZ start(0) width(12) RW {}
+ #[doc = "Transfer Width"]
+ WIDTH start(16) width(2) RW {
+ #[doc = "1 bit transfer"]
+ SINGLE = 0,
+ #[doc = "2 bit transfer"]
+ DUAL = 0x1,
+ #[doc = "4 bit transfer"]
+ QUAD = 0x2,
+ }
+ #[doc = "Transmit Data Mask"]
+ TXMSK start(18) width(1) RW {}
+ #[doc = "Receive Data Mask"]
+ RXMSK start(19) width(1) RW {}
+ #[doc = "Continuing Command"]
+ CONTC start(20) width(1) RW {}
+ #[doc = "Continuous Transfer"]
+ CONT start(21) width(1) RW {}
+ #[doc = "Byte Swap"]
+ BYSW start(22) width(1) RW {}
+ #[doc = "LSB First"]
+ LSBF start(23) width(1) RW {
+ #[doc = "Data is transferred MSB first"]
+ MSB = 0,
+ #[doc = "Data is transferred LSB first"]
+ LSB = 0x1,
+ }
+ #[doc = "Peripheral Chip Select"]
+ PCS start(24) width(2) RW {
+ #[doc = "Transfer using LPSPI_PCS[0]"]
+ PCS_0 = 0,
+ #[doc = "Transfer using LPSPI_PCS[1]"]
+ PCS_1 = 0x1,
+ #[doc = "Transfer using LPSPI_PCS[2]"]
+ PCS_2 = 0x2,
+ #[doc = "Transfer using LPSPI_PCS[3]"]
+ PCS_3 = 0x3,
+ }
+ #[doc = "Prescaler Value"]
+ PRESCALE start(27) width(3) RW {
+ #[doc = "Divide by 1"]
+ DIVIDE_BY_1= 0,
+ #[doc = "Divide by 2"]
+ DIVIDE_BY_2= 0x1,
+ #[doc = "Divide by 4"]
+ DIVIDE_BY_4= 0x2,
+ #[doc = "Divide by 8"]
+ DIVIDE_BY_8= 0x3,
+ #[doc = "Divide by 16"]
+ DIVIDE_BY_16= 0x4,
+ #[doc = "Divide by 32"]
+ DIVIDE_BY_32= 0x5,
+ #[doc = "Divide by 64"]
+ DIVIDE_BY_64= 0x6,
+ #[doc = "Divide by 128"]
+ DIVIDE_BY_128= 0x7,
+ }
+ #[doc = "Clock Phase"]
+ CPHA start(30) width(1) RW {
+ #[doc = "Data is captured on the leading edge of SCK and changed on the following edge of SCK"]
+ CAPTURE_LEADING_SETUP_FOLLOWING = 0,
+ #[doc = "Data is changed on the leading edge of SCK and captured on the following edge of SCK"]
+ SETUP_LEADING_CAPTURE_FOLLOWING = 0x1,
+ }
+ #[doc = "Clock Polarity"]
+ CPOL start(31) width(1) RW {
+ #[doc = "The inactive state value of SCK is low"]
+ INACTIVE_LOW = 0,
+ #[doc = "The inactive state value of SCK is high"]
+ INACTIVE_HIGH = 0x1,
+ }
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Transmit Data Register"]
+ pub TDR<u32> WO []
+}
+
+ral_registers::register! {
+ #[doc = "Receive Status Register"]
+ pub RSR<u32> RO [
+ #[doc = "Start Of Frame"]
+ SOF start(0) width(1) RO {
+ #[doc = "Subsequent data word received after LPSPI_PCS assertion"]
+ SUBSEQUENT = 0,
+ #[doc = "First data word received after LPSPI_PCS assertion"]
+ FIRST = 0x1,
+ }
+ #[doc = "RX FIFO Empty"]
+ RXEMPTY start(1) width(1) RO {}
+ ]
+}
+
+ral_registers::register! {
+ #[doc = "Receive Data Register"]
+ pub RDR<u32> RO []
+}
+
+/// A command for the next LPSPI transfer.
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub struct TransmitCommand(u32);
+
+impl TransmitCommand {
+ /// Create a transmit command for a single frame.
+ ///
+ /// `frame_size_bits` describes the number of bits to transmit within
+ /// a single transmission. `frame_size_bits` must be at least 8 bits
+ /// large, but no larger than 4096 bits. The device's native word is
+ /// 32 bits large. If there are more than 32 bits to transfer, there
+ /// must be more than one bit remaining.
+ ///
+ /// If the frame size is valid, this returns the command for further
+ /// configuration. Otherwise, this returns `None`.
+ pub const fn for_frame(frame_size_bits: usize) -> Option<Self> {
+ const MIN_FRAME_SIZE: usize = 8;
+ const MAX_FRAME_SIZE: usize = 1 << 12;
+ const WORD_SIZE_BITS: usize = 32;
+
+ if MIN_FRAME_SIZE <= frame_size_bits
+ && frame_size_bits <= MAX_FRAME_SIZE
+ && frame_size_bits % WORD_SIZE_BITS != 1
+ {
+ Some(Self(frame_size_bits as u32 - 1))
+ } else {
+ None
+ }
+ }
+
+ /// Construct a command for the given bytes.
+ pub const fn for_u8s(bytes: &[u8]) -> Option<Self> {
+ Self::for_frame(size_of_val(bytes) * 8)
+ }
+
+ /// Construct a command for the given `u16`s.
+ pub const fn for_u16s(halves: &[u16]) -> Option<Self> {
+ Self::for_frame(size_of_val(halves) * 8)
+ }
+
+ /// Construct a command for the given `u32`s.
+ pub const fn for_u32s(words: &[u32]) -> Option<Self> {
+ Self::for_frame(size_of_val(words) * 8)
+ }
+
+ /// Set the transfer width.
+ ///
+ /// By default, the width is 1 bit.
+ pub const fn with_width(mut self, width: TransferWidth) -> Self {
+ self.0 &= !(0b11 << 16);
+ self.0 |= (width as u32) << 16;
+ self
+ }
+
+ /// Mask or unmask the transmit data.
+ ///
+ /// By default, transmit data is not masked.
+ pub const fn with_transmit_mask(mut self, mask: bool) -> Self {
+ self.0 &= !(1 << 18);
+ self.0 |= (mask as u32) << 18;
+ self
+ }
+
+ /// Mask or unmask the receive data.
+ ///
+ /// By default, receive data is not masked.
+ pub const fn with_receive_mask(mut self, mask: bool) -> Self {
+ self.0 &= !(1 << 19);
+ self.0 |= (mask as u32) << 19;
+ self
+ }
+
+ pub const fn with_continuing(mut self, continuing: bool) -> Self {
+ self.0 &= !(1 << 20);
+ self.0 |= (continuing as u32) << 20;
+ self
+ }
+
+ pub const fn with_continuous(mut self, continuous: bool) -> Self {
+ self.0 &= !(1 << 21);
+ self.0 |= (continuous as u32) << 21;
+ self
+ }
+
+ pub const fn with_byte_swap(mut self, swap: bool) -> Self {
+ self.0 &= !(1 << 22);
+ self.0 |= (swap as u32) << 22;
+ self
+ }
+
+ pub const fn with_lsb_first(mut self, lsb: bool) -> Self {
+ self.0 &= !(1 << 23);
+ self.0 |= (lsb as u32) << 23;
+ self
+ }
+
+ pub const fn with_chip_select(mut self, pcs: Pcs) -> Self {
+ self.0 &= !(0b11 << 24);
+ self.0 |= (pcs as u32) << 24;
+ self
+ }
+
+ pub const fn with_prescale(mut self, prescale: Prescale) -> Self {
+ self.0 &= !(0b111 << 27);
+ self.0 |= (prescale as u32) << 27;
+ self
+ }
+
+ pub const fn with_clock_phase(mut self, phase: ClockPhase) -> Self {
+ self.0 &= !(1 << 30);
+ self.0 |= (phase as u32) << 30;
+ self
+ }
+
+ pub const fn with_clock_polarity(mut self, polarity: ClockPolarity) -> Self {
+ self.0 &= !(1 << 31);
+ self.0 |= (polarity as u32) << 31;
+ self
+ }
+
+ /// Returns the raw command value.
+ pub const fn get(self) -> u32 {
+ self.0
+ }
+}
+
+/// Write the transmit command.
+///
+/// This doesn't check the TX FIFO for space to receive
+/// the command.
+#[inline]
+pub fn write_transmit_command(lpspi: LPSPI, cmd: TransmitCommand) {
+ ral_registers::write_reg!(self, lpspi, TCR, cmd.0);
+}
+
+/// The resting state of clock.
+///
+/// Updates when PCS negates.
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum ClockPolarity {
+ /// Idle the clock low.
+ #[default]
+ InactiveLow,
+ /// Idle the clock high.
+ InactiveHigh,
+}
+
+/// Describes the clock edge that captures and changes data.
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum ClockPhase {
+ /// Captured on leading, changed on following.
+ #[default]
+ Captured,
+ /// Changed on leading, captured on following.
+ Changed,
+}
+
+/// Divider applied to all properties in the clock
+/// configuration register.
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum Prescale {
+ #[default]
+ Div1,
+ Div2,
+ Div4,
+ Div8,
+ Div16,
+ Div32,
+ Div64,
+ Div128,
+}
+
+/// The chip select used for the transfer.
+///
+/// You're responsible for muxing this pin and making
+/// sure it's supported for your peripheral instance.
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum Pcs {
+ #[default]
+ Pcs0,
+ Pcs1,
+ Pcs2,
+ Pcs3,
+}
+
+/// Describes the data transfer width.
+///
+/// Nominal SPI is [`Bit1`]. Dual and quad SPI can
+/// use additional bits.
+#[derive(Default, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum TransferWidth {
+ /// One bit for data transfer.
+ #[default]
+ Bit1,
+ /// Two bits for data transfer.
+ Bit2,
+ /// Four bits for data transfer.
+ Bit4,
+}
+
+/// Provides words for LPSPI transmits.
+pub trait TxWordSource {
+ /// Returns how many words are available in this
+ /// source.
+ fn words_available(&self) -> usize;
+
+ /// Produces the next word, or an unspecified
+ /// value if there is no next word.
+ fn next_word(&mut self) -> u32;
+
+ /// Returns `true` if this source can provide
+ /// another word.
+ fn has_word(&self) -> bool {
+ self.words_available() != 0
+ }
+}
+
+/// A destination for LPSPI receives.
+pub trait RxWordSink {
+ /// Returns how many words remain unoccupied
+ /// in this sink.
+ fn words_available(&self) -> usize;
+
+ /// Store the next word, or do nothing if there
+ /// is no more space.
+ fn next_word(&mut self, word: u32);
+
+ /// Returns `true` if this sink can accept another
+ /// word.
+ fn has_word(&self) -> bool {
+ self.words_available() != 0
+ }
+}
+
+/// Produces LPSPI words for transmit.
+///
+/// The LPSPI word size is 32 bits. Stuffing smaller
+/// 8 or 16 bit primitives into that 32 bit word requires
+/// the CPU. This type helps with that stuffing.
+///
+/// Use [`next_word`](Self::next_word) to access the next
+/// available word from your slice. You can write this
+/// word directly to the LPSPI TX FIFO.
+///
+/// To perform a bidirectional I/O with a single buffer,
+/// use [`bidirectional_u8s`], [`bidirectional_u16s`], or
+/// [`bidirectional_u32s`].
+pub struct TxWords<'a, W> {
+ ptr: *const W,
+ end: *const W,
+ _buffer: PhantomData<&'a W>,
+}
+
+impl<'a, W> TxWords<'a, W> {
+ fn new(ptr: *const W, end: *const W) -> Self {
+ Self {
+ ptr,
+ end,
+ _buffer: PhantomData,
+ }
+ }
+
+ fn bytes_available(&self) -> usize {
+ // Safety: pointers remain in range of the same allocation.
+ // Module inspection shows that ptr never crosses beyond
+ // end.
+ unsafe { self.end.byte_offset_from_unsigned(self.ptr) }
+ }
+
+ fn words_available(&self) -> usize {
+ self.bytes_available().div_ceil(size_of::<u32>())
+ }
+
+ fn has_word(&self) -> bool {
+ self.ptr != self.end
+ }
+}
+
+impl<'a> TxWords<'a, u8> {
+ /// Transmit words from a slice of bytes.
+ pub fn from_u8s(bytes: &'a [u8]) -> Self {
+ let Range { start, end } = bytes.as_ptr_range();
+ Self::new(start, end)
+ }
+}
+
+impl TxWordSource for TxWords<'_, u8> {
+ fn words_available(&self) -> usize {
+ TxWords::words_available(self)
+ }
+
+ fn has_word(&self) -> bool {
+ TxWords::has_word(self)
+ }
+
+ fn next_word(&mut self) -> u32 {
+ // Safety: access through pointers guarded by the
+ // number of bytes available in that pointer. Reads
+ // are aligned for a u8 and valid.
+ unsafe {
+ let size = self.bytes_available().min(size_of::<u32>());
+
+ let mut word = 0_u32;
+ if size == 4 {
+ word |= u32::from(self.ptr.add(0).read()) << 24;
+ word |= u32::from(self.ptr.add(1).read()) << 16;
+ word |= u32::from(self.ptr.add(2).read()) << 8;
+ word |= u32::from(self.ptr.add(3).read());
+ } else if size == 3 {
+ word |= u32::from(self.ptr.add(0).read()) << 16;
+ word |= u32::from(self.ptr.add(1).read()) << 8;
+ word |= u32::from(self.ptr.add(2).read());
+ } else if size == 2 {
+ word |= u32::from(self.ptr.add(0).read()) << 8;
+ word |= u32::from(self.ptr.add(1).read());
+ } else if size == 1 {
+ word |= u32::from(self.ptr.read());
+ }
+
+ self.ptr = self.ptr.add(size);
+ word
+ }
+ }
+}
+
+impl<'a> TxWords<'a, u32> {
+ /// Transmit words from a slice of words.
+ pub fn from_u32s(words: &'a [u32]) -> Self {
+ let Range { start, end } = words.as_ptr_range();
+ Self::new(start, end)
+ }
+}
+
+impl TxWordSource for TxWords<'_, u32> {
+ fn words_available(&self) -> usize {
+ TxWords::words_available(self)
+ }
+
+ fn has_word(&self) -> bool {
+ TxWords::has_word(self)
+ }
+
+ fn next_word(&mut self) -> u32 {
+ // Safety: bounds check prevents out of bounds
+ // read. Module inspection shows that ptr never
+ // extends beyond end. Pointer is aligned and
+ // valid for reads given the public API required
+ // to construct this type.
+ unsafe {
+ if self.ptr == self.end {
+ return 0;
+ }
+
+ let word = self.ptr.read();
+ self.ptr = self.ptr.add(1);
+ word
+ }
+ }
+}
+
+/// Place received LPSPI words into a buffer.
+///
+/// The LPSPI word size is 32 bits. Separating that word
+/// into smaller 8 or 16 bit primitives requires the CPU.
+///
+/// After reading the LPSPI RX FIFO, use [`set_next_word`](Self::next_word)
+/// to store it into the user's buffer.
+///
+/// To perform a bidirectional I/O with a single buffer,
+/// use [`bidirectional_u8s`], [`bidirectional_u16s`], or
+/// [`bidirectional_u32s`].
+pub struct RxWords<'a, W> {
+ ptr: *mut W,
+ end: *mut W,
+ _buffer: PhantomData<&'a W>,
+}
+
+impl<'a, W> RxWords<'a, W> {
+ fn new(ptr: *mut W, end: *mut W) -> Self {
+ Self {
+ ptr,
+ end,
+ _buffer: PhantomData,
+ }
+ }
+
+ fn bytes_available(&self) -> usize {
+ // Safety: pointers remain in range of the same allocation.
+ // Module inspection shows that ptr never crosses beyond
+ // end.
+ unsafe { self.end.byte_offset_from_unsigned(self.ptr) }
+ }
+
+ fn words_available(&self) -> usize {
+ self.bytes_available().div_ceil(size_of::<u32>())
+ }
+
+ fn has_word(&self) -> bool {
+ self.ptr != self.end
+ }
+}
+
+impl<'a> RxWords<'a, u8> {
+ /// Read data into a byte slice.
+ pub fn from_u8s(bytes: &'a mut [u8]) -> Self {
+ let Range { start, end } = bytes.as_mut_ptr_range();
+ Self::new(start, end)
+ }
+}
+
+impl RxWordSink for RxWords<'_, u8> {
+ fn words_available(&self) -> usize {
+ RxWords::words_available(self)
+ }
+
+ fn has_word(&self) -> bool {
+ RxWords::has_word(self)
+ }
+
+ fn next_word(&mut self, word: u32) {
+ // Safety: access through pointer guarded by the number
+ // of bytes available through that pointer. Similarly,
+ // pointer offset guaded by the available bytes in the
+ // pointer. Access of u8s, u16s, and u32s are all valid
+ // through u8 access.
+ unsafe {
+ let size = self.bytes_available().min(size_of_val(&word));
+
+ if size == 4 {
+ self.ptr.add(0).write((word >> 24) as u8);
+ self.ptr.add(1).write((word >> 16) as u8);
+ self.ptr.add(2).write((word >> 8) as u8);
+ self.ptr.add(3).write(word as u8);
+ } else if size == 3 {
+ self.ptr.add(0).write((word >> 16) as u8);
+ self.ptr.add(1).write((word >> 8) as u8);
+ self.ptr.add(2).write(word as u8);
+ } else if size == 2 {
+ self.ptr.add(0).write((word >> 8) as u8);
+ self.ptr.add(1).write(word as u8);
+ } else if size == 1 {
+ self.ptr.write(word as u8);
+ }
+
+ self.ptr = self.ptr.add(size);
+ }
+ }
+}
+
+impl<'a> RxWords<'a, u32> {
+ /// Read data into a word slice.
+ pub fn from_u32s(words: &'a mut [u32]) -> Self {
+ let Range { start, end } = words.as_mut_ptr_range();
+ Self::new(start, end)
+ }
+}
+
+impl RxWordSink for RxWords<'_, u32> {
+ fn words_available(&self) -> usize {
+ RxWords::words_available(self)
+ }
+
+ fn has_word(&self) -> bool {
+ RxWords::has_word(self)
+ }
+
+ fn next_word(&mut self, word: u32) {
+ // Safety: pointer remains in bound of allocation,
+ // aligned for u32 access, and valid for writes.
+ unsafe {
+ if self.ptr != self.end {
+ self.ptr.write(word);
+ self.ptr = self.ptr.add(1);
+ }
+ }
+ }
+}
+
+/// Transmit and receive data from this byte slice.
+pub fn bidirectional_u8s<'a>(bytes: &'a mut [u8]) -> (TxWords<'a, u8>, RxWords<'a, u8>) {
+ let Range { start, end } = bytes.as_mut_ptr_range();
+ (TxWords::new(start, end), RxWords::new(start, end))
+}
+
+/// Transmit and receive data from this word slice.
+pub fn bidirectional_u32s<'a>(words: &'a mut [u32]) -> (TxWords<'a, u32>, RxWords<'a, u32>) {
+ let Range { start, end } = words.as_mut_ptr_range();
+ (TxWords::new(start, end), RxWords::new(start, end))
+}