diff options
| author | Ian McIntyre <me@mciantyre.dev> | 2025-11-30 18:52:34 -0500 |
|---|---|---|
| committer | Ian McIntyre <me@mciantyre.dev> | 2025-11-30 19:10:51 -0500 |
| commit | 76199f21616ad86cf68f3b063c1ce23c6fc5a52f (patch) | |
| tree | 4c076d0afd649803a2bd9a5ed5cbb1f1c74fb459 /drivers/lpspi/src | |
First commit
Diffstat (limited to 'drivers/lpspi/src')
| -rw-r--r-- | drivers/lpspi/src/lib.rs | 877 |
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)) +} |
