From 7724d4286704b9565c2f36dd8d4cc2c80ec587ed Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 17 Nov 2023 22:48:15 +0100 Subject: [PATCH 01/73] Initial ideas --- Cargo.toml | 42 +- board/src/teensy4.rs | 1 - examples/rtic_spi.rs | 6 +- src/chip/dma.rs | 178 +++--- src/common/lpspi.rs | 1054 +------------------------------- src/common/lpspi/builder.rs | 100 ++++ src/common/lpspi/driver.rs | 0 src/common/lpspi/lpspi_eh1.rs | 1 + src/common/lpspi_old.rs | 1064 +++++++++++++++++++++++++++++++++ 9 files changed, 1313 insertions(+), 1133 deletions(-) create mode 100644 src/common/lpspi/builder.rs create mode 100644 src/common/lpspi/driver.rs create mode 100644 src/common/lpspi/lpspi_eh1.rs create mode 100644 src/common/lpspi_old.rs diff --git a/Cargo.toml b/Cargo.toml index 6b431724..d3aea436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "imxrt-hal" -authors = ["Tom Burdick ", "Ian McIntyre "] +authors = [ + "Tom Burdick ", + "Ian McIntyre ", +] description = """ Hardware abstraction layer for NXP i.MX RT microcontrollers. """ readme = "README.md" -repository = { workspace = true } -keywords = { workspace = true } -categories = { workspace = true } -license = { workspace = true } +repository = { workspace = true } +keywords = { workspace = true } +categories = { workspace = true } +license = { workspace = true } edition = { workspace = true } version = "0.5.3" @@ -33,11 +36,23 @@ version = "1" package = "embedded-hal" version = "0.2" +[dependencies.eh1] +package = "embedded-hal" +version = "1.0.0-rc.1" + +[dependencies.eh1-async] +package = "embedded-hal-async" +version = "1.0.0-rc.1" +optional = true + [dependencies.rand_core] -version = "0.5" +version = "0.6" default-features = false optional = true +[dependencies.rtic-sync] +version = "1.0.2" + ####################### # imxrt-rs dependencies ####################### @@ -80,16 +95,17 @@ imxrt1170 = ["imxrt-iomuxc/imxrt1170"] eh02-unproven = [] [workspace] -members = [ - "board", - "logging", -] +members = ["board", "logging"] [workspace.dependencies] imxrt-dma = "0.1" imxrt-iomuxc = "0.2.1" imxrt-hal = { version = "0.5", path = "." } -imxrt-log = { path = "logging", default-features = false, features = ["log", "lpuart", "usbd"] } +imxrt-log = { path = "logging", default-features = false, features = [ + "log", + "lpuart", + "usbd", +] } imxrt-ral = "0.5" imxrt-rt = "0.1" imxrt-usbd = "0.2" @@ -126,8 +142,8 @@ codegen-units = 256 [dev-dependencies] cortex-m = "0.7" imxrt-rt = { workspace = true } -menu = "0.3.2" -cortex-m-rtic = "1.0" +menu = "0.4.0" +rtic = { version = "2.0.1", features = ["thumbv7-backend"] } log = "0.4" defmt = "0.3" pin-utils = "0.1" diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index 97e42293..c1a27845 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -49,7 +49,6 @@ pub type SpiPins = hal::lpspi::Pins< iomuxc::gpio_b0::GPIO_B0_02, // SDO, P11 iomuxc::gpio_b0::GPIO_B0_01, // SDI, P12 iomuxc::gpio_b0::GPIO_B0_03, // SCK, P13 - iomuxc::gpio_b0::GPIO_B0_00, // PCS0, P10 >; #[cfg(not(feature = "spi"))] diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index 29fe5faa..e90a09d2 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -23,7 +23,7 @@ mod app { struct Shared {} #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + fn init(_: init::Context) -> (Shared, Local) { let (_, board::Specifics { mut spi, .. }) = board::new(); spi.disabled(|spi| { // Trigger when the TX FIFO is empty. @@ -34,12 +34,12 @@ mod app { // Starts the I/O as soon as we're done initializing, since // the TX FIFO is empty. spi.set_interrupts(Interrupts::TRANSMIT_DATA); - (Shared {}, Local { spi }, init::Monotonics()) + (Shared {}, Local { spi }) } #[task(binds = BOARD_SPI, local = [spi])] fn spi_interrupt(cx: spi_interrupt::Context) { - let spi_interrupt::LocalResources { spi } = cx.local; + let spi_interrupt::LocalResources { spi, .. } = cx.local; let status = spi.status(); spi.clear_status(Status::TRANSMIT_DATA | Status::RECEIVE_DATA); diff --git a/src/chip/dma.rs b/src/chip/dma.rs index 4fa17b68..0a9e2940 100644 --- a/src/chip/dma.rs +++ b/src/chip/dma.rs @@ -138,104 +138,104 @@ impl lpuart::Lpuart { } } -// LPSPI -use crate::lpspi; +// // LPSPI +// use crate::lpspi; -unsafe impl peripheral::Source for lpspi::Lpspi { - fn source_signal(&self) -> u32 { - LPSPI_DMA_RX_MAPPING[N as usize - 1] - } - fn source_address(&self) -> *const u32 { - self.rdr().cast() - } - fn enable_source(&mut self) { - self.enable_dma_receive() - } - fn disable_source(&mut self) { - self.disable_dma_receive(); - } -} +// unsafe impl peripheral::Source for lpspi::Lpspi { +// fn source_signal(&self) -> u32 { +// LPSPI_DMA_RX_MAPPING[N as usize - 1] +// } +// fn source_address(&self) -> *const u32 { +// self.rdr().cast() +// } +// fn enable_source(&mut self) { +// self.enable_dma_receive() +// } +// fn disable_source(&mut self) { +// self.disable_dma_receive(); +// } +// } -unsafe impl peripheral::Destination for lpspi::Lpspi { - fn destination_signal(&self) -> u32 { - LPSPI_DMA_TX_MAPPING[N as usize - 1] - } - fn destination_address(&self) -> *const u32 { - self.tdr().cast() - } - fn enable_destination(&mut self) { - self.enable_dma_transmit(); - } - fn disable_destination(&mut self) { - self.disable_dma_transmit(); - } -} +// unsafe impl peripheral::Destination for lpspi::Lpspi { +// fn destination_signal(&self) -> u32 { +// LPSPI_DMA_TX_MAPPING[N as usize - 1] +// } +// fn destination_address(&self) -> *const u32 { +// self.tdr().cast() +// } +// fn enable_destination(&mut self) { +// self.enable_dma_transmit(); +// } +// fn disable_destination(&mut self) { +// self.disable_dma_transmit(); +// } +// } -unsafe impl peripheral::Bidirectional for lpspi::Lpspi {} +// unsafe impl peripheral::Bidirectional for lpspi::Lpspi {} -impl lpspi::Lpspi { - /// Use a DMA channel to write data to the LPSPI peripheral. - /// - /// The future completes when all data in `buffer` has been written to the - /// peripheral. This call may block until space is available in the - /// command queue. An error indicates that there was an issue preparing the - /// transaction, or there was an issue while waiting for space in the command - /// queue. - pub fn dma_write<'a>( - &'a mut self, - channel: &'a mut Channel, - buffer: &'a [u32], - ) -> Result, lpspi::LpspiError> { - let mut transaction = lpspi::Transaction::new_u32s(buffer)?; - transaction.bit_order = self.bit_order(); +// impl lpspi::Lpspi { +// /// Use a DMA channel to write data to the LPSPI peripheral. +// /// +// /// The future completes when all data in `buffer` has been written to the +// /// peripheral. This call may block until space is available in the +// /// command queue. An error indicates that there was an issue preparing the +// /// transaction, or there was an issue while waiting for space in the command +// /// queue. +// pub fn dma_write<'a>( +// &'a mut self, +// channel: &'a mut Channel, +// buffer: &'a [u32], +// ) -> Result, lpspi::LpspiError> { +// let mut transaction = lpspi::Transaction::new_u32s(buffer)?; +// transaction.bit_order = self.bit_order(); - transaction.receive_data_mask = true; - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - Ok(peripheral::write(channel, buffer, self)) - } +// transaction.receive_data_mask = true; +// self.wait_for_transmit_fifo_space()?; +// self.enqueue_transaction(&transaction); +// Ok(peripheral::write(channel, buffer, self)) +// } - /// Use a DMA channel to read data from the LPSPI peripheral. - /// - /// The future completes when `buffer` is filled. This call may block until - /// space is available in the command queue. An error indicates that there was - /// an issue preparing the transaction, or there was an issue waiting for space - /// in the command queue. - pub fn dma_read<'a>( - &'a mut self, - channel: &'a mut Channel, - buffer: &'a mut [u32], - ) -> Result, lpspi::LpspiError> { - let mut transaction = lpspi::Transaction::new_u32s(buffer)?; - transaction.bit_order = self.bit_order(); +// /// Use a DMA channel to read data from the LPSPI peripheral. +// /// +// /// The future completes when `buffer` is filled. This call may block until +// /// space is available in the command queue. An error indicates that there was +// /// an issue preparing the transaction, or there was an issue waiting for space +// /// in the command queue. +// pub fn dma_read<'a>( +// &'a mut self, +// channel: &'a mut Channel, +// buffer: &'a mut [u32], +// ) -> Result, lpspi::LpspiError> { +// let mut transaction = lpspi::Transaction::new_u32s(buffer)?; +// transaction.bit_order = self.bit_order(); - transaction.transmit_data_mask = true; - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - Ok(peripheral::read(channel, self, buffer)) - } +// transaction.transmit_data_mask = true; +// self.wait_for_transmit_fifo_space()?; +// self.enqueue_transaction(&transaction); +// Ok(peripheral::read(channel, self, buffer)) +// } - /// Use a DMA channel to simultaneously read and write from a buffer - /// and the LPSPI peripheral. - /// - /// The future completes when `buffer` is filled and after sending `buffer` elements. - /// This call may block until space is available in the command queue. An error - /// indicates that there was an issue preparing the transaction, or there was an - /// issue waiting for space in the command queue. - pub fn dma_full_duplex<'a>( - &'a mut self, - rx: &'a mut Channel, - tx: &'a mut Channel, - buffer: &'a mut [u32], - ) -> Result, lpspi::LpspiError> { - let mut transaction = lpspi::Transaction::new_u32s(buffer)?; - transaction.bit_order = self.bit_order(); +// /// Use a DMA channel to simultaneously read and write from a buffer +// /// and the LPSPI peripheral. +// /// +// /// The future completes when `buffer` is filled and after sending `buffer` elements. +// /// This call may block until space is available in the command queue. An error +// /// indicates that there was an issue preparing the transaction, or there was an +// /// issue waiting for space in the command queue. +// pub fn dma_full_duplex<'a>( +// &'a mut self, +// rx: &'a mut Channel, +// tx: &'a mut Channel, +// buffer: &'a mut [u32], +// ) -> Result, lpspi::LpspiError> { +// let mut transaction = lpspi::Transaction::new_u32s(buffer)?; +// transaction.bit_order = self.bit_order(); - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - Ok(peripheral::full_duplex(rx, tx, self, buffer)) - } -} +// self.wait_for_transmit_fifo_space()?; +// self.enqueue_transaction(&transaction); +// Ok(peripheral::full_duplex(rx, tx, self, buffer)) +// } +// } // ADC #[cfg(family = "imxrt10xx")] diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 802abe0b..63886a59 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,356 +1,20 @@ -//! Low-power serial peripheral interface. -//! -//! [`Lpspi`] implements select embedded HAL SPI traits for coordinating SPI I/O. -//! When using the trait implementations, make sure that [`set_bit_order`](Lpspi::set_bit_order) -//! is correct for your device. These settings apply when the driver internally defines the transaction. -//! -//! This driver also exposes the peripheral's lower-level, hardware-dependent transaction interface. -//! Create a [`Transaction`], then [`enqueue_transaction`](Lpspi::enqueue_transaction) before -//! sending data with [`enqueue_data`](Lpspi::enqueue_data). When using the transaction interface, -//! you're responsible for serializing your data into `u32` SPI words. -//! -//! # Chip selects (CS) for SPI peripherals -//! -//! The iMXRT SPI peripherals have one or more peripheral-controlled chip selects (CS). Using -//! the peripheral-controlled CS means that you do not need a GPIO to coordinate SPI operations. -//! Blocking full-duplex transfers and writes will observe an asserted chip select while data -//! frames are exchanged / written. -//! -//! This driver generally assumes that you're using the peripheral-controlled chip select. If -//! you instead want to manage chip select in software, you should be able to multiplex your own -//! pins, then construct the driver [`without_pins`](Lpspi::without_pins). -//! -//! # Example -//! -//! Initialize an LPSPI with a 1MHz SCK. To understand how to configure the LPSPI -//! peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. -//! -//! ```no_run -//! use imxrt_hal as hal; -//! use imxrt_ral as ral; -//! # use eh02 as embedded_hal; -//! use embedded_hal::blocking::spi::Transfer; -//! use hal::lpspi::{Lpspi, Pins, SamplePoint}; -//! use ral::lpspi::LPSPI4; -//! -//! let mut pads = // Handle to all processor pads... -//! # unsafe { imxrt_iomuxc::imxrt1060::Pads::new() }; -//! -//! # || -> Option<()> { -//! let spi_pins = Pins { -//! sdo: pads.gpio_b0.p02, -//! sdi: pads.gpio_b0.p01, -//! sck: pads.gpio_b0.p03, -//! pcs0: pads.gpio_b0.p00, -//! }; -//! -//! let mut spi4 = unsafe { LPSPI4::instance() }; -//! let mut spi = Lpspi::new( -//! spi4, -//! spi_pins, -//! ); -//! -//! # const LPSPI_CLK_HZ: u32 = 1; -//! spi.disabled(|spi| { -//! spi.set_clock_hz(LPSPI_CLK_HZ, 1_000_000); -//! spi.set_sample_point(SamplePoint::Edge); -//! }); -//! -//! let mut buffer: [u8; 3] = [1, 2, 3]; -//! spi.transfer(&mut buffer).ok()?; -//! -//! let (spi4, pins) = spi.release(); -//! -//! // Re-construct without pins: -//! let mut spi = Lpspi::without_pins(spi4); -//! # Some(()) }(); -//! ``` -//! -//! # Limitations -//! -//! Due to [a hardware defect][1], this driver does not yet support the EH02 SPI transaction API. -//! An early iteration of this driver reproduced the issue discussed in that forum. This driver may -//! be able to work around the defect in software, but it hasn't been explored. -//! -//! [1]: https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 -//! -//! [`Transaction`] exposes the continuous / continuing flags, so you're free to model advanced -//! transactions. However, keep in mind that disabling the receiver during a continuous transaction -//! may not work as expected. +pub use eh1::spi::Mode; +use imxrt_dma::channel::Channel; +use rtic_sync::arbiter::Arbiter; -use crate::iomuxc::{consts, lpspi}; use crate::ral; -pub use eh02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +mod builder; +mod driver; -/// Data direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - /// Transmit direction (leaving the peripheral). - Tx, - /// Receive direction (entering the peripheral). - Rx, -} - -/// Bit order. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum BitOrder { - /// Data is transferred most significant bit first (default). - #[default] - Msb, - /// Data is transferred least significant bit first. - Lsb, -} - -/// Receive sample point behavior. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SamplePoint { - /// Input data is sampled on SCK edge. - Edge, - /// Input data is sampled on delayed SCK edge. - DelayedEdge, -} - -/// Possible errors when interfacing the LPSPI. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LpspiError { - /// The transaction frame size is incorrect. - /// - /// The frame size, in bits, must be between 8 bits and - /// 4095 bits. - FrameSize, - /// FIFO error in the given direction. - Fifo(Direction), - /// Bus is busy at the start of a transfer. - Busy, - /// Caller provided no data. - NoData, -} - -/// An LPSPI transaction definition. -/// -/// The transaction defines how many bits the driver sends or recieves. -/// It also describes -/// -/// - endianness -/// - bit order -/// - transmit and receive masking -/// - continuous and continuing transfers (default: both disabled) -/// -/// The LPSPI enqueues the transaction data into the transmit -/// FIFO. When it pops the values from the FIFO, the values take -/// effect immediately. This may affect, or abort, any ongoing -/// transactions. Consult the reference manual to understand when -/// you should enqueue transaction definitions, since it may only -/// be supported on word / frame boundaries. -/// -/// Construct `Transaction` with [`new`](Self::new), and supply -/// the number of **bits** to transmit per frame. -/// -/// ``` -/// use imxrt_hal as hal; -/// use hal::lpspi::Transaction; -/// -/// // Send one u32. -/// let mut transaction -/// = Transaction::new(8 * core::mem::size_of::() as u16); -/// ``` -/// -/// Once constructed, manipulate the public members to change the -/// configuration. -/// -/// # Continuous transactions -/// -/// The pseudo-code below shows how to set [`continuous`](Self::continuous) and -/// [`continuing`](Self::continuing) to model a continuous transaction. Keep in -/// mind the hardware limitations; see the [module-level docs](crate::lpspi#limitations) for -/// details. -/// -/// ``` -/// use imxrt_hal as hal; -/// use hal::lpspi::Transaction; -/// -/// // Skipping LPSPI initialization; see module-level example. -/// -/// // Start byte exchange as a continuous transaction. Each frame -/// // exchanges one byte (eight bits) with a device. -/// # || -> Result<(), hal::lpspi::LpspiError> { -/// let mut transaction = Transaction::new(8)?; -/// transaction.continuous = true; -/// // Enqueue transaction with LPSPI... -/// // Enqueue one byte with LPSPI... <-- PCS asserts here. -/// -/// # let buffer: [u8; 5] = [0; 5]; -/// for byte in buffer { -/// // Set 'continuing' to indicate that the next -/// // transaction continues the previous one... -/// transaction.continuing = true; -/// -/// // Enqueue transaction with LPSPI... -/// // Enqueue byte with LPSPI... -/// } -/// -/// transaction.continuous = false; -/// transaction.continuing = false; -/// // Enqueue transaction with LPSPI... <-- PCS de-asserts here. -/// # Ok(()) }().unwrap(); -/// ``` -pub struct Transaction { - /// Enable byte swap. - /// - /// When enabled (`true`), swap bytes with the `u32` word. This allows - /// you to change the endianness of the 32-bit word transfer. The - /// default is `false`. - pub byte_swap: bool, - /// Bit order. - /// - /// See [`BitOrder`] for details. The default is [`BitOrder::Msb`]. - pub bit_order: BitOrder, - /// Mask the received data. - /// - /// If `true`, the peripheral discards received data. Use this - /// when you only care about sending data. The default is `false`; - /// the peripheral puts received data in the receive FIFO. - pub receive_data_mask: bool, - /// Mask the transmit data. - /// - /// If `true`, the peripheral doesn't send any data. Use this when - /// you only care about receiving data. The default is `false`; - /// the peripheral expects to send data using the transmit FIFO. - pub transmit_data_mask: bool, - /// Indicates (`true`) the start of a continuous transfer. - /// - /// If set, the peripherals chip select will remain asserted after - /// exchanging the frame. This allows you to enqueue new commands - /// and data words within the same transaction. Those new commands - /// should have [`continuing`](Self::continuing) set to `true`. - /// - /// The default is `false`; chip select de-asserts after exchanging - /// the frame. To stop a continuous transfer, enqueue a new `Transaction` - /// in which this flag, and `continuing`, is false. - pub continuous: bool, - /// Indicates (`true`) that this command belongs to a previous transaction. - /// - /// Set this to indicate that this new `Transaction` belongs to a previous - /// `Transaction`, one that had [`continuous`](Self::continuous) set. - /// The default value is `false`. - pub continuing: bool, - - frame_size: u16, -} - -impl Transaction { - /// Defines a transaction for a `u32` buffer. - /// - /// After successfully defining a transaction of this buffer, - /// supply it to the LPSPI driver, then start sending the - /// data. - /// - /// Returns an error if any are true: - /// - /// - the buffer is empty. - /// - there's more than 128 elements in the buffer. - pub fn new_u32s(data: &[u32]) -> Result { - Transaction::new_words(data) - } - - fn new_words(data: &[W]) -> Result { - Transaction::new(8 * core::mem::size_of_val(data) as u16) - } - - /// Define a transaction by specifying the frame size, in bits. - /// - /// The frame size describes the number of bits that will be transferred and - /// received during the next transaction. Specifically, it describes the number - /// of bits for which the PCS pin signals a transaction. - /// - /// # Requirements - /// - /// - `frame_size` fits within 12 bits; the implementation enforces this maximum value. - /// - The minimum value for `frame_size` is 8; the implementation enforces this minimum - /// value. - pub fn new(frame_size: u16) -> Result { - const MIN_FRAME_SIZE: u16 = 8; - const MAX_FRAME_SIZE: u16 = 1 << 12; - if (MIN_FRAME_SIZE..MAX_FRAME_SIZE).contains(&frame_size) { - Ok(Self { - byte_swap: false, - bit_order: Default::default(), - receive_data_mask: false, - transmit_data_mask: false, - frame_size: frame_size - 1, - continuing: false, - continuous: false, - }) - } else { - Err(LpspiError::FrameSize) - } - } -} - -/// Sets the clock speed parameters. -/// -/// This should only happen when the LPSPI peripheral is disabled. -fn set_spi_clock(source_clock_hz: u32, spi_clock_hz: u32, reg: &ral::lpspi::RegisterBlock) { - let mut div = source_clock_hz / spi_clock_hz; - - if source_clock_hz / div > spi_clock_hz { - div += 1; - } - - // 0 <= div <= 255, and the true coefficient is really div + 2 - let div = div.saturating_sub(2).clamp(0, 255); - ral::write_reg!( - ral::lpspi, - reg, - CCR, - SCKDIV: div, - // Both of these delays are arbitrary choices, and they should - // probably be configurable by the end-user. - DBT: div / 2, - SCKPCS: 0x1F, - PCSSCK: 0x1F - ); -} - -/// An LPSPI driver. -/// -/// The driver exposes low-level methods for coordinating -/// DMA transfers. However, you may find it easier to use the -/// [`dma`](crate::dma) interface to coordinate DMA transfers. -/// -/// The driver implements `embedded-hal` SPI traits. You should prefer -/// these implementations for their ease of use. -/// -/// See the [module-level documentation](crate::lpspi) for an example -/// of how to construct this driver. -pub struct Lpspi { - lpspi: ral::lpspi::Instance, +struct LpspiBuilder { + data: &'static mut Option>, + dma: D, pins: P, - bit_order: BitOrder, + lpspi: ral::lpspi::Instance, } -/// Pins for a LPSPI device. -/// -/// Consider using type aliases to simplify your usage: -/// -/// ```no_run -/// use imxrt_hal as hal; -/// use imxrt_iomuxc::imxrt1060::gpio_b0::*; -/// -/// // SPI pins used in my application -/// type LpspiPins = hal::lpspi::Pins< -/// GPIO_B0_02, -/// GPIO_B0_01, -/// GPIO_B0_03, -/// GPIO_B0_00, -/// >; -/// -/// // Helper type for your SPI peripheral -/// type Lpspi = hal::lpspi::Lpspi; -/// ``` -pub struct Pins { +pub struct Pins { /// Serial data out /// /// Data travels from the SPI host controller to the SPI device. @@ -361,698 +25,34 @@ pub struct Pins { pub sdi: SDI, /// Serial clock pub sck: SCK, - /// Chip select 0 - /// - /// (PCSx) convention matches the hardware. - pub pcs0: PCS0, -} - -impl Lpspi, N> -where - SDO: lpspi::Pin, Signal = lpspi::Sdo>, - SDI: lpspi::Pin, Signal = lpspi::Sdi>, - SCK: lpspi::Pin, Signal = lpspi::Sck>, - PCS0: lpspi::Pin, Signal = lpspi::Pcs0>, -{ - /// Create a new LPSPI driver from the RAL LPSPI instance and a set of pins. - /// - /// When this call returns, the LPSPI pins are configured for their function. - /// The peripheral is enabled after reset. The LPSPI clock speed is unspecified. - /// The mode is [`MODE_0`]. The sample point is [`SamplePoint::DelayedEdge`]. - pub fn new(lpspi: ral::lpspi::Instance, mut pins: Pins) -> Self { - lpspi::prepare(&mut pins.sdo); - lpspi::prepare(&mut pins.sdi); - lpspi::prepare(&mut pins.sck); - lpspi::prepare(&mut pins.pcs0); - Self::init(lpspi, pins) - } -} - -impl Lpspi<(), N> { - /// Create a new LPSPI driver from the RAL LPSPI instance. - /// - /// This is similar to [`new()`](Self::new), but it does not configure - /// pins. You're responsible for configuring pins, and for making sure - /// the pin configuration doesn't change while this driver is in use. - pub fn without_pins(lpspi: ral::lpspi::Instance) -> Self { - Self::init(lpspi, ()) - } -} - -impl Lpspi { - /// The peripheral instance. - pub const N: u8 = N; - - fn init(lpspi: ral::lpspi::Instance, pins: P) -> Self { - let mut spi = Lpspi { - lpspi, - pins, - bit_order: BitOrder::default(), - }; - ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_1); - ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_0); - ral::write_reg!( - ral::lpspi, - spi.lpspi, - CFGR1, - MASTER: MASTER_1, - SAMPLE: SAMPLE_1 - ); - Disabled::new(&mut spi.lpspi).set_mode(MODE_0); - ral::write_reg!(ral::lpspi, spi.lpspi, FCR, RXWATER: 0xF, TXWATER: 0xF); - ral::write_reg!(ral::lpspi, spi.lpspi, CR, MEN: MEN_1); - spi - } - - /// Indicates if the driver is (`true`) or is not (`false`) enabled. - pub fn is_enabled(&self) -> bool { - ral::read_reg!(ral::lpspi, self.lpspi, CR, MEN == MEN_1) - } - - /// Enable (`true`) or disable (`false`) the peripheral. - pub fn set_enable(&mut self, enable: bool) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: enable as u32) - } - - /// Reset the driver. - /// - /// Note that this may not not reset all peripheral state, like the - /// enabled state. - pub fn reset(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_1); - while ral::read_reg!(ral::lpspi, self.lpspi, CR, RST == RST_1) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_0); - } - } - - /// Release the SPI driver components. - /// - /// This does not change any component state; it releases the components as-is. - /// If you need to obtain the registers in a known, good state, consider calling - /// methods like [`reset()`](Self::reset) before releasing the registers. - pub fn release(self) -> (ral::lpspi::Instance, P) { - (self.lpspi, self.pins) - } - - /// Returns the bit order configuration. - /// - /// See notes in [`set_bit_order`](Lpspi::set_bit_order) to - /// understand when this configuration takes effect. - pub fn bit_order(&self) -> BitOrder { - self.bit_order - } - - /// Set the bit order configuration. - /// - /// This applies to all higher-level write and transfer operations. - /// If you're using the [`Transaction`] API with manual word reads - /// and writes, set the configuration as part of the transaction. - pub fn set_bit_order(&mut self, bit_order: BitOrder) { - self.bit_order = bit_order; - } - - /// Temporarily disable the LPSPI peripheral. - /// - /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify - /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit - /// and receive FIFOs. - pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { - self.clear_fifos(); - let mut disabled = Disabled::new(&mut self.lpspi); - func(&mut disabled) - } - - /// Read the status register. - pub fn status(&self) -> Status { - Status::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, SR)) - } - - /// Clear the status flags. - /// - /// To clear status flags, set them high, then call `clear_status()`. - /// - /// The implementation will ensure that only the W1C bits are written, so it's - /// OK to supply `Status::all()` to clear all bits. - pub fn clear_status(&self, flags: Status) { - let flags = flags & Status::W1C; - ral::write_reg!(ral::lpspi, self.lpspi, SR, flags.bits()); - } - - /// Read the interrupt enable bits. - pub fn interrupts(&self) -> Interrupts { - Interrupts::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, IER)) - } - - /// Set the interrupt enable bits. - /// - /// This writes the bits described by `interrupts` as is to the register. - /// To modify the existing interrupts flags, you should first call [`interrupts`](Lpspi::interrupts) - /// to get the current state, then modify that state. - pub fn set_interrupts(&self, interrupts: Interrupts) { - ral::write_reg!(ral::lpspi, self.lpspi, IER, interrupts.bits()); - } - - /// Clear any existing data in the SPI receive or transfer FIFOs. - #[inline] - pub fn clear_fifo(&mut self, direction: Direction) { - match direction { - Direction::Tx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1), - Direction::Rx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RRF: RRF_1), - } - } - - /// Clear both FIFOs. - pub fn clear_fifos(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1, RRF: RRF_1); - } - - /// Returns the watermark level for the given direction. - #[inline] - pub fn watermark(&self, direction: Direction) -> u8 { - (match direction { - Direction::Rx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, RXWATER), - Direction::Tx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, TXWATER), - }) as u8 - } - - /// Returns the FIFO status. - #[inline] - pub fn fifo_status(&self) -> FifoStatus { - let (rxcount, txcount) = ral::read_reg!(ral::lpspi, self.lpspi, FSR, RXCOUNT, TXCOUNT); - FifoStatus { - rxcount: rxcount as u16, - txcount: txcount as u16, - } - } - - /// Simply read whatever is in the receiver data register. - fn read_data_unchecked(&self) -> u32 { - ral::read_reg!(ral::lpspi, self.lpspi, RDR) - } - - /// Read the data register. - /// - /// Returns `None` if the receive FIFO is empty. Otherwise, returns the complete - /// read of the register. You're reponsible for interpreting the raw value as - /// a data word, depending on the frame size. - pub fn read_data(&mut self) -> Option { - if ral::read_reg!(ral::lpspi, self.lpspi, RSR, RXEMPTY == RXEMPTY_0) { - Some(self.read_data_unchecked()) - } else { - None - } - } - - /// Check for any receiver errors. - fn recv_ok(&self) -> Result<(), LpspiError> { - let status = self.status(); - if status.intersects(Status::RECEIVE_ERROR) { - Err(LpspiError::Fifo(Direction::Rx)) - } else { - Ok(()) - } - } - - /// Place `word` into the transmit FIFO. - /// - /// This will result in the value being sent from the LPSPI. - /// You're responsible for making sure that the transmit FIFO can - /// fit this word. - pub fn enqueue_data(&self, word: u32) { - ral::write_reg!(ral::lpspi, self.lpspi, TDR, word); - } - - pub(crate) fn wait_for_transmit_fifo_space(&mut self) -> Result<(), LpspiError> { - loop { - let status = self.status(); - if status.intersects(Status::TRANSMIT_ERROR) { - return Err(LpspiError::Fifo(Direction::Tx)); - } - let fifo_status = self.fifo_status(); - if !fifo_status.is_full(Direction::Tx) { - return Ok(()); - } - } - } - - /// Place a transaction definition into the transmit FIFO. - /// - /// Once this definition is popped from the transmit FIFO, this may - /// affect, or abort, any ongoing transactions. - /// - /// You're responsible for making sure there's space in the transmit - /// FIFO for this transaction command. - pub fn enqueue_transaction(&mut self, transaction: &Transaction) { - ral::modify_reg!(ral::lpspi, self.lpspi, TCR, - LSBF: transaction.bit_order as u32, - BYSW: transaction.byte_swap as u32, - RXMSK: transaction.receive_data_mask as u32, - TXMSK: transaction.transmit_data_mask as u32, - FRAMESZ: transaction.frame_size as u32, - CONT: transaction.continuous as u32, - CONTC: transaction.continuing as u32 - ); - } - - /// Exchanges data with the SPI device. - /// - /// This routine uses continuous transfers to perform the transaction, no matter the - /// primitive type. There's an optimization for &[u32] that we're missing; in this case, - /// we don't necessarily need to use continuous transfers. The frame size could be set to - /// 8 * buffer.len() * sizeof(u32), and we copy user words into the transmit queue as-is. - /// But handling the packing of u8s and u16s into the u32 transmit queue in software is - /// extra work, work that's effectively achieved when we use continuous transfers. - /// We're guessing that the time to pop a transmit command from the queue is much faster - /// than the time taken to pop from the data queue, so the extra queue utilization shouldn't - /// matter. - fn exchange(&mut self, buffer: &mut [W]) -> Result<(), LpspiError> - where - W: Word, - { - if self.status().intersects(Status::BUSY) { - return Err(LpspiError::Busy); - } else if buffer.is_empty() { - return Err(LpspiError::NoData); - } - - self.clear_fifos(); - - let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; - transaction.bit_order = self.bit_order(); - transaction.continuous = true; - - let mut tx_idx = 0usize; - let mut rx_idx = 0usize; - - // Continue looping while there is either tx OR rx remaining - while tx_idx < buffer.len() || rx_idx < buffer.len() { - if tx_idx < buffer.len() { - let word = buffer[tx_idx]; - - // Turn off TCR CONT on last tx as a workaround so that the final - // falling edge comes through: - // https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 - if tx_idx + 1 == buffer.len() { - transaction.continuous = false; - } - - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - self.wait_for_transmit_fifo_space()?; - self.enqueue_data(word.into()); - transaction.continuing = true; - tx_idx += 1; - } - - if rx_idx < buffer.len() { - self.recv_ok()?; - if let Some(word) = self.read_data() { - buffer[rx_idx] = word.try_into().unwrap_or(W::MAX); - rx_idx += 1; - } - } - } - - Ok(()) - } - - /// Write data to the transmit queue without subsequently reading - /// the receive queue. - /// - /// Use this method when you know that the receiver queue is disabled - /// (RXMASK high in TCR). - /// - /// Similar to `exchange`, this is using continuous transfers for all supported primitives. - fn write_no_read(&mut self, buffer: &[W]) -> Result<(), LpspiError> - where - W: Word, - { - if self.status().intersects(Status::BUSY) { - return Err(LpspiError::Busy); - } else if buffer.is_empty() { - return Err(LpspiError::NoData); - } - - self.clear_fifos(); - - let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; - transaction.bit_order = self.bit_order(); - transaction.continuous = true; - transaction.receive_data_mask = true; - - for word in buffer { - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - self.wait_for_transmit_fifo_space()?; - self.enqueue_data((*word).into()); - transaction.continuing = true; - } - - transaction.continuing = false; - transaction.continuous = false; - - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - Ok(()) - } - - /// Let the peripheral act as a DMA source. - /// - /// After this call, the peripheral will signal to the DMA engine whenever - /// it has data available to read. - pub fn enable_dma_receive(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: 0); // No watermarks; affects DMA signaling - ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 1); - } - - /// Stop the peripheral from acting as a DMA source. - /// - /// See the DMA chapter in the reference manual to understand when this - /// should be called in the DMA transfer lifecycle. - pub fn disable_dma_receive(&mut self) { - while ral::read_reg!(ral::lpspi, self.lpspi, DER, RDDE == 1) { - ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 0); - } - } - - /// Let the peripheral act as a DMA destination. - /// - /// After this call, the peripheral will signal to the DMA engine whenever - /// it has free space in its transfer buffer. - pub fn enable_dma_transmit(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: 0); // No watermarks; affects DMA signaling - ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 1); - } - - /// Stop the peripheral from acting as a DMA destination. - /// - /// See the DMA chapter in the reference manual to understand when this - /// should be called in the DMA transfer lifecycle. - pub fn disable_dma_transmit(&mut self) { - while ral::read_reg!(ral::lpspi, self.lpspi, DER, TDDE == 1) { - ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 0); - } - } - - /// Produces a pointer to the receiver data register. - /// - /// You should use this pointer when coordinating a DMA transfer. - /// You're not expected to read from this pointer in software. - pub fn rdr(&self) -> *const ral::RORegister { - core::ptr::addr_of!(self.lpspi.RDR) - } - - /// Produces a pointer to the transfer data register. - /// - /// You should use this pointer when coordinating a DMA transfer. - /// You're not expected to read from this pointer in software. - pub fn tdr(&self) -> *const ral::WORegister { - core::ptr::addr_of!(self.lpspi.TDR) - } } -bitflags::bitflags! { - /// Status flags for the LPSPI interface. - pub struct Status : u32 { - /// Module busy flag. - /// - /// This flag is read only. - const BUSY = 1 << 24; - - // - // Start W1C bits. - // - - /// Data match flag. - /// - /// Indicates that received data has matched one or both of the match - /// fields. To clear this flag, write this bit to the status register - /// (W1C). - const DATA_MATCH = 1 << 13; - /// Receive error flag. - /// - /// Set when the receive FIFO has overflowed. Before clearing this bit, - /// empty the receive FIFO. Then, write this bit to clear the flag (W1C). - const RECEIVE_ERROR = 1 << 12; - /// Transmit error flag. - /// - /// Set when the transmit FIFO has underruns. Before clearing this bit, - /// end the transfer. Then, write this bit to clear the flag (W1C). - const TRANSMIT_ERROR = 1 << 11; - /// Transfer complete flag. - /// - /// Set when the LPSPI returns to an idle state, and the transmit FIFO - /// is empty. To clear this flag, write this bit (W1C). - const TRANSFER_COMPLETE = 1 << 10; - /// Frame complete flag. - /// - /// Set at the end of each frame transfer, when PCS negates. To clear this - /// flag, write this bit (W1C). - const FRAME_COMPLETE = 1 << 9; - /// Word complete flag. - /// - /// Set when the last bit of a received word is sampled. To clear this flag, write - /// this bit (W1C). - const WORD_COMPLETE = 1 << 8; +/// The internal driver implementation +struct LpspiDriver {} - // - // End W1C bits. - // - - /// Receive data flag. - /// - /// Set when the number of words in the receive FIFO is greater than the watermark. - /// This flag is read only. To clear the flag, exhaust the receive FIFO. - const RECEIVE_DATA = 1 << 1; - /// Transmit data flag. - /// - /// Set when the number of words in the transmit FIFO is less than or equal to the - /// watermark. This flag is read only. TO clear the flag, fill the transmit FIFO. - const TRANSMIT_DATA = 1 << 0; - } +struct LpspiDataInner { + driver: LpspiDriver, + dma: Option, } -impl Status { - const W1C: Self = Self::from_bits_truncate( - Self::DATA_MATCH.bits() - | Self::RECEIVE_ERROR.bits() - | Self::TRANSMIT_ERROR.bits() - | Self::TRANSFER_COMPLETE.bits() - | Self::FRAME_COMPLETE.bits() - | Self::WORD_COMPLETE.bits(), - ); +/// Static shared data allocated by the user +struct LpspiData { + inner: Arbiter>, } -/// The number of words in each FIFO. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct FifoStatus { - /// Number of words in the receive FIFO. - pub rxcount: u16, - /// Number of words in the transmit FIFO. - pub txcount: u16, +struct LpspiBus { + data: &'static LpspiData, + mode: Mode, } -impl FifoStatus { - /// Indicates if the FIFO is full for the given direction. - #[inline] - pub const fn is_full(self, direction: Direction) -> bool { - /// See PARAM register docs. - const MAX_FIFO_SIZE: u16 = 16; - let count = match direction { - Direction::Tx => self.txcount, - Direction::Rx => self.rxcount, - }; - count >= MAX_FIFO_SIZE - } +struct LpspiInterruptHandler { + data: &'static LpspiData, } -bitflags::bitflags! { - /// Interrupt flags. - /// - /// A high bit indicates that the condition generates an interrupt. - /// See the status bits for more information. - pub struct Interrupts : u32 { - /// Data match interrupt enable. - const DATA_MATCH = 1 << 13; - /// Receive error interrupt enable. - const RECEIVE_ERROR = 1 << 12; - /// Transmit error interrupt enable. - const TRANSMIT_ERROR = 1 << 11; - /// Transmit complete interrupt enable. - const TRANSMIT_COMPLETE = 1 << 10; - /// Frame complete interrupt enable. - const FRAME_COMPLETE = 1 << 9; - /// Word complete interrupt enable. - const WORD_COMPLETE = 1 << 8; - - /// Receive data interrupt enable. - const RECEIVE_DATA = 1 << 1; - /// Transmit data interrupt enable. - const TRANSMIT_DATA = 1 << 0; +impl LpspiBus { + pub fn device(cs: ()) -> LpspiDevice { + todo!() } } -/// An LPSPI peripheral which is temporarily disabled. -pub struct Disabled<'a, const N: u8> { - lpspi: &'a ral::lpspi::Instance, - men: bool, -} - -impl<'a, const N: u8> Disabled<'a, N> { - fn new(lpspi: &'a mut ral::lpspi::Instance) -> Self { - let men = ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1); - ral::modify_reg!(ral::lpspi, lpspi, CR, MEN: MEN_0); - Self { lpspi, men } - } - - /// Set the SPI mode for the peripheral - pub fn set_mode(&mut self, mode: Mode) { - // This could probably be changed when we're not disabled. - // However, there's rules about when you can read TCR. - // Specifically, reading TCR while it's being loaded from - // the transmit FIFO could result in an incorrect reading. - // Only permitting this when we're disabled might help - // us avoid something troublesome. - ral::modify_reg!( - ral::lpspi, - self.lpspi, - TCR, - CPOL: ((mode.polarity == Polarity::IdleHigh) as u32), - CPHA: ((mode.phase == Phase::CaptureOnSecondTransition) as u32) - ); - } - - /// Set the LPSPI clock speed (Hz). - /// - /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the - /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn set_clock_hz(&mut self, source_clock_hz: u32, clock_hz: u32) { - set_spi_clock(source_clock_hz, clock_hz, self.lpspi); - } - - /// Set the watermark level for a given direction. - /// - /// Returns the watermark level committed to the hardware. This may be different - /// than the supplied `watermark`, since it's limited by the hardware. - /// - /// When `direction == Direction::Rx`, the receive data flag is set whenever the - /// number of words in the receive FIFO is greater than `watermark`. - /// - /// When `direction == Direction::Tx`, the transmit data flag is set whenever the - /// the number of words in the transmit FIFO is less than, or equal, to `watermark`. - #[inline] - pub fn set_watermark(&mut self, direction: Direction, watermark: u8) -> u8 { - let max_watermark = match direction { - Direction::Rx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, RXFIFO), - Direction::Tx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, TXFIFO), - }; - - let watermark = watermark.min(max_watermark - 1); - - match direction { - Direction::Rx => { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark as u32) - } - Direction::Tx => { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: watermark as u32) - } - } - - watermark - } - - /// Set the sampling point of the LPSPI peripheral. - /// - /// When set to `SamplePoint::DelayedEdge`, the LPSPI will sample the input data - /// on a delayed LPSPI_SCK edge, which improves the setup time when sampling data. - #[inline] - pub fn set_sample_point(&mut self, sample_point: SamplePoint) { - match sample_point { - SamplePoint::Edge => ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_0), - SamplePoint::DelayedEdge => { - ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_1) - } - } - } -} - -impl Drop for Disabled<'_, N> { - fn drop(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: self.men as u32); - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u8]) -> Result<&'a [u8], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u16]) -> Result<&'a [u16], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u32]) -> Result<&'a [u32], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -// Not supporting WriteIter right now. Since we don't know how many bytes we're -// going to write, we can't specify the frame size. There might be ways around -// this by playing with CONTC and CONT bits, but we can evaluate that later. - -/// Describes SPI words that can participate in transactions. -trait Word: Copy + Into + TryFrom { - const MAX: Self; -} - -impl Word for u8 { - const MAX: u8 = u8::MAX; -} - -impl Word for u16 { - const MAX: u16 = u16::MAX; -} - -impl Word for u32 { - const MAX: u32 = u32::MAX; -} +struct LpspiDevice {} diff --git a/src/common/lpspi/builder.rs b/src/common/lpspi/builder.rs new file mode 100644 index 00000000..9593b5f3 --- /dev/null +++ b/src/common/lpspi/builder.rs @@ -0,0 +1,100 @@ +use eh1::spi::MODE_0; +use imxrt_dma::channel::Channel; + +use super::{ + Arbiter, LpspiBuilder, LpspiBus, LpspiData, LpspiDataInner, LpspiDriver, LpspiInterruptHandler, + Pins, +}; +use crate::ral; + +impl LpspiBuilder, N, (), false> { + pub fn new( + lpspi: ral::lpspi::Instance, + pins: Pins, + data: &'static mut Option>, + ) -> Self { + Self { + data, + pins, + lpspi, + dma: (), + } + } +} + +impl LpspiBuilder { + pub fn with_dma(self, dma: Channel) -> LpspiBuilder { + let Self { + pins, + lpspi, + data, + dma: (), + } = self; + LpspiBuilder { + dma, + data, + pins, + lpspi, + } + } +} + +impl LpspiBuilder { + pub fn with_interrupts(self) -> LpspiBuilder { + let Self { + pins, + lpspi, + data, + dma, + } = self; + LpspiBuilder { + dma, + data, + pins, + lpspi, + } + } +} + +fn build_bus( + pins: Pins, + data_storage: &'static mut Option>, + dma: Option, +) -> LpspiBus { + let driver = LpspiDriver {}; + + let data = LpspiData { + inner: Arbiter::new(LpspiDataInner { driver, dma }), + }; + + LpspiBus { + data: data_storage.insert(data), + mode: MODE_0, + } +} + +impl LpspiBuilder, N, Channel, false> { + pub fn build(self) -> LpspiBus { + build_bus(self.pins, self.data, Some(self.dma)) + } +} +impl LpspiBuilder, N, (), false> { + pub fn build(self) -> LpspiBus { + build_bus(self.pins, self.data, None) + } +} + +impl LpspiBuilder, N, Channel, true> { + pub fn build(self) -> (LpspiBus, LpspiInterruptHandler) { + let bus = build_bus(self.pins, self.data, Some(self.dma)); + let interrupt_handler = LpspiInterruptHandler { data: bus.data }; + (bus, interrupt_handler) + } +} +impl LpspiBuilder, N, (), true> { + pub fn build(self) -> (LpspiBus, LpspiInterruptHandler) { + let bus = build_bus(self.pins, self.data, None); + let interrupt_handler = LpspiInterruptHandler { data: bus.data }; + (bus, interrupt_handler) + } +} diff --git a/src/common/lpspi/driver.rs b/src/common/lpspi/driver.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/common/lpspi/lpspi_eh1.rs b/src/common/lpspi/lpspi_eh1.rs new file mode 100644 index 00000000..cad68b6c --- /dev/null +++ b/src/common/lpspi/lpspi_eh1.rs @@ -0,0 +1 @@ +use eh1::spi::SpiBus; diff --git a/src/common/lpspi_old.rs b/src/common/lpspi_old.rs new file mode 100644 index 00000000..01107296 --- /dev/null +++ b/src/common/lpspi_old.rs @@ -0,0 +1,1064 @@ +//! Low-power serial peripheral interface. +//! +//! [`Lpspi`] implements select embedded HAL SPI traits for coordinating SPI I/O. +//! When using the trait implementations, make sure that [`set_bit_order`](Lpspi::set_bit_order) +//! is correct for your device. These settings apply when the driver internally defines the transaction. +//! +//! This driver also exposes the peripheral's lower-level, hardware-dependent transaction interface. +//! Create a [`Transaction`], then [`enqueue_transaction`](Lpspi::enqueue_transaction) before +//! sending data with [`enqueue_data`](Lpspi::enqueue_data). When using the transaction interface, +//! you're responsible for serializing your data into `u32` SPI words. +//! +//! # Chip selects (CS) for SPI peripherals +//! +//! The iMXRT SPI peripherals have one or more peripheral-controlled chip selects (CS). Using +//! the peripheral-controlled CS means that you do not need a GPIO to coordinate SPI operations. +//! Blocking full-duplex transfers and writes will observe an asserted chip select while data +//! frames are exchanged / written. +//! +//! This driver generally assumes that you're using the peripheral-controlled chip select. If +//! you instead want to manage chip select in software, you should be able to multiplex your own +//! pins, then construct the driver [`without_pins`](Lpspi::without_pins). +//! +//! # Example +//! +//! Initialize an LPSPI with a 1MHz SCK. To understand how to configure the LPSPI +//! peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. +//! +//! ```no_run +//! use imxrt_hal as hal; +//! use imxrt_ral as ral; +//! # use eh02 as embedded_hal; +//! use embedded_hal::blocking::spi::Transfer; +//! use hal::lpspi::{Lpspi, Pins, SamplePoint}; +//! use ral::lpspi::LPSPI4; +//! +//! let mut pads = // Handle to all processor pads... +//! # unsafe { imxrt_iomuxc::imxrt1060::Pads::new() }; +//! +//! # || -> Option<()> { +//! let spi_pins = Pins { +//! sdo: pads.gpio_b0.p02, +//! sdi: pads.gpio_b0.p01, +//! sck: pads.gpio_b0.p03, +//! pcs0: pads.gpio_b0.p00, +//! }; +//! +//! let mut spi4 = unsafe { LPSPI4::instance() }; +//! let mut spi = Lpspi::new( +//! spi4, +//! spi_pins, +//! ); +//! +//! # const LPSPI_CLK_HZ: u32 = 1; +//! spi.disabled(|spi| { +//! spi.set_clock_hz(LPSPI_CLK_HZ, 1_000_000); +//! spi.set_sample_point(SamplePoint::Edge); +//! }); +//! +//! let mut buffer: [u8; 3] = [1, 2, 3]; +//! spi.transfer(&mut buffer).ok()?; +//! +//! let (spi4, pins) = spi.release(); +//! +//! // Re-construct without pins: +//! let mut spi = Lpspi::without_pins(spi4); +//! # Some(()) }(); +//! ``` +//! +//! # Limitations +//! +//! Due to [a hardware defect][1], this driver does not yet support the EH02 SPI transaction API. +//! An early iteration of this driver reproduced the issue discussed in that forum. This driver may +//! be able to work around the defect in software, but it hasn't been explored. +//! +//! [1]: https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 +//! +//! [`Transaction`] exposes the continuous / continuing flags, so you're free to model advanced +//! transactions. However, keep in mind that disabling the receiver during a continuous transaction +//! may not work as expected. + +#[cfg(feature = "eh1")] +mod lpspi_eh1; + +use crate::iomuxc::{consts, lpspi}; +use crate::ral; + +#[cfg(not(feature = "eh1"))] +pub use eh02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +#[cfg(feature = "eh1")] +pub use eh1::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +/// Data direction. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + /// Transmit direction (leaving the peripheral). + Tx, + /// Receive direction (entering the peripheral). + Rx, +} + +/// Bit order. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum BitOrder { + /// Data is transferred most significant bit first (default). + #[default] + Msb, + /// Data is transferred least significant bit first. + Lsb, +} + +/// Receive sample point behavior. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SamplePoint { + /// Input data is sampled on SCK edge. + Edge, + /// Input data is sampled on delayed SCK edge. + DelayedEdge, +} + +/// Possible errors when interfacing the LPSPI. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LpspiError { + /// The transaction frame size is incorrect. + /// + /// The frame size, in bits, must be between 8 bits and + /// 4095 bits. + FrameSize, + /// FIFO error in the given direction. + Fifo(Direction), + /// Bus is busy at the start of a transfer. + Busy, + /// Caller provided no data. + NoData, +} + +/// An LPSPI transaction definition. +/// +/// The transaction defines how many bits the driver sends or recieves. +/// It also describes +/// +/// - endianness +/// - bit order +/// - transmit and receive masking +/// - continuous and continuing transfers (default: both disabled) +/// +/// The LPSPI enqueues the transaction data into the transmit +/// FIFO. When it pops the values from the FIFO, the values take +/// effect immediately. This may affect, or abort, any ongoing +/// transactions. Consult the reference manual to understand when +/// you should enqueue transaction definitions, since it may only +/// be supported on word / frame boundaries. +/// +/// Construct `Transaction` with [`new`](Self::new), and supply +/// the number of **bits** to transmit per frame. +/// +/// ``` +/// use imxrt_hal as hal; +/// use hal::lpspi::Transaction; +/// +/// // Send one u32. +/// let mut transaction +/// = Transaction::new(8 * core::mem::size_of::() as u16); +/// ``` +/// +/// Once constructed, manipulate the public members to change the +/// configuration. +/// +/// # Continuous transactions +/// +/// The pseudo-code below shows how to set [`continuous`](Self::continuous) and +/// [`continuing`](Self::continuing) to model a continuous transaction. Keep in +/// mind the hardware limitations; see the [module-level docs](crate::lpspi#limitations) for +/// details. +/// +/// ``` +/// use imxrt_hal as hal; +/// use hal::lpspi::Transaction; +/// +/// // Skipping LPSPI initialization; see module-level example. +/// +/// // Start byte exchange as a continuous transaction. Each frame +/// // exchanges one byte (eight bits) with a device. +/// # || -> Result<(), hal::lpspi::LpspiError> { +/// let mut transaction = Transaction::new(8)?; +/// transaction.continuous = true; +/// // Enqueue transaction with LPSPI... +/// // Enqueue one byte with LPSPI... <-- PCS asserts here. +/// +/// # let buffer: [u8; 5] = [0; 5]; +/// for byte in buffer { +/// // Set 'continuing' to indicate that the next +/// // transaction continues the previous one... +/// transaction.continuing = true; +/// +/// // Enqueue transaction with LPSPI... +/// // Enqueue byte with LPSPI... +/// } +/// +/// transaction.continuous = false; +/// transaction.continuing = false; +/// // Enqueue transaction with LPSPI... <-- PCS de-asserts here. +/// # Ok(()) }().unwrap(); +/// ``` +pub struct Transaction { + /// Enable byte swap. + /// + /// When enabled (`true`), swap bytes with the `u32` word. This allows + /// you to change the endianness of the 32-bit word transfer. The + /// default is `false`. + pub byte_swap: bool, + /// Bit order. + /// + /// See [`BitOrder`] for details. The default is [`BitOrder::Msb`]. + pub bit_order: BitOrder, + /// Mask the received data. + /// + /// If `true`, the peripheral discards received data. Use this + /// when you only care about sending data. The default is `false`; + /// the peripheral puts received data in the receive FIFO. + pub receive_data_mask: bool, + /// Mask the transmit data. + /// + /// If `true`, the peripheral doesn't send any data. Use this when + /// you only care about receiving data. The default is `false`; + /// the peripheral expects to send data using the transmit FIFO. + pub transmit_data_mask: bool, + /// Indicates (`true`) the start of a continuous transfer. + /// + /// If set, the peripherals chip select will remain asserted after + /// exchanging the frame. This allows you to enqueue new commands + /// and data words within the same transaction. Those new commands + /// should have [`continuing`](Self::continuing) set to `true`. + /// + /// The default is `false`; chip select de-asserts after exchanging + /// the frame. To stop a continuous transfer, enqueue a new `Transaction` + /// in which this flag, and `continuing`, is false. + pub continuous: bool, + /// Indicates (`true`) that this command belongs to a previous transaction. + /// + /// Set this to indicate that this new `Transaction` belongs to a previous + /// `Transaction`, one that had [`continuous`](Self::continuous) set. + /// The default value is `false`. + pub continuing: bool, + + frame_size: u16, +} + +impl Transaction { + /// Defines a transaction for a `u32` buffer. + /// + /// After successfully defining a transaction of this buffer, + /// supply it to the LPSPI driver, then start sending the + /// data. + /// + /// Returns an error if any are true: + /// + /// - the buffer is empty. + /// - there's more than 128 elements in the buffer. + pub fn new_u32s(data: &[u32]) -> Result { + Transaction::new_words(data) + } + + fn new_words(data: &[W]) -> Result { + Transaction::new(8 * core::mem::size_of_val(data) as u16) + } + + /// Define a transaction by specifying the frame size, in bits. + /// + /// The frame size describes the number of bits that will be transferred and + /// received during the next transaction. Specifically, it describes the number + /// of bits for which the PCS pin signals a transaction. + /// + /// # Requirements + /// + /// - `frame_size` fits within 12 bits; the implementation enforces this maximum value. + /// - The minimum value for `frame_size` is 8; the implementation enforces this minimum + /// value. + pub fn new(frame_size: u16) -> Result { + const MIN_FRAME_SIZE: u16 = 8; + const MAX_FRAME_SIZE: u16 = 1 << 12; + if (MIN_FRAME_SIZE..MAX_FRAME_SIZE).contains(&frame_size) { + Ok(Self { + byte_swap: false, + bit_order: Default::default(), + receive_data_mask: false, + transmit_data_mask: false, + frame_size: frame_size - 1, + continuing: false, + continuous: false, + }) + } else { + Err(LpspiError::FrameSize) + } + } +} + +/// Sets the clock speed parameters. +/// +/// This should only happen when the LPSPI peripheral is disabled. +fn set_spi_clock(source_clock_hz: u32, spi_clock_hz: u32, reg: &ral::lpspi::RegisterBlock) { + let mut div = source_clock_hz / spi_clock_hz; + + if source_clock_hz / div > spi_clock_hz { + div += 1; + } + + // 0 <= div <= 255, and the true coefficient is really div + 2 + let div = div.saturating_sub(2).clamp(0, 255); + ral::write_reg!( + ral::lpspi, + reg, + CCR, + SCKDIV: div, + // Both of these delays are arbitrary choices, and they should + // probably be configurable by the end-user. + DBT: div / 2, + SCKPCS: 0x1F, + PCSSCK: 0x1F + ); +} + +/// An LPSPI driver. +/// +/// The driver exposes low-level methods for coordinating +/// DMA transfers. However, you may find it easier to use the +/// [`dma`](crate::dma) interface to coordinate DMA transfers. +/// +/// The driver implements `embedded-hal` SPI traits. You should prefer +/// these implementations for their ease of use. +/// +/// See the [module-level documentation](crate::lpspi) for an example +/// of how to construct this driver. +pub struct Lpspi { + lpspi: ral::lpspi::Instance, + pins: P, + bit_order: BitOrder, +} + +/// Pins for a LPSPI device. +/// +/// Consider using type aliases to simplify your usage: +/// +/// ```no_run +/// use imxrt_hal as hal; +/// use imxrt_iomuxc::imxrt1060::gpio_b0::*; +/// +/// // SPI pins used in my application +/// type LpspiPins = hal::lpspi::Pins< +/// GPIO_B0_02, +/// GPIO_B0_01, +/// GPIO_B0_03, +/// GPIO_B0_00, +/// >; +/// +/// // Helper type for your SPI peripheral +/// type Lpspi = hal::lpspi::Lpspi; +/// ``` +pub struct Pins { + /// Serial data out + /// + /// Data travels from the SPI host controller to the SPI device. + pub sdo: SDO, + /// Serial data in + /// + /// Data travels from the SPI device to the SPI host controller. + pub sdi: SDI, + /// Serial clock + pub sck: SCK, + /// Chip select 0 + /// + /// (PCSx) convention matches the hardware. + pub pcs0: PCS0, +} + +impl Lpspi, N> +where + SDO: lpspi::Pin, Signal = lpspi::Sdo>, + SDI: lpspi::Pin, Signal = lpspi::Sdi>, + SCK: lpspi::Pin, Signal = lpspi::Sck>, + PCS0: lpspi::Pin, Signal = lpspi::Pcs0>, +{ + /// Create a new LPSPI driver from the RAL LPSPI instance and a set of pins. + /// + /// When this call returns, the LPSPI pins are configured for their function. + /// The peripheral is enabled after reset. The LPSPI clock speed is unspecified. + /// The mode is [`MODE_0`]. The sample point is [`SamplePoint::DelayedEdge`]. + pub fn new(lpspi: ral::lpspi::Instance, mut pins: Pins) -> Self { + lpspi::prepare(&mut pins.sdo); + lpspi::prepare(&mut pins.sdi); + lpspi::prepare(&mut pins.sck); + lpspi::prepare(&mut pins.pcs0); + Self::init(lpspi, pins) + } +} + +impl Lpspi<(), N> { + /// Create a new LPSPI driver from the RAL LPSPI instance. + /// + /// This is similar to [`new()`](Self::new), but it does not configure + /// pins. You're responsible for configuring pins, and for making sure + /// the pin configuration doesn't change while this driver is in use. + pub fn without_pins(lpspi: ral::lpspi::Instance) -> Self { + Self::init(lpspi, ()) + } +} + +impl Lpspi { + /// The peripheral instance. + pub const N: u8 = N; + + fn init(lpspi: ral::lpspi::Instance, pins: P) -> Self { + let mut spi = Lpspi { + lpspi, + pins, + bit_order: BitOrder::default(), + }; + ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_1); + ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_0); + ral::write_reg!( + ral::lpspi, + spi.lpspi, + CFGR1, + MASTER: MASTER_1, + SAMPLE: SAMPLE_1 + ); + Disabled::new(&mut spi.lpspi).set_mode(MODE_0); + ral::write_reg!(ral::lpspi, spi.lpspi, FCR, RXWATER: 0xF, TXWATER: 0xF); + ral::write_reg!(ral::lpspi, spi.lpspi, CR, MEN: MEN_1); + spi + } + + /// Indicates if the driver is (`true`) or is not (`false`) enabled. + pub fn is_enabled(&self) -> bool { + ral::read_reg!(ral::lpspi, self.lpspi, CR, MEN == MEN_1) + } + + /// Enable (`true`) or disable (`false`) the peripheral. + pub fn set_enable(&mut self, enable: bool) { + ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: enable as u32) + } + + /// Reset the driver. + /// + /// Note that this may not not reset all peripheral state, like the + /// enabled state. + pub fn reset(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_1); + while ral::read_reg!(ral::lpspi, self.lpspi, CR, RST == RST_1) { + ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_0); + } + } + + /// Release the SPI driver components. + /// + /// This does not change any component state; it releases the components as-is. + /// If you need to obtain the registers in a known, good state, consider calling + /// methods like [`reset()`](Self::reset) before releasing the registers. + pub fn release(self) -> (ral::lpspi::Instance, P) { + (self.lpspi, self.pins) + } + + /// Returns the bit order configuration. + /// + /// See notes in [`set_bit_order`](Lpspi::set_bit_order) to + /// understand when this configuration takes effect. + pub fn bit_order(&self) -> BitOrder { + self.bit_order + } + + /// Set the bit order configuration. + /// + /// This applies to all higher-level write and transfer operations. + /// If you're using the [`Transaction`] API with manual word reads + /// and writes, set the configuration as part of the transaction. + pub fn set_bit_order(&mut self, bit_order: BitOrder) { + self.bit_order = bit_order; + } + + /// Temporarily disable the LPSPI peripheral. + /// + /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify + /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit + /// and receive FIFOs. + pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { + self.clear_fifos(); + let mut disabled = Disabled::new(&mut self.lpspi); + func(&mut disabled) + } + + /// Read the status register. + pub fn status(&self) -> Status { + Status::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, SR)) + } + + /// Clear the status flags. + /// + /// To clear status flags, set them high, then call `clear_status()`. + /// + /// The implementation will ensure that only the W1C bits are written, so it's + /// OK to supply `Status::all()` to clear all bits. + pub fn clear_status(&self, flags: Status) { + let flags = flags & Status::W1C; + ral::write_reg!(ral::lpspi, self.lpspi, SR, flags.bits()); + } + + /// Read the interrupt enable bits. + pub fn interrupts(&self) -> Interrupts { + Interrupts::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, IER)) + } + + /// Set the interrupt enable bits. + /// + /// This writes the bits described by `interrupts` as is to the register. + /// To modify the existing interrupts flags, you should first call [`interrupts`](Lpspi::interrupts) + /// to get the current state, then modify that state. + pub fn set_interrupts(&self, interrupts: Interrupts) { + ral::write_reg!(ral::lpspi, self.lpspi, IER, interrupts.bits()); + } + + /// Clear any existing data in the SPI receive or transfer FIFOs. + #[inline] + pub fn clear_fifo(&mut self, direction: Direction) { + match direction { + Direction::Tx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1), + Direction::Rx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RRF: RRF_1), + } + } + + /// Clear both FIFOs. + pub fn clear_fifos(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1, RRF: RRF_1); + } + + /// Returns the watermark level for the given direction. + #[inline] + pub fn watermark(&self, direction: Direction) -> u8 { + (match direction { + Direction::Rx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, RXWATER), + Direction::Tx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, TXWATER), + }) as u8 + } + + /// Returns the FIFO status. + #[inline] + pub fn fifo_status(&self) -> FifoStatus { + let (rxcount, txcount) = ral::read_reg!(ral::lpspi, self.lpspi, FSR, RXCOUNT, TXCOUNT); + FifoStatus { + rxcount: rxcount as u16, + txcount: txcount as u16, + } + } + + /// Simply read whatever is in the receiver data register. + fn read_data_unchecked(&self) -> u32 { + ral::read_reg!(ral::lpspi, self.lpspi, RDR) + } + + /// Read the data register. + /// + /// Returns `None` if the receive FIFO is empty. Otherwise, returns the complete + /// read of the register. You're reponsible for interpreting the raw value as + /// a data word, depending on the frame size. + pub fn read_data(&mut self) -> Option { + if ral::read_reg!(ral::lpspi, self.lpspi, RSR, RXEMPTY == RXEMPTY_0) { + Some(self.read_data_unchecked()) + } else { + None + } + } + + /// Check for any receiver errors. + fn recv_ok(&self) -> Result<(), LpspiError> { + let status = self.status(); + if status.intersects(Status::RECEIVE_ERROR) { + Err(LpspiError::Fifo(Direction::Rx)) + } else { + Ok(()) + } + } + + /// Place `word` into the transmit FIFO. + /// + /// This will result in the value being sent from the LPSPI. + /// You're responsible for making sure that the transmit FIFO can + /// fit this word. + pub fn enqueue_data(&self, word: u32) { + ral::write_reg!(ral::lpspi, self.lpspi, TDR, word); + } + + pub(crate) fn wait_for_transmit_fifo_space(&mut self) -> Result<(), LpspiError> { + loop { + let status = self.status(); + if status.intersects(Status::TRANSMIT_ERROR) { + return Err(LpspiError::Fifo(Direction::Tx)); + } + let fifo_status = self.fifo_status(); + if !fifo_status.is_full(Direction::Tx) { + return Ok(()); + } + } + } + + /// Place a transaction definition into the transmit FIFO. + /// + /// Once this definition is popped from the transmit FIFO, this may + /// affect, or abort, any ongoing transactions. + /// + /// You're responsible for making sure there's space in the transmit + /// FIFO for this transaction command. + pub fn enqueue_transaction(&mut self, transaction: &Transaction) { + ral::modify_reg!(ral::lpspi, self.lpspi, TCR, + LSBF: transaction.bit_order as u32, + BYSW: transaction.byte_swap as u32, + RXMSK: transaction.receive_data_mask as u32, + TXMSK: transaction.transmit_data_mask as u32, + FRAMESZ: transaction.frame_size as u32, + CONT: transaction.continuous as u32, + CONTC: transaction.continuing as u32 + ); + } + + /// Exchanges data with the SPI device. + /// + /// This routine uses continuous transfers to perform the transaction, no matter the + /// primitive type. There's an optimization for &[u32] that we're missing; in this case, + /// we don't necessarily need to use continuous transfers. The frame size could be set to + /// 8 * buffer.len() * sizeof(u32), and we copy user words into the transmit queue as-is. + /// But handling the packing of u8s and u16s into the u32 transmit queue in software is + /// extra work, work that's effectively achieved when we use continuous transfers. + /// We're guessing that the time to pop a transmit command from the queue is much faster + /// than the time taken to pop from the data queue, so the extra queue utilization shouldn't + /// matter. + fn exchange(&mut self, buffer: &mut [W]) -> Result<(), LpspiError> + where + W: Word, + { + if self.status().intersects(Status::BUSY) { + return Err(LpspiError::Busy); + } else if buffer.is_empty() { + return Err(LpspiError::NoData); + } + + self.clear_fifos(); + + let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; + transaction.bit_order = self.bit_order(); + transaction.continuous = true; + + let mut tx_idx = 0usize; + let mut rx_idx = 0usize; + + // Continue looping while there is either tx OR rx remaining + while tx_idx < buffer.len() || rx_idx < buffer.len() { + if tx_idx < buffer.len() { + let word = buffer[tx_idx]; + + // Turn off TCR CONT on last tx as a workaround so that the final + // falling edge comes through: + // https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 + if tx_idx + 1 == buffer.len() { + transaction.continuous = false; + } + + self.wait_for_transmit_fifo_space()?; + self.enqueue_transaction(&transaction); + + self.wait_for_transmit_fifo_space()?; + self.enqueue_data(word.into()); + transaction.continuing = true; + tx_idx += 1; + } + + if rx_idx < buffer.len() { + self.recv_ok()?; + if let Some(word) = self.read_data() { + buffer[rx_idx] = word.try_into().unwrap_or(W::MAX); + rx_idx += 1; + } + } + } + + Ok(()) + } + + /// Write data to the transmit queue without subsequently reading + /// the receive queue. + /// + /// Use this method when you know that the receiver queue is disabled + /// (RXMASK high in TCR). + /// + /// Similar to `exchange`, this is using continuous transfers for all supported primitives. + fn write_no_read(&mut self, buffer: &[W]) -> Result<(), LpspiError> + where + W: Word, + { + if self.status().intersects(Status::BUSY) { + return Err(LpspiError::Busy); + } else if buffer.is_empty() { + return Err(LpspiError::NoData); + } + + self.clear_fifos(); + + let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; + transaction.bit_order = self.bit_order(); + transaction.continuous = true; + transaction.receive_data_mask = true; + + for word in buffer { + self.wait_for_transmit_fifo_space()?; + self.enqueue_transaction(&transaction); + + self.wait_for_transmit_fifo_space()?; + self.enqueue_data((*word).into()); + transaction.continuing = true; + } + + transaction.continuing = false; + transaction.continuous = false; + + self.wait_for_transmit_fifo_space()?; + self.enqueue_transaction(&transaction); + + Ok(()) + } + + /// Let the peripheral act as a DMA source. + /// + /// After this call, the peripheral will signal to the DMA engine whenever + /// it has data available to read. + pub fn enable_dma_receive(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: 0); // No watermarks; affects DMA signaling + ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 1); + } + + /// Stop the peripheral from acting as a DMA source. + /// + /// See the DMA chapter in the reference manual to understand when this + /// should be called in the DMA transfer lifecycle. + pub fn disable_dma_receive(&mut self) { + while ral::read_reg!(ral::lpspi, self.lpspi, DER, RDDE == 1) { + ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 0); + } + } + + /// Let the peripheral act as a DMA destination. + /// + /// After this call, the peripheral will signal to the DMA engine whenever + /// it has free space in its transfer buffer. + pub fn enable_dma_transmit(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: 0); // No watermarks; affects DMA signaling + ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 1); + } + + /// Stop the peripheral from acting as a DMA destination. + /// + /// See the DMA chapter in the reference manual to understand when this + /// should be called in the DMA transfer lifecycle. + pub fn disable_dma_transmit(&mut self) { + while ral::read_reg!(ral::lpspi, self.lpspi, DER, TDDE == 1) { + ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 0); + } + } + + /// Produces a pointer to the receiver data register. + /// + /// You should use this pointer when coordinating a DMA transfer. + /// You're not expected to read from this pointer in software. + pub fn rdr(&self) -> *const ral::RORegister { + core::ptr::addr_of!(self.lpspi.RDR) + } + + /// Produces a pointer to the transfer data register. + /// + /// You should use this pointer when coordinating a DMA transfer. + /// You're not expected to read from this pointer in software. + pub fn tdr(&self) -> *const ral::WORegister { + core::ptr::addr_of!(self.lpspi.TDR) + } +} + +bitflags::bitflags! { + /// Status flags for the LPSPI interface. + pub struct Status : u32 { + /// Module busy flag. + /// + /// This flag is read only. + const BUSY = 1 << 24; + + // + // Start W1C bits. + // + + /// Data match flag. + /// + /// Indicates that received data has matched one or both of the match + /// fields. To clear this flag, write this bit to the status register + /// (W1C). + const DATA_MATCH = 1 << 13; + /// Receive error flag. + /// + /// Set when the receive FIFO has overflowed. Before clearing this bit, + /// empty the receive FIFO. Then, write this bit to clear the flag (W1C). + const RECEIVE_ERROR = 1 << 12; + /// Transmit error flag. + /// + /// Set when the transmit FIFO has underruns. Before clearing this bit, + /// end the transfer. Then, write this bit to clear the flag (W1C). + const TRANSMIT_ERROR = 1 << 11; + /// Transfer complete flag. + /// + /// Set when the LPSPI returns to an idle state, and the transmit FIFO + /// is empty. To clear this flag, write this bit (W1C). + const TRANSFER_COMPLETE = 1 << 10; + /// Frame complete flag. + /// + /// Set at the end of each frame transfer, when PCS negates. To clear this + /// flag, write this bit (W1C). + const FRAME_COMPLETE = 1 << 9; + /// Word complete flag. + /// + /// Set when the last bit of a received word is sampled. To clear this flag, write + /// this bit (W1C). + const WORD_COMPLETE = 1 << 8; + + // + // End W1C bits. + // + + /// Receive data flag. + /// + /// Set when the number of words in the receive FIFO is greater than the watermark. + /// This flag is read only. To clear the flag, exhaust the receive FIFO. + const RECEIVE_DATA = 1 << 1; + /// Transmit data flag. + /// + /// Set when the number of words in the transmit FIFO is less than or equal to the + /// watermark. This flag is read only. TO clear the flag, fill the transmit FIFO. + const TRANSMIT_DATA = 1 << 0; + } +} + +impl Status { + const W1C: Self = Self::from_bits_truncate( + Self::DATA_MATCH.bits() + | Self::RECEIVE_ERROR.bits() + | Self::TRANSMIT_ERROR.bits() + | Self::TRANSFER_COMPLETE.bits() + | Self::FRAME_COMPLETE.bits() + | Self::WORD_COMPLETE.bits(), + ); +} + +/// The number of words in each FIFO. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FifoStatus { + /// Number of words in the receive FIFO. + pub rxcount: u16, + /// Number of words in the transmit FIFO. + pub txcount: u16, +} + +impl FifoStatus { + /// Indicates if the FIFO is full for the given direction. + #[inline] + pub const fn is_full(self, direction: Direction) -> bool { + /// See PARAM register docs. + const MAX_FIFO_SIZE: u16 = 16; + let count = match direction { + Direction::Tx => self.txcount, + Direction::Rx => self.rxcount, + }; + count >= MAX_FIFO_SIZE + } +} + +bitflags::bitflags! { + /// Interrupt flags. + /// + /// A high bit indicates that the condition generates an interrupt. + /// See the status bits for more information. + pub struct Interrupts : u32 { + /// Data match interrupt enable. + const DATA_MATCH = 1 << 13; + /// Receive error interrupt enable. + const RECEIVE_ERROR = 1 << 12; + /// Transmit error interrupt enable. + const TRANSMIT_ERROR = 1 << 11; + /// Transmit complete interrupt enable. + const TRANSMIT_COMPLETE = 1 << 10; + /// Frame complete interrupt enable. + const FRAME_COMPLETE = 1 << 9; + /// Word complete interrupt enable. + const WORD_COMPLETE = 1 << 8; + + /// Receive data interrupt enable. + const RECEIVE_DATA = 1 << 1; + /// Transmit data interrupt enable. + const TRANSMIT_DATA = 1 << 0; + } +} + +/// An LPSPI peripheral which is temporarily disabled. +pub struct Disabled<'a, const N: u8> { + lpspi: &'a ral::lpspi::Instance, + men: bool, +} + +impl<'a, const N: u8> Disabled<'a, N> { + fn new(lpspi: &'a mut ral::lpspi::Instance) -> Self { + let men = ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1); + ral::modify_reg!(ral::lpspi, lpspi, CR, MEN: MEN_0); + Self { lpspi, men } + } + + /// Set the SPI mode for the peripheral + pub fn set_mode(&mut self, mode: Mode) { + // This could probably be changed when we're not disabled. + // However, there's rules about when you can read TCR. + // Specifically, reading TCR while it's being loaded from + // the transmit FIFO could result in an incorrect reading. + // Only permitting this when we're disabled might help + // us avoid something troublesome. + ral::modify_reg!( + ral::lpspi, + self.lpspi, + TCR, + CPOL: ((mode.polarity == Polarity::IdleHigh) as u32), + CPHA: ((mode.phase == Phase::CaptureOnSecondTransition) as u32) + ); + } + + /// Set the LPSPI clock speed (Hz). + /// + /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the + /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. + pub fn set_clock_hz(&mut self, source_clock_hz: u32, clock_hz: u32) { + set_spi_clock(source_clock_hz, clock_hz, self.lpspi); + } + + /// Set the watermark level for a given direction. + /// + /// Returns the watermark level committed to the hardware. This may be different + /// than the supplied `watermark`, since it's limited by the hardware. + /// + /// When `direction == Direction::Rx`, the receive data flag is set whenever the + /// number of words in the receive FIFO is greater than `watermark`. + /// + /// When `direction == Direction::Tx`, the transmit data flag is set whenever the + /// the number of words in the transmit FIFO is less than, or equal, to `watermark`. + #[inline] + pub fn set_watermark(&mut self, direction: Direction, watermark: u8) -> u8 { + let max_watermark = match direction { + Direction::Rx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, RXFIFO), + Direction::Tx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, TXFIFO), + }; + + let watermark = watermark.min(max_watermark - 1); + + match direction { + Direction::Rx => { + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark as u32) + } + Direction::Tx => { + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: watermark as u32) + } + } + + watermark + } + + /// Set the sampling point of the LPSPI peripheral. + /// + /// When set to `SamplePoint::DelayedEdge`, the LPSPI will sample the input data + /// on a delayed LPSPI_SCK edge, which improves the setup time when sampling data. + #[inline] + pub fn set_sample_point(&mut self, sample_point: SamplePoint) { + match sample_point { + SamplePoint::Edge => ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_0), + SamplePoint::DelayedEdge => { + ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_1) + } + } + } +} + +impl Drop for Disabled<'_, N> { + fn drop(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: self.men as u32); + } +} + +impl eh02::blocking::spi::Transfer for Lpspi { + type Error = LpspiError; + + fn transfer<'a>(&mut self, words: &'a mut [u8]) -> Result<&'a [u8], Self::Error> { + self.exchange(words)?; + Ok(words) + } +} + +impl eh02::blocking::spi::Transfer for Lpspi { + type Error = LpspiError; + + fn transfer<'a>(&mut self, words: &'a mut [u16]) -> Result<&'a [u16], Self::Error> { + self.exchange(words)?; + Ok(words) + } +} + +impl eh02::blocking::spi::Transfer for Lpspi { + type Error = LpspiError; + + fn transfer<'a>(&mut self, words: &'a mut [u32]) -> Result<&'a [u32], Self::Error> { + self.exchange(words)?; + Ok(words) + } +} + +impl eh02::blocking::spi::Write for Lpspi { + type Error = LpspiError; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write_no_read(words) + } +} + +impl eh02::blocking::spi::Write for Lpspi { + type Error = LpspiError; + + fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { + self.write_no_read(words) + } +} + +impl eh02::blocking::spi::Write for Lpspi { + type Error = LpspiError; + + fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + self.write_no_read(words) + } +} + +// Not supporting WriteIter right now. Since we don't know how many bytes we're +// going to write, we can't specify the frame size. There might be ways around +// this by playing with CONTC and CONT bits, but we can evaluate that later. + +/// Describes SPI words that can participate in transactions. +trait Word: Copy + Into + TryFrom { + const MAX: Self; +} + +impl Word for u8 { + const MAX: u8 = u8::MAX; +} + +impl Word for u16 { + const MAX: u16 = u16::MAX; +} + +impl Word for u32 { + const MAX: u32 = u32::MAX; +} From 42422effd511e030bc179ae6f8c87cba33ed09d5 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 18 Nov 2023 20:50:40 +0100 Subject: [PATCH 02/73] More ideas --- board/src/teensy4.rs | 26 +++++++--- src/common/lpspi.rs | 60 ++++++++++++++-------- src/common/lpspi/builder.rs | 100 ------------------------------------ src/common/lpspi/bus.rs | 71 +++++++++++++++++++++++++ src/common/lpspi/device.rs | 6 +++ 5 files changed, 136 insertions(+), 127 deletions(-) delete mode 100644 src/common/lpspi/builder.rs create mode 100644 src/common/lpspi/bus.rs create mode 100644 src/common/lpspi/device.rs diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index c1a27845..a2f1eb20 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -53,11 +53,19 @@ pub type SpiPins = hal::lpspi::Pins< #[cfg(not(feature = "spi"))] /// Activate the `"spi"` feature to configure the SPI peripheral. -pub type Spi = (); +mod lpspi_types { + pub type SpiBuilder = (); + pub type SpiCsPin = (); +} #[cfg(feature = "spi")] /// SPI peripheral. -pub type Spi = hal::lpspi::Lpspi; +mod lpspi_types { + pub type SpiBus = super::hal::lpspi::LpspiBus<4>; + pub type SpiCsPin = super::iomuxc::gpio_b0::GPIO_B0_00; +} + +pub use lpspi_types::*; pub type I2cPins = hal::lpi2c::Pins< iomuxc::gpio_ad_b1::GPIO_AD_B1_07, // SCL, P16 @@ -109,7 +117,7 @@ pub struct Specifics { pub button: Button, pub ports: GpioPorts, pub console: Console, - pub spi: Spi, + pub spi: (SpiBus, SpiCsPin), pub i2c: I2c, pub pwm: Pwm, pub trng: hal::trng::Trng, @@ -152,13 +160,17 @@ impl Specifics { sdo: iomuxc.gpio_b0.p02, sdi: iomuxc.gpio_b0.p01, sck: iomuxc.gpio_b0.p03, - pcs0: iomuxc.gpio_b0.p00, }; - let mut spi = Spi::new(lpspi4, pins); + let cs_pin = iomuxc.gpio_b0.p00; + + static mut SPI_DATA: Option> = None; + let mut spi = SpiBus::new(lpspi4, pins, unsafe { &mut SPI_DATA }); + spi.disabled(|spi| { spi.set_clock_hz(super::LPSPI_CLK_FREQUENCY, super::SPI_BAUD_RATE_FREQUENCY); }); - spi + + (spi, cs_pin) }; #[cfg(not(feature = "spi"))] #[allow(clippy::let_unit_value)] @@ -217,7 +229,7 @@ pub(crate) const CLOCK_GATES: &[clock_gate::Locator] = &[ clock_gate::gpio::<2>(), clock_gate::lpuart::<{ Console::N }>(), #[cfg(feature = "spi")] - clock_gate::lpspi::<{ Spi::N }>(), + clock_gate::lpspi::<{ SpiBus::N }>(), clock_gate::lpi2c::<{ I2c::N }>(), clock_gate::flexpwm::<{ pwm::Peripheral::N }>(), ]; diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 63886a59..b06c799d 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,17 +1,39 @@ +use eh1::delay::DelayUs; pub use eh1::spi::Mode; + use imxrt_dma::channel::Channel; use rtic_sync::arbiter::Arbiter; use crate::ral; -mod builder; -mod driver; +mod bus; +mod device; -struct LpspiBuilder { - data: &'static mut Option>, - dma: D, - pins: P, - lpspi: ral::lpspi::Instance, +pub enum LpspiDma { + /// Everything is CPU driven + Disable, + /// Read and Write are DMA based, + /// but Transfers are only partially + /// DMA based + Partial(Channel), + /// Everything is DMA based + Full(Channel, Channel), +} + +/// Possible errors when interfacing the LPSPI. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LpspiError { + // /// The transaction frame size is incorrect. + // /// + // /// The frame size, in bits, must be between 8 bits and + // /// 4095 bits. + // FrameSize, + // /// FIFO error in the given direction. + // Fifo(Direction), + /// Bus is busy at the start of a transfer. + Busy, + // /// Caller provided no data. + // NoData, } pub struct Pins { @@ -32,27 +54,25 @@ struct LpspiDriver {} struct LpspiDataInner { driver: LpspiDriver, - dma: Option, + dma: LpspiDma, + timer: Option<&'static mut dyn DelayUs>, + lpspi: ral::lpspi::Instance, } /// Static shared data allocated by the user -struct LpspiData { - inner: Arbiter>, +pub struct LpspiData { + bus: Arbiter>, + // TODO: interrupt register struct } -struct LpspiBus { +pub struct LpspiInterruptHandler {} + +pub struct LpspiBus { data: &'static LpspiData, mode: Mode, } -struct LpspiInterruptHandler { +pub struct LpspiDevice { data: &'static LpspiData, + cs: CS, } - -impl LpspiBus { - pub fn device(cs: ()) -> LpspiDevice { - todo!() - } -} - -struct LpspiDevice {} diff --git a/src/common/lpspi/builder.rs b/src/common/lpspi/builder.rs deleted file mode 100644 index 9593b5f3..00000000 --- a/src/common/lpspi/builder.rs +++ /dev/null @@ -1,100 +0,0 @@ -use eh1::spi::MODE_0; -use imxrt_dma::channel::Channel; - -use super::{ - Arbiter, LpspiBuilder, LpspiBus, LpspiData, LpspiDataInner, LpspiDriver, LpspiInterruptHandler, - Pins, -}; -use crate::ral; - -impl LpspiBuilder, N, (), false> { - pub fn new( - lpspi: ral::lpspi::Instance, - pins: Pins, - data: &'static mut Option>, - ) -> Self { - Self { - data, - pins, - lpspi, - dma: (), - } - } -} - -impl LpspiBuilder { - pub fn with_dma(self, dma: Channel) -> LpspiBuilder { - let Self { - pins, - lpspi, - data, - dma: (), - } = self; - LpspiBuilder { - dma, - data, - pins, - lpspi, - } - } -} - -impl LpspiBuilder { - pub fn with_interrupts(self) -> LpspiBuilder { - let Self { - pins, - lpspi, - data, - dma, - } = self; - LpspiBuilder { - dma, - data, - pins, - lpspi, - } - } -} - -fn build_bus( - pins: Pins, - data_storage: &'static mut Option>, - dma: Option, -) -> LpspiBus { - let driver = LpspiDriver {}; - - let data = LpspiData { - inner: Arbiter::new(LpspiDataInner { driver, dma }), - }; - - LpspiBus { - data: data_storage.insert(data), - mode: MODE_0, - } -} - -impl LpspiBuilder, N, Channel, false> { - pub fn build(self) -> LpspiBus { - build_bus(self.pins, self.data, Some(self.dma)) - } -} -impl LpspiBuilder, N, (), false> { - pub fn build(self) -> LpspiBus { - build_bus(self.pins, self.data, None) - } -} - -impl LpspiBuilder, N, Channel, true> { - pub fn build(self) -> (LpspiBus, LpspiInterruptHandler) { - let bus = build_bus(self.pins, self.data, Some(self.dma)); - let interrupt_handler = LpspiInterruptHandler { data: bus.data }; - (bus, interrupt_handler) - } -} -impl LpspiBuilder, N, (), true> { - pub fn build(self) -> (LpspiBus, LpspiInterruptHandler) { - let bus = build_bus(self.pins, self.data, None); - let interrupt_handler = LpspiInterruptHandler { data: bus.data }; - (bus, interrupt_handler) - } -} diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs new file mode 100644 index 00000000..ae8498c0 --- /dev/null +++ b/src/common/lpspi/bus.rs @@ -0,0 +1,71 @@ +use eh1::{delay::DelayUs, spi::MODE_0}; +use imxrt_dma::channel::Channel; + +use super::{ + Arbiter, LpspiBus, LpspiData, LpspiDataInner, LpspiDevice, LpspiDma, LpspiDriver, LpspiError, + LpspiInterruptHandler, Pins, +}; +use crate::{ + iomuxc::{consts, lpspi}, + ral, +}; + +impl LpspiBus { + /// The peripheral instance. + pub const N: u8 = N; + + /// TODO + pub fn new( + lpspi: ral::lpspi::Instance, + mut pins: Pins, + data_storage: &'static mut Option>, + ) -> Self + where + SDO: lpspi::Pin, Signal = lpspi::Sdo>, + SDI: lpspi::Pin, Signal = lpspi::Sdi>, + SCK: lpspi::Pin, Signal = lpspi::Sck>, + { + let driver = LpspiDriver {}; + + lpspi::prepare(&mut pins.sdo); + lpspi::prepare(&mut pins.sdi); + lpspi::prepare(&mut pins.sck); + + let data = LpspiData { + bus: Arbiter::new(LpspiDataInner { + driver, + lpspi, + dma: LpspiDma::Disable, + timer: None, + }), + }; + + Self { + data: data_storage.insert(data), + mode: MODE_0, + } + } + + /// TODO + pub fn set_dma(&mut self, dma: LpspiDma) -> Result { + let mut bus = self.data.bus.try_access().ok_or(LpspiError::Busy)?; + Ok(core::mem::replace(&mut bus.dma, dma)) + } + + /// TODO + pub fn set_delay_source(&mut self, delay: &'static mut dyn DelayUs) -> Result<(), LpspiError> { + let mut bus = self.data.bus.try_access().ok_or(LpspiError::Busy)?; + bus.timer = Some(delay); + Ok(()) + } + + /// TODO + pub fn enable_interrupts(&mut self) -> Result { + todo!() + } + + /// TODO + pub fn device(cs: CS) -> LpspiDevice { + todo!() + } +} diff --git a/src/common/lpspi/device.rs b/src/common/lpspi/device.rs new file mode 100644 index 00000000..eed8400f --- /dev/null +++ b/src/common/lpspi/device.rs @@ -0,0 +1,6 @@ +use super::LpspiDevice; + +impl LpspiDevice { + /// The peripheral instance. + pub const N: u8 = N; +} From 854f72734729f9c2954949b02f8fe484f3e9091c Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 18 Nov 2023 22:10:47 +0100 Subject: [PATCH 03/73] More work --- Cargo.toml | 1 + board/src/teensy4.rs | 23 +++++---- examples/rtic_spi.rs | 91 +++++++++++++++++++++-------------- rust-toolchain.toml | 4 ++ src/common/lpspi.rs | 23 +++++++-- src/common/lpspi/bus.rs | 16 +++++- src/common/lpspi/lpspi_eh1.rs | 1 - 7 files changed, 106 insertions(+), 53 deletions(-) create mode 100644 rust-toolchain.toml delete mode 100644 src/common/lpspi/lpspi_eh1.rs diff --git a/Cargo.toml b/Cargo.toml index d3aea436..63defc27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,6 +144,7 @@ cortex-m = "0.7" imxrt-rt = { workspace = true } menu = "0.4.0" rtic = { version = "2.0.1", features = ["thumbv7-backend"] } +rtic-monotonics = { version = "1.2.0", features = ["cortex-m-systick"] } log = "0.4" defmt = "0.3" pin-utils = "0.1" diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index a2f1eb20..317dc615 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -54,15 +54,18 @@ pub type SpiPins = hal::lpspi::Pins< #[cfg(not(feature = "spi"))] /// Activate the `"spi"` feature to configure the SPI peripheral. mod lpspi_types { - pub type SpiBuilder = (); + pub type SpiBus = (); pub type SpiCsPin = (); + pub type SpiDevice = (); } #[cfg(feature = "spi")] /// SPI peripheral. mod lpspi_types { - pub type SpiBus = super::hal::lpspi::LpspiBus<4>; - pub type SpiCsPin = super::iomuxc::gpio_b0::GPIO_B0_00; + use super::*; + pub type SpiBus = hal::lpspi::LpspiBus<4>; + pub type SpiCsPin = hal::gpio::Output; + pub type SpiDevice = hal::lpspi::LpspiDevice<4, iomuxc::gpio_b0::GPIO_B0_00>; } pub use lpspi_types::*; @@ -161,14 +164,16 @@ impl Specifics { sdi: iomuxc.gpio_b0.p01, sck: iomuxc.gpio_b0.p03, }; - let cs_pin = iomuxc.gpio_b0.p00; + let cs_pin = gpio2.output(iomuxc.gpio_b0.p00); static mut SPI_DATA: Option> = None; - let mut spi = SpiBus::new(lpspi4, pins, unsafe { &mut SPI_DATA }); - - spi.disabled(|spi| { - spi.set_clock_hz(super::LPSPI_CLK_FREQUENCY, super::SPI_BAUD_RATE_FREQUENCY); - }); + let mut spi = SpiBus::new( + lpspi4, + pins, + unsafe { &mut SPI_DATA }, + super::LPSPI_CLK_FREQUENCY, + ); + spi.set_baud_rate(super::SPI_BAUD_RATE_FREQUENCY); (spi, cs_pin) }; diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index e90a09d2..2b9d42ea 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -4,64 +4,81 @@ //! schedule transfers, and to receive data. You can observe the //! I/O with a scope / logic analyzer. The SPI CLK runs at 1MHz, //! and the frame size is 64 bits. +//! +//! TODO: update description #![no_std] #![no_main] +// Required for RTIC 2 (for now) +#![feature(type_alias_impl_trait)] -#[rtic::app(device = board, peripherals = false)] +#[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] mod app { - use hal::lpspi::{Direction, Interrupts, Status, Transaction}; use imxrt_hal as hal; + use hal::lpspi::LpspiInterruptHandler; + + use rtic_monotonics::systick::*; + #[local] struct Local { - spi: board::Spi, + spi_device: board::SpiDevice, + spi_interrupt_handler: LpspiInterruptHandler, } #[shared] struct Shared {} - #[init] - fn init(_: init::Context) -> (Shared, Local) { - let (_, board::Specifics { mut spi, .. }) = board::new(); - spi.disabled(|spi| { - // Trigger when the TX FIFO is empty. - spi.set_watermark(Direction::Tx, 0); - // Wait to receive at least 2 u32s. - spi.set_watermark(Direction::Rx, 1); - }); - // Starts the I/O as soon as we're done initializing, since - // the TX FIFO is empty. - spi.set_interrupts(Interrupts::TRANSMIT_DATA); - (Shared {}, Local { spi }) - } - - #[task(binds = BOARD_SPI, local = [spi])] - fn spi_interrupt(cx: spi_interrupt::Context) { - let spi_interrupt::LocalResources { spi, .. } = cx.local; + #[init(local = [ + spi_systick: Option = None, + ])] + fn init(cx: init::Context) -> (Shared, Local) { + let ( + _, + board::Specifics { + spi: (mut spi_bus, spi_cs_pin), + .. + }, + ) = board::new(); - let status = spi.status(); - spi.clear_status(Status::TRANSMIT_DATA | Status::RECEIVE_DATA); + // Init monotonic + let systick_token = rtic_monotonics::create_systick_token!(); + Systick::start( + cx.core.SYST, + 600_000_000, /* TODO: fix */ + systick_token, + ); - if status.intersects(Status::TRANSMIT_DATA) { - // This write clears TRANSMIT_DATA. - spi.set_interrupts(Interrupts::RECEIVE_DATA); + // Configure SPI + let spi_systick = cx.local.spi_systick.insert(Systick); + spi_bus.set_delay_source(spi_systick).unwrap(); + let spi_interrupt_handler = spi_bus.enable_interrupts().unwrap(); - // Sending two u32s. Frame size is represented by bits. - let transaction = Transaction::new(2 * 8 * core::mem::size_of::() as u16) - .expect("Transaction frame size is within bounds"); - spi.enqueue_transaction(&transaction); + // Create SPI device + let spi_device = spi_bus.device(spi_cs_pin); - spi.enqueue_data(0xDEADBEEF); - spi.enqueue_data(!0xDEADBEEF); - } else if status.intersects(Status::RECEIVE_DATA) { - // This write clears RECEIVE_DATA. - spi.set_interrupts(Interrupts::TRANSMIT_DATA); + ( + Shared {}, + Local { + spi_device, + spi_interrupt_handler, + }, + ) + } - assert!(spi.fifo_status().rxcount == 2); + #[task(priority = 1, local = [spi_device])] + async fn app(cx: app::Context) { + let app::LocalResources { spi_device, .. } = cx.local; - while let Some(_) = spi.read_data() {} + loop { + Systick::delay(1000.millis()).await; + //spi_device.transfer(TODO); } } + + #[task(binds = BOARD_SPI, local = [spi_interrupt_handler])] + fn spi_interrupt(cx: spi_interrupt::Context) { + cx.local.spi_interrupt_handler.on_interrupt(); + } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..fbbccb45 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "llvm-tools"] +targets = ["thumbv7em-none-eabihf"] diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index b06c799d..a3adfd32 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,14 +1,17 @@ +//! TODO + use eh1::delay::DelayUs; pub use eh1::spi::Mode; use imxrt_dma::channel::Channel; use rtic_sync::arbiter::Arbiter; -use crate::ral; +use crate::{gpio, ral}; mod bus; mod device; +/// TODO pub enum LpspiDma { /// Everything is CPU driven Disable, @@ -36,6 +39,7 @@ pub enum LpspiError { // NoData, } +/// TODO pub struct Pins { /// Serial data out /// @@ -55,6 +59,7 @@ struct LpspiDriver {} struct LpspiDataInner { driver: LpspiDriver, dma: LpspiDma, + clk_frequency: u32, timer: Option<&'static mut dyn DelayUs>, lpspi: ral::lpspi::Instance, } @@ -65,14 +70,24 @@ pub struct LpspiData { // TODO: interrupt register struct } -pub struct LpspiInterruptHandler {} - +/// TODO pub struct LpspiBus { data: &'static LpspiData, mode: Mode, + baud_rate: u32, } +/// TODO pub struct LpspiDevice { data: &'static LpspiData, - cs: CS, + cs: gpio::Output, +} + +/// TODO +pub struct LpspiInterruptHandler {} +impl LpspiInterruptHandler { + /// TODO + pub fn on_interrupt(&mut self) { + todo!() + } } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index ae8498c0..b057f7f6 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -19,6 +19,7 @@ impl LpspiBus { lpspi: ral::lpspi::Instance, mut pins: Pins, data_storage: &'static mut Option>, + clk_frequency: u32, ) -> Self where SDO: lpspi::Pin, Signal = lpspi::Sdo>, @@ -35,6 +36,7 @@ impl LpspiBus { bus: Arbiter::new(LpspiDataInner { driver, lpspi, + clk_frequency, dma: LpspiDma::Disable, timer: None, }), @@ -42,7 +44,9 @@ impl LpspiBus { Self { data: data_storage.insert(data), + // Sane defaults mode: MODE_0, + baud_rate: 1_000_000, } } @@ -65,7 +69,15 @@ impl LpspiBus { } /// TODO - pub fn device(cs: CS) -> LpspiDevice { - todo!() + pub fn set_baud_rate(&mut self, baud_rate: u32) { + self.baud_rate = baud_rate; + } + + /// TODO + pub fn device(&self, cs: crate::gpio::Output) -> LpspiDevice { + LpspiDevice { + data: self.data, + cs, + } } } diff --git a/src/common/lpspi/lpspi_eh1.rs b/src/common/lpspi/lpspi_eh1.rs deleted file mode 100644 index cad68b6c..00000000 --- a/src/common/lpspi/lpspi_eh1.rs +++ /dev/null @@ -1 +0,0 @@ -use eh1::spi::SpiBus; From 9a65d368aa43bdf60fe90613d901a78662a54ccc Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 18 Nov 2023 22:56:55 +0100 Subject: [PATCH 04/73] Dummy implement SPI traits --- Cargo.toml | 1 + examples/rtic_spi.rs | 34 +++++++-- src/common/lpspi.rs | 1 + src/common/lpspi/bus.rs | 139 +++++++++++++++++++++++++++++++++++++ src/common/lpspi/device.rs | 65 ++++++++++++++++- src/common/lpspi/error.rs | 11 +++ 6 files changed, 246 insertions(+), 5 deletions(-) create mode 100644 src/common/lpspi/error.rs diff --git a/Cargo.toml b/Cargo.toml index 63defc27..e00beeb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ imxrt1020 = ["imxrt-iomuxc/imxrt1020"] imxrt1060 = ["imxrt-iomuxc/imxrt1060"] imxrt1064 = ["imxrt-iomuxc/imxrt1060"] imxrt1170 = ["imxrt-iomuxc/imxrt1170"] +async = ["dep:eh1-async"] ################ # Extra features diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index 2b9d42ea..b818e27a 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -17,8 +17,10 @@ mod app { use imxrt_hal as hal; - use hal::lpspi::LpspiInterruptHandler; + use hal::lpspi::{LpspiDma, LpspiInterruptHandler}; + use eh1::spi::Operation; + use eh1_async::spi::SpiDevice; use rtic_monotonics::systick::*; #[local] @@ -35,7 +37,7 @@ mod app { ])] fn init(cx: init::Context) -> (Shared, Local) { let ( - _, + board::Common { mut dma, .. }, board::Specifics { spi: (mut spi_bus, spi_cs_pin), .. @@ -50,9 +52,17 @@ mod app { systick_token, ); + // Init DMA + let mut chan_a = dma[board::BOARD_DMA_A_INDEX].take().unwrap(); + chan_a.set_disable_on_completion(true); + + let mut chan_b = dma[board::BOARD_DMA_B_INDEX].take().unwrap(); + chan_b.set_disable_on_completion(true); + // Configure SPI let spi_systick = cx.local.spi_systick.insert(Systick); spi_bus.set_delay_source(spi_systick).unwrap(); + spi_bus.set_dma(LpspiDma::Full(chan_a, chan_b)).unwrap(); let spi_interrupt_handler = spi_bus.enable_interrupts().unwrap(); // Create SPI device @@ -73,11 +83,27 @@ mod app { loop { Systick::delay(1000.millis()).await; - //spi_device.transfer(TODO); + + // To demonstrate normal operation + spi_device + .transaction(&mut [ + Operation::DelayUs(100), + Operation::Write(&[12345u16]), + Operation::DelayUs(10), + Operation::Write(&[420, 69, 42]), + Operation::Write(&[0xFFFF]), + Operation::DelayUs(50), + ]) + .await + .unwrap(); + + // To demonstrate larger, DMA based transfers + let mut buf = [0xf5u32; 512]; + spi_device.transfer_in_place(&mut buf).await.unwrap(); } } - #[task(binds = BOARD_SPI, local = [spi_interrupt_handler])] + #[task(priority = 2, binds = BOARD_SPI, local = [spi_interrupt_handler])] fn spi_interrupt(cx: spi_interrupt::Context) { cx.local.spi_interrupt_handler.on_interrupt(); } diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index a3adfd32..c0807693 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -10,6 +10,7 @@ use crate::{gpio, ral}; mod bus; mod device; +mod error; /// TODO pub enum LpspiDma { diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index b057f7f6..9dcde6e3 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -81,3 +81,142 @@ impl LpspiBus { } } } + +impl eh1::spi::ErrorType for LpspiBus { + type Error = LpspiError; +} + +impl eh1::spi::SpiBus for LpspiBus { + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiBus for LpspiBus { + fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiBus for LpspiBus { + fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for LpspiBus { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for LpspiBus { + async fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for LpspiBus { + async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} diff --git a/src/common/lpspi/device.rs b/src/common/lpspi/device.rs index eed8400f..4acdb5b7 100644 --- a/src/common/lpspi/device.rs +++ b/src/common/lpspi/device.rs @@ -1,6 +1,69 @@ -use super::LpspiDevice; +use super::{LpspiDevice, LpspiError}; impl LpspiDevice { /// The peripheral instance. pub const N: u8 = N; } + +impl eh1::spi::ErrorType for LpspiDevice { + type Error = LpspiError; +} + +impl eh1::spi::SpiDevice for LpspiDevice { + fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiDevice for LpspiDevice { + fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u16>], + ) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiDevice for LpspiDevice { + fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u32>], + ) -> Result<(), Self::Error> { + todo!() + } +} + +// TODO: should we support u64 and u128? + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiDevice for LpspiDevice { + async fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiDevice for LpspiDevice { + async fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u16>], + ) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiDevice for LpspiDevice { + async fn transaction( + &mut self, + operations: &mut [eh1::spi::Operation<'_, u32>], + ) -> Result<(), Self::Error> { + todo!() + } +} diff --git a/src/common/lpspi/error.rs b/src/common/lpspi/error.rs new file mode 100644 index 00000000..478bdfbd --- /dev/null +++ b/src/common/lpspi/error.rs @@ -0,0 +1,11 @@ +use super::LpspiError; + +use eh1::spi::{Error, ErrorKind}; + +impl Error for LpspiError { + fn kind(&self) -> ErrorKind { + match self { + LpspiError::Busy => ErrorKind::Other, + } + } +} From d7bc1b51196c346978268611621d55f4728dc522 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 18 Nov 2023 23:08:12 +0100 Subject: [PATCH 05/73] Minor comment --- src/common/lpspi/bus.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 9dcde6e3..7d42e3cb 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,5 +1,4 @@ use eh1::{delay::DelayUs, spi::MODE_0}; -use imxrt_dma::channel::Channel; use super::{ Arbiter, LpspiBus, LpspiData, LpspiDataInner, LpspiDevice, LpspiDma, LpspiDriver, LpspiError, @@ -17,6 +16,8 @@ impl LpspiBus { /// TODO pub fn new( lpspi: ral::lpspi::Instance, + // TODO: Open question: How to make those pins optional? (For example, WS2812 driver only uses SDO pin) + // Or should we simply do a `new_without_pins` again? mut pins: Pins, data_storage: &'static mut Option>, clk_frequency: u32, From 47ff70abb8c96e5d1a23d246f636626f8bdd06cb Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 10:59:39 +0100 Subject: [PATCH 06/73] More work; remove device, as it seems to be best practices to leave that to the user --- Cargo.toml | 4 +- run.bat | 2 + src/common/lpspi.rs | 42 +++-- src/common/lpspi/bus.rs | 264 ++++++++++------------------- src/common/lpspi/device.rs | 69 -------- src/common/lpspi/disabled.rs | 49 ++++++ src/common/lpspi/driver.rs | 0 src/common/lpspi/eh1_impl.rs | 140 +++++++++++++++ src/common/lpspi/status_watcher.rs | 124 ++++++++++++++ src/common/lpspi_old.rs | 2 + 10 files changed, 427 insertions(+), 269 deletions(-) create mode 100644 run.bat delete mode 100644 src/common/lpspi/device.rs create mode 100644 src/common/lpspi/disabled.rs delete mode 100644 src/common/lpspi/driver.rs create mode 100644 src/common/lpspi/eh1_impl.rs create mode 100644 src/common/lpspi/status_watcher.rs diff --git a/Cargo.toml b/Cargo.toml index e00beeb6..edccbfba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,9 @@ optional = true [dependencies.rtic-sync] version = "1.0.2" +[dependencies.cortex-m] +version = "0.7" + ####################### # imxrt-rs dependencies ####################### @@ -141,7 +144,6 @@ codegen-units = 256 ###################################### [dev-dependencies] -cortex-m = "0.7" imxrt-rt = { workspace = true } menu = "0.4.0" rtic = { version = "2.0.1", features = ["thumbv7-backend"] } diff --git a/run.bat b/run.bat new file mode 100644 index 00000000..3b020f6c --- /dev/null +++ b/run.bat @@ -0,0 +1,2 @@ +cargo objcopy --example=hal_trng --features=board/teensy4 --target=thumbv7em-none-eabihf -- -O ihex firmware.hex +teensy_loader_cli --mcu=TEENSY40 -wsv .\firmware.hex diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index c0807693..b46fe298 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,16 +1,19 @@ //! TODO -use eh1::delay::DelayUs; pub use eh1::spi::Mode; use imxrt_dma::channel::Channel; -use rtic_sync::arbiter::Arbiter; -use crate::{gpio, ral}; +use crate::ral; +use cortex_m::interrupt::Mutex; mod bus; -mod device; +mod disabled; +mod eh1_impl; mod error; +mod status_watcher; + +use status_watcher::StatusWatcher; /// TODO pub enum LpspiDma { @@ -54,34 +57,29 @@ pub struct Pins { pub sck: SCK, } -/// The internal driver implementation -struct LpspiDriver {} - struct LpspiDataInner { - driver: LpspiDriver, - dma: LpspiDma, - clk_frequency: u32, - timer: Option<&'static mut dyn DelayUs>, - lpspi: ral::lpspi::Instance, + // TODO: interrupt stuff } /// Static shared data allocated by the user pub struct LpspiData { - bus: Arbiter>, - // TODO: interrupt register struct + shared: Mutex>, + lpspi: status_watcher::StatusWatcher, } /// TODO -pub struct LpspiBus { - data: &'static LpspiData, - mode: Mode, - baud_rate: u32, +pub struct Lpspi<'a, const N: u8> { + dma: LpspiDma, + source_clock_hz: u32, + data: &'a LpspiData, + rx_fifo_size: u32, + tx_fifo_size: u32, } -/// TODO -pub struct LpspiDevice { - data: &'static LpspiData, - cs: gpio::Output, +/// An LPSPI peripheral which is temporarily disabled. +pub struct Disabled<'a, 'b, const N: u8> { + bus: &'a mut Lpspi<'b, N>, + men: bool, } /// TODO diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 7d42e3cb..237c3f51 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,223 +1,133 @@ -use eh1::{delay::DelayUs, spi::MODE_0}; +use cortex_m::interrupt::Mutex; +use eh1::spi::{Mode, MODE_0}; use super::{ - Arbiter, LpspiBus, LpspiData, LpspiDataInner, LpspiDevice, LpspiDma, LpspiDriver, LpspiError, - LpspiInterruptHandler, Pins, + Disabled, Lpspi, LpspiData, LpspiDataInner, LpspiDma, LpspiError, LpspiInterruptHandler, Pins, + StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, ral, }; -impl LpspiBus { +impl<'a, const N: u8> Lpspi<'a, N> { /// The peripheral instance. pub const N: u8 = N; - /// TODO + /// Create a new LPSPI peripheral. + /// + /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the + /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. pub fn new( lpspi: ral::lpspi::Instance, // TODO: Open question: How to make those pins optional? (For example, WS2812 driver only uses SDO pin) // Or should we simply do a `new_without_pins` again? mut pins: Pins, - data_storage: &'static mut Option>, - clk_frequency: u32, + data_storage: &'a mut Option>, + source_clock_hz: u32, ) -> Self where SDO: lpspi::Pin, Signal = lpspi::Sdo>, SDI: lpspi::Pin, Signal = lpspi::Sdi>, SCK: lpspi::Pin, Signal = lpspi::Sck>, { - let driver = LpspiDriver {}; - - lpspi::prepare(&mut pins.sdo); - lpspi::prepare(&mut pins.sdi); - lpspi::prepare(&mut pins.sck); + let (rx_fifo_size_exp, tx_fifo_size_exp) = + ral::read_reg!(ral::lpspi, lpspi, PARAM, RXFIFO, TXFIFO); + let rx_fifo_size = 1 << rx_fifo_size_exp; + let tx_fifo_size = 1 << tx_fifo_size_exp; let data = LpspiData { - bus: Arbiter::new(LpspiDataInner { - driver, - lpspi, - clk_frequency, - dma: LpspiDma::Disable, - timer: None, - }), + lpspi: StatusWatcher::new(lpspi), + shared: Mutex::new(LpspiDataInner {}), }; - Self { + let mut this = Self { + source_clock_hz, + dma: LpspiDma::Disable, data: data_storage.insert(data), - // Sane defaults - mode: MODE_0, - baud_rate: 1_000_000, - } - } + rx_fifo_size, + tx_fifo_size, + }; - /// TODO - pub fn set_dma(&mut self, dma: LpspiDma) -> Result { - let mut bus = self.data.bus.try_access().ok_or(LpspiError::Busy)?; - Ok(core::mem::replace(&mut bus.dma, dma)) - } + ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_1); + ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_0); + ral::write_reg!( + ral::lpspi, + this.data.lpspi.instance(), + CFGR1, + MASTER: MASTER_1, + SAMPLE: SAMPLE_1 + ); + this.disabled(|bus| { + // Sane defaults + bus.set_clock_hz(1_000_000); + bus.set_mode(MODE_0) + }); - /// TODO - pub fn set_delay_source(&mut self, delay: &'static mut dyn DelayUs) -> Result<(), LpspiError> { - let mut bus = self.data.bus.try_access().ok_or(LpspiError::Busy)?; - bus.timer = Some(delay); - Ok(()) - } + lpspi::prepare(&mut pins.sdo); + lpspi::prepare(&mut pins.sdi); + lpspi::prepare(&mut pins.sck); - /// TODO + // TODO think about this + ral::write_reg!(ral::lpspi, this.lpspi(), FCR, + RXWATER: this.rx_fifo_size / 2 - 1, // always divisible by two + TXWATER: this.tx_fifo_size / 2 - 1 + ); + ral::write_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_1); + + this + } + + /// Temporarily disable the LPSPI peripheral. + /// + /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify + /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit + /// and receive FIFOs. + pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { + self.clear_fifos(); + let mut disabled = Disabled::new(self); + func(&mut disabled) + } + + /// Provides the SPI bus with one or two DMA channels. + /// + /// This drastically increases the efficiency of u32 based reads/writes. + /// + /// For simultaneous read/write, two DMA channels are required. + pub fn set_dma(&mut self, dma: LpspiDma) -> LpspiDma { + core::mem::replace(&mut self.dma, dma) + } + + /// Switches the SPI bus to interrupt based operation. + /// + /// This increases efficiency drastically, as it avoids busy waiting. + /// + /// Note that it is the caller's responsibility to connect the interrupt source + /// to the returned interrupt handler object. pub fn enable_interrupts(&mut self) -> Result { todo!() } /// TODO - pub fn set_baud_rate(&mut self, baud_rate: u32) { - self.baud_rate = baud_rate; - } - - /// TODO - pub fn device(&self, cs: crate::gpio::Output) -> LpspiDevice { - LpspiDevice { - data: self.data, - cs, - } - } -} - -impl eh1::spi::ErrorType for LpspiBus { - type Error = LpspiError; -} - -impl eh1::spi::SpiBus for LpspiBus { - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + pub fn set_spi_clock_hz(&mut self, _clk_hz: u32) { todo!() } - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + /// Set the SPI mode for the peripheral + pub fn set_mode(&mut self, mode: Mode) { todo!() } - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - todo!() - } + // ////////////////// PRIVATE DRIVER STUFF /////////////////////// - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() + /// Get LPSPI Register Instance + #[inline] + fn lpspi(&self) -> &ral::lpspi::Instance { + self.data.lpspi.instance() } - fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -impl eh1::spi::SpiBus for LpspiBus { - fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -impl eh1::spi::SpiBus for LpspiBus { - fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() - } - - fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() - } - - fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for LpspiBus { - async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for LpspiBus { - async fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for LpspiBus { - async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() - } - - async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() + /// Clear both FIFOs. + fn clear_fifos(&mut self) { + ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RTF: RTF_1, RRF: RRF_1); } } diff --git a/src/common/lpspi/device.rs b/src/common/lpspi/device.rs deleted file mode 100644 index 4acdb5b7..00000000 --- a/src/common/lpspi/device.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::{LpspiDevice, LpspiError}; - -impl LpspiDevice { - /// The peripheral instance. - pub const N: u8 = N; -} - -impl eh1::spi::ErrorType for LpspiDevice { - type Error = LpspiError; -} - -impl eh1::spi::SpiDevice for LpspiDevice { - fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u8>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -impl eh1::spi::SpiDevice for LpspiDevice { - fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u16>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -impl eh1::spi::SpiDevice for LpspiDevice { - fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u32>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -// TODO: should we support u64 and u128? - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiDevice for LpspiDevice { - async fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u8>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiDevice for LpspiDevice { - async fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u16>], - ) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiDevice for LpspiDevice { - async fn transaction( - &mut self, - operations: &mut [eh1::spi::Operation<'_, u32>], - ) -> Result<(), Self::Error> { - todo!() - } -} diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs new file mode 100644 index 00000000..e6c9296d --- /dev/null +++ b/src/common/lpspi/disabled.rs @@ -0,0 +1,49 @@ +use eh1::spi::{Phase, Polarity}; + +use super::{ral, Disabled, Lpspi, Mode}; + +impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { + pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { + let men = ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1); + ral::modify_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN: MEN_0); + Self { bus, men } + } + + /// Set the SPI mode for the peripheral + pub fn set_mode(&mut self, mode: Mode) { + // This could probably be changed when we're not disabled. + // However, there's rules about when you can read TCR. + // Specifically, reading TCR while it's being loaded from + // the transmit FIFO could result in an incorrect reading. + // Only permitting this when we're disabled might help + // us avoid something troublesome. + ral::modify_reg!( + ral::lpspi, + self.bus.data.lpspi.instance(), + TCR, + CPOL: ((mode.polarity == Polarity::IdleHigh) as u32), + CPHA: ((mode.phase == Phase::CaptureOnSecondTransition) as u32) + ); + } + + /// Set the LPSPI clock speed (Hz). + pub fn set_clock_hz(&mut self, spi_clock_hz: u32) { + // Round up, so we always get a real spi clock that is + // equal or less than the requested speed. + let mut div = 1 + (self.bus.source_clock_hz - 1) / spi_clock_hz; + + // 0 <= div <= 255, and the true coefficient is really div + 2 + let div = div.saturating_sub(2).clamp(0, 255); + ral::write_reg!( + ral::lpspi, + self.bus.data.lpspi.instance(), + CCR, + SCKDIV: div, + // These all don't matter, because we do not use a CS pin. + // embedded-hal controls the CS pins through `OutputPin`. + DBT: 0, + SCKPCS: 0, + PCSSCK: 0 + ); + } +} diff --git a/src/common/lpspi/driver.rs b/src/common/lpspi/driver.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/common/lpspi/eh1_impl.rs b/src/common/lpspi/eh1_impl.rs new file mode 100644 index 00000000..181e021b --- /dev/null +++ b/src/common/lpspi/eh1_impl.rs @@ -0,0 +1,140 @@ +use super::{Lpspi, LpspiError}; + +impl eh1::spi::ErrorType for Lpspi<'_, N> { + type Error = LpspiError; +} + +impl eh1::spi::SpiBus for Lpspi<'_, N> { + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiBus for Lpspi<'_, N> { + fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +impl eh1::spi::SpiBus for Lpspi<'_, N> { + fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for Lpspi<'_, N> { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for Lpspi<'_, N> { + async fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} + +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for Lpspi<'_, N> { + async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + todo!() + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + todo!() + } +} diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs new file mode 100644 index 00000000..4d02477b --- /dev/null +++ b/src/common/lpspi/status_watcher.rs @@ -0,0 +1,124 @@ +use core::{ + cell::RefCell, + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use cortex_m::interrupt::{self, Mutex}; + +use super::ral; + +struct StatusWatcherInner { + transfer_complete_happened: bool, + transfer_complete_waker: Option, +} + +pub(crate) struct StatusWatcher { + inner: Mutex>>, + lpspi: ral::lpspi::Instance, +} + +impl StatusWatcherInner { + pub fn check_and_reset(&mut self, lpspi: &ral::lpspi::Instance) { + let transfer_complete_set = ral::read_reg!(ral::lpspi, lpspi, SR, TCF == TCF_1); + + if transfer_complete_set { + ral::write_reg!(ral::lpspi, lpspi, SR, TCF: TCF_1); + + self.transfer_complete_happened = true; + if let Some(waker) = self.transfer_complete_waker.take() { + waker.wake(); + } + } + } +} + +impl StatusWatcher { + pub fn new(lpspi: ral::lpspi::Instance) -> Self { + Self { + inner: Mutex::new(RefCell::new(StatusWatcherInner { + transfer_complete_happened: false, + transfer_complete_waker: None, + })), + lpspi, + } + } + + #[inline] + pub fn instance(&self) -> &ral::lpspi::Instance { + &self.lpspi + } + + fn with_check_and_reset(&self, f: impl FnOnce(&mut StatusWatcherInner) -> R) -> R { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs); + let mut inner = inner.borrow_mut(); + inner.check_and_reset(&self.lpspi); + + f(&mut inner) + }) + } + + pub fn on_interrupt(&self) { + self.with_check_and_reset(|_| {}); + } + + pub fn clear_transfer_complete(&self) { + self.with_check_and_reset(|inner| { + inner.transfer_complete_happened = false; + }); + } + + pub fn poll_transfer_complete(&self) -> bool { + self.with_check_and_reset(|inner| inner.transfer_complete_happened) + } + + pub fn wait_transfer_complete(&self) -> WaitTransferComplete { + WaitTransferComplete(self) + } +} + +pub struct WaitTransferComplete<'a, const N: u8>(&'a StatusWatcher); + +impl Future for WaitTransferComplete<'_, N> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.0.with_check_and_reset(|inner| { + if inner.transfer_complete_happened { + Poll::Ready(()) + } else { + let new_waker = cx.waker(); + + // From embassy + // https://github.com/embassy-rs/embassy/blob/b99533607ceed225dd12ae73aaa9a0d969a7365e/embassy-sync/src/waitqueue/waker.rs#L59-L61 + match &inner.transfer_complete_waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(w2) if (w2.will_wake(new_waker)) => {} + _ => { + // clone the new waker and store it + if let Some(old_waker) = + inner.transfer_complete_waker.replace(new_waker.clone()) + { + // We had a waker registered for another task. Wake it, so the other task can + // reregister itself if it's still interested. + // + // If two tasks are waiting on the same thing concurrently, this will cause them + // to wake each other in a loop fighting over this WakerRegistration. This wastes + // CPU but things will still work. + // + // If the user wants to have two tasks waiting on the same thing they should use + // a more appropriate primitive that can store multiple wakers. + old_waker.wake() + } + } + } + + Poll::Pending + } + }) + } +} diff --git a/src/common/lpspi_old.rs b/src/common/lpspi_old.rs index 01107296..04c22216 100644 --- a/src/common/lpspi_old.rs +++ b/src/common/lpspi_old.rs @@ -609,6 +609,8 @@ impl Lpspi { /// You're responsible for making sure there's space in the transmit /// FIFO for this transaction command. pub fn enqueue_transaction(&mut self, transaction: &Transaction) { + // TODO: This is technically unsafe. TCR is not meant to be read. + // Solution is simple: Write it completely, every time. ral::modify_reg!(ral::lpspi, self.lpspi, TCR, LSBF: transaction.bit_order as u32, BYSW: transaction.byte_swap as u32, From c4d938d2bc9a5e9d19717f825aa4327d65a7e717 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 11:01:33 +0100 Subject: [PATCH 07/73] Add some comments --- src/common/lpspi/bus.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 237c3f51..98f6bc6f 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -49,6 +49,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { tx_fifo_size, }; + // Reset, enable master mode ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_1); ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_0); ral::write_reg!( @@ -58,12 +59,14 @@ impl<'a, const N: u8> Lpspi<'a, N> { MASTER: MASTER_1, SAMPLE: SAMPLE_1 ); + + // Set sane default parameters this.disabled(|bus| { - // Sane defaults bus.set_clock_hz(1_000_000); bus.set_mode(MODE_0) }); + // Configure pins lpspi::prepare(&mut pins.sdo); lpspi::prepare(&mut pins.sdi); lpspi::prepare(&mut pins.sck); @@ -73,6 +76,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { RXWATER: this.rx_fifo_size / 2 - 1, // always divisible by two TXWATER: this.tx_fifo_size / 2 - 1 ); + + // Enable ral::write_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_1); this From 07a83433176189b1a162a219f9cafb68918bfefb Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 11:15:45 +0100 Subject: [PATCH 08/73] Bla --- src/common/lpspi.rs | 10 +++++--- src/common/lpspi/bus.rs | 31 +++++++++--------------- src/common/lpspi/disabled.rs | 6 ++--- src/common/lpspi/eh1_impl.rs | 47 +----------------------------------- 4 files changed, 22 insertions(+), 72 deletions(-) diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index b46fe298..d1960f77 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -64,7 +64,7 @@ struct LpspiDataInner { /// Static shared data allocated by the user pub struct LpspiData { shared: Mutex>, - lpspi: status_watcher::StatusWatcher, + lpspi: StatusWatcher, } /// TODO @@ -83,10 +83,12 @@ pub struct Disabled<'a, 'b, const N: u8> { } /// TODO -pub struct LpspiInterruptHandler {} -impl LpspiInterruptHandler { +pub struct LpspiInterruptHandler<'a, const N: u8> { + status_watcher: &'a StatusWatcher, +} +impl LpspiInterruptHandler<'_, N> { /// TODO pub fn on_interrupt(&mut self) { - todo!() + self.status_watcher.on_interrupt(); } } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 98f6bc6f..afa9aef2 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,8 +1,8 @@ use cortex_m::interrupt::Mutex; -use eh1::spi::{Mode, MODE_0}; +use eh1::spi::MODE_0; use super::{ - Disabled, Lpspi, LpspiData, LpspiDataInner, LpspiDma, LpspiError, LpspiInterruptHandler, Pins, + Disabled, Lpspi, LpspiData, LpspiDataInner, LpspiDma, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ @@ -71,11 +71,12 @@ impl<'a, const N: u8> Lpspi<'a, N> { lpspi::prepare(&mut pins.sdi); lpspi::prepare(&mut pins.sck); - // TODO think about this - ral::write_reg!(ral::lpspi, this.lpspi(), FCR, - RXWATER: this.rx_fifo_size / 2 - 1, // always divisible by two - TXWATER: this.tx_fifo_size / 2 - 1 - ); + // Configure watermarks. + // This is more for good measure, we don't really use the watermarks. + // ral::write_reg!(ral::lpspi, this.lpspi(), FCR, + // RXWATER: 0, + // TXWATER: u32::MAX + // ); // Enable ral::write_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_1); @@ -109,18 +110,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { /// /// Note that it is the caller's responsibility to connect the interrupt source /// to the returned interrupt handler object. - pub fn enable_interrupts(&mut self) -> Result { - todo!() - } - - /// TODO - pub fn set_spi_clock_hz(&mut self, _clk_hz: u32) { - todo!() - } - - /// Set the SPI mode for the peripheral - pub fn set_mode(&mut self, mode: Mode) { - todo!() + pub fn enable_interrupts(&mut self) -> LpspiInterruptHandler<'a, N> { + LpspiInterruptHandler { + status_watcher: &self.data.lpspi, + } } // ////////////////// PRIVATE DRIVER STUFF /////////////////////// diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index e6c9296d..1c507739 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -28,9 +28,9 @@ impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { /// Set the LPSPI clock speed (Hz). pub fn set_clock_hz(&mut self, spi_clock_hz: u32) { - // Round up, so we always get a real spi clock that is - // equal or less than the requested speed. - let mut div = 1 + (self.bus.source_clock_hz - 1) / spi_clock_hz; + // Round up, so we always get a resulting SPI clock that is + // equal or less than the requested frequency. + let div = 1 + (self.bus.source_clock_hz - 1) / spi_clock_hz; // 0 <= div <= 255, and the true coefficient is really div + 2 let div = div.saturating_sub(2).clamp(0, 255); diff --git a/src/common/lpspi/eh1_impl.rs b/src/common/lpspi/eh1_impl.rs index 181e021b..d4dcd4ba 100644 --- a/src/common/lpspi/eh1_impl.rs +++ b/src/common/lpspi/eh1_impl.rs @@ -70,52 +70,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N> { } } -#[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for Lpspi<'_, N> { - async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - -#[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for Lpspi<'_, N> { - async fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() - } -} - +// Async only makes sense for u32, with DMA #[cfg(feature = "async")] impl eh1_async::spi::SpiBus for Lpspi<'_, N> { async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { From b2619de652fd53f5e0b7ccf8e2b1d0cf6bf356c5 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 11:39:50 +0100 Subject: [PATCH 09/73] Make DMA compile time configurable --- src/common/lpspi.rs | 60 ++++++++++++++++++------ src/common/lpspi/bus.rs | 91 ++++++++++++++++++++++++++++++------ src/common/lpspi/disabled.rs | 4 +- src/common/lpspi/eh1_impl.rs | 14 +++--- 4 files changed, 131 insertions(+), 38 deletions(-) diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index d1960f77..1d6b6707 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -15,16 +15,48 @@ mod status_watcher; use status_watcher::StatusWatcher; -/// TODO -pub enum LpspiDma { - /// Everything is CPU driven - Disable, - /// Read and Write are DMA based, - /// but Transfers are only partially - /// DMA based - Partial(Channel), - /// Everything is DMA based - Full(Channel, Channel), +trait LpspiDma { + fn get_one(&mut self) -> Option<&mut Channel>; + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)>; +} + +/// Everything is CPU driven +struct NoDma; + +/// Read and Write are DMA based, +/// but Transfers are only partially +/// DMA based +/// +struct PartialDma(Channel); + +/// Everything is DMA based. +/// +/// This is a requirement for the async interface. +struct FullDma(Channel, Channel); + +impl LpspiDma for NoDma { + fn get_one(&mut self) -> Option<&mut Channel> { + None + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + None + } +} +impl LpspiDma for PartialDma { + fn get_one(&mut self) -> Option<&mut Channel> { + Some(&mut self.0) + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + None + } +} +impl LpspiDma for FullDma { + fn get_one(&mut self) -> Option<&mut Channel> { + Some(&mut self.0) + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + Some((&mut self.0, &mut self.1)) + } } /// Possible errors when interfacing the LPSPI. @@ -68,8 +100,8 @@ pub struct LpspiData { } /// TODO -pub struct Lpspi<'a, const N: u8> { - dma: LpspiDma, +pub struct Lpspi<'a, const N: u8, DMA> { + dma: DMA, source_clock_hz: u32, data: &'a LpspiData, rx_fifo_size: u32, @@ -77,8 +109,8 @@ pub struct Lpspi<'a, const N: u8> { } /// An LPSPI peripheral which is temporarily disabled. -pub struct Disabled<'a, 'b, const N: u8> { - bus: &'a mut Lpspi<'b, N>, +pub struct Disabled<'a, 'b, const N: u8, DMA> { + bus: &'a mut Lpspi<'b, N, DMA>, men: bool, } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index afa9aef2..ba1856b8 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -2,15 +2,84 @@ use cortex_m::interrupt::Mutex; use eh1::spi::MODE_0; use super::{ - Disabled, Lpspi, LpspiData, LpspiDataInner, LpspiDma, LpspiInterruptHandler, Pins, - StatusWatcher, + Channel, Disabled, FullDma, Lpspi, LpspiData, LpspiDataInner, LpspiInterruptHandler, NoDma, + PartialDma, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, ral, }; -impl<'a, const N: u8> Lpspi<'a, N> { +impl<'a, const N: u8> Lpspi<'a, N, NoDma> { + /// Create a new LPSPI peripheral without DMA support. + /// + /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the + /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. + fn new( + lpspi: ral::lpspi::Instance, + mut pins: Pins, + data_storage: &'a mut Option>, + source_clock_hz: u32, + ) -> Self + where + SDO: lpspi::Pin, Signal = lpspi::Sdo>, + SDI: lpspi::Pin, Signal = lpspi::Sdi>, + SCK: lpspi::Pin, Signal = lpspi::Sck>, + { + Self::create(lpspi, pins, data_storage, source_clock_hz, NoDma) + } +} + +impl<'a, const N: u8> Lpspi<'a, N, PartialDma> { + /// Create a new LPSPI peripheral with partial DMA support. + /// + /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the + /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. + fn new( + lpspi: ral::lpspi::Instance, + mut pins: Pins, + data_storage: &'a mut Option>, + source_clock_hz: u32, + dma: Channel, + ) -> Self + where + SDO: lpspi::Pin, Signal = lpspi::Sdo>, + SDI: lpspi::Pin, Signal = lpspi::Sdi>, + SCK: lpspi::Pin, Signal = lpspi::Sck>, + { + Self::create(lpspi, pins, data_storage, source_clock_hz, PartialDma(dma)) + } +} + +impl<'a, const N: u8> Lpspi<'a, N, FullDma> { + /// Create a new LPSPI peripheral with partial DMA support. + /// + /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the + /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. + fn new( + lpspi: ral::lpspi::Instance, + mut pins: Pins, + data_storage: &'a mut Option>, + source_clock_hz: u32, + dma1: Channel, + dma2: Channel, + ) -> Self + where + SDO: lpspi::Pin, Signal = lpspi::Sdo>, + SDI: lpspi::Pin, Signal = lpspi::Sdi>, + SCK: lpspi::Pin, Signal = lpspi::Sck>, + { + Self::create( + lpspi, + pins, + data_storage, + source_clock_hz, + FullDma(dma1, dma2), + ) + } +} + +impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// The peripheral instance. pub const N: u8 = N; @@ -18,13 +87,14 @@ impl<'a, const N: u8> Lpspi<'a, N> { /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn new( + fn create( lpspi: ral::lpspi::Instance, // TODO: Open question: How to make those pins optional? (For example, WS2812 driver only uses SDO pin) // Or should we simply do a `new_without_pins` again? mut pins: Pins, data_storage: &'a mut Option>, source_clock_hz: u32, + dma: DMA, ) -> Self where SDO: lpspi::Pin, Signal = lpspi::Sdo>, @@ -43,7 +113,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { let mut this = Self { source_clock_hz, - dma: LpspiDma::Disable, + dma, data: data_storage.insert(data), rx_fifo_size, tx_fifo_size, @@ -89,21 +159,12 @@ impl<'a, const N: u8> Lpspi<'a, N> { /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit /// and receive FIFOs. - pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { + pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { self.clear_fifos(); let mut disabled = Disabled::new(self); func(&mut disabled) } - /// Provides the SPI bus with one or two DMA channels. - /// - /// This drastically increases the efficiency of u32 based reads/writes. - /// - /// For simultaneous read/write, two DMA channels are required. - pub fn set_dma(&mut self, dma: LpspiDma) -> LpspiDma { - core::mem::replace(&mut self.dma, dma) - } - /// Switches the SPI bus to interrupt based operation. /// /// This increases efficiency drastically, as it avoids busy waiting. diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 1c507739..6ca3d2b7 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -2,8 +2,8 @@ use eh1::spi::{Phase, Polarity}; use super::{ral, Disabled, Lpspi, Mode}; -impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { - pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { +impl<'a, 'b, const N: u8, DMA> Disabled<'a, 'b, N, DMA> { + pub(crate) fn new(bus: &'a mut Lpspi<'b, N, DMA>) -> Self { let men = ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1); ral::modify_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN: MEN_0); Self { bus, men } diff --git a/src/common/lpspi/eh1_impl.rs b/src/common/lpspi/eh1_impl.rs index d4dcd4ba..662413b3 100644 --- a/src/common/lpspi/eh1_impl.rs +++ b/src/common/lpspi/eh1_impl.rs @@ -1,10 +1,10 @@ -use super::{Lpspi, LpspiError}; +use super::{FullDma, Lpspi, LpspiError}; -impl eh1::spi::ErrorType for Lpspi<'_, N> { +impl eh1::spi::ErrorType for Lpspi<'_, N, DMA> { type Error = LpspiError; } -impl eh1::spi::SpiBus for Lpspi<'_, N> { +impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { todo!() } @@ -26,7 +26,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N> { } } -impl eh1::spi::SpiBus for Lpspi<'_, N> { +impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { todo!() } @@ -48,7 +48,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N> { } } -impl eh1::spi::SpiBus for Lpspi<'_, N> { +impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { todo!() } @@ -70,9 +70,9 @@ impl eh1::spi::SpiBus for Lpspi<'_, N> { } } -// Async only makes sense for u32, with DMA +// Async only makes sense for DMA; DMA only supports u32. #[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for Lpspi<'_, N> { +impl eh1_async::spi::SpiBus for Lpspi<'_, N, FullDma> { async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { todo!() } From bfc7619f1b4de2991717e98b807627d9538a4c13 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 11:45:58 +0100 Subject: [PATCH 10/73] Small fixes --- src/common/lpspi.rs | 6 ------ src/common/lpspi/bus.rs | 18 ++++++++---------- src/common/lpspi/disabled.rs | 6 ++++++ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 1d6b6707..3710bd8c 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -5,7 +5,6 @@ pub use eh1::spi::Mode; use imxrt_dma::channel::Channel; use crate::ral; -use cortex_m::interrupt::Mutex; mod bus; mod disabled; @@ -89,13 +88,8 @@ pub struct Pins { pub sck: SCK, } -struct LpspiDataInner { - // TODO: interrupt stuff -} - /// Static shared data allocated by the user pub struct LpspiData { - shared: Mutex>, lpspi: StatusWatcher, } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index ba1856b8..48180e02 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,9 +1,8 @@ -use cortex_m::interrupt::Mutex; use eh1::spi::MODE_0; use super::{ - Channel, Disabled, FullDma, Lpspi, LpspiData, LpspiDataInner, LpspiInterruptHandler, NoDma, - PartialDma, Pins, StatusWatcher, + Channel, Disabled, FullDma, Lpspi, LpspiData, LpspiInterruptHandler, NoDma, PartialDma, Pins, + StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, @@ -15,9 +14,9 @@ impl<'a, const N: u8> Lpspi<'a, N, NoDma> { /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - fn new( + pub fn new( lpspi: ral::lpspi::Instance, - mut pins: Pins, + pins: Pins, data_storage: &'a mut Option>, source_clock_hz: u32, ) -> Self @@ -35,9 +34,9 @@ impl<'a, const N: u8> Lpspi<'a, N, PartialDma> { /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - fn new( + pub fn new( lpspi: ral::lpspi::Instance, - mut pins: Pins, + pins: Pins, data_storage: &'a mut Option>, source_clock_hz: u32, dma: Channel, @@ -56,9 +55,9 @@ impl<'a, const N: u8> Lpspi<'a, N, FullDma> { /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - fn new( + pub fn new( lpspi: ral::lpspi::Instance, - mut pins: Pins, + pins: Pins, data_storage: &'a mut Option>, source_clock_hz: u32, dma1: Channel, @@ -108,7 +107,6 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { let data = LpspiData { lpspi: StatusWatcher::new(lpspi), - shared: Mutex::new(LpspiDataInner {}), }; let mut this = Self { diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 6ca3d2b7..4a58dbfe 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -47,3 +47,9 @@ impl<'a, 'b, const N: u8, DMA> Disabled<'a, 'b, N, DMA> { ); } } + +impl Drop for Disabled<'_, '_, N, DMA> { + fn drop(&mut self) { + ral::modify_reg!(ral::lpspi, self.bus.data.lpspi.instance(), CR, MEN: self.men as u32); + } +} From 9fa738b9f726171829cd9254c0a4c0924958bad9 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 11:50:18 +0100 Subject: [PATCH 11/73] Refactor dma stuff int lpspi/dma.rs --- src/common/lpspi.rs | 45 +----------------------------------- src/common/lpspi/bus.rs | 4 ++-- src/common/lpspi/dma.rs | 45 ++++++++++++++++++++++++++++++++++++ src/common/lpspi/eh1_impl.rs | 2 +- 4 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 src/common/lpspi/dma.rs diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 3710bd8c..5b14876e 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -8,56 +8,13 @@ use crate::ral; mod bus; mod disabled; +mod dma; mod eh1_impl; mod error; mod status_watcher; use status_watcher::StatusWatcher; -trait LpspiDma { - fn get_one(&mut self) -> Option<&mut Channel>; - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)>; -} - -/// Everything is CPU driven -struct NoDma; - -/// Read and Write are DMA based, -/// but Transfers are only partially -/// DMA based -/// -struct PartialDma(Channel); - -/// Everything is DMA based. -/// -/// This is a requirement for the async interface. -struct FullDma(Channel, Channel); - -impl LpspiDma for NoDma { - fn get_one(&mut self) -> Option<&mut Channel> { - None - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - None - } -} -impl LpspiDma for PartialDma { - fn get_one(&mut self) -> Option<&mut Channel> { - Some(&mut self.0) - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - None - } -} -impl LpspiDma for FullDma { - fn get_one(&mut self) -> Option<&mut Channel> { - Some(&mut self.0) - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - Some((&mut self.0, &mut self.1)) - } -} - /// Possible errors when interfacing the LPSPI. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LpspiError { diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 48180e02..151e3b49 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,8 +1,8 @@ use eh1::spi::MODE_0; use super::{ - Channel, Disabled, FullDma, Lpspi, LpspiData, LpspiInterruptHandler, NoDma, PartialDma, Pins, - StatusWatcher, + dma::{FullDma, NoDma, PartialDma}, + Channel, Disabled, Lpspi, LpspiData, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, diff --git a/src/common/lpspi/dma.rs b/src/common/lpspi/dma.rs new file mode 100644 index 00000000..6ad90b32 --- /dev/null +++ b/src/common/lpspi/dma.rs @@ -0,0 +1,45 @@ +use imxrt_dma::channel::Channel; + +pub trait LpspiDma { + fn get_one(&mut self) -> Option<&mut Channel>; + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)>; +} + +/// Everything is CPU driven +pub struct NoDma; + +/// Read and Write are DMA based, +/// but Transfers are only partially +/// DMA based +/// +pub struct PartialDma(pub(crate) Channel); + +/// Everything is DMA based. +/// +/// This is a requirement for the async interface. +pub struct FullDma(pub(crate) Channel, pub(crate) Channel); + +impl LpspiDma for NoDma { + fn get_one(&mut self) -> Option<&mut Channel> { + None + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + None + } +} +impl LpspiDma for PartialDma { + fn get_one(&mut self) -> Option<&mut Channel> { + Some(&mut self.0) + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + None + } +} +impl LpspiDma for FullDma { + fn get_one(&mut self) -> Option<&mut Channel> { + Some(&mut self.0) + } + fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { + Some((&mut self.0, &mut self.1)) + } +} diff --git a/src/common/lpspi/eh1_impl.rs b/src/common/lpspi/eh1_impl.rs index 662413b3..092f1ac0 100644 --- a/src/common/lpspi/eh1_impl.rs +++ b/src/common/lpspi/eh1_impl.rs @@ -1,4 +1,4 @@ -use super::{FullDma, Lpspi, LpspiError}; +use super::{dma::FullDma, Lpspi, LpspiError}; impl eh1::spi::ErrorType for Lpspi<'_, N, DMA> { type Error = LpspiError; From 95109a904b9b4110ab6105a74cf0e678b648ad51 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 24 Nov 2023 17:07:51 +0100 Subject: [PATCH 12/73] More work --- src/common/lpspi.rs | 17 ++- src/common/lpspi/bus.rs | 48 +++++++-- src/common/lpspi/{ => bus}/eh1_impl.rs | 8 +- src/common/lpspi/error.rs | 3 + src/common/lpspi/word_types.rs | 142 +++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 21 deletions(-) rename src/common/lpspi/{ => bus}/eh1_impl.rs (94%) create mode 100644 src/common/lpspi/word_types.rs diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 5b14876e..0a02c3fb 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -9,26 +9,23 @@ use crate::ral; mod bus; mod disabled; mod dma; -mod eh1_impl; mod error; mod status_watcher; +mod word_types; use status_watcher::StatusWatcher; /// Possible errors when interfacing the LPSPI. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LpspiError { - // /// The transaction frame size is incorrect. - // /// - // /// The frame size, in bits, must be between 8 bits and - // /// 4095 bits. - // FrameSize, - // /// FIFO error in the given direction. - // Fifo(Direction), + /// An error occurred in the receive fifo. + ReceiveFifo, + /// An error occurred in the transmit fifo. + TransmitFifo, /// Bus is busy at the start of a transfer. Busy, - // /// Caller provided no data. - // NoData, + /// Caller provided no data. + NoData, } /// TODO diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 151e3b49..5ca57b5b 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,14 +1,16 @@ -use eh1::spi::MODE_0; +use eh1::spi::{SpiBus, MODE_0}; use super::{ dma::{FullDma, NoDma, PartialDma}, - Channel, Disabled, Lpspi, LpspiData, LpspiInterruptHandler, Pins, StatusWatcher, + Channel, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, ral, }; +mod eh1_impl; + impl<'a, const N: u8> Lpspi<'a, N, NoDma> { /// Create a new LPSPI peripheral without DMA support. /// @@ -51,7 +53,9 @@ impl<'a, const N: u8> Lpspi<'a, N, PartialDma> { } impl<'a, const N: u8> Lpspi<'a, N, FullDma> { - /// Create a new LPSPI peripheral with partial DMA support. + /// Create a new LPSPI peripheral with full DMA support. + /// + /// This is required for `async` operation. /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. @@ -155,17 +159,16 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// Temporarily disable the LPSPI peripheral. /// /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify - /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit - /// and receive FIFOs. + /// LPSPI settings that require a fully disabled peripheral. pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { - self.clear_fifos(); + SpiBus::::flush(self); let mut disabled = Disabled::new(self); func(&mut disabled) } /// Switches the SPI bus to interrupt based operation. /// - /// This increases efficiency drastically, as it avoids busy waiting. + /// This increases the efficiency of the `async` interface, as it avoids busy waiting. /// /// Note that it is the caller's responsibility to connect the interrupt source /// to the returned interrupt handler object. @@ -187,4 +190,35 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { fn clear_fifos(&mut self) { ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RTF: RTF_1, RRF: RRF_1); } + + /// Returns whether or not the busy flag is set. + fn busy(&self) -> bool { + ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) + } + + /// Waits for the current operation to finish, while performing error checks. + fn block_until_finished(&mut self) -> Result<(), LpspiError> { + while self.busy() { + self.check_errors()?; + } + self.check_errors() + } + + /// Returns errors, if any there are any. + fn check_errors(&mut self) -> Result<(), LpspiError> { + let (rx_error, tx_error) = ral::read_reg!(ral::lpspi, self.lpspi(), SR, REF, TEF); + + if tx_error != 0 || rx_error != 0 { + ral::write_reg!(ral::lpspi, self.lpspi(), SR, REF: rx_error, TEF: tx_error); + self.clear_fifos(); + + if tx_error != 0 { + Err(LpspiError::TransmitFifo) + } else { + Err(LpspiError::ReceiveFifo) + } + } else { + Ok(()) + } + } } diff --git a/src/common/lpspi/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs similarity index 94% rename from src/common/lpspi/eh1_impl.rs rename to src/common/lpspi/bus/eh1_impl.rs index 092f1ac0..5c79f1d0 100644 --- a/src/common/lpspi/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,4 +1,4 @@ -use super::{dma::FullDma, Lpspi, LpspiError}; +use super::{FullDma, Lpspi, LpspiError}; impl eh1::spi::ErrorType for Lpspi<'_, N, DMA> { type Error = LpspiError; @@ -22,7 +22,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { } fn flush(&mut self) -> Result<(), Self::Error> { - todo!() + self.block_until_finished() } } @@ -44,7 +44,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { } fn flush(&mut self) -> Result<(), Self::Error> { - todo!() + self.block_until_finished() } } @@ -66,7 +66,7 @@ impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { } fn flush(&mut self) -> Result<(), Self::Error> { - todo!() + self.block_until_finished() } } diff --git a/src/common/lpspi/error.rs b/src/common/lpspi/error.rs index 478bdfbd..9af6106f 100644 --- a/src/common/lpspi/error.rs +++ b/src/common/lpspi/error.rs @@ -6,6 +6,9 @@ impl Error for LpspiError { fn kind(&self) -> ErrorKind { match self { LpspiError::Busy => ErrorKind::Other, + LpspiError::ReceiveFifo => ErrorKind::Overrun, + LpspiError::TransmitFifo => ErrorKind::Other, + LpspiError::NoData => ErrorKind::Other, } } } diff --git a/src/common/lpspi/word_types.rs b/src/common/lpspi/word_types.rs new file mode 100644 index 00000000..4986e787 --- /dev/null +++ b/src/common/lpspi/word_types.rs @@ -0,0 +1,142 @@ +use super::LpspiError; + +// TODO: need two traits, one for &[X] and one for &mut[x]. +pub trait LpspiInputData { + /// Length in bytes + fn length(&self) -> usize; + + /// Retreive the next chunk to transmit. + /// Filled with zeros when overflown. + fn take(&mut self) -> u32; + + /// Store the next received chunk. + /// + fn store(&mut self, val: u32); +} + +pub trait AsLpspiInputData { + fn process( + &self, + f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, + ) -> Result<(), LpspiError>; +} + +struct U8LpspiInputData<'a> { + read_pos: usize, + write_pos: usize, + data: &'a [u8], +} +struct U16LpspiInputData<'a> { + read_pos: usize, + write_pos: usize, + data: &'a [u16], +} +struct U32LpspiInputData<'a> { + read_pos: usize, + write_pos: usize, + data: &'a [u32], +} + +impl AsLpspiInputData for &[u8] { + fn process( + &self, + f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, + ) -> Result<(), LpspiError> { + let mut data = U8LpspiInputData { + read_pos: 0, + write_pos: 0, + data: &self, + }; + + f(&mut data) + } +} +impl AsLpspiInputData for &[u16] { + fn process( + &self, + f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, + ) -> Result<(), LpspiError> { + let mut data = U16LpspiInputData { + read_pos: 0, + write_pos: 0, + data: &self, + }; + + f(&mut data) + } +} +impl AsLpspiInputData for &[u32] { + fn process( + &self, + f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, + ) -> Result<(), LpspiError> { + let mut data = U32LpspiInputData { + read_pos: 0, + write_pos: 0, + data: &self, + }; + + f(&mut data) + } +} + +impl LpspiInputData for U8LpspiInputData<'_> { + fn length(&self) -> usize { + self.data.len() + } + + fn take(&mut self) -> Option { + let mut output = 0; + + for _ in 0..4 { + output = output << 8; + output += u32::from(*self.data.get(self.read_pos)?); + self.read_pos += 1; + } + + Some(output) + } + + fn store(&mut self, val: u32) { + todo!() + } +} + +impl LpspiInputData for U16LpspiInputData<'_> { + fn length(&self) -> usize { + self.data.len() * 2 + } + + fn take(&mut self) -> u32 { + // Only check the first one, because we need to zero fill otherwise + let mut output = 0; + + for _ in 0..2 { + output = output << 16; + output += u32::from(self.data.get(self.read_pos).copied().unwrap_or_default()); + self.read_pos += 1; + } + + output + } + + fn store(&mut self, val: u32) { + todo!() + } +} + +impl LpspiInputData for U32LpspiInputData<'_> { + fn length(&self) -> usize { + self.data.len() * 4 + } + + fn take(&mut self) -> Option { + let data = *self.data.get(self.read_pos)?; + self.read_pos += 1; + Some(data) + } + + fn store(&mut self, val: u32) { + todo!() + } +} From a74fc6984ac4c9cd566af24a3c2db63f4f462dde Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 12:23:47 +0100 Subject: [PATCH 13/73] Add word_types --- rust-toolchain.toml | 4 - src/common/lpspi/word_types.rs | 229 +++++++++++++++++++-------------- 2 files changed, 129 insertions(+), 104 deletions(-) delete mode 100644 rust-toolchain.toml diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index fbbccb45..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "nightly" -components = ["rustfmt", "llvm-tools"] -targets = ["thumbv7em-none-eabihf"] diff --git a/src/common/lpspi/word_types.rs b/src/common/lpspi/word_types.rs index 4986e787..455020dc 100644 --- a/src/common/lpspi/word_types.rs +++ b/src/common/lpspi/word_types.rs @@ -1,142 +1,171 @@ use super::LpspiError; -// TODO: need two traits, one for &[X] and one for &mut[x]. -pub trait LpspiInputData { +/// A data type that can be used for LPSPI transfers +pub trait LpspiDataBuffer { /// Length in bytes fn length(&self) -> usize; - /// Retreive the next chunk to transmit. + /// Retreive a chunk to transmit. /// Filled with zeros when overflown. - fn take(&mut self) -> u32; - - /// Store the next received chunk. /// - fn store(&mut self, val: u32); -} + /// # Arguments + /// + /// * `pos` - The `[u32]`-offset from where to read. + /// + fn read(&self, pos: usize) -> u32; -pub trait AsLpspiInputData { - fn process( - &self, - f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, - ) -> Result<(), LpspiError>; + /// Store a next received chunk. + /// + /// # Arguments + /// + /// * `pos` - The `[u32]`-offset where to write. + /// * `val` - The data to write + /// + fn write(&mut self, pos: usize, val: u32); } -struct U8LpspiInputData<'a> { - read_pos: usize, - write_pos: usize, - data: &'a [u8], -} -struct U16LpspiInputData<'a> { - read_pos: usize, - write_pos: usize, - data: &'a [u16], -} -struct U32LpspiInputData<'a> { - read_pos: usize, - write_pos: usize, - data: &'a [u32], -} +impl LpspiDataBuffer for [u8] { + fn length(&self) -> usize { + self.len() + } -impl AsLpspiInputData for &[u8] { - fn process( - &self, - f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, - ) -> Result<(), LpspiError> { - let mut data = U8LpspiInputData { - read_pos: 0, - write_pos: 0, - data: &self, - }; + fn read(&self, pos: usize) -> u32 { + let pos = 4 * pos; - f(&mut data) - } -} -impl AsLpspiInputData for &[u16] { - fn process( - &self, - f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, - ) -> Result<(), LpspiError> { - let mut data = U16LpspiInputData { - read_pos: 0, - write_pos: 0, - data: &self, - }; - - f(&mut data) + u32::from_be_bytes([ + self.get(pos + 0).copied().unwrap_or_default(), + self.get(pos + 1).copied().unwrap_or_default(), + self.get(pos + 2).copied().unwrap_or_default(), + self.get(pos + 3).copied().unwrap_or_default(), + ]) } -} -impl AsLpspiInputData for &[u32] { - fn process( - &self, - f: &dyn FnOnce(&mut dyn LpspiInputData) -> Result<(), LpspiError>, - ) -> Result<(), LpspiError> { - let mut data = U32LpspiInputData { - read_pos: 0, - write_pos: 0, - data: &self, - }; - - f(&mut data) + + fn write(&mut self, pos: usize, val: u32) { + let pos = 4 * pos; + + for (offset, val) in val.to_be_bytes().into_iter().enumerate() { + if let Some(x) = self.get_mut(pos + offset) { + *x = val; + } + } } } -impl LpspiInputData for U8LpspiInputData<'_> { +impl LpspiDataBuffer for [u16] { fn length(&self) -> usize { - self.data.len() + self.len() * 2 } - fn take(&mut self) -> Option { - let mut output = 0; + fn read(&self, pos: usize) -> u32 { + let pos = 2 * pos; - for _ in 0..4 { - output = output << 8; - output += u32::from(*self.data.get(self.read_pos)?); - self.read_pos += 1; - } + let [b0, b1] = self.get(pos + 0).copied().unwrap_or_default().to_be_bytes(); + let [b2, b3] = self.get(pos + 1).copied().unwrap_or_default().to_be_bytes(); - Some(output) + u32::from_be_bytes([b0, b1, b2, b3]) } - fn store(&mut self, val: u32) { - todo!() + fn write(&mut self, pos: usize, val: u32) { + let pos = 2 * pos; + + let [b0, b1, b2, b3] = val.to_be_bytes(); + + if let Some(x) = self.get_mut(pos + 0) { + *x = u16::from_be_bytes([b0, b1]); + } + if let Some(x) = self.get_mut(pos + 1) { + *x = u16::from_be_bytes([b2, b3]); + } } } -impl LpspiInputData for U16LpspiInputData<'_> { +impl LpspiDataBuffer for [u32] { fn length(&self) -> usize { - self.data.len() * 2 + self.len() * 4 } - fn take(&mut self) -> u32 { - // Only check the first one, because we need to zero fill otherwise - let mut output = 0; + fn read(&self, pos: usize) -> u32 { + self.get(pos).copied().unwrap_or_default() + } - for _ in 0..2 { - output = output << 16; - output += u32::from(self.data.get(self.read_pos).copied().unwrap_or_default()); - self.read_pos += 1; + fn write(&mut self, pos: usize, val: u32) { + if let Some(x) = self.get_mut(pos) { + *x = val; } + } +} + +#[cfg(test)] +mod tests { + use super::*; - output + macro_rules! check_read { + ($t:ty, $data:expr, $pos:expr, $expected:expr) => {{ + let buf: &[$t] = &$data; + assert_eq!(buf.read($pos), $expected) + }}; } - fn store(&mut self, val: u32) { - todo!() + macro_rules! check_write { + ($init_val:expr, $pos:expr, $val:expr, $expected:expr) => {{ + let buf = &mut $init_val; + buf.write($pos, $val); + assert_eq!(buf, &$expected) + }}; } -} -impl LpspiInputData for U32LpspiInputData<'_> { - fn length(&self) -> usize { - self.data.len() * 4 + #[test] + fn read_u8() { + check_read!( + u8, + [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], + 0, + 0x12345678 + ); + check_read!( + u8, + [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], + 1, + 0x9abcde00 + ); + } + + #[test] + fn write_u8() { + check_write!([0u8; 5], 0, 0x12345678, [0x12, 0x34, 0x56, 0x78, 0]); + check_write!( + [0u8; 9], + 1, + 0x12345678, + [0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0] + ); + check_write!([0u8; 5], 1, 0x12345678, [0, 0, 0, 0, 0x12]); + } + + #[test] + fn read_u16() { + check_read!(u16, [0x1234, 0x5678, 0x9abc], 0, 0x12345678); + check_read!(u16, [0x1234, 0x5678, 0x9abc], 1, 0x9abc0000); + } + + #[test] + fn write_u16() { + check_write!([0u16; 3], 0, 0x12345678, [0x1234, 0x5678, 0]); + check_write!([0u16; 5], 1, 0x12345678, [0, 0, 0x1234, 0x5678, 0]); + check_write!([0u16; 3], 1, 0x12345678, [0, 0, 0x1234]); } - fn take(&mut self) -> Option { - let data = *self.data.get(self.read_pos)?; - self.read_pos += 1; - Some(data) + #[test] + fn read_u32() { + check_read!(u32, [0x12345678, 0x9abcdeff], 0, 0x12345678); + check_read!(u32, [0x12345678, 0x9abcdeff], 1, 0x9abcdeff); + check_read!(u32, [0x12345678, 0x9abcdeff], 2, 0); } - fn store(&mut self, val: u32) { - todo!() + #[test] + fn write_u32() { + check_write!([0u32; 2], 0, 0x12345678, [0x12345678, 0]); + check_write!([0u32; 2], 1, 0x12345678, [0, 0x12345678]); + check_write!([0u32; 2], 2, 0x12345678, [0, 0]); } } From e6ae1974c94fca9f9c2e61200a52ef5ef9177eef Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 12:37:35 +0100 Subject: [PATCH 14/73] Bla. --- src/common/lpspi/bus/eh1_impl.rs | 60 ++++++-------------------------- 1 file changed, 11 insertions(+), 49 deletions(-) diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 5c79f1d0..2d446888 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,67 +1,29 @@ +use crate::lpspi::word_types::LpspiDataBuffer; + use super::{FullDma, Lpspi, LpspiError}; impl eh1::spi::ErrorType for Lpspi<'_, N, DMA> { type Error = LpspiError; } -impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - todo!() - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.block_until_finished() - } -} - -impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { - fn read(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer(&mut self, read: &mut [u16], write: &[u16]) -> Result<(), Self::Error> { - todo!() - } - - fn transfer_in_place(&mut self, words: &mut [u16]) -> Result<(), Self::Error> { - todo!() - } - - fn flush(&mut self) -> Result<(), Self::Error> { - self.block_until_finished() - } -} - -impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> { - fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { +impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> +where + T: 'static + Copy, + [T]: LpspiDataBuffer, +{ + fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { todo!() } - fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { todo!() } - fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { todo!() } - fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { todo!() } From c91cfb3ad2b7350943d1228ef3cf1f5c62ddcc67 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 12:41:04 +0100 Subject: [PATCH 15/73] Add error handling to SPI bus --- src/common/lpspi/bus.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 5ca57b5b..23cd123e 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -160,10 +160,13 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify /// LPSPI settings that require a fully disabled peripheral. - pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { - SpiBus::::flush(self); + pub fn disabled( + &mut self, + func: impl FnOnce(&mut Disabled) -> R, + ) -> Result { + SpiBus::::flush(self)?; let mut disabled = Disabled::new(self); - func(&mut disabled) + Ok(func(&mut disabled)) } /// Switches the SPI bus to interrupt based operation. From b27d56ebe53dc43d40291af8c4b70576a9302519 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 15:57:21 +0100 Subject: [PATCH 16/73] Add data buffer tests --- board/src/teensy4.rs | 2 +- rust-toolchain.toml | 4 + src/common/lpspi.rs | 2 +- src/common/lpspi/bus.rs | 3 +- src/common/lpspi/bus/eh1_impl.rs | 2 +- src/common/lpspi/data_buffer.rs | 455 +++++++++++++++++++++++++++++++ src/common/lpspi/word_types.rs | 171 ------------ 7 files changed, 464 insertions(+), 175 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 src/common/lpspi/data_buffer.rs delete mode 100644 src/common/lpspi/word_types.rs diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index 317dc615..de67580e 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -179,7 +179,7 @@ impl Specifics { }; #[cfg(not(feature = "spi"))] #[allow(clippy::let_unit_value)] - let spi = (); + let spi = ((), ()); let lpi2c3 = unsafe { ral::lpi2c::LPI2C3::instance() }; let i2c = I2c::new( diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..fa481889 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +# components = ["rustfmt", "llvm-tools"] +# targets = ["thumbv7em-none-eabihf"] diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 0a02c3fb..ea119e56 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -7,11 +7,11 @@ use imxrt_dma::channel::Channel; use crate::ral; mod bus; +mod data_buffer; mod disabled; mod dma; mod error; mod status_watcher; -mod word_types; use status_watcher::StatusWatcher; diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 23cd123e..26d296c3 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -136,7 +136,8 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { this.disabled(|bus| { bus.set_clock_hz(1_000_000); bus.set_mode(MODE_0) - }); + }) + .unwrap(); // Configure pins lpspi::prepare(&mut pins.sdo); diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 2d446888..353ba55b 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,4 +1,4 @@ -use crate::lpspi::word_types::LpspiDataBuffer; +use crate::lpspi::data_buffer::LpspiDataBuffer; use super::{FullDma, Lpspi, LpspiError}; diff --git a/src/common/lpspi/data_buffer.rs b/src/common/lpspi/data_buffer.rs new file mode 100644 index 00000000..762a4a9e --- /dev/null +++ b/src/common/lpspi/data_buffer.rs @@ -0,0 +1,455 @@ +use core::ops::Range; + +/// A data type that can be used for LPSPI transfers +pub trait LpspiDataBuffer { + /// Length in bytes + fn bytecount(&self) -> usize; + + /// Retreive a chunk to transmit. + /// Filled with zeros when overflown. + /// + /// # Arguments + /// + /// * `pos` - The `[u32]`-offset from where to read. + /// + fn read(&self, pos: usize) -> u32; + + /// Store a next received chunk. + /// + /// # Arguments + /// + /// * `pos` - The `[u32]`-offset where to write. + /// * `val` - The data to write + /// + fn write(&mut self, pos: usize, val: u32); + + fn index_chunks(&self, chunk_size: u32) -> LpspiIndexChunks { + let chunk_size = (chunk_size / 4) * 4; // Round down to next divisible by 4 + + LpspiIndexChunks { + bytecount: self.bytecount(), + offset: 0, + chunk_size, + } + } +} + +impl LpspiDataBuffer for [u8] { + fn bytecount(&self) -> usize { + self.len() + } + + fn read(&self, pos: usize) -> u32 { + let pos = 4 * pos; + + u32::from_be_bytes([ + self.get(pos + 0).copied().unwrap_or_default(), + self.get(pos + 1).copied().unwrap_or_default(), + self.get(pos + 2).copied().unwrap_or_default(), + self.get(pos + 3).copied().unwrap_or_default(), + ]) + } + + fn write(&mut self, pos: usize, val: u32) { + let pos = 4 * pos; + + for (offset, val) in val.to_be_bytes().into_iter().enumerate() { + if let Some(x) = self.get_mut(pos + offset) { + *x = val; + } + } + } +} + +impl LpspiDataBuffer for [u16] { + fn bytecount(&self) -> usize { + self.len() * 2 + } + + fn read(&self, pos: usize) -> u32 { + let pos = 2 * pos; + + let [b0, b1] = self.get(pos + 0).copied().unwrap_or_default().to_be_bytes(); + let [b2, b3] = self.get(pos + 1).copied().unwrap_or_default().to_be_bytes(); + + u32::from_be_bytes([b0, b1, b2, b3]) + } + + fn write(&mut self, pos: usize, val: u32) { + let pos = 2 * pos; + + let [b0, b1, b2, b3] = val.to_be_bytes(); + + if let Some(x) = self.get_mut(pos + 0) { + *x = u16::from_be_bytes([b0, b1]); + } + if let Some(x) = self.get_mut(pos + 1) { + *x = u16::from_be_bytes([b2, b3]); + } + } +} + +impl LpspiDataBuffer for [u32] { + fn bytecount(&self) -> usize { + self.len() * 4 + } + + fn read(&self, pos: usize) -> u32 { + self.get(pos).copied().unwrap_or_default() + } + + fn write(&mut self, pos: usize, val: u32) { + if let Some(x) = self.get_mut(pos) { + *x = val; + } + } +} + +pub struct LpspiIndexChunks { + bytecount: usize, + offset: usize, + chunk_size: u32, +} + +impl Iterator for LpspiIndexChunks { + type Item = LpspiIndexChunk; + + fn next(&mut self) -> Option { + let offset_bytes = self.offset * 4; + if offset_bytes >= self.bytecount { + return None; + } + + let leftover_bytes = self.bytecount - offset_bytes; + let chunk_bytes = leftover_bytes.min(usize::try_from(self.chunk_size).unwrap()); + let chunk_u32s = (chunk_bytes + 3) / 4; // Round up + + let chunk = LpspiIndexChunk { + bytecount: u32::try_from(chunk_bytes).unwrap(), + offsets: self.offset..(self.offset + chunk_u32s), + }; + + self.offset += chunk_u32s; + + Some(chunk) + } +} + +#[derive(PartialEq, Eq, Clone)] +pub struct LpspiIndexChunk { + bytecount: u32, + offsets: Range, +} + +impl LpspiIndexChunk { + pub fn bytecount(&self) -> u32 { + self.bytecount + } + pub fn offsets(&self) -> Range { + self.offsets.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + extern crate std; + use std::vec::Vec; + + macro_rules! check_read { + ($t:ty, $data:expr, $pos:expr, $expected:expr) => {{ + let buf: &[$t] = &$data; + assert_eq!(buf.read($pos), $expected) + }}; + } + + macro_rules! check_write { + ($init_val:expr, $pos:expr, $val:expr, $expected:expr) => {{ + let buf = &mut $init_val; + buf.write($pos, $val); + assert_eq!(buf, &$expected) + }}; + } + + #[test] + fn bytecount_u8() { + assert_eq!([0x12u8, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde].bytecount(), 7); + } + #[test] + fn bytecount_u16() { + assert_eq!([0x1234u16, 0x5678, 0x9abc].bytecount(), 6); + } + #[test] + fn bytecount_u32() { + assert_eq!([0x12345678u32, 0x9abcdeff].bytecount(), 8); + } + + #[test] + fn read_u8() { + check_read!( + u8, + [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], + 0, + 0x12345678 + ); + check_read!( + u8, + [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], + 1, + 0x9abcde00 + ); + } + + #[test] + fn write_u8() { + check_write!([0u8; 5], 0, 0x12345678, [0x12, 0x34, 0x56, 0x78, 0]); + check_write!( + [0u8; 9], + 1, + 0x12345678, + [0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0] + ); + check_write!([0u8; 5], 1, 0x12345678, [0, 0, 0, 0, 0x12]); + } + + #[test] + fn read_u16() { + check_read!(u16, [0x1234, 0x5678, 0x9abc], 0, 0x12345678); + check_read!(u16, [0x1234, 0x5678, 0x9abc], 1, 0x9abc0000); + } + + #[test] + fn write_u16() { + check_write!([0u16; 3], 0, 0x12345678, [0x1234, 0x5678, 0]); + check_write!([0u16; 5], 1, 0x12345678, [0, 0, 0x1234, 0x5678, 0]); + check_write!([0u16; 3], 1, 0x12345678, [0, 0, 0x1234]); + } + + #[test] + fn read_u32() { + check_read!(u32, [0x12345678, 0x9abcdeff], 0, 0x12345678); + check_read!(u32, [0x12345678, 0x9abcdeff], 1, 0x9abcdeff); + check_read!(u32, [0x12345678, 0x9abcdeff], 2, 0); + } + + #[test] + fn write_u32() { + check_write!([0u32; 2], 0, 0x12345678, [0x12345678, 0]); + check_write!([0u32; 2], 1, 0x12345678, [0, 0x12345678]); + check_write!([0u32; 2], 2, 0x12345678, [0, 0]); + } + + macro_rules! check_chunks { + ($t:ty, $count:expr, $chunksize:expr, $expected:expr) => {{ + let chunk_iter = [<$t>::MIN; $count].index_chunks($chunksize); + + let actual_owned = chunk_iter + .map(|c| (c.bytecount(), c.offsets().collect::>())) + .collect::)>>(); + + let actual = actual_owned + .iter() + .map(|(count, range)| (*count, range.as_slice())) + .collect::>(); + + let expected: &[(u32, &[usize])] = &$expected; + + assert_eq!(actual, expected); + }}; + } + + #[test] + fn chunksize_u8() { + check_chunks!( + u8, + 41, + 17, + [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (9, &[8, 9, 10])] + ); + check_chunks!(u8, 0, 9, []); + check_chunks!(u8, 1, 9, [(1, &[0])]); + check_chunks!(u8, 2, 9, [(2, &[0])]); + check_chunks!(u8, 3, 9, [(3, &[0])]); + check_chunks!(u8, 4, 9, [(4, &[0])]); + check_chunks!(u8, 5, 9, [(5, &[0, 1])]); + check_chunks!(u8, 6, 9, [(6, &[0, 1])]); + check_chunks!(u8, 7, 9, [(7, &[0, 1])]); + check_chunks!(u8, 8, 9, [(8, &[0, 1])]); + check_chunks!(u8, 9, 9, [(8, &[0, 1]), (1, &[2])]); + check_chunks!(u8, 10, 9, [(8, &[0, 1]), (2, &[2])]); + check_chunks!(u8, 11, 9, [(8, &[0, 1]), (3, &[2])]); + check_chunks!(u8, 12, 9, [(8, &[0, 1]), (4, &[2])]); + check_chunks!(u8, 13, 9, [(8, &[0, 1]), (5, &[2, 3])]); + check_chunks!(u8, 14, 9, [(8, &[0, 1]), (6, &[2, 3])]); + check_chunks!(u8, 15, 9, [(8, &[0, 1]), (7, &[2, 3])]); + check_chunks!(u8, 16, 9, [(8, &[0, 1]), (8, &[2, 3])]); + check_chunks!(u8, 17, 9, [(8, &[0, 1]), (8, &[2, 3]), (1, &[4])]); + } + + #[test] + fn chunksize_u16() { + check_chunks!( + u16, + 21, + 17, + [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (10, &[8, 9, 10])] + ); + check_chunks!(u16, 0, 9, []); + check_chunks!(u16, 1, 9, [(2, &[0])]); + check_chunks!(u16, 2, 9, [(4, &[0])]); + check_chunks!(u16, 3, 9, [(6, &[0, 1])]); + check_chunks!(u16, 4, 9, [(8, &[0, 1])]); + check_chunks!(u16, 5, 9, [(8, &[0, 1]), (2, &[2])]); + check_chunks!(u16, 6, 9, [(8, &[0, 1]), (4, &[2])]); + check_chunks!(u16, 7, 9, [(8, &[0, 1]), (6, &[2, 3])]); + check_chunks!(u16, 8, 9, [(8, &[0, 1]), (8, &[2, 3])]); + check_chunks!(u16, 9, 9, [(8, &[0, 1]), (8, &[2, 3]), (2, &[4])]); + } + + #[test] + fn chunksize_u32() { + check_chunks!( + u32, + 11, + 17, + [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (12, &[8, 9, 10])] + ); + check_chunks!(u32, 0, 9, []); + check_chunks!(u32, 1, 9, [(4, &[0])]); + check_chunks!(u32, 2, 9, [(8, &[0, 1])]); + check_chunks!(u32, 3, 9, [(8, &[0, 1]), (4, &[2])]); + check_chunks!(u32, 4, 9, [(8, &[0, 1]), (8, &[2, 3])]); + check_chunks!(u32, 5, 9, [(8, &[0, 1]), (8, &[2, 3]), (4, &[4])]); + } + + #[test] + fn combined_u8() { + let data = { + let mut e = [0u8; 99]; + e.iter_mut() + .enumerate() + .for_each(|(i, v)| *v = (i + 1) as u8); + e + }; + + let mut data_out = [0u8; 100]; + + let chunks = data.index_chunks(9); + + let data_collected = chunks + .flat_map(|chunk| { + let mut local = std::vec![0u8; chunk.bytecount() as usize]; + let mut local_offset = 0; + for offset in chunk.offsets() { + let val = data.read(offset); + data_out.write(offset, val); + for val in val.to_be_bytes() { + if local_offset < chunk.bytecount() as usize { + local[local_offset] = val; + } + local_offset += 1; + } + } + local + }) + .collect::>(); + + assert_eq!(&data_out[..data.len()], data); + assert_eq!(data_collected, data); + + data_out[data.len()..] + .iter() + .for_each(|val| assert_eq!(*val, 0)); + } + + #[test] + fn combined_u16() { + let data = { + let mut e = [0u16; 99]; + e.iter_mut() + .enumerate() + .for_each(|(i, v)| *v = (((2 * i + 1) << 8) + 2 * i + 2) as u16); + e + }; + + let mut data_out = [0u16; 100]; + + let chunks = data.index_chunks(9); + + let data_collected = chunks + .flat_map(|chunk| { + let mut local = std::vec![0u8; chunk.bytecount() as usize]; + let mut local_offset = 0; + for offset in chunk.offsets() { + let val = data.read(offset); + data_out.write(offset, val); + for val in val.to_be_bytes() { + if local_offset < chunk.bytecount() as usize { + local[local_offset] = val; + } + local_offset += 1; + } + } + local + }) + .collect::>(); + + assert_eq!(&data_out[..data.len()], data); + data_out[data.len()..] + .iter() + .for_each(|val| assert_eq!(*val, 0)); + + assert_eq!( + data_collected, + data.iter() + .flat_map(|v| v.to_be_bytes()) + .collect::>() + ) + } + + #[test] + fn combined_u32() { + let data = { + let mut e = [0u32; 99]; + e.iter_mut().enumerate().for_each(|(i, v)| { + *v = (((((((4 * i + 1) << 8) + 4 * i + 2) << 8) + 4 * i + 3) << 8) + 4 * i + 4) + as u32 + }); + e + }; + + let mut data_out = [0u32; 100]; + + let chunks = data.index_chunks(9); + + let data_collected = chunks + .flat_map(|chunk| { + let mut local = std::vec![0u8; chunk.bytecount() as usize]; + let mut local_offset = 0; + for offset in chunk.offsets() { + let val = data.read(offset); + data_out.write(offset, val); + for val in val.to_be_bytes() { + if local_offset < chunk.bytecount() as usize { + local[local_offset] = val; + } + local_offset += 1; + } + } + local + }) + .collect::>(); + + assert_eq!(&data_out[..data.len()], data); + data_out[data.len()..] + .iter() + .for_each(|val| assert_eq!(*val, 0)); + + assert_eq!( + data_collected, + data.iter() + .flat_map(|v| v.to_be_bytes()) + .collect::>() + ) + } +} diff --git a/src/common/lpspi/word_types.rs b/src/common/lpspi/word_types.rs deleted file mode 100644 index 455020dc..00000000 --- a/src/common/lpspi/word_types.rs +++ /dev/null @@ -1,171 +0,0 @@ -use super::LpspiError; - -/// A data type that can be used for LPSPI transfers -pub trait LpspiDataBuffer { - /// Length in bytes - fn length(&self) -> usize; - - /// Retreive a chunk to transmit. - /// Filled with zeros when overflown. - /// - /// # Arguments - /// - /// * `pos` - The `[u32]`-offset from where to read. - /// - fn read(&self, pos: usize) -> u32; - - /// Store a next received chunk. - /// - /// # Arguments - /// - /// * `pos` - The `[u32]`-offset where to write. - /// * `val` - The data to write - /// - fn write(&mut self, pos: usize, val: u32); -} - -impl LpspiDataBuffer for [u8] { - fn length(&self) -> usize { - self.len() - } - - fn read(&self, pos: usize) -> u32 { - let pos = 4 * pos; - - u32::from_be_bytes([ - self.get(pos + 0).copied().unwrap_or_default(), - self.get(pos + 1).copied().unwrap_or_default(), - self.get(pos + 2).copied().unwrap_or_default(), - self.get(pos + 3).copied().unwrap_or_default(), - ]) - } - - fn write(&mut self, pos: usize, val: u32) { - let pos = 4 * pos; - - for (offset, val) in val.to_be_bytes().into_iter().enumerate() { - if let Some(x) = self.get_mut(pos + offset) { - *x = val; - } - } - } -} - -impl LpspiDataBuffer for [u16] { - fn length(&self) -> usize { - self.len() * 2 - } - - fn read(&self, pos: usize) -> u32 { - let pos = 2 * pos; - - let [b0, b1] = self.get(pos + 0).copied().unwrap_or_default().to_be_bytes(); - let [b2, b3] = self.get(pos + 1).copied().unwrap_or_default().to_be_bytes(); - - u32::from_be_bytes([b0, b1, b2, b3]) - } - - fn write(&mut self, pos: usize, val: u32) { - let pos = 2 * pos; - - let [b0, b1, b2, b3] = val.to_be_bytes(); - - if let Some(x) = self.get_mut(pos + 0) { - *x = u16::from_be_bytes([b0, b1]); - } - if let Some(x) = self.get_mut(pos + 1) { - *x = u16::from_be_bytes([b2, b3]); - } - } -} - -impl LpspiDataBuffer for [u32] { - fn length(&self) -> usize { - self.len() * 4 - } - - fn read(&self, pos: usize) -> u32 { - self.get(pos).copied().unwrap_or_default() - } - - fn write(&mut self, pos: usize, val: u32) { - if let Some(x) = self.get_mut(pos) { - *x = val; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - macro_rules! check_read { - ($t:ty, $data:expr, $pos:expr, $expected:expr) => {{ - let buf: &[$t] = &$data; - assert_eq!(buf.read($pos), $expected) - }}; - } - - macro_rules! check_write { - ($init_val:expr, $pos:expr, $val:expr, $expected:expr) => {{ - let buf = &mut $init_val; - buf.write($pos, $val); - assert_eq!(buf, &$expected) - }}; - } - - #[test] - fn read_u8() { - check_read!( - u8, - [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], - 0, - 0x12345678 - ); - check_read!( - u8, - [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], - 1, - 0x9abcde00 - ); - } - - #[test] - fn write_u8() { - check_write!([0u8; 5], 0, 0x12345678, [0x12, 0x34, 0x56, 0x78, 0]); - check_write!( - [0u8; 9], - 1, - 0x12345678, - [0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0] - ); - check_write!([0u8; 5], 1, 0x12345678, [0, 0, 0, 0, 0x12]); - } - - #[test] - fn read_u16() { - check_read!(u16, [0x1234, 0x5678, 0x9abc], 0, 0x12345678); - check_read!(u16, [0x1234, 0x5678, 0x9abc], 1, 0x9abc0000); - } - - #[test] - fn write_u16() { - check_write!([0u16; 3], 0, 0x12345678, [0x1234, 0x5678, 0]); - check_write!([0u16; 5], 1, 0x12345678, [0, 0, 0x1234, 0x5678, 0]); - check_write!([0u16; 3], 1, 0x12345678, [0, 0, 0x1234]); - } - - #[test] - fn read_u32() { - check_read!(u32, [0x12345678, 0x9abcdeff], 0, 0x12345678); - check_read!(u32, [0x12345678, 0x9abcdeff], 1, 0x9abcdeff); - check_read!(u32, [0x12345678, 0x9abcdeff], 2, 0); - } - - #[test] - fn write_u32() { - check_write!([0u32; 2], 0, 0x12345678, [0x12345678, 0]); - check_write!([0u32; 2], 1, 0x12345678, [0, 0x12345678]); - check_write!([0u32; 2], 2, 0x12345678, [0, 0]); - } -} From 8711814d11a65aad271d9e57c521417a23e5ab05 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 18:18:39 +0100 Subject: [PATCH 17/73] Implement blocking transfer --- src/common/lpspi/bus.rs | 103 ++++++++++++++++++++++++++----- src/common/lpspi/bus/eh1_impl.rs | 12 ++-- src/common/lpspi/data_buffer.rs | 29 ++++----- 3 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 26d296c3..e7981595 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,6 +1,7 @@ -use eh1::spi::{SpiBus, MODE_0}; +use eh1::spi::MODE_0; use super::{ + data_buffer::{LpspiDataBuffer, LpspiIndexChunks}, dma::{FullDma, NoDma, PartialDma}, Channel, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; @@ -11,6 +12,8 @@ use crate::{ mod eh1_impl; +const MAX_FRAME_SIZE_BIT: u32 = 1 << 12; + impl<'a, const N: u8> Lpspi<'a, N, NoDma> { /// Create a new LPSPI peripheral without DMA support. /// @@ -136,8 +139,7 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { this.disabled(|bus| { bus.set_clock_hz(1_000_000); bus.set_mode(MODE_0) - }) - .unwrap(); + }); // Configure pins lpspi::prepare(&mut pins.sdo); @@ -161,13 +163,13 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify /// LPSPI settings that require a fully disabled peripheral. - pub fn disabled( - &mut self, - func: impl FnOnce(&mut Disabled) -> R, - ) -> Result { - SpiBus::::flush(self)?; + pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { + // Disable DMA and clear fifos + ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: RDDE_0, TDDE: TDDE_0); + self.clear_fifos(); + let mut disabled = Disabled::new(self); - Ok(func(&mut disabled)) + func(&mut disabled) } /// Switches the SPI bus to interrupt based operation. @@ -200,14 +202,6 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) } - /// Waits for the current operation to finish, while performing error checks. - fn block_until_finished(&mut self) -> Result<(), LpspiError> { - while self.busy() { - self.check_errors()?; - } - self.check_errors() - } - /// Returns errors, if any there are any. fn check_errors(&mut self) -> Result<(), LpspiError> { let (rx_error, tx_error) = ral::read_reg!(ral::lpspi, self.lpspi(), SR, REF, TEF); @@ -225,4 +219,79 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { Ok(()) } } + + /// Prepares the device for a blocking transfer + fn blocking_pre_transfer(&mut self) -> Result<(), LpspiError> { + if self.busy() { + return Err(LpspiError::Busy); + } + + // Disable DMA + ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: RDDE_0, TDDE: TDDE_0); + self.clear_fifos(); + + self.data.lpspi.clear_transfer_complete(); + + Ok(()) + } + + fn fifo_read_data_available(&mut self) -> bool { + ral::read_reg!(ral::lpspi, self.lpspi(), RSR, RXEMPTY == RXEMPTY_0) + } + + fn fifo_write_space_available(&mut self) -> bool { + ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) + } + + /// Writes to the bus + fn blocking_transfer( + &mut self, + tx_buffer: &[T], + rx_buffer: &mut [T], + ) -> Result<(), LpspiError> + where + [T]: LpspiDataBuffer, + { + self.blocking_pre_transfer()?; + + let size = tx_buffer.bytecount().max(rx_buffer.bytecount()); + if size < 1 { + return Err(LpspiError::NoData); + } + + let mut rx_offset = 0; + let mut do_receive = |this: &mut Self| -> Result<(), LpspiError> { + while this.fifo_read_data_available() { + this.check_errors()?; + let rx_data = ral::read_reg!(ral::lpspi, this.lpspi(), RDR); + rx_buffer.write(rx_offset, rx_data); + rx_offset += 1; + } + Ok(()) + }; + + for chunk in LpspiIndexChunks::new(size, MAX_FRAME_SIZE_BIT / 8) { + self.check_errors()?; + ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + RXMSK: RXMSK_0, + TXMSK: TXMSK_1, + FRAMESZ: chunk.bytecount() * 8 + ); + + for tx_offset in chunk.offsets() { + while !self.fifo_write_space_available() { + do_receive(self)?; + self.check_errors()?; + } + ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_buffer.read(tx_offset)); + } + } + + while !self.data.lpspi.poll_transfer_complete() { + do_receive(self)?; + } + do_receive(self)?; + + self.check_errors() + } } diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 353ba55b..cf33bb6f 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -12,15 +12,15 @@ where [T]: LpspiDataBuffer, { fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(&[], words) } fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(words, &mut []) } fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(write, read) } fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { @@ -28,7 +28,8 @@ where } fn flush(&mut self) -> Result<(), Self::Error> { - self.block_until_finished() + // Nothing to do, all other calls only return after the device is finished + Ok(()) } } @@ -52,6 +53,7 @@ impl eh1_async::spi::SpiBus for Lpspi<'_, N, FullDma> { } async fn flush(&mut self) -> Result<(), Self::Error> { - todo!() + // Nothing to do, all other calls only return after the device is finished + Ok(()) } } diff --git a/src/common/lpspi/data_buffer.rs b/src/common/lpspi/data_buffer.rs index 762a4a9e..3599d632 100644 --- a/src/common/lpspi/data_buffer.rs +++ b/src/common/lpspi/data_buffer.rs @@ -22,16 +22,6 @@ pub trait LpspiDataBuffer { /// * `val` - The data to write /// fn write(&mut self, pos: usize, val: u32); - - fn index_chunks(&self, chunk_size: u32) -> LpspiIndexChunks { - let chunk_size = (chunk_size / 4) * 4; // Round down to next divisible by 4 - - LpspiIndexChunks { - bytecount: self.bytecount(), - offset: 0, - chunk_size, - } - } } impl LpspiDataBuffer for [u8] { @@ -111,6 +101,17 @@ pub struct LpspiIndexChunks { chunk_size: u32, } +impl LpspiIndexChunks { + pub fn new(bytecount: usize, chunk_size: u32) -> Self { + let chunk_size = (chunk_size / 4) * 4; // Round down to next divisible by 4 + Self { + bytecount, + offset: 0, + chunk_size, + } + } +} + impl Iterator for LpspiIndexChunks { type Item = LpspiIndexChunk; @@ -241,7 +242,7 @@ mod tests { macro_rules! check_chunks { ($t:ty, $count:expr, $chunksize:expr, $expected:expr) => {{ - let chunk_iter = [<$t>::MIN; $count].index_chunks($chunksize); + let chunk_iter = LpspiIndexChunks::new([<$t>::MIN; $count].bytecount(), $chunksize); let actual_owned = chunk_iter .map(|c| (c.bytecount(), c.offsets().collect::>())) @@ -334,7 +335,7 @@ mod tests { let mut data_out = [0u8; 100]; - let chunks = data.index_chunks(9); + let chunks = LpspiIndexChunks::new(data.bytecount(), 9); let data_collected = chunks .flat_map(|chunk| { @@ -374,7 +375,7 @@ mod tests { let mut data_out = [0u16; 100]; - let chunks = data.index_chunks(9); + let chunks = LpspiIndexChunks::new(data.bytecount(), 9); let data_collected = chunks .flat_map(|chunk| { @@ -420,7 +421,7 @@ mod tests { let mut data_out = [0u32; 100]; - let chunks = data.index_chunks(9); + let chunks = LpspiIndexChunks::new(data.bytecount(), 9); let data_collected = chunks .flat_map(|chunk| { From ddef8d82707702d436f5c4b5be8b63688c0142dd Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 18:34:26 +0100 Subject: [PATCH 18/73] Refactoring to enable in-place transfer --- src/common/lpspi/bus.rs | 32 ++++++++++++++++++-------------- src/common/lpspi/bus/eh1_impl.rs | 10 +++++----- src/common/lpspi/data_buffer.rs | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index e7981595..a177e609 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,7 +1,7 @@ use eh1::spi::MODE_0; use super::{ - data_buffer::{LpspiDataBuffer, LpspiIndexChunks}, + data_buffer::{LpspiDataBuffer, LpspiIndexChunks, TransferBuffer}, dma::{FullDma, NoDma, PartialDma}, Channel, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; @@ -243,28 +243,27 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } - /// Writes to the bus - fn blocking_transfer( - &mut self, - tx_buffer: &[T], - rx_buffer: &mut [T], - ) -> Result<(), LpspiError> + /// Read + write into separate buffers + fn blocking_transfer(&mut self, mut buffers: TransferBuffer) -> Result<(), LpspiError> where [T]: LpspiDataBuffer, { self.blocking_pre_transfer()?; - let size = tx_buffer.bytecount().max(rx_buffer.bytecount()); + let size = buffers + .tx_buffer() + .bytecount() + .max(buffers.rx_buffer().bytecount()); if size < 1 { return Err(LpspiError::NoData); } let mut rx_offset = 0; - let mut do_receive = |this: &mut Self| -> Result<(), LpspiError> { + let mut do_receive = |this: &mut Self, buffer: &mut [T]| -> Result<(), LpspiError> { while this.fifo_read_data_available() { this.check_errors()?; let rx_data = ral::read_reg!(ral::lpspi, this.lpspi(), RDR); - rx_buffer.write(rx_offset, rx_data); + buffer.write(rx_offset, rx_data); rx_offset += 1; } Ok(()) @@ -280,17 +279,22 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { for tx_offset in chunk.offsets() { while !self.fifo_write_space_available() { - do_receive(self)?; + do_receive(self, buffers.rx_buffer())?; self.check_errors()?; } - ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_buffer.read(tx_offset)); + ral::write_reg!( + ral::lpspi, + self.lpspi(), + TDR, + buffers.tx_buffer().read(tx_offset) + ); } } while !self.data.lpspi.poll_transfer_complete() { - do_receive(self)?; + do_receive(self, buffers.rx_buffer())?; } - do_receive(self)?; + do_receive(self, buffers.rx_buffer())?; self.check_errors() } diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index cf33bb6f..4b535634 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,4 +1,4 @@ -use crate::lpspi::data_buffer::LpspiDataBuffer; +use crate::lpspi::data_buffer::{LpspiDataBuffer, TransferBuffer}; use super::{FullDma, Lpspi, LpspiError}; @@ -12,19 +12,19 @@ where [T]: LpspiDataBuffer, { fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.blocking_transfer(&[], words) + self.blocking_transfer(TransferBuffer::Dual(words, &[])) } fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - self.blocking_transfer(words, &mut []) + self.blocking_transfer(TransferBuffer::Dual(&mut [], words)) } fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - self.blocking_transfer(write, read) + self.blocking_transfer(TransferBuffer::Dual(read, write)) } fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(TransferBuffer::Single(words)) } fn flush(&mut self) -> Result<(), Self::Error> { diff --git a/src/common/lpspi/data_buffer.rs b/src/common/lpspi/data_buffer.rs index 3599d632..e1eb17b1 100644 --- a/src/common/lpspi/data_buffer.rs +++ b/src/common/lpspi/data_buffer.rs @@ -1,5 +1,25 @@ use core::ops::Range; +pub enum TransferBuffer<'a, T> { + Single(&'a mut [T]), + Dual(&'a mut [T], &'a [T]), +} + +impl TransferBuffer<'_, T> { + pub fn tx_buffer(&self) -> &[T] { + match self { + TransferBuffer::Single(x) => x, + TransferBuffer::Dual(_, x) => x, + } + } + pub fn rx_buffer(&mut self) -> &mut [T] { + match self { + TransferBuffer::Single(x) => x, + TransferBuffer::Dual(x, _) => x, + } + } +} + /// A data type that can be used for LPSPI transfers pub trait LpspiDataBuffer { /// Length in bytes From 210e86f129489f4c49d93fdcc7961e1f3bdd678f Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 18:35:48 +0100 Subject: [PATCH 19/73] Remove unused variable --- src/common/lpspi.rs | 1 - src/common/lpspi/bus.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index ea119e56..70f067f7 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -52,7 +52,6 @@ pub struct Lpspi<'a, const N: u8, DMA> { dma: DMA, source_clock_hz: u32, data: &'a LpspiData, - rx_fifo_size: u32, tx_fifo_size: u32, } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index a177e609..f30f1047 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -107,9 +107,7 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { SDI: lpspi::Pin, Signal = lpspi::Sdi>, SCK: lpspi::Pin, Signal = lpspi::Sck>, { - let (rx_fifo_size_exp, tx_fifo_size_exp) = - ral::read_reg!(ral::lpspi, lpspi, PARAM, RXFIFO, TXFIFO); - let rx_fifo_size = 1 << rx_fifo_size_exp; + let tx_fifo_size_exp = ral::read_reg!(ral::lpspi, lpspi, PARAM, TXFIFO); let tx_fifo_size = 1 << tx_fifo_size_exp; let data = LpspiData { @@ -120,7 +118,6 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { source_clock_hz, dma, data: data_storage.insert(data), - rx_fifo_size, tx_fifo_size, }; From af51c0a6238626de213f459bf07e072d293d6cf3 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 19:40:56 +0100 Subject: [PATCH 20/73] First time compiling! Not working yet, though. --- Cargo.toml | 8 +++- board/src/teensy4.rs | 14 +++++-- examples/rtic_spi.rs | 25 ++++++------ run.bat | 2 +- rust-toolchain.toml | 2 +- src/common/gpio.rs | 15 ++++++++ src/common/lpspi.rs | 1 + src/common/lpspi/bus.rs | 65 ++++++-------------------------- src/common/lpspi/bus/eh1_impl.rs | 8 ++-- src/common/lpspi/dma.rs | 4 +- 10 files changed, 64 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index edccbfba..018d11ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,13 +147,17 @@ codegen-units = 256 imxrt-rt = { workspace = true } menu = "0.4.0" rtic = { version = "2.0.1", features = ["thumbv7-backend"] } -rtic-monotonics = { version = "1.2.0", features = ["cortex-m-systick"] } +rtic-monotonics = { version = "1.2.0", features = [ + "cortex-m-systick", + "embedded-hal-async", +] } log = "0.4" defmt = "0.3" pin-utils = "0.1" usb-device = { version = "0.2", features = ["test-class-high-speed"] } usbd-serial = "0.1" usbd-hid = "0.6" +embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] } [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dev-dependencies] board = { path = "board" } @@ -168,7 +172,7 @@ required-features = ["board/spi"] [[example]] name = "rtic_spi" -required-features = ["board/spi"] +#required-features = ["board/spi"] [[example]] name = "hal_logging" diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index de67580e..81ede751 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -55,17 +55,21 @@ pub type SpiPins = hal::lpspi::Pins< /// Activate the `"spi"` feature to configure the SPI peripheral. mod lpspi_types { pub type SpiBus = (); + pub type SpiBusDma = (); pub type SpiCsPin = (); - pub type SpiDevice = (); + pub type SpiInterruptHandler = (); } #[cfg(feature = "spi")] /// SPI peripheral. mod lpspi_types { + use hal::lpspi::{FullDma, NoDma}; + use super::*; - pub type SpiBus = hal::lpspi::LpspiBus<4>; + pub type SpiBus = hal::lpspi::Lpspi<'static, 4, NoDma>; + pub type SpiBusDma = hal::lpspi::Lpspi<'static, 4, FullDma>; pub type SpiCsPin = hal::gpio::Output; - pub type SpiDevice = hal::lpspi::LpspiDevice<4, iomuxc::gpio_b0::GPIO_B0_00>; + pub type SpiInterruptHandler = hal::lpspi::LpspiInterruptHandler<'static, 4>; } pub use lpspi_types::*; @@ -173,7 +177,9 @@ impl Specifics { unsafe { &mut SPI_DATA }, super::LPSPI_CLK_FREQUENCY, ); - spi.set_baud_rate(super::SPI_BAUD_RATE_FREQUENCY); + spi.disabled(|bus| { + bus.set_clock_hz(super::SPI_BAUD_RATE_FREQUENCY); + }); (spi, cs_pin) }; diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index b818e27a..81d3f1d8 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -15,18 +15,20 @@ #[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] mod app { + use embedded_hal_bus::spi::ExclusiveDevice; + use hal::lpspi::FullDma; use imxrt_hal as hal; - use hal::lpspi::{LpspiDma, LpspiInterruptHandler}; - use eh1::spi::Operation; - use eh1_async::spi::SpiDevice; + use eh1::spi::SpiDevice; use rtic_monotonics::systick::*; + use board::{SpiBusDma, SpiCsPin, SpiInterruptHandler}; + #[local] struct Local { - spi_device: board::SpiDevice, - spi_interrupt_handler: LpspiInterruptHandler, + spi_device: ExclusiveDevice, + spi_interrupt_handler: SpiInterruptHandler, } #[shared] @@ -39,7 +41,7 @@ mod app { let ( board::Common { mut dma, .. }, board::Specifics { - spi: (mut spi_bus, spi_cs_pin), + spi: (spi_bus, spi_cs_pin), .. }, ) = board::new(); @@ -60,13 +62,11 @@ mod app { chan_b.set_disable_on_completion(true); // Configure SPI - let spi_systick = cx.local.spi_systick.insert(Systick); - spi_bus.set_delay_source(spi_systick).unwrap(); - spi_bus.set_dma(LpspiDma::Full(chan_a, chan_b)).unwrap(); - let spi_interrupt_handler = spi_bus.enable_interrupts().unwrap(); + let mut spi_bus = spi_bus.with_dma(FullDma(chan_a, chan_b)); + let spi_interrupt_handler = spi_bus.enable_interrupts(); // Create SPI device - let spi_device = spi_bus.device(spi_cs_pin); + let spi_device = ExclusiveDevice::new(spi_bus, spi_cs_pin, Systick); ( Shared {}, @@ -94,12 +94,11 @@ mod app { Operation::Write(&[0xFFFF]), Operation::DelayUs(50), ]) - .await .unwrap(); // To demonstrate larger, DMA based transfers let mut buf = [0xf5u32; 512]; - spi_device.transfer_in_place(&mut buf).await.unwrap(); + spi_device.transfer_in_place(&mut buf).unwrap(); } } diff --git a/run.bat b/run.bat index 3b020f6c..b294adef 100644 --- a/run.bat +++ b/run.bat @@ -1,2 +1,2 @@ -cargo objcopy --example=hal_trng --features=board/teensy4 --target=thumbv7em-none-eabihf -- -O ihex firmware.hex +cargo objcopy --example=rtic_spi --features=board/spi,board/teensy4,async --target=thumbv7em-none-eabihf -- -O ihex firmware.hex teensy_loader_cli --mcu=TEENSY40 -wsv .\firmware.hex diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fa481889..672e30b7 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "nightly" # components = ["rustfmt", "llvm-tools"] -# targets = ["thumbv7em-none-eabihf"] +targets = ["thumbv7em-none-eabihf"] diff --git a/src/common/gpio.rs b/src/common/gpio.rs index 74f61336..e0c1585e 100644 --- a/src/common/gpio.rs +++ b/src/common/gpio.rs @@ -317,6 +317,21 @@ impl

eh02::digital::v2::OutputPin for Output

{ } } +impl

eh1::digital::ErrorType for Output

{ + type Error = core::convert::Infallible; +} + +impl

eh1::digital::OutputPin for Output

{ + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set(); + Ok(()) + } + fn set_low(&mut self) -> Result<(), Self::Error> { + self.clear(); + Ok(()) + } +} + #[cfg(feature = "eh02-unproven")] impl

eh02::digital::v2::StatefulOutputPin for Output

{ fn is_set_high(&self) -> Result { diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 70f067f7..685dcb63 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -13,6 +13,7 @@ mod dma; mod error; mod status_watcher; +pub use dma::{FullDma, NoDma, PartialDma}; use status_watcher::StatusWatcher; /// Possible errors when interfacing the LPSPI. diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index f30f1047..c07289b8 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -2,8 +2,8 @@ use eh1::spi::MODE_0; use super::{ data_buffer::{LpspiDataBuffer, LpspiIndexChunks, TransferBuffer}, - dma::{FullDma, NoDma, PartialDma}, - Channel, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, + dma::{FullDma, LpspiDma, NoDma}, + Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, @@ -34,61 +34,20 @@ impl<'a, const N: u8> Lpspi<'a, N, NoDma> { } } -impl<'a, const N: u8> Lpspi<'a, N, PartialDma> { - /// Create a new LPSPI peripheral with partial DMA support. - /// - /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the - /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn new( - lpspi: ral::lpspi::Instance, - pins: Pins, - data_storage: &'a mut Option>, - source_clock_hz: u32, - dma: Channel, - ) -> Self - where - SDO: lpspi::Pin, Signal = lpspi::Sdo>, - SDI: lpspi::Pin, Signal = lpspi::Sdi>, - SCK: lpspi::Pin, Signal = lpspi::Sck>, - { - Self::create(lpspi, pins, data_storage, source_clock_hz, PartialDma(dma)) - } -} - -impl<'a, const N: u8> Lpspi<'a, N, FullDma> { - /// Create a new LPSPI peripheral with full DMA support. - /// - /// This is required for `async` operation. - /// - /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the - /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn new( - lpspi: ral::lpspi::Instance, - pins: Pins, - data_storage: &'a mut Option>, - source_clock_hz: u32, - dma1: Channel, - dma2: Channel, - ) -> Self - where - SDO: lpspi::Pin, Signal = lpspi::Sdo>, - SDI: lpspi::Pin, Signal = lpspi::Sdi>, - SCK: lpspi::Pin, Signal = lpspi::Sck>, - { - Self::create( - lpspi, - pins, - data_storage, - source_clock_hz, - FullDma(dma1, dma2), - ) - } -} - impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// The peripheral instance. pub const N: u8 = N; + /// Attaches DMA channels to the device. + pub fn with_dma(self, dma: D) -> Lpspi<'a, N, D> { + Lpspi { + dma, + source_clock_hz: self.source_clock_hz, + data: self.data, + tx_fifo_size: self.tx_fifo_size, + } + } + /// Create a new LPSPI peripheral. /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 4b535634..7dd8e3d5 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -37,19 +37,19 @@ where #[cfg(feature = "async")] impl eh1_async::spi::SpiBus for Lpspi<'_, N, FullDma> { async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(TransferBuffer::Dual(words, &[])) } async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(TransferBuffer::Dual(&mut [], words)) } async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(TransferBuffer::Dual(read, write)) } async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - todo!() + self.blocking_transfer(TransferBuffer::Single(words)) } async fn flush(&mut self) -> Result<(), Self::Error> { diff --git a/src/common/lpspi/dma.rs b/src/common/lpspi/dma.rs index 6ad90b32..1e50346f 100644 --- a/src/common/lpspi/dma.rs +++ b/src/common/lpspi/dma.rs @@ -12,12 +12,12 @@ pub struct NoDma; /// but Transfers are only partially /// DMA based /// -pub struct PartialDma(pub(crate) Channel); +pub struct PartialDma(pub Channel); /// Everything is DMA based. /// /// This is a requirement for the async interface. -pub struct FullDma(pub(crate) Channel, pub(crate) Channel); +pub struct FullDma(pub Channel, pub Channel); impl LpspiDma for NoDma { fn get_one(&mut self) -> Option<&mut Channel> { From 5275bdc0e9714c9b571b36c3753698bcdbfcd6fc Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 22:33:26 +0100 Subject: [PATCH 21/73] More rework; split data into dma and non-dma parts. To be determined if byte order is correct. --- Cargo.toml | 3 + src/common/lpspi.rs | 4 - src/common/lpspi/bus.rs | 155 ++++++--- src/common/lpspi/bus/eh1_impl.rs | 27 +- src/common/lpspi/data_buffer.rs | 493 ++++------------------------- src/common/lpspi/error.rs | 1 - src/common/lpspi/status_watcher.rs | 23 +- 7 files changed, 208 insertions(+), 498 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 018d11ce..cd86d6b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,9 @@ version = "1.0.2" [dependencies.cortex-m] version = "0.7" +[dependencies.cassette] +version = "0.2.3" + ####################### # imxrt-rs dependencies ####################### diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 685dcb63..4cc7db32 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -2,8 +2,6 @@ pub use eh1::spi::Mode; -use imxrt_dma::channel::Channel; - use crate::ral; mod bus; @@ -25,8 +23,6 @@ pub enum LpspiError { TransmitFifo, /// Bus is busy at the start of a transfer. Busy, - /// Caller provided no data. - NoData, } /// TODO diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index c07289b8..d96ff644 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,7 +1,7 @@ use eh1::spi::MODE_0; use super::{ - data_buffer::{LpspiDataBuffer, LpspiIndexChunks, TransferBuffer}, + data_buffer::{LpspiDataBuffer, TransferBuffer}, dma::{FullDma, LpspiDma, NoDma}, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; @@ -12,7 +12,7 @@ use crate::{ mod eh1_impl; -const MAX_FRAME_SIZE_BIT: u32 = 1 << 12; +const MAX_FRAME_SIZE_BITS: u32 = 1 << 12; impl<'a, const N: u8> Lpspi<'a, N, NoDma> { /// Create a new LPSPI peripheral without DMA support. @@ -135,6 +135,7 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// Note that it is the caller's responsibility to connect the interrupt source /// to the returned interrupt handler object. pub fn enable_interrupts(&mut self) -> LpspiInterruptHandler<'a, N> { + self.data.lpspi.enable_interrupts(); LpspiInterruptHandler { status_watcher: &self.data.lpspi, } @@ -177,18 +178,21 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { } /// Prepares the device for a blocking transfer - fn blocking_pre_transfer(&mut self) -> Result<(), LpspiError> { + fn prepare_transfer(&mut self, dma_read: bool, dma_write: bool) -> Result<(), LpspiError> { if self.busy() { return Err(LpspiError::Busy); } - // Disable DMA - ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: RDDE_0, TDDE: TDDE_0); + // Configure DMA + ral::modify_reg!(ral::lpspi, self.lpspi(), DER, + RDDE: if dma_read {RDDE_1} else {RDDE_0}, + TDDE: if dma_write {TDDE_1} else {TDDE_0} + ); self.clear_fifos(); self.data.lpspi.clear_transfer_complete(); - Ok(()) + self.check_errors() } fn fifo_read_data_available(&mut self) -> bool { @@ -199,59 +203,110 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } + async fn wait_for_transfer_complete(&mut self) { + self.data.lpspi.wait_transfer_complete().await; + } + + async fn transfer_single_word( + &mut self, + mut buffer: TransferBuffer<'_, u8>, + ) -> Result<(), LpspiError> { + self.prepare_transfer(false, false)?; + + assert!(buffer.max_len() < 4); + + ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + RXMSK: RXMSK_0, + TXMSK: TXMSK_1, + FRAMESZ: buffer.max_len() as u32 * 8 + ); + + let tx_buffer = buffer.tx_buffer(); + let tx_data = u32::from_le_bytes([ + tx_buffer.get(0).copied().unwrap_or_default(), + tx_buffer.get(1).copied().unwrap_or_default(), + tx_buffer.get(2).copied().unwrap_or_default(), + tx_buffer.get(3).copied().unwrap_or_default(), + ]); + ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); + + self.check_errors()?; + self.wait_for_transfer_complete().await; + + if !self.fifo_read_data_available() { + return Err(LpspiError::ReceiveFifo); + } + + let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); + let [r0, r1, r2, r3] = rx_data.to_le_bytes(); + let rx_buffer = buffer.rx_buffer(); + rx_buffer.get_mut(0).map(|x| *x = r0); + rx_buffer.get_mut(1).map(|x| *x = r1); + rx_buffer.get_mut(2).map(|x| *x = r2); + rx_buffer.get_mut(3).map(|x| *x = r3); + + self.check_errors() + } + /// Read + write into separate buffers - fn blocking_transfer(&mut self, mut buffers: TransferBuffer) -> Result<(), LpspiError> + async fn transfer(&mut self, mut buffers: TransferBuffer<'_, T>) -> Result<(), LpspiError> where [T]: LpspiDataBuffer, { - self.blocking_pre_transfer()?; - - let size = buffers - .tx_buffer() - .bytecount() - .max(buffers.rx_buffer().bytecount()); - if size < 1 { - return Err(LpspiError::NoData); - } + let (data_pre, data_main, data_post) = buffers.dma_align(); - let mut rx_offset = 0; - let mut do_receive = |this: &mut Self, buffer: &mut [T]| -> Result<(), LpspiError> { - while this.fifo_read_data_available() { - this.check_errors()?; - let rx_data = ral::read_reg!(ral::lpspi, this.lpspi(), RDR); - buffer.write(rx_offset, rx_data); - rx_offset += 1; - } - Ok(()) - }; + if data_pre.max_len() > 0 { + self.transfer_single_word(data_pre).await?; + } - for chunk in LpspiIndexChunks::new(size, MAX_FRAME_SIZE_BIT / 8) { - self.check_errors()?; - ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - RXMSK: RXMSK_0, - TXMSK: TXMSK_1, - FRAMESZ: chunk.bytecount() * 8 - ); - - for tx_offset in chunk.offsets() { - while !self.fifo_write_space_available() { - do_receive(self, buffers.rx_buffer())?; - self.check_errors()?; - } - ral::write_reg!( - ral::lpspi, - self.lpspi(), - TDR, - buffers.tx_buffer().read(tx_offset) - ); - } + if data_main.max_len() > 0 { + // TODO: main data } - while !self.data.lpspi.poll_transfer_complete() { - do_receive(self, buffers.rx_buffer())?; + if data_post.max_len() > 0 { + self.transfer_single_word(data_post).await?; } - do_receive(self, buffers.rx_buffer())?; - self.check_errors() + Ok(()) + + // let mut rx_offset = 0; + // let mut do_receive = |this: &mut Self, buffer: &mut [T]| -> Result<(), LpspiError> { + // while this.fifo_read_data_available() { + // this.check_errors()?; + // let rx_data = ral::read_reg!(ral::lpspi, this.lpspi(), RDR); + // buffer.write(rx_offset, rx_data); + // rx_offset += 1; + // } + // Ok(()) + // }; + + // for chunk in LpspiIndexChunks::new(size, MAX_FRAME_SIZE_BIT / 8) { + // self.check_errors()?; + // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + // RXMSK: RXMSK_0, + // TXMSK: TXMSK_1, + // FRAMESZ: chunk.bytecount() * 8 + // ); + + // for tx_offset in chunk.offsets() { + // while !self.fifo_write_space_available() { + // do_receive(self, buffers.rx_buffer())?; + // self.check_errors()?; + // } + // ral::write_reg!( + // ral::lpspi, + // self.lpspi(), + // TDR, + // buffers.tx_buffer().read(tx_offset) + // ); + // } + // } + + // while !self.data.lpspi.poll_transfer_complete() { + // do_receive(self, buffers.rx_buffer())?; + // } + // do_receive(self, buffers.rx_buffer())?; + + // self.check_errors() } } diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 7dd8e3d5..d2a2b25a 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,3 +1,5 @@ +use cassette::Cassette; + use crate::lpspi::data_buffer::{LpspiDataBuffer, TransferBuffer}; use super::{FullDma, Lpspi, LpspiError}; @@ -12,19 +14,28 @@ where [T]: LpspiDataBuffer, { fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(words, &[])) + Cassette::new(core::pin::pin!( + self.transfer(TransferBuffer::Dual(words, &[])), + )) + .block_on() } fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(&mut [], words)) + Cassette::new(core::pin::pin!( + self.transfer(TransferBuffer::Dual(&mut [], words)) + )) + .block_on() } fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(read, write)) + Cassette::new(core::pin::pin!( + self.transfer(TransferBuffer::Dual(read, write)) + )) + .block_on() } fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Single(words)) + Cassette::new(core::pin::pin!(self.transfer(TransferBuffer::Single(words)))).block_on() } fn flush(&mut self) -> Result<(), Self::Error> { @@ -37,19 +48,19 @@ where #[cfg(feature = "async")] impl eh1_async::spi::SpiBus for Lpspi<'_, N, FullDma> { async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(words, &[])) + self.transfer(TransferBuffer::Dual(words, &[])).await } async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(&mut [], words)) + self.transfer(TransferBuffer::Dual(&mut [], words)).await } async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Dual(read, write)) + self.transfer(TransferBuffer::Dual(read, write)).await } async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { - self.blocking_transfer(TransferBuffer::Single(words)) + self.transfer(TransferBuffer::Single(words)).await } async fn flush(&mut self) -> Result<(), Self::Error> { diff --git a/src/common/lpspi/data_buffer.rs b/src/common/lpspi/data_buffer.rs index e1eb17b1..6aa33dfc 100644 --- a/src/common/lpspi/data_buffer.rs +++ b/src/common/lpspi/data_buffer.rs @@ -1,476 +1,105 @@ -use core::ops::Range; - pub enum TransferBuffer<'a, T> { Single(&'a mut [T]), Dual(&'a mut [T], &'a [T]), } -impl TransferBuffer<'_, T> { +impl TransferBuffer<'_, T> +where + [T]: LpspiDataBuffer, +{ + #[inline] pub fn tx_buffer(&self) -> &[T] { match self { TransferBuffer::Single(x) => x, TransferBuffer::Dual(_, x) => x, } } + + #[inline] pub fn rx_buffer(&mut self) -> &mut [T] { match self { TransferBuffer::Single(x) => x, TransferBuffer::Dual(x, _) => x, } } -} - -/// A data type that can be used for LPSPI transfers -pub trait LpspiDataBuffer { - /// Length in bytes - fn bytecount(&self) -> usize; - - /// Retreive a chunk to transmit. - /// Filled with zeros when overflown. - /// - /// # Arguments - /// - /// * `pos` - The `[u32]`-offset from where to read. - /// - fn read(&self, pos: usize) -> u32; - - /// Store a next received chunk. - /// - /// # Arguments - /// - /// * `pos` - The `[u32]`-offset where to write. - /// * `val` - The data to write - /// - fn write(&mut self, pos: usize, val: u32); -} - -impl LpspiDataBuffer for [u8] { - fn bytecount(&self) -> usize { - self.len() - } - - fn read(&self, pos: usize) -> u32 { - let pos = 4 * pos; - u32::from_be_bytes([ - self.get(pos + 0).copied().unwrap_or_default(), - self.get(pos + 1).copied().unwrap_or_default(), - self.get(pos + 2).copied().unwrap_or_default(), - self.get(pos + 3).copied().unwrap_or_default(), - ]) + pub fn max_len(&self) -> usize { + match self { + TransferBuffer::Single(x) => x.len(), + TransferBuffer::Dual(x1, x2) => x1.len().max(x2.len()), + } } - fn write(&mut self, pos: usize, val: u32) { - let pos = 4 * pos; - - for (offset, val) in val.to_be_bytes().into_iter().enumerate() { - if let Some(x) = self.get_mut(pos + offset) { - *x = val; + pub fn dma_align(&mut self) -> (TransferBuffer, TransferBuffer, TransferBuffer) { + match self { + TransferBuffer::Single(x) => { + let (a, b, c) = x.dma_align_mut(); + ( + TransferBuffer::Single(a), + TransferBuffer::Single(b), + TransferBuffer::Single(c), + ) + } + TransferBuffer::Dual(x1, x2) => { + let (a1, b1, c1) = x1.dma_align_mut(); + let (a2, b2, c2) = x2.dma_align(); + ( + TransferBuffer::Dual(a1, a2), + TransferBuffer::Dual(b1, b2), + TransferBuffer::Dual(c1, c2), + ) } } } } -impl LpspiDataBuffer for [u16] { - fn bytecount(&self) -> usize { - self.len() * 2 - } - - fn read(&self, pos: usize) -> u32 { - let pos = 2 * pos; - - let [b0, b1] = self.get(pos + 0).copied().unwrap_or_default().to_be_bytes(); - let [b2, b3] = self.get(pos + 1).copied().unwrap_or_default().to_be_bytes(); - - u32::from_be_bytes([b0, b1, b2, b3]) - } - - fn write(&mut self, pos: usize, val: u32) { - let pos = 2 * pos; - - let [b0, b1, b2, b3] = val.to_be_bytes(); +/// A data type that can be used for LPSPI transfers +pub trait LpspiDataBuffer { + /// Splits the buffer into DMA and non-DMA parts. + fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]); - if let Some(x) = self.get_mut(pos + 0) { - *x = u16::from_be_bytes([b0, b1]); - } - if let Some(x) = self.get_mut(pos + 1) { - *x = u16::from_be_bytes([b2, b3]); - } - } + /// Splits the buffer into DMA and non-DMA parts. + fn dma_align(&self) -> (&[u8], &[u32], &[u8]); } -impl LpspiDataBuffer for [u32] { - fn bytecount(&self) -> usize { - self.len() * 4 - } - - fn read(&self, pos: usize) -> u32 { - self.get(pos).copied().unwrap_or_default() +impl LpspiDataBuffer for [u8] { + fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { + unsafe { self.align_to_mut() } } - fn write(&mut self, pos: usize, val: u32) { - if let Some(x) = self.get_mut(pos) { - *x = val; - } + fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { + unsafe { self.align_to() } } } -pub struct LpspiIndexChunks { - bytecount: usize, - offset: usize, - chunk_size: u32, -} - -impl LpspiIndexChunks { - pub fn new(bytecount: usize, chunk_size: u32) -> Self { - let chunk_size = (chunk_size / 4) * 4; // Round down to next divisible by 4 - Self { - bytecount, - offset: 0, - chunk_size, +impl LpspiDataBuffer for [u16] { + fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { + unsafe { + let data: &mut [u8] = core::slice::from_raw_parts_mut( + self.as_mut_ptr() as *mut u8, + self.len() * core::mem::size_of::(), + ); + data.align_to_mut() } } -} - -impl Iterator for LpspiIndexChunks { - type Item = LpspiIndexChunk; - fn next(&mut self) -> Option { - let offset_bytes = self.offset * 4; - if offset_bytes >= self.bytecount { - return None; + fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { + unsafe { + let data: &[u8] = core::slice::from_raw_parts( + self.as_ptr() as *const u8, + self.len() * core::mem::size_of::(), + ); + data.align_to() } - - let leftover_bytes = self.bytecount - offset_bytes; - let chunk_bytes = leftover_bytes.min(usize::try_from(self.chunk_size).unwrap()); - let chunk_u32s = (chunk_bytes + 3) / 4; // Round up - - let chunk = LpspiIndexChunk { - bytecount: u32::try_from(chunk_bytes).unwrap(), - offsets: self.offset..(self.offset + chunk_u32s), - }; - - self.offset += chunk_u32s; - - Some(chunk) - } -} - -#[derive(PartialEq, Eq, Clone)] -pub struct LpspiIndexChunk { - bytecount: u32, - offsets: Range, -} - -impl LpspiIndexChunk { - pub fn bytecount(&self) -> u32 { - self.bytecount - } - pub fn offsets(&self) -> Range { - self.offsets.clone() } } -#[cfg(test)] -mod tests { - use super::*; - extern crate std; - use std::vec::Vec; - - macro_rules! check_read { - ($t:ty, $data:expr, $pos:expr, $expected:expr) => {{ - let buf: &[$t] = &$data; - assert_eq!(buf.read($pos), $expected) - }}; - } - - macro_rules! check_write { - ($init_val:expr, $pos:expr, $val:expr, $expected:expr) => {{ - let buf = &mut $init_val; - buf.write($pos, $val); - assert_eq!(buf, &$expected) - }}; - } - - #[test] - fn bytecount_u8() { - assert_eq!([0x12u8, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde].bytecount(), 7); - } - #[test] - fn bytecount_u16() { - assert_eq!([0x1234u16, 0x5678, 0x9abc].bytecount(), 6); - } - #[test] - fn bytecount_u32() { - assert_eq!([0x12345678u32, 0x9abcdeff].bytecount(), 8); - } - - #[test] - fn read_u8() { - check_read!( - u8, - [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], - 0, - 0x12345678 - ); - check_read!( - u8, - [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde], - 1, - 0x9abcde00 - ); - } - - #[test] - fn write_u8() { - check_write!([0u8; 5], 0, 0x12345678, [0x12, 0x34, 0x56, 0x78, 0]); - check_write!( - [0u8; 9], - 1, - 0x12345678, - [0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78, 0] - ); - check_write!([0u8; 5], 1, 0x12345678, [0, 0, 0, 0, 0x12]); - } - - #[test] - fn read_u16() { - check_read!(u16, [0x1234, 0x5678, 0x9abc], 0, 0x12345678); - check_read!(u16, [0x1234, 0x5678, 0x9abc], 1, 0x9abc0000); - } - - #[test] - fn write_u16() { - check_write!([0u16; 3], 0, 0x12345678, [0x1234, 0x5678, 0]); - check_write!([0u16; 5], 1, 0x12345678, [0, 0, 0x1234, 0x5678, 0]); - check_write!([0u16; 3], 1, 0x12345678, [0, 0, 0x1234]); - } - - #[test] - fn read_u32() { - check_read!(u32, [0x12345678, 0x9abcdeff], 0, 0x12345678); - check_read!(u32, [0x12345678, 0x9abcdeff], 1, 0x9abcdeff); - check_read!(u32, [0x12345678, 0x9abcdeff], 2, 0); - } - - #[test] - fn write_u32() { - check_write!([0u32; 2], 0, 0x12345678, [0x12345678, 0]); - check_write!([0u32; 2], 1, 0x12345678, [0, 0x12345678]); - check_write!([0u32; 2], 2, 0x12345678, [0, 0]); - } - - macro_rules! check_chunks { - ($t:ty, $count:expr, $chunksize:expr, $expected:expr) => {{ - let chunk_iter = LpspiIndexChunks::new([<$t>::MIN; $count].bytecount(), $chunksize); - - let actual_owned = chunk_iter - .map(|c| (c.bytecount(), c.offsets().collect::>())) - .collect::)>>(); - - let actual = actual_owned - .iter() - .map(|(count, range)| (*count, range.as_slice())) - .collect::>(); - - let expected: &[(u32, &[usize])] = &$expected; - - assert_eq!(actual, expected); - }}; - } - - #[test] - fn chunksize_u8() { - check_chunks!( - u8, - 41, - 17, - [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (9, &[8, 9, 10])] - ); - check_chunks!(u8, 0, 9, []); - check_chunks!(u8, 1, 9, [(1, &[0])]); - check_chunks!(u8, 2, 9, [(2, &[0])]); - check_chunks!(u8, 3, 9, [(3, &[0])]); - check_chunks!(u8, 4, 9, [(4, &[0])]); - check_chunks!(u8, 5, 9, [(5, &[0, 1])]); - check_chunks!(u8, 6, 9, [(6, &[0, 1])]); - check_chunks!(u8, 7, 9, [(7, &[0, 1])]); - check_chunks!(u8, 8, 9, [(8, &[0, 1])]); - check_chunks!(u8, 9, 9, [(8, &[0, 1]), (1, &[2])]); - check_chunks!(u8, 10, 9, [(8, &[0, 1]), (2, &[2])]); - check_chunks!(u8, 11, 9, [(8, &[0, 1]), (3, &[2])]); - check_chunks!(u8, 12, 9, [(8, &[0, 1]), (4, &[2])]); - check_chunks!(u8, 13, 9, [(8, &[0, 1]), (5, &[2, 3])]); - check_chunks!(u8, 14, 9, [(8, &[0, 1]), (6, &[2, 3])]); - check_chunks!(u8, 15, 9, [(8, &[0, 1]), (7, &[2, 3])]); - check_chunks!(u8, 16, 9, [(8, &[0, 1]), (8, &[2, 3])]); - check_chunks!(u8, 17, 9, [(8, &[0, 1]), (8, &[2, 3]), (1, &[4])]); - } - - #[test] - fn chunksize_u16() { - check_chunks!( - u16, - 21, - 17, - [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (10, &[8, 9, 10])] - ); - check_chunks!(u16, 0, 9, []); - check_chunks!(u16, 1, 9, [(2, &[0])]); - check_chunks!(u16, 2, 9, [(4, &[0])]); - check_chunks!(u16, 3, 9, [(6, &[0, 1])]); - check_chunks!(u16, 4, 9, [(8, &[0, 1])]); - check_chunks!(u16, 5, 9, [(8, &[0, 1]), (2, &[2])]); - check_chunks!(u16, 6, 9, [(8, &[0, 1]), (4, &[2])]); - check_chunks!(u16, 7, 9, [(8, &[0, 1]), (6, &[2, 3])]); - check_chunks!(u16, 8, 9, [(8, &[0, 1]), (8, &[2, 3])]); - check_chunks!(u16, 9, 9, [(8, &[0, 1]), (8, &[2, 3]), (2, &[4])]); - } - - #[test] - fn chunksize_u32() { - check_chunks!( - u32, - 11, - 17, - [(16, &[0, 1, 2, 3]), (16, &[4, 5, 6, 7]), (12, &[8, 9, 10])] - ); - check_chunks!(u32, 0, 9, []); - check_chunks!(u32, 1, 9, [(4, &[0])]); - check_chunks!(u32, 2, 9, [(8, &[0, 1])]); - check_chunks!(u32, 3, 9, [(8, &[0, 1]), (4, &[2])]); - check_chunks!(u32, 4, 9, [(8, &[0, 1]), (8, &[2, 3])]); - check_chunks!(u32, 5, 9, [(8, &[0, 1]), (8, &[2, 3]), (4, &[4])]); - } - - #[test] - fn combined_u8() { - let data = { - let mut e = [0u8; 99]; - e.iter_mut() - .enumerate() - .for_each(|(i, v)| *v = (i + 1) as u8); - e - }; - - let mut data_out = [0u8; 100]; - - let chunks = LpspiIndexChunks::new(data.bytecount(), 9); - - let data_collected = chunks - .flat_map(|chunk| { - let mut local = std::vec![0u8; chunk.bytecount() as usize]; - let mut local_offset = 0; - for offset in chunk.offsets() { - let val = data.read(offset); - data_out.write(offset, val); - for val in val.to_be_bytes() { - if local_offset < chunk.bytecount() as usize { - local[local_offset] = val; - } - local_offset += 1; - } - } - local - }) - .collect::>(); - - assert_eq!(&data_out[..data.len()], data); - assert_eq!(data_collected, data); - - data_out[data.len()..] - .iter() - .for_each(|val| assert_eq!(*val, 0)); - } - - #[test] - fn combined_u16() { - let data = { - let mut e = [0u16; 99]; - e.iter_mut() - .enumerate() - .for_each(|(i, v)| *v = (((2 * i + 1) << 8) + 2 * i + 2) as u16); - e - }; - - let mut data_out = [0u16; 100]; - - let chunks = LpspiIndexChunks::new(data.bytecount(), 9); - - let data_collected = chunks - .flat_map(|chunk| { - let mut local = std::vec![0u8; chunk.bytecount() as usize]; - let mut local_offset = 0; - for offset in chunk.offsets() { - let val = data.read(offset); - data_out.write(offset, val); - for val in val.to_be_bytes() { - if local_offset < chunk.bytecount() as usize { - local[local_offset] = val; - } - local_offset += 1; - } - } - local - }) - .collect::>(); - - assert_eq!(&data_out[..data.len()], data); - data_out[data.len()..] - .iter() - .for_each(|val| assert_eq!(*val, 0)); - - assert_eq!( - data_collected, - data.iter() - .flat_map(|v| v.to_be_bytes()) - .collect::>() - ) +impl LpspiDataBuffer for [u32] { + fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { + (&mut [], self, &mut []) } - #[test] - fn combined_u32() { - let data = { - let mut e = [0u32; 99]; - e.iter_mut().enumerate().for_each(|(i, v)| { - *v = (((((((4 * i + 1) << 8) + 4 * i + 2) << 8) + 4 * i + 3) << 8) + 4 * i + 4) - as u32 - }); - e - }; - - let mut data_out = [0u32; 100]; - - let chunks = LpspiIndexChunks::new(data.bytecount(), 9); - - let data_collected = chunks - .flat_map(|chunk| { - let mut local = std::vec![0u8; chunk.bytecount() as usize]; - let mut local_offset = 0; - for offset in chunk.offsets() { - let val = data.read(offset); - data_out.write(offset, val); - for val in val.to_be_bytes() { - if local_offset < chunk.bytecount() as usize { - local[local_offset] = val; - } - local_offset += 1; - } - } - local - }) - .collect::>(); - - assert_eq!(&data_out[..data.len()], data); - data_out[data.len()..] - .iter() - .for_each(|val| assert_eq!(*val, 0)); - - assert_eq!( - data_collected, - data.iter() - .flat_map(|v| v.to_be_bytes()) - .collect::>() - ) + fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { + (&mut [], self, &mut []) } } diff --git a/src/common/lpspi/error.rs b/src/common/lpspi/error.rs index 9af6106f..00910ab5 100644 --- a/src/common/lpspi/error.rs +++ b/src/common/lpspi/error.rs @@ -8,7 +8,6 @@ impl Error for LpspiError { LpspiError::Busy => ErrorKind::Other, LpspiError::ReceiveFifo => ErrorKind::Overrun, LpspiError::TransmitFifo => ErrorKind::Other, - LpspiError::NoData => ErrorKind::Other, } } } diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index 4d02477b..9255b1b4 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -12,6 +12,7 @@ use super::ral; struct StatusWatcherInner { transfer_complete_happened: bool, transfer_complete_waker: Option, + interrupts_enabled: bool, } pub(crate) struct StatusWatcher { @@ -40,11 +41,20 @@ impl StatusWatcher { inner: Mutex::new(RefCell::new(StatusWatcherInner { transfer_complete_happened: false, transfer_complete_waker: None, + interrupts_enabled: false, })), lpspi, } } + pub fn enable_interrupts(&self) { + interrupt::free(|cs| { + let inner = self.inner.borrow(cs); + let mut inner = inner.borrow_mut(); + inner.interrupts_enabled = true; + }); + } + #[inline] pub fn instance(&self) -> &ral::lpspi::Instance { &self.lpspi @@ -70,9 +80,9 @@ impl StatusWatcher { }); } - pub fn poll_transfer_complete(&self) -> bool { - self.with_check_and_reset(|inner| inner.transfer_complete_happened) - } + // pub fn poll_transfer_complete(&self) -> bool { + // self.with_check_and_reset(|inner| inner.transfer_complete_happened) + // } pub fn wait_transfer_complete(&self) -> WaitTransferComplete { WaitTransferComplete(self) @@ -117,6 +127,13 @@ impl Future for WaitTransferComplete<'_, N> { } } + // If interrupts are disabled, notify right away to provoke busy-waiting + if !inner.interrupts_enabled { + if let Some(waker) = inner.transfer_complete_waker.take() { + waker.wake(); + } + } + Poll::Pending } }) From 6f54cc9ba6d8d942d1a0909351e12330d37f3eb9 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 22:53:00 +0100 Subject: [PATCH 22/73] Make dma config a member again --- examples/rtic_spi.rs | 6 ++-- src/common/lpspi.rs | 23 +++++++++++---- src/common/lpspi/bus.rs | 50 +++++++++----------------------- src/common/lpspi/bus/eh1_impl.rs | 20 ++++++++----- src/common/lpspi/disabled.rs | 6 ++-- src/common/lpspi/dma.rs | 45 ---------------------------- 6 files changed, 50 insertions(+), 100 deletions(-) delete mode 100644 src/common/lpspi/dma.rs diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index 81d3f1d8..dc170f74 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -68,6 +68,8 @@ mod app { // Create SPI device let spi_device = ExclusiveDevice::new(spi_bus, spi_cs_pin, Systick); + demo::spawn().unwrap(); + ( Shared {}, Local { @@ -78,8 +80,8 @@ mod app { } #[task(priority = 1, local = [spi_device])] - async fn app(cx: app::Context) { - let app::LocalResources { spi_device, .. } = cx.local; + async fn demo(cx: demo::Context) { + let demo::LocalResources { spi_device, .. } = cx.local; loop { Systick::delay(1000.millis()).await; diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 4cc7db32..ca9599d9 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,19 +1,30 @@ //! TODO pub use eh1::spi::Mode; +use imxrt_dma::channel::Channel; use crate::ral; mod bus; mod data_buffer; mod disabled; -mod dma; mod error; mod status_watcher; -pub use dma::{FullDma, NoDma, PartialDma}; use status_watcher::StatusWatcher; +/// TODO +pub enum LpspiDma { + /// Everything is CPU driven + Disabled, + /// Read and Write are DMA based, + /// but Transfers are only partially + /// DMA based + Partial(Channel), + /// Everything is DMA based + Full(Channel, Channel), +} + /// Possible errors when interfacing the LPSPI. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LpspiError { @@ -45,16 +56,16 @@ pub struct LpspiData { } /// TODO -pub struct Lpspi<'a, const N: u8, DMA> { - dma: DMA, +pub struct Lpspi<'a, const N: u8> { + dma: LpspiDma, source_clock_hz: u32, data: &'a LpspiData, tx_fifo_size: u32, } /// An LPSPI peripheral which is temporarily disabled. -pub struct Disabled<'a, 'b, const N: u8, DMA> { - bus: &'a mut Lpspi<'b, N, DMA>, +pub struct Disabled<'a, 'b, const N: u8> { + bus: &'a mut Lpspi<'b, N>, men: bool, } diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index d96ff644..82f9fc5d 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -2,11 +2,11 @@ use eh1::spi::MODE_0; use super::{ data_buffer::{LpspiDataBuffer, TransferBuffer}, - dma::{FullDma, LpspiDma, NoDma}, Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, + lpspi::LpspiDma, ral, }; @@ -14,52 +14,21 @@ mod eh1_impl; const MAX_FRAME_SIZE_BITS: u32 = 1 << 12; -impl<'a, const N: u8> Lpspi<'a, N, NoDma> { - /// Create a new LPSPI peripheral without DMA support. - /// - /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the - /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn new( - lpspi: ral::lpspi::Instance, - pins: Pins, - data_storage: &'a mut Option>, - source_clock_hz: u32, - ) -> Self - where - SDO: lpspi::Pin, Signal = lpspi::Sdo>, - SDI: lpspi::Pin, Signal = lpspi::Sdi>, - SCK: lpspi::Pin, Signal = lpspi::Sck>, - { - Self::create(lpspi, pins, data_storage, source_clock_hz, NoDma) - } -} - -impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { +impl<'a, const N: u8> Lpspi<'a, N> { /// The peripheral instance. pub const N: u8 = N; - /// Attaches DMA channels to the device. - pub fn with_dma(self, dma: D) -> Lpspi<'a, N, D> { - Lpspi { - dma, - source_clock_hz: self.source_clock_hz, - data: self.data, - tx_fifo_size: self.tx_fifo_size, - } - } - /// Create a new LPSPI peripheral. /// /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - fn create( + pub fn new( lpspi: ral::lpspi::Instance, // TODO: Open question: How to make those pins optional? (For example, WS2812 driver only uses SDO pin) // Or should we simply do a `new_without_pins` again? mut pins: Pins, data_storage: &'a mut Option>, source_clock_hz: u32, - dma: DMA, ) -> Self where SDO: lpspi::Pin, Signal = lpspi::Sdo>, @@ -75,7 +44,7 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { let mut this = Self { source_clock_hz, - dma, + dma: LpspiDma::Disabled, data: data_storage.insert(data), tx_fifo_size, }; @@ -119,7 +88,7 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { /// /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify /// LPSPI settings that require a fully disabled peripheral. - pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { + pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { // Disable DMA and clear fifos ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: RDDE_0, TDDE: TDDE_0); self.clear_fifos(); @@ -141,6 +110,15 @@ impl<'a, const N: u8, DMA> Lpspi<'a, N, DMA> { } } + /// Provides the SPI bus with one or two DMA channels. + /// + /// This drastically increases the efficiency of reads/writes. + /// + /// For simultaneous read/write, two DMA channels are required. + pub fn set_dma(&mut self, dma: LpspiDma) -> LpspiDma { + core::mem::replace(&mut self.dma, dma) + } + // ////////////////// PRIVATE DRIVER STUFF /////////////////////// /// Get LPSPI Register Instance diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index d2a2b25a..3599fc47 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -2,13 +2,13 @@ use cassette::Cassette; use crate::lpspi::data_buffer::{LpspiDataBuffer, TransferBuffer}; -use super::{FullDma, Lpspi, LpspiError}; +use super::{Lpspi, LpspiError}; -impl eh1::spi::ErrorType for Lpspi<'_, N, DMA> { +impl eh1::spi::ErrorType for Lpspi<'_, N> { type Error = LpspiError; } -impl eh1::spi::SpiBus for Lpspi<'_, N, DMA> +impl eh1::spi::SpiBus for Lpspi<'_, N> where T: 'static + Copy, [T]: LpspiDataBuffer, @@ -46,20 +46,24 @@ where // Async only makes sense for DMA; DMA only supports u32. #[cfg(feature = "async")] -impl eh1_async::spi::SpiBus for Lpspi<'_, N, FullDma> { - async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { +impl eh1_async::spi::SpiBus for Lpspi<'_, N> +where + T: 'static + Copy, + [T]: LpspiDataBuffer, +{ + async fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { self.transfer(TransferBuffer::Dual(words, &[])).await } - async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + async fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { self.transfer(TransferBuffer::Dual(&mut [], words)).await } - async fn transfer(&mut self, read: &mut [u32], write: &[u32]) -> Result<(), Self::Error> { + async fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { self.transfer(TransferBuffer::Dual(read, write)).await } - async fn transfer_in_place(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + async fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { self.transfer(TransferBuffer::Single(words)).await } diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 4a58dbfe..6c2f0858 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -2,8 +2,8 @@ use eh1::spi::{Phase, Polarity}; use super::{ral, Disabled, Lpspi, Mode}; -impl<'a, 'b, const N: u8, DMA> Disabled<'a, 'b, N, DMA> { - pub(crate) fn new(bus: &'a mut Lpspi<'b, N, DMA>) -> Self { +impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { + pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { let men = ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1); ral::modify_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN: MEN_0); Self { bus, men } @@ -48,7 +48,7 @@ impl<'a, 'b, const N: u8, DMA> Disabled<'a, 'b, N, DMA> { } } -impl Drop for Disabled<'_, '_, N, DMA> { +impl Drop for Disabled<'_, '_, N> { fn drop(&mut self) { ral::modify_reg!(ral::lpspi, self.bus.data.lpspi.instance(), CR, MEN: self.men as u32); } diff --git a/src/common/lpspi/dma.rs b/src/common/lpspi/dma.rs deleted file mode 100644 index 1e50346f..00000000 --- a/src/common/lpspi/dma.rs +++ /dev/null @@ -1,45 +0,0 @@ -use imxrt_dma::channel::Channel; - -pub trait LpspiDma { - fn get_one(&mut self) -> Option<&mut Channel>; - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)>; -} - -/// Everything is CPU driven -pub struct NoDma; - -/// Read and Write are DMA based, -/// but Transfers are only partially -/// DMA based -/// -pub struct PartialDma(pub Channel); - -/// Everything is DMA based. -/// -/// This is a requirement for the async interface. -pub struct FullDma(pub Channel, pub Channel); - -impl LpspiDma for NoDma { - fn get_one(&mut self) -> Option<&mut Channel> { - None - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - None - } -} -impl LpspiDma for PartialDma { - fn get_one(&mut self) -> Option<&mut Channel> { - Some(&mut self.0) - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - None - } -} -impl LpspiDma for FullDma { - fn get_one(&mut self) -> Option<&mut Channel> { - Some(&mut self.0) - } - fn get_two(&mut self) -> Option<(&mut Channel, &mut Channel)> { - Some((&mut self.0, &mut self.1)) - } -} From 26482d4033fe19e0ae036495c074e8a4812575b5 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 23:19:05 +0100 Subject: [PATCH 23/73] Fix example and board --- board/src/teensy4.rs | 5 +---- examples/rtic_spi.rs | 10 +++++----- src/common/lpspi/bus.rs | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index 81ede751..1f8a4794 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -55,7 +55,6 @@ pub type SpiPins = hal::lpspi::Pins< /// Activate the `"spi"` feature to configure the SPI peripheral. mod lpspi_types { pub type SpiBus = (); - pub type SpiBusDma = (); pub type SpiCsPin = (); pub type SpiInterruptHandler = (); } @@ -63,11 +62,9 @@ mod lpspi_types { #[cfg(feature = "spi")] /// SPI peripheral. mod lpspi_types { - use hal::lpspi::{FullDma, NoDma}; use super::*; - pub type SpiBus = hal::lpspi::Lpspi<'static, 4, NoDma>; - pub type SpiBusDma = hal::lpspi::Lpspi<'static, 4, FullDma>; + pub type SpiBus = hal::lpspi::Lpspi<'static, 4>; pub type SpiCsPin = hal::gpio::Output; pub type SpiInterruptHandler = hal::lpspi::LpspiInterruptHandler<'static, 4>; } diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index dc170f74..4dac258c 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -16,18 +16,18 @@ mod app { use embedded_hal_bus::spi::ExclusiveDevice; - use hal::lpspi::FullDma; use imxrt_hal as hal; use eh1::spi::Operation; use eh1::spi::SpiDevice; + use hal::lpspi::LpspiDma; use rtic_monotonics::systick::*; - use board::{SpiBusDma, SpiCsPin, SpiInterruptHandler}; + use board::{SpiBus, SpiCsPin, SpiInterruptHandler}; #[local] struct Local { - spi_device: ExclusiveDevice, + spi_device: ExclusiveDevice, spi_interrupt_handler: SpiInterruptHandler, } @@ -41,7 +41,7 @@ mod app { let ( board::Common { mut dma, .. }, board::Specifics { - spi: (spi_bus, spi_cs_pin), + spi: (mut spi_bus, spi_cs_pin), .. }, ) = board::new(); @@ -62,7 +62,7 @@ mod app { chan_b.set_disable_on_completion(true); // Configure SPI - let mut spi_bus = spi_bus.with_dma(FullDma(chan_a, chan_b)); + spi_bus.set_dma(LpspiDma::Full(chan_a, chan_b)); let spi_interrupt_handler = spi_bus.enable_interrupts(); // Create SPI device diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 82f9fc5d..06ab4b4f 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -192,11 +192,12 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.prepare_transfer(false, false)?; assert!(buffer.max_len() < 4); + assert!(buffer.max_len() >= 1); ral::write_reg!(ral::lpspi, self.lpspi(), TCR, RXMSK: RXMSK_0, TXMSK: TXMSK_1, - FRAMESZ: buffer.max_len() as u32 * 8 + FRAMESZ: buffer.max_len() as u32 * 8 - 1 ); let tx_buffer = buffer.tx_buffer(); @@ -263,7 +264,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, // RXMSK: RXMSK_0, // TXMSK: TXMSK_1, - // FRAMESZ: chunk.bytecount() * 8 + // FRAMESZ: chunk.bytecount() * 8 - 1 // ); // for tx_offset in chunk.offsets() { From ae6ee7d068d9246d9245057b403290cdc005a81c Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 25 Nov 2023 23:36:17 +0100 Subject: [PATCH 24/73] Remove rtic-sync dependency --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cd86d6b1..87533d1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,9 +50,6 @@ version = "0.6" default-features = false optional = true -[dependencies.rtic-sync] -version = "1.0.2" - [dependencies.cortex-m] version = "0.7" From ef4d4bc2f0bad26fc81b534c4a6644eb9f55df83 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Mon, 4 Dec 2023 12:34:43 +0100 Subject: [PATCH 25/73] Fix lpspi clock config --- src/common/lpspi.rs | 2 ++ src/common/lpspi/bus.rs | 24 ++++++++++++----- src/common/lpspi/disabled.rs | 52 +++++++++++++++--------------------- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index ca9599d9..6e3fc727 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -61,6 +61,8 @@ pub struct Lpspi<'a, const N: u8> { source_clock_hz: u32, data: &'a LpspiData, tx_fifo_size: u32, + rx_fifo_size: u32, + mode: Mode, } /// An LPSPI peripheral which is temporarily disabled. diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 06ab4b4f..ec1fe73e 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -35,8 +35,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { SDI: lpspi::Pin, Signal = lpspi::Sdi>, SCK: lpspi::Pin, Signal = lpspi::Sck>, { - let tx_fifo_size_exp = ral::read_reg!(ral::lpspi, lpspi, PARAM, TXFIFO); + let (tx_fifo_size_exp, rx_fifo_size_exp) = + ral::read_reg!(ral::lpspi, lpspi, PARAM, TXFIFO, RXFIFO); let tx_fifo_size = 1 << tx_fifo_size_exp; + let rx_fifo_size = 1 << rx_fifo_size_exp; let data = LpspiData { lpspi: StatusWatcher::new(lpspi), @@ -47,11 +49,16 @@ impl<'a, const N: u8> Lpspi<'a, N> { dma: LpspiDma::Disabled, data: data_storage.insert(data), tx_fifo_size, + rx_fifo_size, + mode: MODE_0, }; - // Reset, enable master mode - ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_1); - ral::write_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_0); + // Reset and disable + ral::modify_reg!(ral::lpspi, lpspi, CR, MEN: MEN_0, RST: RST_1); + while ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1) {} + ral::modify_reg!(ral::lpspi, lpspi, CR, RST: RST_0, RTF: RTF_1, RRF: RRF_1); + + // Configure master mode ral::write_reg!( ral::lpspi, this.data.lpspi.instance(), @@ -63,7 +70,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { // Set sane default parameters this.disabled(|bus| { bus.set_clock_hz(1_000_000); - bus.set_mode(MODE_0) }); // Configure pins @@ -194,10 +200,15 @@ impl<'a, const N: u8> Lpspi<'a, N> { assert!(buffer.max_len() < 4); assert!(buffer.max_len() >= 1); + let framesz = buffer.max_len() as u32 * 8 - 1; + ral::write_reg!(ral::lpspi, self.lpspi(), TCR, RXMSK: RXMSK_0, TXMSK: TXMSK_1, - FRAMESZ: buffer.max_len() as u32 * 8 - 1 + PRESCALE: 0b110, + BYSW: BYSW_1, + FRAMESZ: 8 + //FRAMESZ: 2 ); let tx_buffer = buffer.tx_buffer(); @@ -233,7 +244,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { [T]: LpspiDataBuffer, { let (data_pre, data_main, data_post) = buffers.dma_align(); - if data_pre.max_len() > 0 { self.transfer_single_word(data_pre).await?; } diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 6c2f0858..59e797a1 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -6,50 +6,40 @@ impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { let men = ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1); ral::modify_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN: MEN_0); + while ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1) {} Self { bus, men } } - /// Set the SPI mode for the peripheral - pub fn set_mode(&mut self, mode: Mode) { - // This could probably be changed when we're not disabled. - // However, there's rules about when you can read TCR. - // Specifically, reading TCR while it's being loaded from - // the transmit FIFO could result in an incorrect reading. - // Only permitting this when we're disabled might help - // us avoid something troublesome. - ral::modify_reg!( - ral::lpspi, - self.bus.data.lpspi.instance(), - TCR, - CPOL: ((mode.polarity == Polarity::IdleHigh) as u32), - CPHA: ((mode.phase == Phase::CaptureOnSecondTransition) as u32) - ); - } - /// Set the LPSPI clock speed (Hz). pub fn set_clock_hz(&mut self, spi_clock_hz: u32) { // Round up, so we always get a resulting SPI clock that is // equal or less than the requested frequency. - let div = 1 + (self.bus.source_clock_hz - 1) / spi_clock_hz; + let half_div = u32::try_from( + 1 + u64::from(self.bus.source_clock_hz - 1) / (u64::from(spi_clock_hz) * 2), + ) + .unwrap(); + + // Make sure SCKDIV is between 0 and 255 + // For some reason SCK starts to misbehave if half_div is less than 3 + let half_div = half_div.clamp(3, 128); + // Because half_div is in range [3,128], sckdiv is in range [4, 254]. + let sckdiv = 2 * (half_div - 1); - // 0 <= div <= 255, and the true coefficient is really div + 2 - let div = div.saturating_sub(2).clamp(0, 255); - ral::write_reg!( - ral::lpspi, - self.bus.data.lpspi.instance(), - CCR, - SCKDIV: div, - // These all don't matter, because we do not use a CS pin. - // embedded-hal controls the CS pins through `OutputPin`. - DBT: 0, - SCKPCS: 0, - PCSSCK: 0 + ral::write_reg!(ral::lpspi, self.bus.data.lpspi.instance(), CCR, + // Delay between two clock transitions of two consecutive transfers + // is exactly sckdiv/2, which causes the transfer to be seamless. + DBT: half_div - 1, + // Add one sckdiv/2 setup and hold time before and after the transfer, + // to make sure the signal is stable at sample time + PCSSCK: half_div - 1, + SCKPCS: half_div - 1, + SCKDIV: sckdiv ); } } impl Drop for Disabled<'_, '_, N> { fn drop(&mut self) { - ral::modify_reg!(ral::lpspi, self.bus.data.lpspi.instance(), CR, MEN: self.men as u32); + ral::modify_reg!(ral::lpspi, self.bus.data.lpspi.instance(), CR, MEN: if self.men {MEN_1} else {MEN_0}); } } From fd90b716f5b942c4e36f59325b31f615f3b61125 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Mon, 4 Dec 2023 12:44:26 +0100 Subject: [PATCH 26/73] Add comment to set_clock_hz --- src/common/lpspi/disabled.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 59e797a1..30dc9a49 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -11,6 +11,13 @@ impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { } /// Set the LPSPI clock speed (Hz). + /// + /// The maximum possible clock speed is `source_clock_hz / 6`. + /// If `spi_clk_hz` is larger than this, it will + /// be clamped to that value. + /// + /// If `spi_clk_hz` cannot divide `source_clock_hz` evenly, + /// then `spi_clk_hz` will be rounded down. pub fn set_clock_hz(&mut self, spi_clock_hz: u32) { // Round up, so we always get a resulting SPI clock that is // equal or less than the requested frequency. @@ -20,7 +27,8 @@ impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { .unwrap(); // Make sure SCKDIV is between 0 and 255 - // For some reason SCK starts to misbehave if half_div is less than 3 + // For some reason SCK starts to misbehave in between frames + // if half_div is less than 3. let half_div = half_div.clamp(3, 128); // Because half_div is in range [3,128], sckdiv is in range [4, 254]. let sckdiv = 2 * (half_div - 1); From 844d1c4f90c7f96c0f685c940e3026612f2e29df Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 7 Dec 2023 22:05:57 +0100 Subject: [PATCH 27/73] Partial rewrite --- src/common/lpspi.rs | 8 +- src/common/lpspi/bus.rs | 176 ++++++++++----------------- src/common/lpspi/bus/eh1_impl.rs | 37 +++--- src/common/lpspi/data_buffer.rs | 105 ---------------- src/common/lpspi/disabled.rs | 2 +- src/common/lpspi/status_watcher.rs | 4 +- src/common/lpspi/transfer_actions.rs | 136 +++++++++++++++++++++ 7 files changed, 227 insertions(+), 241 deletions(-) delete mode 100644 src/common/lpspi/data_buffer.rs create mode 100644 src/common/lpspi/transfer_actions.rs diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 6e3fc727..00165d85 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -6,10 +6,10 @@ use imxrt_dma::channel::Channel; use crate::ral; mod bus; -mod data_buffer; mod disabled; mod error; mod status_watcher; +mod transfer_actions; use status_watcher::StatusWatcher; @@ -81,3 +81,9 @@ impl LpspiInterruptHandler<'_, N> { self.status_watcher.on_interrupt(); } } + +/// A data word for LPSPI +pub trait LpspiWord: transfer_actions::BufferType {} +impl LpspiWord for u8 {} +impl LpspiWord for u16 {} +impl LpspiWord for u32 {} diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index ec1fe73e..0e6899d8 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,8 +1,8 @@ use eh1::spi::MODE_0; use super::{ - data_buffer::{LpspiDataBuffer, TransferBuffer}, - Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, + transfer_actions::ActionSequence, Disabled, Lpspi, LpspiData, LpspiError, + LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, @@ -54,14 +54,14 @@ impl<'a, const N: u8> Lpspi<'a, N> { }; // Reset and disable - ral::modify_reg!(ral::lpspi, lpspi, CR, MEN: MEN_0, RST: RST_1); - while ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1) {} - ral::modify_reg!(ral::lpspi, lpspi, CR, RST: RST_0, RTF: RTF_1, RRF: RRF_1); + ral::modify_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_0, RST: RST_1); + while ral::read_reg!(ral::lpspi, this.lpspi(), CR, MEN == MEN_1) {} + ral::modify_reg!(ral::lpspi, this.lpspi(), CR, RST: RST_0, RTF: RTF_1, RRF: RRF_1); // Configure master mode ral::write_reg!( ral::lpspi, - this.data.lpspi.instance(), + this.lpspi(), CFGR1, MASTER: MASTER_1, SAMPLE: SAMPLE_1 @@ -161,22 +161,12 @@ impl<'a, const N: u8> Lpspi<'a, N> { } } - /// Prepares the device for a blocking transfer - fn prepare_transfer(&mut self, dma_read: bool, dma_write: bool) -> Result<(), LpspiError> { - if self.busy() { - return Err(LpspiError::Busy); - } - + fn configure_dma(&mut self, dma_read: bool, dma_write: bool) { // Configure DMA ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: if dma_read {RDDE_1} else {RDDE_0}, TDDE: if dma_write {TDDE_1} else {TDDE_0} ); - self.clear_fifos(); - - self.data.lpspi.clear_transfer_complete(); - - self.check_errors() } fn fifo_read_data_available(&mut self) -> bool { @@ -191,111 +181,69 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.data.lpspi.wait_transfer_complete().await; } - async fn transfer_single_word( - &mut self, - mut buffer: TransferBuffer<'_, u8>, - ) -> Result<(), LpspiError> { - self.prepare_transfer(false, false)?; + // async fn transfer_single_word( + // &mut self, + // mut buffer: , + // ) -> Result<(), LpspiError> { + // assert!(buffer.max_len() < 4); + // assert!(buffer.max_len() >= 1); + + // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + // RXMSK: RXMSK_0, + // TXMSK: TXMSK_0, + // PRESCALE: PRESCALE_7, + // FRAMESZ: buffer.max_len() as u32 * 8 - 1 + // ); + + // let tx_buffer = buffer.tx_buffer(); + // let tx_data = u32::from_le_bytes([ + // tx_buffer.get(0).copied().unwrap_or_default(), + // tx_buffer.get(1).copied().unwrap_or_default(), + // tx_buffer.get(2).copied().unwrap_or_default(), + // tx_buffer.get(3).copied().unwrap_or_default(), + // ]); + // ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); + + // self.check_errors()?; + // self.wait_for_transfer_complete().await; + + // if !self.fifo_read_data_available() { + // return Err(LpspiError::ReceiveFifo); + // } + + // let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); + // let [r0, r1, r2, r3] = rx_data.to_le_bytes(); + // let rx_buffer = buffer.rx_buffer(); + // rx_buffer.get_mut(0).map(|x| *x = r0); + // rx_buffer.get_mut(1).map(|x| *x = r1); + // rx_buffer.get_mut(2).map(|x| *x = r2); + // rx_buffer.get_mut(3).map(|x| *x = r3); + + // self.check_errors() + // } - assert!(buffer.max_len() < 4); - assert!(buffer.max_len() >= 1); - - let framesz = buffer.max_len() as u32 * 8 - 1; - - ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - RXMSK: RXMSK_0, - TXMSK: TXMSK_1, - PRESCALE: 0b110, - BYSW: BYSW_1, - FRAMESZ: 8 - //FRAMESZ: 2 - ); - - let tx_buffer = buffer.tx_buffer(); - let tx_data = u32::from_le_bytes([ - tx_buffer.get(0).copied().unwrap_or_default(), - tx_buffer.get(1).copied().unwrap_or_default(), - tx_buffer.get(2).copied().unwrap_or_default(), - tx_buffer.get(3).copied().unwrap_or_default(), - ]); - ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); + /// Read + write into separate buffers + async fn transfer(&mut self, mut buffers: ActionSequence<'_>) -> Result<(), LpspiError> { + if self.busy() { + return Err(LpspiError::Busy); + } + self.clear_fifos(); self.check_errors()?; - self.wait_for_transfer_complete().await; - if !self.fifo_read_data_available() { - return Err(LpspiError::ReceiveFifo); - } + let read_task = async {}; - let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); - let [r0, r1, r2, r3] = rx_data.to_le_bytes(); - let rx_buffer = buffer.rx_buffer(); - rx_buffer.get_mut(0).map(|x| *x = r0); - rx_buffer.get_mut(1).map(|x| *x = r1); - rx_buffer.get_mut(2).map(|x| *x = r2); - rx_buffer.get_mut(3).map(|x| *x = r3); + let write_task = async {}; - self.check_errors() + Ok(()) } - /// Read + write into separate buffers - async fn transfer(&mut self, mut buffers: TransferBuffer<'_, T>) -> Result<(), LpspiError> - where - [T]: LpspiDataBuffer, - { - let (data_pre, data_main, data_post) = buffers.dma_align(); - if data_pre.max_len() > 0 { - self.transfer_single_word(data_pre).await?; - } - - if data_main.max_len() > 0 { - // TODO: main data - } - - if data_post.max_len() > 0 { - self.transfer_single_word(data_post).await?; + async fn flush(&mut self) -> Result<(), LpspiError> { + self.data.lpspi.clear_transfer_complete(); + while ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) { + self.data.lpspi.wait_transfer_complete().await; + self.data.lpspi.clear_transfer_complete(); } - - Ok(()) - - // let mut rx_offset = 0; - // let mut do_receive = |this: &mut Self, buffer: &mut [T]| -> Result<(), LpspiError> { - // while this.fifo_read_data_available() { - // this.check_errors()?; - // let rx_data = ral::read_reg!(ral::lpspi, this.lpspi(), RDR); - // buffer.write(rx_offset, rx_data); - // rx_offset += 1; - // } - // Ok(()) - // }; - - // for chunk in LpspiIndexChunks::new(size, MAX_FRAME_SIZE_BIT / 8) { - // self.check_errors()?; - // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - // RXMSK: RXMSK_0, - // TXMSK: TXMSK_1, - // FRAMESZ: chunk.bytecount() * 8 - 1 - // ); - - // for tx_offset in chunk.offsets() { - // while !self.fifo_write_space_available() { - // do_receive(self, buffers.rx_buffer())?; - // self.check_errors()?; - // } - // ral::write_reg!( - // ral::lpspi, - // self.lpspi(), - // TDR, - // buffers.tx_buffer().read(tx_offset) - // ); - // } - // } - - // while !self.data.lpspi.poll_transfer_complete() { - // do_receive(self, buffers.rx_buffer())?; - // } - // do_receive(self, buffers.rx_buffer())?; - - // self.check_errors() + self.check_errors() } } diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index 3599fc47..aad7ecc9 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -1,6 +1,8 @@ use cassette::Cassette; -use crate::lpspi::data_buffer::{LpspiDataBuffer, TransferBuffer}; +use crate::lpspi::transfer_actions::{ + create_actions_read_write, create_actions_read_write_in_place, +}; use super::{Lpspi, LpspiError}; @@ -10,37 +12,38 @@ impl eh1::spi::ErrorType for Lpspi<'_, N> { impl eh1::spi::SpiBus for Lpspi<'_, N> where - T: 'static + Copy, - [T]: LpspiDataBuffer, + T: crate::lpspi::LpspiWord, { fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { Cassette::new(core::pin::pin!( - self.transfer(TransferBuffer::Dual(words, &[])), + self.transfer(create_actions_read_write(words, &[])), )) .block_on() } fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { Cassette::new(core::pin::pin!( - self.transfer(TransferBuffer::Dual(&mut [], words)) + self.transfer(create_actions_read_write(&mut [], words)) )) .block_on() } fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { Cassette::new(core::pin::pin!( - self.transfer(TransferBuffer::Dual(read, write)) + self.transfer(create_actions_read_write(read, write)) )) .block_on() } fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - Cassette::new(core::pin::pin!(self.transfer(TransferBuffer::Single(words)))).block_on() + Cassette::new(core::pin::pin!( + self.transfer(create_actions_read_write_in_place(words)) + )) + .block_on() } fn flush(&mut self) -> Result<(), Self::Error> { - // Nothing to do, all other calls only return after the device is finished - Ok(()) + Cassette::new(core::pin::pin!(self.flush())).block_on() } } @@ -48,27 +51,27 @@ where #[cfg(feature = "async")] impl eh1_async::spi::SpiBus for Lpspi<'_, N> where - T: 'static + Copy, - [T]: LpspiDataBuffer, + T: crate::lpspi::LpspiWord, { async fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.transfer(TransferBuffer::Dual(words, &[])).await + self.transfer(create_actions_read_write(words, &[])).await } async fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - self.transfer(TransferBuffer::Dual(&mut [], words)).await + self.transfer(create_actions_read_write(&mut [], words)) + .await } async fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - self.transfer(TransferBuffer::Dual(read, write)).await + self.transfer(create_actions_read_write(read, write)).await } async fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.transfer(TransferBuffer::Single(words)).await + self.transfer(create_actions_read_write_in_place(words)) + .await } async fn flush(&mut self) -> Result<(), Self::Error> { - // Nothing to do, all other calls only return after the device is finished - Ok(()) + self.flush().await } } diff --git a/src/common/lpspi/data_buffer.rs b/src/common/lpspi/data_buffer.rs deleted file mode 100644 index 6aa33dfc..00000000 --- a/src/common/lpspi/data_buffer.rs +++ /dev/null @@ -1,105 +0,0 @@ -pub enum TransferBuffer<'a, T> { - Single(&'a mut [T]), - Dual(&'a mut [T], &'a [T]), -} - -impl TransferBuffer<'_, T> -where - [T]: LpspiDataBuffer, -{ - #[inline] - pub fn tx_buffer(&self) -> &[T] { - match self { - TransferBuffer::Single(x) => x, - TransferBuffer::Dual(_, x) => x, - } - } - - #[inline] - pub fn rx_buffer(&mut self) -> &mut [T] { - match self { - TransferBuffer::Single(x) => x, - TransferBuffer::Dual(x, _) => x, - } - } - - pub fn max_len(&self) -> usize { - match self { - TransferBuffer::Single(x) => x.len(), - TransferBuffer::Dual(x1, x2) => x1.len().max(x2.len()), - } - } - - pub fn dma_align(&mut self) -> (TransferBuffer, TransferBuffer, TransferBuffer) { - match self { - TransferBuffer::Single(x) => { - let (a, b, c) = x.dma_align_mut(); - ( - TransferBuffer::Single(a), - TransferBuffer::Single(b), - TransferBuffer::Single(c), - ) - } - TransferBuffer::Dual(x1, x2) => { - let (a1, b1, c1) = x1.dma_align_mut(); - let (a2, b2, c2) = x2.dma_align(); - ( - TransferBuffer::Dual(a1, a2), - TransferBuffer::Dual(b1, b2), - TransferBuffer::Dual(c1, c2), - ) - } - } - } -} - -/// A data type that can be used for LPSPI transfers -pub trait LpspiDataBuffer { - /// Splits the buffer into DMA and non-DMA parts. - fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]); - - /// Splits the buffer into DMA and non-DMA parts. - fn dma_align(&self) -> (&[u8], &[u32], &[u8]); -} - -impl LpspiDataBuffer for [u8] { - fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { - unsafe { self.align_to_mut() } - } - - fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { - unsafe { self.align_to() } - } -} - -impl LpspiDataBuffer for [u16] { - fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { - unsafe { - let data: &mut [u8] = core::slice::from_raw_parts_mut( - self.as_mut_ptr() as *mut u8, - self.len() * core::mem::size_of::(), - ); - data.align_to_mut() - } - } - - fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { - unsafe { - let data: &[u8] = core::slice::from_raw_parts( - self.as_ptr() as *const u8, - self.len() * core::mem::size_of::(), - ); - data.align_to() - } - } -} - -impl LpspiDataBuffer for [u32] { - fn dma_align_mut(&mut self) -> (&mut [u8], &mut [u32], &mut [u8]) { - (&mut [], self, &mut []) - } - - fn dma_align(&self) -> (&[u8], &[u32], &[u8]) { - (&mut [], self, &mut []) - } -} diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 30dc9a49..211613ed 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -6,7 +6,7 @@ impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { let men = ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1); ral::modify_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN: MEN_0); - while ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1) {} + while ral::read_reg!(ral::lpspi, bus.data.lpspi.instance(), CR, MEN == MEN_1) {} Self { bus, men } } diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index 9255b1b4..a442752c 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -22,9 +22,7 @@ pub(crate) struct StatusWatcher { impl StatusWatcherInner { pub fn check_and_reset(&mut self, lpspi: &ral::lpspi::Instance) { - let transfer_complete_set = ral::read_reg!(ral::lpspi, lpspi, SR, TCF == TCF_1); - - if transfer_complete_set { + if ral::read_reg!(ral::lpspi, lpspi, SR, TCF == TCF_1) { ral::write_reg!(ral::lpspi, lpspi, SR, TCF: TCF_1); self.transfer_complete_happened = true; diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs new file mode 100644 index 00000000..6c32fe9e --- /dev/null +++ b/src/common/lpspi/transfer_actions.rs @@ -0,0 +1,136 @@ +use core::marker::PhantomData; + +pub(crate) struct DualDirectionActions { + read_buf: *mut u8, + write_buf: *const u8, + len: [usize; 3], +} +pub(crate) struct ReadActions { + read_buf: *mut u8, + len: [usize; 3], +} + +pub(crate) struct WriteActions { + write_buf: *const u8, + len: [usize; 3], +} + +pub(crate) enum SingleDirectionActions { + Read(ReadActions), + Write(WriteActions), +} + +/// The order in which the bytes need +/// to be transferred on the bus +pub(crate) enum ByteOrder { + /// Bytes need to be transferred in the order + /// that they are in + Normal, + /// Every group of 4 bytes needs to be reversed + WordReversed, + /// Every group of 2 bytes needs to be reversed + HalfWordReversed, +} + +pub(crate) enum TransferDirection { + Read, + Write, +} + +pub(crate) struct ActionSequence<'a> { + phase1: Option, + phase2: Option, + byteorder: ByteOrder, + _lifetimes: PhantomData<&'a [u8]>, +} + +pub(crate) trait BufferType: Copy + 'static { + fn byte_order() -> ByteOrder; +} + +impl BufferType for u8 { + fn byte_order() -> ByteOrder { + ByteOrder::Normal + } +} +impl BufferType for u16 { + fn byte_order() -> ByteOrder { + ByteOrder::HalfWordReversed + } +} +impl BufferType for u32 { + fn byte_order() -> ByteOrder { + ByteOrder::WordReversed + } +} + +fn determine_dma_alignment(dat: &[T]) -> [usize; 3] { + let (a, b, c) = unsafe { dat.align_to::() }; + [ + a.len() * core::mem::size_of::(), + b.len() * core::mem::size_of::(), + c.len() * core::mem::size_of::(), + ] +} + +pub(crate) fn create_actions_read_write<'a, T: BufferType>( + read: &'a mut [T], + write: &'a [T], +) -> ActionSequence<'a> { + let phase1; + let phase2; + + if read.len() > write.len() { + let (read1, read2) = read.split_at_mut(write.len()); + + phase1 = (!read1.is_empty()).then(|| DualDirectionActions { + read_buf: read1.as_mut_ptr().cast(), + write_buf: write.as_ptr().cast(), + len: determine_dma_alignment(read1), + }); + + phase2 = (!read2.is_empty()).then(|| { + SingleDirectionActions::Read(ReadActions { + read_buf: read2.as_mut_ptr().cast(), + len: determine_dma_alignment(read2), + }) + }); + } else { + let (write1, write2) = write.split_at(read.len()); + + phase1 = (!write1.is_empty()).then(|| DualDirectionActions { + read_buf: read.as_mut_ptr().cast(), + write_buf: write1.as_ptr().cast(), + len: determine_dma_alignment(write1), + }); + + phase2 = (!write2.is_empty()).then(|| { + SingleDirectionActions::Write(WriteActions { + write_buf: write2.as_ptr().cast(), + len: determine_dma_alignment(write2), + }) + }); + } + + ActionSequence { + phase1, + phase2, + byteorder: T::byte_order(), + _lifetimes: PhantomData, + } +} + +pub(crate) fn create_actions_read_write_in_place<'a, T: BufferType>( + buf: &'a mut [T], +) -> ActionSequence<'a> { + ActionSequence { + phase1: Some(DualDirectionActions { + read_buf: buf.as_mut_ptr().cast(), + write_buf: buf.as_ptr().cast(), + len: determine_dma_alignment(buf), + }), + phase2: None, + byteorder: T::byte_order(), + _lifetimes: PhantomData, + } +} From c426668535341c0347e5515f428b0a7280004e24 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 7 Dec 2023 22:06:21 +0100 Subject: [PATCH 28/73] Remove obsolete bat script --- run.bat | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 run.bat diff --git a/run.bat b/run.bat deleted file mode 100644 index b294adef..00000000 --- a/run.bat +++ /dev/null @@ -1,2 +0,0 @@ -cargo objcopy --example=rtic_spi --features=board/spi,board/teensy4,async --target=thumbv7em-none-eabihf -- -O ihex firmware.hex -teensy_loader_cli --mcu=TEENSY40 -wsv .\firmware.hex From e828ca2404db2476ee979beeef041ed5a0af6e53 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 7 Dec 2023 22:07:57 +0100 Subject: [PATCH 29/73] Remove obsolete use statements --- src/common/lpspi/disabled.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs index 211613ed..f96ce89b 100644 --- a/src/common/lpspi/disabled.rs +++ b/src/common/lpspi/disabled.rs @@ -1,6 +1,4 @@ -use eh1::spi::{Phase, Polarity}; - -use super::{ral, Disabled, Lpspi, Mode}; +use super::{ral, Disabled, Lpspi}; impl<'a, 'b, const N: u8> Disabled<'a, 'b, N> { pub(crate) fn new(bus: &'a mut Lpspi<'b, N>) -> Self { From 5fdd6b2d92e86d3ceec84b50b31570d081f2543a Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 7 Dec 2023 22:09:24 +0100 Subject: [PATCH 30/73] Fix warning --- src/common/lpspi/transfer_actions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 6c32fe9e..4af3d3f5 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -44,7 +44,7 @@ pub(crate) struct ActionSequence<'a> { _lifetimes: PhantomData<&'a [u8]>, } -pub(crate) trait BufferType: Copy + 'static { +pub trait BufferType: Copy + 'static { fn byte_order() -> ByteOrder; } From 595b1542a2b39ebc8a3be0c68299c9caf2b75781 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 7 Dec 2023 22:40:25 +0100 Subject: [PATCH 31/73] More work --- src/common/lpspi/bus.rs | 111 +++++++++++++-------------- src/common/lpspi/transfer_actions.rs | 29 ++++++- 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 0e6899d8..069859f7 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -138,11 +138,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RTF: RTF_1, RRF: RRF_1); } - /// Returns whether or not the busy flag is set. - fn busy(&self) -> bool { - ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) - } - /// Returns errors, if any there are any. fn check_errors(&mut self) -> Result<(), LpspiError> { let (rx_error, tx_error) = ral::read_reg!(ral::lpspi, self.lpspi(), SR, REF, TEF); @@ -177,73 +172,75 @@ impl<'a, const N: u8> Lpspi<'a, N> { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } - async fn wait_for_transfer_complete(&mut self) { - self.data.lpspi.wait_transfer_complete().await; + async unsafe fn write_single_word( + &mut self, + write_data: Option<*const u8>, + read: bool, + len: usize, + ) { + if len == 0 { + return; + } + assert!(len <= 4); + + // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + // RXMSK: RXMSK_0, + // TXMSK: TXMSK_0, + // PRESCALE: PRESCALE_7, + // FRAMESZ: len as u32 * 8 - 1 + // ); + + // let tx_buffer = buffer.tx_buffer(); + // let tx_data = u32::from_le_bytes([ + // tx_buffer.get(0).copied().unwrap_or_default(), + // tx_buffer.get(1).copied().unwrap_or_default(), + // tx_buffer.get(2).copied().unwrap_or_default(), + // tx_buffer.get(3).copied().unwrap_or_default(), + // ]); + // ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); + + // self.wait_for_transfer_complete().await; + + // if !self.fifo_read_data_available() { + // return Err(LpspiError::ReceiveFifo); + // } + + // let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); + // let [r0, r1, r2, r3] = rx_data.to_le_bytes(); + // let rx_buffer = buffer.rx_buffer(); + // rx_buffer.get_mut(0).map(|x| *x = r0); + // rx_buffer.get_mut(1).map(|x| *x = r1); + // rx_buffer.get_mut(2).map(|x| *x = r2); + // rx_buffer.get_mut(3).map(|x| *x = r3); } - // async fn transfer_single_word( - // &mut self, - // mut buffer: , - // ) -> Result<(), LpspiError> { - // assert!(buffer.max_len() < 4); - // assert!(buffer.max_len() >= 1); - - // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - // RXMSK: RXMSK_0, - // TXMSK: TXMSK_0, - // PRESCALE: PRESCALE_7, - // FRAMESZ: buffer.max_len() as u32 * 8 - 1 - // ); - - // let tx_buffer = buffer.tx_buffer(); - // let tx_data = u32::from_le_bytes([ - // tx_buffer.get(0).copied().unwrap_or_default(), - // tx_buffer.get(1).copied().unwrap_or_default(), - // tx_buffer.get(2).copied().unwrap_or_default(), - // tx_buffer.get(3).copied().unwrap_or_default(), - // ]); - // ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); - - // self.check_errors()?; - // self.wait_for_transfer_complete().await; - - // if !self.fifo_read_data_available() { - // return Err(LpspiError::ReceiveFifo); - // } - - // let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); - // let [r0, r1, r2, r3] = rx_data.to_le_bytes(); - // let rx_buffer = buffer.rx_buffer(); - // rx_buffer.get_mut(0).map(|x| *x = r0); - // rx_buffer.get_mut(1).map(|x| *x = r1); - // rx_buffer.get_mut(2).map(|x| *x = r2); - // rx_buffer.get_mut(3).map(|x| *x = r3); - - // self.check_errors() - // } - - /// Read + write into separate buffers - async fn transfer(&mut self, mut buffers: ActionSequence<'_>) -> Result<(), LpspiError> { - if self.busy() { - return Err(LpspiError::Busy); - } + /// Perform a sequence of transfer actions + async fn transfer(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { + self.flush().await?; self.clear_fifos(); - self.check_errors()?; - let read_task = async {}; + // TODO: status_watcher based error checking task + let read_task = async { assert!(!sequence.contains_read_actions()) }; let write_task = async {}; Ok(()) } + /// Returns whether or not the busy flag is set. + fn busy(&self) -> bool { + ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) + } + + /// Waits for the device to become idle. async fn flush(&mut self) -> Result<(), LpspiError> { + // TODO: status_watcher based error checking task self.data.lpspi.clear_transfer_complete(); - while ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) { + while self.busy() { self.data.lpspi.wait_transfer_complete().await; self.data.lpspi.clear_transfer_complete(); } - self.check_errors() + Ok(()) } } diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 4af3d3f5..e303bba2 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -20,8 +20,18 @@ pub(crate) enum SingleDirectionActions { Write(WriteActions), } +impl SingleDirectionActions { + pub(crate) fn transfer_direction(&self) -> TransferDirection { + match self { + SingleDirectionActions::Read(_) => TransferDirection::Read, + SingleDirectionActions::Write(_) => TransferDirection::Write, + } + } +} + /// The order in which the bytes need /// to be transferred on the bus +#[derive(Copy, Clone, PartialEq, Eq)] pub(crate) enum ByteOrder { /// Bytes need to be transferred in the order /// that they are in @@ -32,18 +42,31 @@ pub(crate) enum ByteOrder { HalfWordReversed, } +#[derive(Copy, Clone, PartialEq, Eq)] pub(crate) enum TransferDirection { Read, Write, } pub(crate) struct ActionSequence<'a> { - phase1: Option, - phase2: Option, - byteorder: ByteOrder, + pub(crate) phase1: Option, + pub(crate) phase2: Option, + pub(crate) byteorder: ByteOrder, _lifetimes: PhantomData<&'a [u8]>, } +impl ActionSequence<'_> { + pub(crate) fn contains_read_actions(&self) -> bool { + if self.phase1.is_some() { + true + } else if let Some(phase2) = &self.phase2 { + phase2.transfer_direction() == TransferDirection::Read + } else { + false + } + } +} + pub trait BufferType: Copy + 'static { fn byte_order() -> ByteOrder; } From 11bb1317e5c575cb5d5b28ebeed78215f031e5f4 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 11:28:45 +0100 Subject: [PATCH 32/73] Rework error handling --- src/common/lpspi/bus.rs | 97 +++++++++++++++++++------ src/common/lpspi/status_watcher.rs | 112 ++++++++++++++++++++++++----- 2 files changed, 169 insertions(+), 40 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 069859f7..5917b0ed 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,4 +1,5 @@ use eh1::spi::MODE_0; +use futures::FutureExt; use super::{ transfer_actions::ActionSequence, Disabled, Lpspi, LpspiData, LpspiError, @@ -140,20 +141,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { /// Returns errors, if any there are any. fn check_errors(&mut self) -> Result<(), LpspiError> { - let (rx_error, tx_error) = ral::read_reg!(ral::lpspi, self.lpspi(), SR, REF, TEF); - - if tx_error != 0 || rx_error != 0 { - ral::write_reg!(ral::lpspi, self.lpspi(), SR, REF: rx_error, TEF: tx_error); - self.clear_fifos(); - - if tx_error != 0 { - Err(LpspiError::TransmitFifo) - } else { - Err(LpspiError::ReceiveFifo) - } - } else { - Ok(()) - } + self.data.lpspi.check_for_errors() } fn configure_dma(&mut self, dma_read: bool, dma_write: bool) { @@ -212,35 +200,98 @@ impl<'a, const N: u8> Lpspi<'a, N> { // rx_buffer.get_mut(1).map(|x| *x = r1); // rx_buffer.get_mut(2).map(|x| *x = r2); // rx_buffer.get_mut(3).map(|x| *x = r3); + + //self.check_errors() } /// Perform a sequence of transfer actions - async fn transfer(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { - self.flush().await?; + async fn transfer_unchecked(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { + self.flush_unchecked().await?; self.clear_fifos(); - // TODO: status_watcher based error checking task - let read_task = async { assert!(!sequence.contains_read_actions()) }; - - let write_task = async {}; + let _read_task = async { assert!(!sequence.contains_read_actions()) }; + let _write_task = async {}; Ok(()) } + /// Perform a sequence of transfer actions while continuously checking for errors. + async fn transfer(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { + let mut cleanup_on_error = CleanupOnError::new(self); + let this = cleanup_on_error.driver(); + + let data = this.data; + + let result: Result<(), LpspiError> = futures::select_biased! { + res = data.lpspi.watch_for_errors().fuse() => res, + res = this.transfer_unchecked(sequence).fuse() => res, + }; + + cleanup_on_error.finish(result) + } + /// Returns whether or not the busy flag is set. fn busy(&self) -> bool { ral::read_reg!(ral::lpspi, self.lpspi(), SR, MBF == MBF_1) } /// Waits for the device to become idle. - async fn flush(&mut self) -> Result<(), LpspiError> { - // TODO: status_watcher based error checking task + async fn flush_unchecked(&mut self) -> Result<(), LpspiError> { self.data.lpspi.clear_transfer_complete(); while self.busy() { + self.check_errors()?; self.data.lpspi.wait_transfer_complete().await; self.data.lpspi.clear_transfer_complete(); } - Ok(()) + self.check_errors() + } + + /// Waits for the device to become idle while continuously checking for errors. + async fn flush(&mut self) -> Result<(), LpspiError> { + let mut cleanup_on_error = CleanupOnError::new(self); + let this = cleanup_on_error.driver(); + + let data = this.data; + + let result = futures::select_biased! { + res = data.lpspi.watch_for_errors().fuse() => res, + res = this.flush_unchecked().fuse() => res, + }; + + cleanup_on_error.finish(result) + } +} + +struct CleanupOnError<'a, 'b, const N: u8> { + defused: bool, + driver: &'a mut Lpspi<'b, N>, +} + +impl<'a, 'b, const N: u8> CleanupOnError<'a, 'b, N> { + pub fn new(driver: &'a mut Lpspi<'b, N>) -> Self { + Self { + defused: false, + driver, + } + } + pub fn finish(mut self, result: Result<(), LpspiError>) -> Result<(), LpspiError> { + let result = result.and_then(|()| self.driver.check_errors()); + if result.is_ok() { + self.defused = true; + } + result + } + pub fn driver(&mut self) -> &mut Lpspi<'b, N> { + &mut self.driver + } +} + +impl<'a, 'b, const N: u8> Drop for CleanupOnError<'a, 'b, N> { + fn drop(&mut self) { + if !self.defused { + self.driver.clear_fifos(); + self.driver.data.lpspi.clear_errors(); + } } } diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index a442752c..4137cbd7 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -7,11 +7,13 @@ use core::{ use cortex_m::interrupt::{self, Mutex}; -use super::ral; +use super::{ral, LpspiError}; struct StatusWatcherInner { transfer_complete_happened: bool, transfer_complete_waker: Option, + error_caught: Option, + error_caught_waker: Option, interrupts_enabled: bool, } @@ -30,6 +32,24 @@ impl StatusWatcherInner { waker.wake(); } } + + if ral::read_reg!(ral::lpspi, lpspi, SR, TEF == TEF_1) { + ral::write_reg!(ral::lpspi, lpspi, SR, TEF: TEF_1); + + self.error_caught = Some(LpspiError::TransmitFifo); + if let Some(waker) = self.error_caught_waker.take() { + waker.wake(); + } + } + + if ral::read_reg!(ral::lpspi, lpspi, SR, REF == REF_1) { + ral::write_reg!(ral::lpspi, lpspi, SR, REF: REF_1); + + self.error_caught = Some(LpspiError::ReceiveFifo); + if let Some(waker) = self.error_caught_waker.take() { + waker.wake(); + } + } } } @@ -39,6 +59,8 @@ impl StatusWatcher { inner: Mutex::new(RefCell::new(StatusWatcherInner { transfer_complete_happened: false, transfer_complete_waker: None, + error_caught: None, + error_caught_waker: None, interrupts_enabled: false, })), lpspi, @@ -49,6 +71,13 @@ impl StatusWatcher { interrupt::free(|cs| { let inner = self.inner.borrow(cs); let mut inner = inner.borrow_mut(); + + ral::write_reg!(ral::lpspi, self.lpspi, IER, + REIE: REIE_1, + TEIE: TEIE_1, + TCIE: TCIE_1 + ); + inner.interrupts_enabled = true; }); } @@ -78,39 +107,88 @@ impl StatusWatcher { }); } - // pub fn poll_transfer_complete(&self) -> bool { - // self.with_check_and_reset(|inner| inner.transfer_complete_happened) - // } + pub async fn wait_transfer_complete(&self) { + StatusWatcherFuture::new( + self, + |inner| inner.transfer_complete_happened.then_some(()), + |inner| &mut inner.transfer_complete_waker, + ) + .await + } + + pub async fn watch_for_errors(&self) -> Result<(), LpspiError> { + let error = StatusWatcherFuture::new( + self, + |inner| inner.error_caught.take(), + |inner| &mut inner.error_caught_waker, + ) + .await; - pub fn wait_transfer_complete(&self) -> WaitTransferComplete { - WaitTransferComplete(self) + Err(error) + } + + pub fn check_for_errors(&self) -> Result<(), LpspiError> { + match self.with_check_and_reset(|inner| inner.error_caught.take()) { + Some(err) => Err(err), + None => Ok(()), + } + } + + pub fn clear_errors(&self) { + self.with_check_and_reset(|inner| { + inner.error_caught = None; + }); } } -pub struct WaitTransferComplete<'a, const N: u8>(&'a StatusWatcher); +struct StatusWatcherFuture<'a, const N: u8, T, C, W> +where + C: Fn(&mut StatusWatcherInner) -> Option, + W: Fn(&mut StatusWatcherInner) -> &mut Option, +{ + watcher: &'a StatusWatcher, + condition: C, + waker: W, +} + +impl<'a, const N: u8, T, C, W> StatusWatcherFuture<'a, N, T, C, W> +where + C: Fn(&mut StatusWatcherInner) -> Option, + W: Fn(&mut StatusWatcherInner) -> &mut Option, +{ + fn new(watcher: &'a StatusWatcher, condition: C, waker: W) -> Self { + Self { + watcher, + condition, + waker, + } + } +} -impl Future for WaitTransferComplete<'_, N> { - type Output = (); +impl<'a, const N: u8, T, C, W> Future for StatusWatcherFuture<'a, N, T, C, W> +where + C: Fn(&mut StatusWatcherInner) -> Option, + W: Fn(&mut StatusWatcherInner) -> &mut Option, +{ + type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.0.with_check_and_reset(|inner| { - if inner.transfer_complete_happened { - Poll::Ready(()) + self.watcher.with_check_and_reset(|inner| { + if let Some(result) = (self.condition)(inner) { + Poll::Ready(result) } else { let new_waker = cx.waker(); // From embassy // https://github.com/embassy-rs/embassy/blob/b99533607ceed225dd12ae73aaa9a0d969a7365e/embassy-sync/src/waitqueue/waker.rs#L59-L61 - match &inner.transfer_complete_waker { + match (self.waker)(inner) { // Optimization: If both the old and new Wakers wake the same task, we can simply // keep the old waker, skipping the clone. (In most executor implementations, // cloning a waker is somewhat expensive, comparable to cloning an Arc). Some(w2) if (w2.will_wake(new_waker)) => {} _ => { // clone the new waker and store it - if let Some(old_waker) = - inner.transfer_complete_waker.replace(new_waker.clone()) - { + if let Some(old_waker) = (self.waker)(inner).replace(new_waker.clone()) { // We had a waker registered for another task. Wake it, so the other task can // reregister itself if it's still interested. // @@ -127,7 +205,7 @@ impl Future for WaitTransferComplete<'_, N> { // If interrupts are disabled, notify right away to provoke busy-waiting if !inner.interrupts_enabled { - if let Some(waker) = inner.transfer_complete_waker.take() { + if let Some(waker) = (self.waker)(inner).take() { waker.wake(); } } From 4b3e39188dc08881b34fd0b69c843084ca54f684 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 12:50:01 +0100 Subject: [PATCH 33/73] Add start_frame, add write iter --- src/common/lpspi/bus.rs | 78 ++++++++++++++++++++---- src/common/lpspi/transfer_actions.rs | 88 +++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 5917b0ed..7b8a4e3c 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,4 +1,6 @@ -use eh1::spi::MODE_0; +use core::num::NonZeroUsize; + +use eh1::spi::{Phase, Polarity, MODE_0}; use futures::FutureExt; use super::{ @@ -160,23 +162,69 @@ impl<'a, const N: u8> Lpspi<'a, N> { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } + fn start_frame( + &mut self, + reverse_bytes: bool, + is_first_frame: bool, + is_last_frame: bool, + enable_read: bool, + enable_write: bool, + frame_size_bytes: NonZeroUsize, + ) { + let num_bits = frame_size_bytes.get() as u32 * 8; + assert!(num_bits <= MAX_FRAME_SIZE_BITS); + + // TODO enqueue this somewhere so that we don't overflow if the buffer is full. + // Or wait for the buffer to become available. Either works. + // Maybe dynamically enable/disable the watermark interrupts so we can async wait for the watermark + ral::write_reg!(ral::lpspi, self.lpspi(), TCR, + CPOL: if self.mode.polarity == Polarity::IdleHigh {CPOL_1} else {CPOL_0}, + CPHA: if self.mode.phase == Phase::CaptureOnSecondTransition {CPHA_1} else {CPHA_0}, + PRESCALE: PRESCALE_0, + PCS: PCS_0, + LSBF: LSBF_0, + BYSW: if reverse_bytes {BYSW_0} else {BYSW_1}, + CONT: if is_last_frame {CONT_0} else {CONT_1}, + CONTC: if is_first_frame {CONTC_0} else {CONTC_1}, + RXMSK: if enable_read {RXMSK_0} else {RXMSK_1}, + TXMSK: if enable_write {TXMSK_0} else {TXMSK_1}, + WIDTH: WIDTH_0, + FRAMESZ: num_bits - 1 + ); + } + async unsafe fn write_single_word( &mut self, write_data: Option<*const u8>, + reverse: bool, read: bool, - len: usize, + len: NonZeroUsize, + is_first_frame: bool, + is_last_frame: bool, ) { - if len == 0 { - return; - } - assert!(len <= 4); + assert!(len.get() <= 4); + + self.start_frame( + false, + is_first_frame, + is_last_frame, + read, + write_data.is_some(), + len, + ); - // ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - // RXMSK: RXMSK_0, - // TXMSK: TXMSK_0, - // PRESCALE: PRESCALE_7, - // FRAMESZ: len as u32 * 8 - 1 - // ); + if let Some(data) = write_data { + let mut tx_buffer = [0u8; 4]; + if reverse { + for i in 0..len.get() { + tx_buffer[i] = data.add(len.get() - i - 1).read(); + } + } else { + for i in 0..len.get() { + tx_buffer[i] = data.add(i).read(); + } + } + } // let tx_buffer = buffer.tx_buffer(); // let tx_data = u32::from_le_bytes([ @@ -211,7 +259,11 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.clear_fifos(); let _read_task = async { assert!(!sequence.contains_read_actions()) }; - let _write_task = async {}; + let _write_task = async { + let mut first_frame = true; + if let Some(phase1) = sequence.phase1 {} + if let Some(phase2) = sequence.phase2 {} + }; Ok(()) } diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index e303bba2..9234619a 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -1,10 +1,73 @@ -use core::marker::PhantomData; +use core::{marker::PhantomData, num::NonZeroUsize}; pub(crate) struct DualDirectionActions { read_buf: *mut u8, write_buf: *const u8, len: [usize; 3], } + +impl DualDirectionActions { + pub(crate) unsafe fn get_write_actions(&self) -> WriteActionIter { + WriteActionIter::new(WriteActions { + write_buf: self.write_buf, + len: self.len, + }) + } +} + +pub(crate) struct WriteAction { + pub(crate) buf: *const u8, + pub(crate) len: NonZeroUsize, + pub(crate) is_first: bool, + pub(crate) is_last: bool, +} + +pub(crate) struct WriteActionIter { + actions: WriteActions, + pos: usize, +} +impl WriteActionIter { + fn new(actions: WriteActions) -> Self { + Self { actions, pos: 0 } + } +} +impl Iterator for WriteActionIter { + type Item = WriteAction; + + fn next(&mut self) -> Option { + let is_first = self.pos == 0; + + let mut lengths = self.actions.len.get(self.pos..)?; + let len = loop { + if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + break len; + } + self.pos += 1; + lengths = self.actions.len.get(self.pos..)?; + }; + + let buf = self.actions.write_buf; + + self.pos += 1; + self.actions.write_buf = unsafe { self.actions.write_buf.add(len.get()) }; + + let is_last = self + .actions + .len + .get(self.pos..) + .into_iter() + .flatten() + .all(|&val| val == 0); + + Some(WriteAction { + buf, + len, + is_first, + is_last, + }) + } +} + pub(crate) struct ReadActions { read_buf: *mut u8, len: [usize; 3], @@ -157,3 +220,26 @@ pub(crate) fn create_actions_read_write_in_place<'a, T: BufferType>( _lifetimes: PhantomData, } } + +#[cfg(test)] +mod tests { + use super::*; + extern crate std; + use std::vec::Vec; + + macro_rules! actions_write_iter_test { + ($len:expr, $expected:expr) => {{ + let actual = WriteActionIter::new(WriteActions { + write_buf: 1000usize as *const u8, + len: $len, + }) + let expected = + .collect::>(); + }}; + } + + #[test] + fn actions_write_iter() { + actions_write_iter_test([0, 5, 0], [(0, 5, true, true)]); + } +} From 2aca9840a3855540fb9242b6fdadf8f21c32947a Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 12:51:51 +0100 Subject: [PATCH 34/73] Update cargo.toml --- Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 87533d1b..70d8c8d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,11 @@ version = "0.7" [dependencies.cassette] version = "0.2.3" +[dependencies.futures] +version = "0.3.11" +default-features = false +features = ["async-await"] + ####################### # imxrt-rs dependencies ####################### @@ -158,6 +163,9 @@ usb-device = { version = "0.2", features = ["test-class-high-speed"] } usbd-serial = "0.1" usbd-hid = "0.6" embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] } +teensy4-bsp = "0.4.5" +imxrt-uart-panic = "0.1.2" +teensy4-panic = "0.2.3" [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dev-dependencies] board = { path = "board" } From be75933212f51cf072cbf0b60c1151888abc9d3d Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 12:52:48 +0100 Subject: [PATCH 35/73] Update cargo.toml --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b815b72..5a385b8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,11 +38,11 @@ version = "0.2" [dependencies.eh1] package = "embedded-hal" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" [dependencies.eh1-async] package = "embedded-hal-async" -version = "1.0.0-rc.1" +version = "1.0.0-rc.2" optional = true [dependencies.rand_core] From 857076abcd9c7eafd7e08884f2c1b65c813de28a Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 12:58:44 +0100 Subject: [PATCH 36/73] Update cargo --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a385b8c..e04ee368 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ version = "1.0" version = "1.2" [dependencies.fugit] -version = "0.3" +version = "0.3.7" # For EH02 CountDown. [dependencies.void] @@ -152,7 +152,7 @@ codegen-units = 256 imxrt-rt = { workspace = true } menu = "0.4.0" rtic = { version = "2.0.1", features = ["thumbv7-backend"] } -rtic-monotonics = { version = "1.2.0", features = [ +rtic-monotonics = { version = "1.4.1", features = [ "cortex-m-systick", "embedded-hal-async", ] } From 343d555e578d346a387b856996ef8cea4597d3eb Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 13:07:46 +0100 Subject: [PATCH 37/73] Attempt to fix CI --- .github/workflows/rust.yml | 67 +++++++++++--------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 00ccaf27..d8ab590c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,17 +6,11 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable components: rustfmt - - uses: actions-rs/cargo@v1 - name: Check formatting - with: - command: fmt - args: --all -- --check + - name: Check formatting + run: cargo +stable fmt --all -- --check # Checks the common HAL for all chips supported by imxrt-ral. lint-hal: @@ -36,11 +30,8 @@ jobs: - imxrt-ral/imxrt1176_cm7 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable components: clippy - uses: actions-rs/clippy-check@v1 name: Lint imxrt-hal @@ -64,18 +55,15 @@ jobs: - imxrt-ral/imxrt1176_cm7,imxrt1170 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable components: clippy - uses: actions-rs/clippy-check@v1 name: Lint imxrt-log with: token: ${{ secrets.GITHUB_TOKEN }} args: --features=${{ matrix.chips }} --package=imxrt-log --package=imxrt-hal -- -D warnings - + # Lint and build examples for boards. examples: needs: lint-hal @@ -104,23 +92,17 @@ jobs: --example=rtic_usb_serial --example=rtic_usb_test_class --example=rtic_usb_keypress --example=rtic_usb_mouse runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable components: clippy - target: thumbv7em-none-eabihf + targets: thumbv7em-none-eabihf - name: Lint board and examples uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: ${{ matrix.config }} --target=thumbv7em-none-eabihf - name: Build examples - uses: actions-rs/cargo@v1 - with: - command: build - args: ${{ matrix.config }} --target=thumbv7em-none-eabihf --release + run: cargo build ${{ matrix.config }} --target=thumbv7em-none-eabihf --release # Run unit, integration tests. # @@ -140,16 +122,10 @@ jobs: - imxrt-ral/imxrt1176_cm7,imxrt1170 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Run unit, integration tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --features=${{ matrix.chips }} --tests --package=imxrt-hal --package=imxrt-log + run: cargo test --features=${{ matrix.chips }} --tests --package=imxrt-hal --package=imxrt-log # Ensures that documentation builds, and that links are valid docs: @@ -158,14 +134,9 @@ jobs: env: RUSTDOCFLAGS: -D warnings steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Run documentation tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --features=board/teensy4 --workspace --doc + run: cargo test --features=board/teensy4 --workspace --doc - name: Check documentation, doclinks throughout workspace run: cargo doc --workspace --no-deps --features=board/teensy4 From 5686f83a760b26db5b09aac5835099969d3aeabe Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 13:08:59 +0100 Subject: [PATCH 38/73] Another attempt to fix CI --- .github/workflows/rust.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d8ab590c..2bcb05bc 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -93,7 +93,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly components: clippy targets: thumbv7em-none-eabihf - name: Lint board and examples @@ -102,7 +102,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} args: ${{ matrix.config }} --target=thumbv7em-none-eabihf - name: Build examples - run: cargo build ${{ matrix.config }} --target=thumbv7em-none-eabihf --release + run: cargo +nightly build ${{ matrix.config }} --target=thumbv7em-none-eabihf --release # Run unit, integration tests. # @@ -125,7 +125,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Run unit, integration tests - run: cargo test --features=${{ matrix.chips }} --tests --package=imxrt-hal --package=imxrt-log + run: cargo +stable test --features=${{ matrix.chips }} --tests --package=imxrt-hal --package=imxrt-log # Ensures that documentation builds, and that links are valid docs: @@ -137,6 +137,6 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Run documentation tests - run: cargo test --features=board/teensy4 --workspace --doc + run: cargo +stable test --features=board/teensy4 --workspace --doc - name: Check documentation, doclinks throughout workspace - run: cargo doc --workspace --no-deps --features=board/teensy4 + run: cargo +stable doc --workspace --no-deps --features=board/teensy4 From 5b4ecc6f407185bbf90279e2162433f67d37a199 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 13:10:43 +0100 Subject: [PATCH 39/73] Another attempt to fix CI --- .github/workflows/rust.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2bcb05bc..1f523344 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -8,6 +8,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: components: rustfmt - name: Check formatting run: cargo +stable fmt --all -- --check @@ -32,6 +33,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: components: clippy - uses: actions-rs/clippy-check@v1 name: Lint imxrt-hal @@ -57,6 +59,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + with: components: clippy - uses: actions-rs/clippy-check@v1 name: Lint imxrt-log @@ -94,6 +97,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly + with: components: clippy targets: thumbv7em-none-eabihf - name: Lint board and examples From b068ef6d7ab3d232ae02dae34f9a6b58a690996c Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 8 Dec 2023 13:31:33 +0100 Subject: [PATCH 40/73] Add actions_write_test --- src/common/lpspi/transfer_actions.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 9234619a..a62fccc6 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -233,13 +233,34 @@ mod tests { write_buf: 1000usize as *const u8, len: $len, }) - let expected = + .map(|val| { + ( + val.buf as usize - 1000, + val.len.get(), + val.is_first, + val.is_last, + ) + }) .collect::>(); + let expected: &[(usize, usize, bool, bool)] = &$expected; + + assert_eq!(actual, expected); }}; } #[test] fn actions_write_iter() { - actions_write_iter_test([0, 5, 0], [(0, 5, true, true)]); + actions_write_iter_test!([0, 5, 0], [(0, 5, true, true)]); + actions_write_iter_test!( + [2, 3, 4], + [ + (0, 2, true, false), + (2, 3, false, false), + (5, 4, false, true), + ] + ); + actions_write_iter_test!([2, 0, 4], [(0, 2, true, false), (2, 4, false, true)]); + actions_write_iter_test!([2, 0, 0], [(0, 2, true, true)]); + actions_write_iter_test!([0, 0, 4], [(0, 4, true, true)]); } } From 7fb65a58921af187c041f08ba84c8024243a7e1d Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 9 Dec 2023 05:33:00 +0100 Subject: [PATCH 41/73] More write impls --- src/common/lpspi/bus.rs | 120 ++++++++++++++++------- src/common/lpspi/transfer_actions.rs | 139 ++++++++++++++++++++++++++- 2 files changed, 221 insertions(+), 38 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 7b8a4e3c..27d0548e 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -4,8 +4,8 @@ use eh1::spi::{Phase, Polarity, MODE_0}; use futures::FutureExt; use super::{ - transfer_actions::ActionSequence, Disabled, Lpspi, LpspiData, LpspiError, - LpspiInterruptHandler, Pins, StatusWatcher, + transfer_actions::{ActionSequence, ByteOrder}, + Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, @@ -174,9 +174,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { let num_bits = frame_size_bytes.get() as u32 * 8; assert!(num_bits <= MAX_FRAME_SIZE_BITS); - // TODO enqueue this somewhere so that we don't overflow if the buffer is full. - // Or wait for the buffer to become available. Either works. - // Maybe dynamically enable/disable the watermark interrupts so we can async wait for the watermark ral::write_reg!(ral::lpspi, self.lpspi(), TCR, CPOL: if self.mode.polarity == Polarity::IdleHigh {CPOL_1} else {CPOL_0}, CPHA: if self.mode.phase == Phase::CaptureOnSecondTransition {CPHA_1} else {CPHA_0}, @@ -196,14 +193,23 @@ impl<'a, const N: u8> Lpspi<'a, N> { async unsafe fn write_single_word( &mut self, write_data: Option<*const u8>, - reverse: bool, + byteorder: ByteOrder, read: bool, len: NonZeroUsize, is_first_frame: bool, is_last_frame: bool, ) { - assert!(len.get() <= 4); + assert!(len.get() < 4); + + let reverse_bytes = match byteorder { + ByteOrder::Normal => false, + ByteOrder::WordReversed => true, + ByteOrder::HalfWordReversed => true, + }; + // TODO enqueue this somewhere so that we don't overflow if the buffer is full. + // Or wait for the buffer to become available. Either works. + // Maybe dynamically enable/disable the watermark interrupts so we can async wait for the watermark self.start_frame( false, is_first_frame, @@ -215,7 +221,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { if let Some(data) = write_data { let mut tx_buffer = [0u8; 4]; - if reverse { + if reverse_bytes { for i in 0..len.get() { tx_buffer[i] = data.add(len.get() - i - 1).read(); } @@ -225,31 +231,23 @@ impl<'a, const N: u8> Lpspi<'a, N> { } } } + } + + async unsafe fn write_u32_stream( + &mut self, + write_data: Option<*const u8>, + byteorder: ByteOrder, + read: bool, + len: NonZeroUsize, + is_first_frame: bool, + is_last_frame: bool, + // TODO: dma + ) { + assert!(len.get() % 4 == 0); + let len = NonZeroUsize::new(len.get() / 4).unwrap(); + let write_data: Option<*const u32> = write_data.map(|p| p.cast()); - // let tx_buffer = buffer.tx_buffer(); - // let tx_data = u32::from_le_bytes([ - // tx_buffer.get(0).copied().unwrap_or_default(), - // tx_buffer.get(1).copied().unwrap_or_default(), - // tx_buffer.get(2).copied().unwrap_or_default(), - // tx_buffer.get(3).copied().unwrap_or_default(), - // ]); - // ral::write_reg!(ral::lpspi, self.lpspi(), TDR, tx_data); - - // self.wait_for_transfer_complete().await; - - // if !self.fifo_read_data_available() { - // return Err(LpspiError::ReceiveFifo); - // } - - // let rx_data = ral::read_reg!(ral::lpspi, self.lpspi(), RDR); - // let [r0, r1, r2, r3] = rx_data.to_le_bytes(); - // let rx_buffer = buffer.rx_buffer(); - // rx_buffer.get_mut(0).map(|x| *x = r0); - // rx_buffer.get_mut(1).map(|x| *x = r1); - // rx_buffer.get_mut(2).map(|x| *x = r2); - // rx_buffer.get_mut(3).map(|x| *x = r3); - - //self.check_errors() + todo!(); } /// Perform a sequence of transfer actions @@ -260,9 +258,61 @@ impl<'a, const N: u8> Lpspi<'a, N> { let _read_task = async { assert!(!sequence.contains_read_actions()) }; let _write_task = async { - let mut first_frame = true; - if let Some(phase1) = sequence.phase1 {} - if let Some(phase2) = sequence.phase2 {} + unsafe { + let has_phase_1 = sequence.phase1.is_some(); + let has_phase_2 = sequence.phase2.is_some(); + + if let Some(phase1) = &sequence.phase1 { + for action in phase1.get_write_actions() { + if action.len.get() < 4 { + self.write_single_word( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first, + action.is_last && !has_phase_2, + ) + .await + } else { + self.write_u32_stream( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first, + action.is_last && !has_phase_2, + ) + .await; + } + } + } + if let Some(phase2) = &sequence.phase2 { + for action in phase2.get_write_actions() { + if action.len.get() < 4 { + self.write_single_word( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first && !has_phase_1, + action.is_last, + ) + .await + } else { + self.write_u32_stream( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first && !has_phase_1, + action.is_last, + ) + .await + } + } + } + } }; Ok(()) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index a62fccc6..20772916 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -16,7 +16,8 @@ impl DualDirectionActions { } pub(crate) struct WriteAction { - pub(crate) buf: *const u8, + pub(crate) buf: Option<*const u8>, + pub(crate) read: bool, pub(crate) len: NonZeroUsize, pub(crate) is_first: bool, pub(crate) is_last: bool, @@ -51,6 +52,53 @@ impl Iterator for WriteActionIter { self.pos += 1; self.actions.write_buf = unsafe { self.actions.write_buf.add(len.get()) }; + let is_last = self + .actions + .len + .get(self.pos..) + .into_iter() + .flatten() + .all(|&val| val == 0); + + Some(WriteAction { + buf: Some(buf), + read: true, + len, + is_first, + is_last, + }) + } +} + +pub(crate) struct SingleDirectionWriteActionIter { + actions: MaybeWriteActions, + pos: usize, +} +impl SingleDirectionWriteActionIter { + fn new(actions: MaybeWriteActions) -> Self { + Self { actions, pos: 0 } + } +} +impl Iterator for SingleDirectionWriteActionIter { + type Item = WriteAction; + + fn next(&mut self) -> Option { + let is_first = self.pos == 0; + + let mut lengths = self.actions.len.get(self.pos..)?; + let len = loop { + if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + break len; + } + self.pos += 1; + lengths = self.actions.len.get(self.pos..)?; + }; + + let buf = self.actions.write_buf; + + self.pos += 1; + self.actions.write_buf = unsafe { self.actions.write_buf.map(|b| b.add(len.get())) }; + let is_last = self .actions .len @@ -61,6 +109,7 @@ impl Iterator for WriteActionIter { Some(WriteAction { buf, + read: buf.is_none(), len, is_first, is_last, @@ -78,6 +127,11 @@ pub(crate) struct WriteActions { len: [usize; 3], } +pub(crate) struct MaybeWriteActions { + write_buf: Option<*const u8>, + len: [usize; 3], +} + pub(crate) enum SingleDirectionActions { Read(ReadActions), Write(WriteActions), @@ -90,12 +144,25 @@ impl SingleDirectionActions { SingleDirectionActions::Write(_) => TransferDirection::Write, } } + + pub(crate) unsafe fn get_write_actions(&self) -> SingleDirectionWriteActionIter { + SingleDirectionWriteActionIter::new(match self { + SingleDirectionActions::Read(actions) => MaybeWriteActions { + write_buf: None, + len: actions.len, + }, + SingleDirectionActions::Write(actions) => MaybeWriteActions { + write_buf: Some(actions.write_buf), + len: actions.len, + }, + }) + } } /// The order in which the bytes need /// to be transferred on the bus #[derive(Copy, Clone, PartialEq, Eq)] -pub(crate) enum ByteOrder { +pub enum ByteOrder { /// Bytes need to be transferred in the order /// that they are in Normal, @@ -247,9 +314,33 @@ mod tests { assert_eq!(actual, expected); }}; } + macro_rules! actions_single_direction_write_iter_test { + ($write:expr, $len:expr, $expected:expr) => {{ + let actual = SingleDirectionWriteActionIter::new(MaybeWriteActions { + write_buf: if $write { + Somt(1000usize as *const u8) + } else { + None + }, + len: $len, + }) + .map(|val| { + ( + val.buf.map(|b| b as usize - 1000), + val.len.get(), + val.is_first, + val.is_last, + ) + }) + .collect::>(); + let expected: &[(usize, usize, bool, bool)] = &$expected; + + assert_eq!(actual, expected); + }}; + } #[test] - fn actions_write_iter() { + fn write_actions_iter() { actions_write_iter_test!([0, 5, 0], [(0, 5, true, true)]); actions_write_iter_test!( [2, 3, 4], @@ -263,4 +354,46 @@ mod tests { actions_write_iter_test!([2, 0, 0], [(0, 2, true, true)]); actions_write_iter_test!([0, 0, 4], [(0, 4, true, true)]); } + + #[test] + fn single_direction_write_actions_iter_write() { + actions_write_iter_test!(true, [0, 5, 0], [(Some(0), 5, true, true)]); + actions_write_iter_test!( + true, + [2, 3, 4], + [ + (Some(0), 2, true, false), + (Some(2), 3, false, false), + (Some(5), 4, false, true), + ] + ); + actions_write_iter_test!( + true, + [2, 0, 4], + [(Some(0), 2, true, false), (Some(2), 4, false, true)] + ); + actions_write_iter_test!(true, [2, 0, 0], [(Some(0), 2, true, true)]); + actions_write_iter_test!(true, [0, 0, 4], [(Some(0), 4, true, true)]); + } + + #[test] + fn single_direction_write_actions_iter_read() { + actions_write_iter_test!(false, [0, 5, 0], [(None, 5, true, true)]); + actions_write_iter_test!( + false, + [2, 3, 4], + [ + (None, 2, true, false), + (None, 3, false, false), + (None, 4, false, true), + ] + ); + actions_write_iter_test!( + false, + [2, 0, 4], + [(None, 2, true, false), (None, 4, false, true)] + ); + actions_write_iter_test!(false, [2, 0, 0], [(None, 2, true, true)]); + actions_write_iter_test!(false, [0, 0, 4], [(None, 4, true, true)]); + } } From a3c38381bbc09e0e652e05bdd2aec3a91ef99d43 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 9 Dec 2023 11:37:29 +0100 Subject: [PATCH 42/73] Fix tests --- src/common/lpspi/transfer_actions.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 20772916..a4428b30 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -302,7 +302,7 @@ mod tests { }) .map(|val| { ( - val.buf as usize - 1000, + val.buf.unwrap() as usize - 1000, val.len.get(), val.is_first, val.is_last, @@ -318,7 +318,7 @@ mod tests { ($write:expr, $len:expr, $expected:expr) => {{ let actual = SingleDirectionWriteActionIter::new(MaybeWriteActions { write_buf: if $write { - Somt(1000usize as *const u8) + Some(1000usize as *const u8) } else { None }, @@ -333,7 +333,7 @@ mod tests { ) }) .collect::>(); - let expected: &[(usize, usize, bool, bool)] = &$expected; + let expected: &[(Option, usize, bool, bool)] = &$expected; assert_eq!(actual, expected); }}; @@ -357,8 +357,8 @@ mod tests { #[test] fn single_direction_write_actions_iter_write() { - actions_write_iter_test!(true, [0, 5, 0], [(Some(0), 5, true, true)]); - actions_write_iter_test!( + actions_single_direction_write_iter_test!(true, [0, 5, 0], [(Some(0), 5, true, true)]); + actions_single_direction_write_iter_test!( true, [2, 3, 4], [ @@ -367,19 +367,19 @@ mod tests { (Some(5), 4, false, true), ] ); - actions_write_iter_test!( + actions_single_direction_write_iter_test!( true, [2, 0, 4], [(Some(0), 2, true, false), (Some(2), 4, false, true)] ); - actions_write_iter_test!(true, [2, 0, 0], [(Some(0), 2, true, true)]); - actions_write_iter_test!(true, [0, 0, 4], [(Some(0), 4, true, true)]); + actions_single_direction_write_iter_test!(true, [2, 0, 0], [(Some(0), 2, true, true)]); + actions_single_direction_write_iter_test!(true, [0, 0, 4], [(Some(0), 4, true, true)]); } #[test] fn single_direction_write_actions_iter_read() { - actions_write_iter_test!(false, [0, 5, 0], [(None, 5, true, true)]); - actions_write_iter_test!( + actions_single_direction_write_iter_test!(false, [0, 5, 0], [(None, 5, true, true)]); + actions_single_direction_write_iter_test!( false, [2, 3, 4], [ @@ -388,12 +388,12 @@ mod tests { (None, 4, false, true), ] ); - actions_write_iter_test!( + actions_single_direction_write_iter_test!( false, [2, 0, 4], [(None, 2, true, false), (None, 4, false, true)] ); - actions_write_iter_test!(false, [2, 0, 0], [(None, 2, true, true)]); - actions_write_iter_test!(false, [0, 0, 4], [(None, 4, true, true)]); + actions_single_direction_write_iter_test!(false, [2, 0, 0], [(None, 2, true, true)]); + actions_single_direction_write_iter_test!(false, [0, 0, 4], [(None, 4, true, true)]); } } From 3eae4e9e34beee992f7d1a513b69d2af15c94093 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 9 Dec 2023 11:39:31 +0100 Subject: [PATCH 43/73] Fix rust-toolchain.toml --- Cargo.toml | 8 -------- rust-toolchain.toml | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e04ee368..4c1988d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -152,20 +152,12 @@ codegen-units = 256 imxrt-rt = { workspace = true } menu = "0.4.0" rtic = { version = "2.0.1", features = ["thumbv7-backend"] } -rtic-monotonics = { version = "1.4.1", features = [ - "cortex-m-systick", - "embedded-hal-async", -] } log = "0.4" defmt = "0.3" pin-utils = "0.1" usb-device = { version = "0.2", features = ["test-class-high-speed"] } usbd-serial = "0.1" usbd-hid = "0.6" -embedded-hal-bus = { version = "0.1.0-rc.1", features = ["async"] } -teensy4-bsp = "0.4.5" -imxrt-uart-panic = "0.1.2" -teensy4-panic = "0.2.3" [target.'cfg(all(target_arch = "arm", target_os = "none"))'.dev-dependencies] board = { path = "board" } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 672e30b7..fa481889 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] channel = "nightly" # components = ["rustfmt", "llvm-tools"] -targets = ["thumbv7em-none-eabihf"] +# targets = ["thumbv7em-none-eabihf"] From 69fc170443a5f0c8f9e391df133ea07cc1938b2c Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 9 Dec 2023 13:43:54 +0100 Subject: [PATCH 44/73] First bus activity! --- src/common/lpspi/bus.rs | 10 +++++++--- src/common/lpspi/transfer_actions.rs | 10 ++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 27d0548e..147c17a8 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -230,6 +230,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { tx_buffer[i] = data.add(i).read(); } } + + ral::write_reg!(ral::lpspi, self.lpspi(), TDR, u32::from_be_bytes(tx_buffer)); } } @@ -256,8 +258,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.clear_fifos(); - let _read_task = async { assert!(!sequence.contains_read_actions()) }; - let _write_task = async { + let read_task = async { assert!(!sequence.contains_read_actions()) }; + let write_task = async { unsafe { let has_phase_1 = sequence.phase1.is_some(); let has_phase_2 = sequence.phase2.is_some(); @@ -315,7 +317,9 @@ impl<'a, const N: u8> Lpspi<'a, N> { } }; - Ok(()) + futures::join!(read_task, write_task); + + self.check_errors() } /// Perform a sequence of transfer actions while continuously checking for errors. diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index a4428b30..1fec9a93 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -1,5 +1,6 @@ use core::{marker::PhantomData, num::NonZeroUsize}; +#[derive(Debug)] pub(crate) struct DualDirectionActions { read_buf: *mut u8, write_buf: *const u8, @@ -117,21 +118,25 @@ impl Iterator for SingleDirectionWriteActionIter { } } +#[derive(Debug)] pub(crate) struct ReadActions { read_buf: *mut u8, len: [usize; 3], } +#[derive(Debug)] pub(crate) struct WriteActions { write_buf: *const u8, len: [usize; 3], } +#[derive(Debug)] pub(crate) struct MaybeWriteActions { write_buf: Option<*const u8>, len: [usize; 3], } +#[derive(Debug)] pub(crate) enum SingleDirectionActions { Read(ReadActions), Write(WriteActions), @@ -161,7 +166,7 @@ impl SingleDirectionActions { /// The order in which the bytes need /// to be transferred on the bus -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ByteOrder { /// Bytes need to be transferred in the order /// that they are in @@ -172,12 +177,13 @@ pub enum ByteOrder { HalfWordReversed, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum TransferDirection { Read, Write, } +#[derive(Debug)] pub(crate) struct ActionSequence<'a> { pub(crate) phase1: Option, pub(crate) phase2: Option, From fe9816640ca55af89c95606e18795dd58d798281 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sat, 9 Dec 2023 14:18:00 +0100 Subject: [PATCH 45/73] Fix order of single word transfer --- src/common/lpspi/bus.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 147c17a8..1f4636be 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -221,17 +221,21 @@ impl<'a, const N: u8> Lpspi<'a, N> { if let Some(data) = write_data { let mut tx_buffer = [0u8; 4]; + let active_buffer = &mut tx_buffer[(4 - len.get())..]; if reverse_bytes { - for i in 0..len.get() { - tx_buffer[i] = data.add(len.get() - i - 1).read(); - } + active_buffer + .iter_mut() + .rev() + .enumerate() + .for_each(|(pos, val)| *val = data.add(pos).read()); } else { - for i in 0..len.get() { - tx_buffer[i] = data.add(i).read(); - } + active_buffer + .iter_mut() + .enumerate() + .for_each(|(pos, val)| *val = data.add(pos).read()); } - ral::write_reg!(ral::lpspi, self.lpspi(), TDR, u32::from_be_bytes(tx_buffer)); + ral::write_reg!(ral::lpspi, self.lpspi(), TDR, u32::from_le_bytes(tx_buffer)); } } From e2c357c6c2517fbd795c4d9d45c834c263480ed1 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 10 Dec 2023 23:14:20 +0100 Subject: [PATCH 46/73] Prepare status_watcher for interrupt enable/disable --- src/common/lpspi/bus.rs | 9 +++-- src/common/lpspi/status_watcher.rs | 58 +++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 1f4636be..ad01e30b 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -81,11 +81,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { lpspi::prepare(&mut pins.sck); // Configure watermarks. - // This is more for good measure, we don't really use the watermarks. - // ral::write_reg!(ral::lpspi, this.lpspi(), FCR, - // RXWATER: 0, - // TXWATER: u32::MAX - // ); + ral::write_reg!(ral::lpspi, this.lpspi(), FCR, + RXWATER: this.rx_fifo_size/2 - 1, // Notify when we have at least rx_fifo_size/2 data available + TXWATER: this.tx_fifo_size/2 // Nofify when we have at least tx_fifo_size/2 space available + ); // Enable ral::write_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_1); diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index 4137cbd7..7dd15fb4 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -14,6 +14,10 @@ struct StatusWatcherInner { transfer_complete_waker: Option, error_caught: Option, error_caught_waker: Option, + tx_fifo_watermark_busy: bool, + rx_fifo_watermark_busy: bool, + tx_fifo_watermark_waker: Option, + rx_fifo_watermark_waker: Option, interrupts_enabled: bool, } @@ -61,6 +65,10 @@ impl StatusWatcher { transfer_complete_waker: None, error_caught: None, error_caught_waker: None, + tx_fifo_watermark_busy: false, + rx_fifo_watermark_busy: false, + tx_fifo_watermark_waker: None, + rx_fifo_watermark_waker: None, interrupts_enabled: false, })), lpspi, @@ -112,6 +120,8 @@ impl StatusWatcher { self, |inner| inner.transfer_complete_happened.then_some(()), |inner| &mut inner.transfer_complete_waker, + |_| (), + |_| (), ) .await } @@ -121,6 +131,8 @@ impl StatusWatcher { self, |inner| inner.error_caught.take(), |inner| &mut inner.error_caught_waker, + |_| (), + |_| (), ) .await; @@ -139,36 +151,59 @@ impl StatusWatcher { inner.error_caught = None; }); } + + pub async fn wait_for_tx_watermark(&self) { + todo!() + } + pub async fn wait_for_rx_watermark(&self) { + todo!() + } } -struct StatusWatcherFuture<'a, const N: u8, T, C, W> +struct StatusWatcherFuture<'a, const N: u8, T, C, W, I0, I1> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, + I0: Fn(&mut StatusWatcherInner), + I1: Fn(&mut StatusWatcherInner), { watcher: &'a StatusWatcher, condition: C, waker: W, + interrupt_enable: I0, + interrupt_disable: I1, } -impl<'a, const N: u8, T, C, W> StatusWatcherFuture<'a, N, T, C, W> +impl<'a, const N: u8, T, C, W, I0, I1> StatusWatcherFuture<'a, N, T, C, W, I0, I1> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, + I0: Fn(&mut StatusWatcherInner), + I1: Fn(&mut StatusWatcherInner), { - fn new(watcher: &'a StatusWatcher, condition: C, waker: W) -> Self { + fn new( + watcher: &'a StatusWatcher, + condition: C, + waker: W, + interrupt_enable: I0, + interrupt_disable: I1, + ) -> Self { Self { watcher, condition, waker, + interrupt_enable, + interrupt_disable, } } } -impl<'a, const N: u8, T, C, W> Future for StatusWatcherFuture<'a, N, T, C, W> +impl<'a, const N: u8, T, C, W, I0, I1> Future for StatusWatcherFuture<'a, N, T, C, W, I0, I1> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, + I0: Fn(&mut StatusWatcherInner), + I1: Fn(&mut StatusWatcherInner), { type Output = T; @@ -210,8 +245,23 @@ where } } + (self.interrupt_enable)(inner); + Poll::Pending } }) } } + +impl<'a, const N: u8, T, C, W, I0, I1> Drop for StatusWatcherFuture<'a, N, T, C, W, I0, I1> +where + C: Fn(&mut StatusWatcherInner) -> Option, + W: Fn(&mut StatusWatcherInner) -> &mut Option, + I0: Fn(&mut StatusWatcherInner), + I1: Fn(&mut StatusWatcherInner), +{ + fn drop(&mut self) { + self.watcher + .with_check_and_reset(|inner| (self.interrupt_disable)(inner)); + } +} From 92424130ed27bff9e1d7b5ed8546519fb37af13f Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 10 Dec 2023 23:16:22 +0100 Subject: [PATCH 47/73] Minor fix --- src/common/lpspi/status_watcher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index 7dd15fb4..d515c86f 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -243,10 +243,10 @@ where if let Some(waker) = (self.waker)(inner).take() { waker.wake(); } + } else { + (self.interrupt_enable)(inner); } - (self.interrupt_enable)(inner); - Poll::Pending } }) From 7f58e7eaa933f793abbe40a97e90b207f981d15e Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 10 Dec 2023 23:38:57 +0100 Subject: [PATCH 48/73] Add wait-for-watermark --- src/common/lpspi/bus.rs | 14 +++-- src/common/lpspi/status_watcher.rs | 83 ++++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index ad01e30b..f2399a5d 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -161,6 +161,14 @@ impl<'a, const N: u8> Lpspi<'a, N> { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } + async fn wait_for_read_watermark(&mut self) { + self.data.lpspi.wait_for_rx_watermark().await.unwrap(); + } + + async fn wait_for_write_watermark(&mut self) { + self.data.lpspi.wait_for_tx_watermark().await.unwrap(); + } + fn start_frame( &mut self, reverse_bytes: bool, @@ -206,9 +214,9 @@ impl<'a, const N: u8> Lpspi<'a, N> { ByteOrder::HalfWordReversed => true, }; - // TODO enqueue this somewhere so that we don't overflow if the buffer is full. - // Or wait for the buffer to become available. Either works. - // Maybe dynamically enable/disable the watermark interrupts so we can async wait for the watermark + // This should make sure that at least two words are free to be written + self.wait_for_write_watermark().await; + self.start_frame( false, is_first_frame, diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index d515c86f..a5b49d49 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -152,58 +152,97 @@ impl StatusWatcher { }); } - pub async fn wait_for_tx_watermark(&self) { - todo!() + pub async fn wait_for_tx_watermark(&self) -> Result<(), LpspiError> { + self.with_check_and_reset(|inner| -> Result<(), LpspiError> { + if inner.tx_fifo_watermark_busy { + Err(LpspiError::Busy) + } else { + inner.tx_fifo_watermark_busy = true; + Ok(()) + } + })?; + + Ok(StatusWatcherFuture::new( + self, + |_| ral::read_reg!(ral::lpspi, self.lpspi, SR, TDF == TDF_1).then_some(()), + |inner| &mut inner.tx_fifo_watermark_waker, + |_| ral::modify_reg!(ral::lpspi, self.lpspi, IER, TDIE: TDIE_1), + |inner| { + ral::modify_reg!(ral::lpspi, self.lpspi, IER, TDIE: TDIE_0); + inner.tx_fifo_watermark_busy = false; + }, + ) + .await) } - pub async fn wait_for_rx_watermark(&self) { - todo!() + + pub async fn wait_for_rx_watermark(&self) -> Result<(), LpspiError> { + self.with_check_and_reset(|inner| -> Result<(), LpspiError> { + if inner.rx_fifo_watermark_busy { + Err(LpspiError::Busy) + } else { + inner.rx_fifo_watermark_busy = true; + Ok(()) + } + })?; + + Ok(StatusWatcherFuture::new( + self, + |_| ral::read_reg!(ral::lpspi, self.lpspi, SR, RDF == RDF_1).then_some(()), + |inner| &mut inner.rx_fifo_watermark_waker, + |_| ral::modify_reg!(ral::lpspi, self.lpspi, IER, RDIE: RDIE_1), + |inner| { + ral::modify_reg!(ral::lpspi, self.lpspi, IER, RDIE: RDIE_0); + inner.rx_fifo_watermark_busy = false; + }, + ) + .await) } } -struct StatusWatcherFuture<'a, const N: u8, T, C, W, I0, I1> +struct StatusWatcherFuture<'a, const N: u8, T, C, W, I, D> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, - I0: Fn(&mut StatusWatcherInner), - I1: Fn(&mut StatusWatcherInner), + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), { watcher: &'a StatusWatcher, condition: C, waker: W, - interrupt_enable: I0, - interrupt_disable: I1, + interrupt_enable: I, + on_drop: D, } -impl<'a, const N: u8, T, C, W, I0, I1> StatusWatcherFuture<'a, N, T, C, W, I0, I1> +impl<'a, const N: u8, T, C, W, I, D> StatusWatcherFuture<'a, N, T, C, W, I, D> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, - I0: Fn(&mut StatusWatcherInner), - I1: Fn(&mut StatusWatcherInner), + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), { fn new( watcher: &'a StatusWatcher, condition: C, waker: W, - interrupt_enable: I0, - interrupt_disable: I1, + interrupt_enable: I, + on_drop: D, ) -> Self { Self { watcher, condition, waker, interrupt_enable, - interrupt_disable, + on_drop, } } } -impl<'a, const N: u8, T, C, W, I0, I1> Future for StatusWatcherFuture<'a, N, T, C, W, I0, I1> +impl<'a, const N: u8, T, C, W, I, D> Future for StatusWatcherFuture<'a, N, T, C, W, I, D> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, - I0: Fn(&mut StatusWatcherInner), - I1: Fn(&mut StatusWatcherInner), + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), { type Output = T; @@ -253,15 +292,15 @@ where } } -impl<'a, const N: u8, T, C, W, I0, I1> Drop for StatusWatcherFuture<'a, N, T, C, W, I0, I1> +impl<'a, const N: u8, T, C, W, I, D> Drop for StatusWatcherFuture<'a, N, T, C, W, I, D> where C: Fn(&mut StatusWatcherInner) -> Option, W: Fn(&mut StatusWatcherInner) -> &mut Option, - I0: Fn(&mut StatusWatcherInner), - I1: Fn(&mut StatusWatcherInner), + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), { fn drop(&mut self) { self.watcher - .with_check_and_reset(|inner| (self.interrupt_disable)(inner)); + .with_check_and_reset(|inner| (self.on_drop)(inner)); } } From 2a32c5760751023d8c2dfdc2649582369d538b69 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Wed, 13 Dec 2023 22:39:14 +0100 Subject: [PATCH 49/73] First working larger transmission! --- src/common/lpspi/bus.rs | 52 ++++++++++++++++++--- src/common/lpspi/status_watcher.rs | 15 +++++++ src/common/lpspi/transfer_actions.rs | 67 +++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index f2399a5d..b00899a1 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -9,13 +9,15 @@ use super::{ }; use crate::{ iomuxc::{consts, lpspi}, - lpspi::LpspiDma, + lpspi::{transfer_actions::ChunkIter, LpspiDma}, ral, }; mod eh1_impl; const MAX_FRAME_SIZE_BITS: u32 = 1 << 12; +const MAX_FRAME_SIZE_BYTES: u32 = MAX_FRAME_SIZE_BITS / 8; +const MAX_FRAME_SIZE_U32: u32 = MAX_FRAME_SIZE_BYTES / 4; impl<'a, const N: u8> Lpspi<'a, N> { /// The peripheral instance. @@ -169,7 +171,18 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.data.lpspi.wait_for_tx_watermark().await.unwrap(); } - fn start_frame( + async fn wait_for_write_space_available(&mut self) { + if !self.fifo_write_space_available() { + self.wait_for_write_watermark().await; + } + } + + async fn tx_fifo_enqueue_data(&mut self, val: u32) { + self.wait_for_write_space_available().await; + ral::write_reg!(ral::lpspi, self.lpspi(), TDR, val); + } + + async fn start_frame( &mut self, reverse_bytes: bool, is_first_frame: bool, @@ -181,6 +194,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { let num_bits = frame_size_bytes.get() as u32 * 8; assert!(num_bits <= MAX_FRAME_SIZE_BITS); + self.wait_for_write_space_available().await; ral::write_reg!(ral::lpspi, self.lpspi(), TCR, CPOL: if self.mode.polarity == Polarity::IdleHigh {CPOL_1} else {CPOL_0}, CPHA: if self.mode.phase == Phase::CaptureOnSecondTransition {CPHA_1} else {CPHA_0}, @@ -215,8 +229,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { }; // This should make sure that at least two words are free to be written - self.wait_for_write_watermark().await; - self.start_frame( false, is_first_frame, @@ -224,7 +236,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { read, write_data.is_some(), len, - ); + ) + .await; if let Some(data) = write_data { let mut tx_buffer = [0u8; 4]; @@ -242,7 +255,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { .for_each(|(pos, val)| *val = data.add(pos).read()); } - ral::write_reg!(ral::lpspi, self.lpspi(), TDR, u32::from_le_bytes(tx_buffer)); + self.tx_fifo_enqueue_data(u32::from_le_bytes(tx_buffer)) + .await; } } @@ -260,7 +274,31 @@ impl<'a, const N: u8> Lpspi<'a, N> { let len = NonZeroUsize::new(len.get() / 4).unwrap(); let write_data: Option<*const u32> = write_data.map(|p| p.cast()); - todo!(); + for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { + log::info!("Start chunk ... {:?}", chunk); + self.start_frame( + // TODO: optimize u32 by reversing this when necessary + false, + is_first_frame && chunk.first, + is_last_frame && chunk.last, + read, + write_data.is_some(), + chunk.size.saturating_mul(NonZeroUsize::new_unchecked(4)), + ) + .await; + + // Todo: optimize this section into DMA if possible + if let Some(write_data) = write_data { + let write_data = write_data.add(chunk.position); + + for i in 0..chunk.size.get() { + // TODO: optimize to aligned read if possible + let val = write_data.add(i).read_unaligned(); + let val = byteorder.reorder(val); + self.tx_fifo_enqueue_data(val).await; + } + } + } } /// Perform a sequence of transfer actions diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index a5b49d49..407af107 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -54,6 +54,21 @@ impl StatusWatcherInner { waker.wake(); } } + + if ral::read_reg!(ral::lpspi, lpspi, SR, TDF == TDF_1) { + ral::modify_reg!(ral::lpspi, lpspi, IER, TDIE: TDIE_0); + + if let Some(waker) = self.tx_fifo_watermark_waker.take() { + waker.wake(); + } + } + if ral::read_reg!(ral::lpspi, lpspi, SR, RDF == RDF_1) { + ral::modify_reg!(ral::lpspi, lpspi, IER, RDIE: RDIE_0); + + if let Some(waker) = self.rx_fifo_watermark_waker.take() { + waker.wake(); + } + } } } diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 1fec9a93..aab654b4 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -1,4 +1,4 @@ -use core::{marker::PhantomData, num::NonZeroUsize}; +use core::{iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; #[derive(Debug)] pub(crate) struct DualDirectionActions { @@ -177,6 +177,22 @@ pub enum ByteOrder { HalfWordReversed, } +impl ByteOrder { + pub(crate) fn reorder(self, val: u32) -> u32 { + match self { + ByteOrder::Normal => val, + ByteOrder::WordReversed => { + let [a, b, c, d] = val.to_le_bytes(); + u32::from_le_bytes([d, c, b, a]) + } + ByteOrder::HalfWordReversed => { + let [a, b, c, d] = val.to_le_bytes(); + u32::from_le_bytes([b, a, d, c]) + } + } + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) enum TransferDirection { Read, @@ -294,12 +310,61 @@ pub(crate) fn create_actions_read_write_in_place<'a, T: BufferType>( } } +#[derive(Debug)] +pub(crate) struct ChunkIterChunk { + pub(crate) first: bool, + pub(crate) last: bool, + pub(crate) position: usize, + pub(crate) size: NonZeroUsize, +} + +pub(crate) struct ChunkIter { + size: NonZeroUsize, + position: usize, + max_chunk_size: usize, +} +impl ChunkIter { + pub(crate) fn new(size: NonZeroUsize, max_chunk_size: usize) -> Self { + Self { + size, + position: 0, + max_chunk_size, + } + } +} + +impl Iterator for ChunkIter { + type Item = ChunkIterChunk; + + fn next(&mut self) -> Option { + let first = self.position == 0; + + let position = self.position; + let leftover = self.size.get().checked_sub(self.position)?; + let next_chunk_size = leftover.min(self.max_chunk_size); + + self.position += next_chunk_size; + let last = self.position >= self.size.get(); + + Some(ChunkIterChunk { + first, + last, + position, + size: NonZeroUsize::new(next_chunk_size)?, + }) + } +} + #[cfg(test)] mod tests { use super::*; extern crate std; use std::vec::Vec; + // TODO: tests for + // - ChunkIter + // - Byteorder conversion functions + macro_rules! actions_write_iter_test { ($len:expr, $expected:expr) => {{ let actual = WriteActionIter::new(WriteActions { From 43ddf45d4921fdcf711431c2e3e3863019026f17 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Wed, 13 Dec 2023 23:01:36 +0100 Subject: [PATCH 50/73] Remove comment --- src/common/lpspi/bus.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index b00899a1..2b1784d8 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -58,6 +58,8 @@ impl<'a, const N: u8> Lpspi<'a, N> { mode: MODE_0, }; + log::info!("Fifo sizes: {tx_fifo_size}(TX) {rx_fifo_size}(RX)"); + // Reset and disable ral::modify_reg!(ral::lpspi, this.lpspi(), CR, MEN: MEN_0, RST: RST_1); while ral::read_reg!(ral::lpspi, this.lpspi(), CR, MEN == MEN_1) {} @@ -275,7 +277,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { let write_data: Option<*const u32> = write_data.map(|p| p.cast()); for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { - log::info!("Start chunk ... {:?}", chunk); self.start_frame( // TODO: optimize u32 by reversing this when necessary false, From 95ecfcfd30c70df37e9857151f277cdd76b18ec2 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Wed, 13 Dec 2023 23:03:10 +0100 Subject: [PATCH 51/73] Add TODO --- src/common/lpspi/bus.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 2b1784d8..f4871b93 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -379,6 +379,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { let data = this.data; + // TODO: check if error handling actually works let result: Result<(), LpspiError> = futures::select_biased! { res = data.lpspi.watch_for_errors().fuse() => res, res = this.transfer_unchecked(sequence).fuse() => res, From ea86c450a6bc1eae27eeeb110b12abb9d9157260 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 11:55:35 +0100 Subject: [PATCH 52/73] More optimization on write --- src/common/lpspi/bus.rs | 42 ++++++++++++++++++++++------ src/common/lpspi/transfer_actions.rs | 21 +++++++++++--- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index f4871b93..b037f250 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -278,8 +278,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { self.start_frame( - // TODO: optimize u32 by reversing this when necessary - false, + byteorder.requires_flip(), is_first_frame && chunk.first, is_last_frame && chunk.last, read, @@ -288,15 +287,42 @@ impl<'a, const N: u8> Lpspi<'a, N> { ) .await; - // Todo: optimize this section into DMA if possible if let Some(write_data) = write_data { let write_data = write_data.add(chunk.position); - for i in 0..chunk.size.get() { - // TODO: optimize to aligned read if possible - let val = write_data.add(i).read_unaligned(); - let val = byteorder.reorder(val); - self.tx_fifo_enqueue_data(val).await; + let is_aligned = write_data.align_offset(core::mem::align_of::()) == 0; + let requires_reorder = byteorder.requires_reorder(); + + match (is_aligned, requires_reorder) { + (true, true) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read(); + let val = byteorder.reorder(val); + self.tx_fifo_enqueue_data(val).await; + } + } + (true, false) => { + // This is the case that supports DMA. + // TODO: add DMA. + for i in 0..chunk.size.get() { + let val = write_data.add(i).read(); + self.tx_fifo_enqueue_data(val).await; + } + log::info!("Would support DMA."); + } + (false, true) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read_unaligned(); + let val = byteorder.reorder(val); + self.tx_fifo_enqueue_data(val).await; + } + } + (false, false) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read_unaligned(); + self.tx_fifo_enqueue_data(val).await; + } + } } } } diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index aab654b4..eda38c73 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -181,16 +181,29 @@ impl ByteOrder { pub(crate) fn reorder(self, val: u32) -> u32 { match self { ByteOrder::Normal => val, - ByteOrder::WordReversed => { - let [a, b, c, d] = val.to_le_bytes(); - u32::from_le_bytes([d, c, b, a]) - } + ByteOrder::WordReversed => val, ByteOrder::HalfWordReversed => { let [a, b, c, d] = val.to_le_bytes(); u32::from_le_bytes([b, a, d, c]) } } } + + pub(crate) fn requires_reorder(self) -> bool { + match self { + ByteOrder::Normal => false, + ByteOrder::WordReversed => false, + ByteOrder::HalfWordReversed => true, + } + } + + pub(crate) fn requires_flip(self) -> bool { + match self { + ByteOrder::Normal => false, + ByteOrder::WordReversed => true, + ByteOrder::HalfWordReversed => false, + } + } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] From 01995ff84df298a503c8bed93fb4708a27fb7d6d Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 13:32:27 +0100 Subject: [PATCH 53/73] Make ownership non-exclusive for most internal bus functions --- src/common/lpspi/bus.rs | 43 +++++++++++++++++++++--------- src/common/lpspi/status_watcher.rs | 4 ++- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index b037f250..f4e4ee79 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -145,11 +145,11 @@ impl<'a, const N: u8> Lpspi<'a, N> { } /// Returns errors, if any there are any. - fn check_errors(&mut self) -> Result<(), LpspiError> { + fn check_errors(&self) -> Result<(), LpspiError> { self.data.lpspi.check_for_errors() } - fn configure_dma(&mut self, dma_read: bool, dma_write: bool) { + fn configure_dma(&self, dma_read: bool, dma_write: bool) { // Configure DMA ral::modify_reg!(ral::lpspi, self.lpspi(), DER, RDDE: if dma_read {RDDE_1} else {RDDE_0}, @@ -157,35 +157,49 @@ impl<'a, const N: u8> Lpspi<'a, N> { ); } - fn fifo_read_data_available(&mut self) -> bool { + fn fifo_read_data_available(&self) -> bool { ral::read_reg!(ral::lpspi, self.lpspi(), RSR, RXEMPTY == RXEMPTY_0) } - fn fifo_write_space_available(&mut self) -> bool { + fn fifo_write_space_available(&self) -> bool { ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) } - async fn wait_for_read_watermark(&mut self) { - self.data.lpspi.wait_for_rx_watermark().await.unwrap(); + async fn wait_for_read_watermark(&self, watermark: u32) { + self.data + .lpspi + .wait_for_rx_watermark(watermark) + .await + .unwrap(); } - async fn wait_for_write_watermark(&mut self) { + async fn wait_for_write_watermark(&self) { self.data.lpspi.wait_for_tx_watermark().await.unwrap(); } - async fn wait_for_write_space_available(&mut self) { + async fn wait_for_write_space_available(&self) { if !self.fifo_write_space_available() { self.wait_for_write_watermark().await; } } - async fn tx_fifo_enqueue_data(&mut self, val: u32) { + async fn wait_for_read_data_available(&mut self, at_most: usize) { + if !self.fifo_read_data_available() { + let mut watermark = self.rx_fifo_size / 2; + if let Ok(at_most) = u32::try_from(at_most) { + watermark = watermark.min(at_most); + } + self.wait_for_read_watermark(watermark).await; + } + } + + async fn tx_fifo_enqueue_data(&self, val: u32) { self.wait_for_write_space_available().await; ral::write_reg!(ral::lpspi, self.lpspi(), TDR, val); } async fn start_frame( - &mut self, + &self, reverse_bytes: bool, is_first_frame: bool, is_last_frame: bool, @@ -214,7 +228,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { } async unsafe fn write_single_word( - &mut self, + &self, write_data: Option<*const u8>, byteorder: ByteOrder, read: bool, @@ -263,7 +277,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { } async unsafe fn write_u32_stream( - &mut self, + &self, write_data: Option<*const u8>, byteorder: ByteOrder, read: bool, @@ -334,7 +348,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.clear_fifos(); - let read_task = async { assert!(!sequence.contains_read_actions()) }; + let read_task = async { + assert!(!sequence.contains_read_actions()); + self.check_errors() + }; let write_task = async { unsafe { let has_phase_1 = sequence.phase1.is_some(); diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index 407af107..e6af5436 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -190,7 +190,7 @@ impl StatusWatcher { .await) } - pub async fn wait_for_rx_watermark(&self) -> Result<(), LpspiError> { + pub async fn wait_for_rx_watermark(&self, watermark: u32) -> Result<(), LpspiError> { self.with_check_and_reset(|inner| -> Result<(), LpspiError> { if inner.rx_fifo_watermark_busy { Err(LpspiError::Busy) @@ -200,6 +200,8 @@ impl StatusWatcher { } })?; + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark); + Ok(StatusWatcherFuture::new( self, |_| ral::read_reg!(ral::lpspi, self.lpspi, SR, RDF == RDF_1).then_some(()), From fea46a2b6e2ed8e9ee9704c3cabe2a6ee4af367e Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 14:18:19 +0100 Subject: [PATCH 54/73] Split LPSPI into read and write part, for async ownership problems --- src/common/lpspi.rs | 22 ++- src/common/lpspi/bus.rs | 297 +++++++-------------------------- src/common/lpspi/read_part.rs | 30 ++++ src/common/lpspi/write_part.rs | 179 ++++++++++++++++++++ 4 files changed, 286 insertions(+), 242 deletions(-) create mode 100644 src/common/lpspi/read_part.rs create mode 100644 src/common/lpspi/write_part.rs diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 00165d85..2a42fa9d 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -8,11 +8,17 @@ use crate::ral; mod bus; mod disabled; mod error; +mod read_part; mod status_watcher; mod transfer_actions; +mod write_part; use status_watcher::StatusWatcher; +const MAX_FRAME_SIZE_BITS: u32 = 1 << 12; +const MAX_FRAME_SIZE_BYTES: u32 = MAX_FRAME_SIZE_BITS / 8; +const MAX_FRAME_SIZE_U32: u32 = MAX_FRAME_SIZE_BYTES / 4; + /// TODO pub enum LpspiDma { /// Everything is CPU driven @@ -55,14 +61,24 @@ pub struct LpspiData { lpspi: StatusWatcher, } +struct LpspiReadPart<'a, const N: u8> { + data: &'a LpspiData, + rx_fifo_size: u32, +} + +struct LpspiWritePart<'a, const N: u8> { + data: &'a LpspiData, + tx_fifo_size: u32, + mode: Mode, +} + /// TODO pub struct Lpspi<'a, const N: u8> { dma: LpspiDma, source_clock_hz: u32, data: &'a LpspiData, - tx_fifo_size: u32, - rx_fifo_size: u32, - mode: Mode, + read_part: LpspiReadPart<'a, N>, + write_part: LpspiWritePart<'a, N>, } /// An LPSPI peripheral which is temporarily disabled. diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index f4e4ee79..84dcecf7 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -1,24 +1,17 @@ -use core::num::NonZeroUsize; - -use eh1::spi::{Phase, Polarity, MODE_0}; +use eh1::spi::MODE_0; use futures::FutureExt; use super::{ - transfer_actions::{ActionSequence, ByteOrder}, - Disabled, Lpspi, LpspiData, LpspiError, LpspiInterruptHandler, Pins, StatusWatcher, + transfer_actions::ActionSequence, Disabled, Lpspi, LpspiData, LpspiDma, LpspiError, + LpspiInterruptHandler, LpspiReadPart, LpspiWritePart, Pins, StatusWatcher, }; use crate::{ iomuxc::{consts, lpspi}, - lpspi::{transfer_actions::ChunkIter, LpspiDma}, ral, }; mod eh1_impl; -const MAX_FRAME_SIZE_BITS: u32 = 1 << 12; -const MAX_FRAME_SIZE_BYTES: u32 = MAX_FRAME_SIZE_BITS / 8; -const MAX_FRAME_SIZE_U32: u32 = MAX_FRAME_SIZE_BYTES / 4; - impl<'a, const N: u8> Lpspi<'a, N> { /// The peripheral instance. pub const N: u8 = N; @@ -49,13 +42,18 @@ impl<'a, const N: u8> Lpspi<'a, N> { lpspi: StatusWatcher::new(lpspi), }; + let data = data_storage.insert(data); + let mut this = Self { source_clock_hz, dma: LpspiDma::Disabled, - data: data_storage.insert(data), - tx_fifo_size, - rx_fifo_size, - mode: MODE_0, + data: data, + read_part: LpspiReadPart { data, rx_fifo_size }, + write_part: LpspiWritePart { + data, + tx_fifo_size, + mode: MODE_0, + }, }; log::info!("Fifo sizes: {tx_fifo_size}(TX) {rx_fifo_size}(RX)"); @@ -84,10 +82,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { lpspi::prepare(&mut pins.sdi); lpspi::prepare(&mut pins.sck); - // Configure watermarks. + // Configure watermarks ral::write_reg!(ral::lpspi, this.lpspi(), FCR, - RXWATER: this.rx_fifo_size/2 - 1, // Notify when we have at least rx_fifo_size/2 data available - TXWATER: this.tx_fifo_size/2 // Nofify when we have at least tx_fifo_size/2 space available + RXWATER: 0, // Notify when we have any data available + TXWATER: tx_fifo_size/2 // Nofify when we have at least tx_fifo_size/2 space available ); // Enable @@ -157,200 +155,17 @@ impl<'a, const N: u8> Lpspi<'a, N> { ); } - fn fifo_read_data_available(&self) -> bool { - ral::read_reg!(ral::lpspi, self.lpspi(), RSR, RXEMPTY == RXEMPTY_0) - } - - fn fifo_write_space_available(&self) -> bool { - ral::read_reg!(ral::lpspi, self.lpspi(), FSR, TXCOUNT < self.tx_fifo_size) - } - - async fn wait_for_read_watermark(&self, watermark: u32) { - self.data - .lpspi - .wait_for_rx_watermark(watermark) - .await - .unwrap(); - } - - async fn wait_for_write_watermark(&self) { - self.data.lpspi.wait_for_tx_watermark().await.unwrap(); - } - - async fn wait_for_write_space_available(&self) { - if !self.fifo_write_space_available() { - self.wait_for_write_watermark().await; - } - } - - async fn wait_for_read_data_available(&mut self, at_most: usize) { - if !self.fifo_read_data_available() { - let mut watermark = self.rx_fifo_size / 2; - if let Ok(at_most) = u32::try_from(at_most) { - watermark = watermark.min(at_most); - } - self.wait_for_read_watermark(watermark).await; - } - } - - async fn tx_fifo_enqueue_data(&self, val: u32) { - self.wait_for_write_space_available().await; - ral::write_reg!(ral::lpspi, self.lpspi(), TDR, val); - } - - async fn start_frame( - &self, - reverse_bytes: bool, - is_first_frame: bool, - is_last_frame: bool, - enable_read: bool, - enable_write: bool, - frame_size_bytes: NonZeroUsize, - ) { - let num_bits = frame_size_bytes.get() as u32 * 8; - assert!(num_bits <= MAX_FRAME_SIZE_BITS); - - self.wait_for_write_space_available().await; - ral::write_reg!(ral::lpspi, self.lpspi(), TCR, - CPOL: if self.mode.polarity == Polarity::IdleHigh {CPOL_1} else {CPOL_0}, - CPHA: if self.mode.phase == Phase::CaptureOnSecondTransition {CPHA_1} else {CPHA_0}, - PRESCALE: PRESCALE_0, - PCS: PCS_0, - LSBF: LSBF_0, - BYSW: if reverse_bytes {BYSW_0} else {BYSW_1}, - CONT: if is_last_frame {CONT_0} else {CONT_1}, - CONTC: if is_first_frame {CONTC_0} else {CONTC_1}, - RXMSK: if enable_read {RXMSK_0} else {RXMSK_1}, - TXMSK: if enable_write {TXMSK_0} else {TXMSK_1}, - WIDTH: WIDTH_0, - FRAMESZ: num_bits - 1 - ); - } - - async unsafe fn write_single_word( - &self, - write_data: Option<*const u8>, - byteorder: ByteOrder, - read: bool, - len: NonZeroUsize, - is_first_frame: bool, - is_last_frame: bool, - ) { - assert!(len.get() < 4); - - let reverse_bytes = match byteorder { - ByteOrder::Normal => false, - ByteOrder::WordReversed => true, - ByteOrder::HalfWordReversed => true, - }; - - // This should make sure that at least two words are free to be written - self.start_frame( - false, - is_first_frame, - is_last_frame, - read, - write_data.is_some(), - len, - ) - .await; - - if let Some(data) = write_data { - let mut tx_buffer = [0u8; 4]; - let active_buffer = &mut tx_buffer[(4 - len.get())..]; - if reverse_bytes { - active_buffer - .iter_mut() - .rev() - .enumerate() - .for_each(|(pos, val)| *val = data.add(pos).read()); - } else { - active_buffer - .iter_mut() - .enumerate() - .for_each(|(pos, val)| *val = data.add(pos).read()); - } - - self.tx_fifo_enqueue_data(u32::from_le_bytes(tx_buffer)) - .await; - } - } - - async unsafe fn write_u32_stream( - &self, - write_data: Option<*const u8>, - byteorder: ByteOrder, - read: bool, - len: NonZeroUsize, - is_first_frame: bool, - is_last_frame: bool, - // TODO: dma - ) { - assert!(len.get() % 4 == 0); - let len = NonZeroUsize::new(len.get() / 4).unwrap(); - let write_data: Option<*const u32> = write_data.map(|p| p.cast()); - - for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { - self.start_frame( - byteorder.requires_flip(), - is_first_frame && chunk.first, - is_last_frame && chunk.last, - read, - write_data.is_some(), - chunk.size.saturating_mul(NonZeroUsize::new_unchecked(4)), - ) - .await; - - if let Some(write_data) = write_data { - let write_data = write_data.add(chunk.position); - - let is_aligned = write_data.align_offset(core::mem::align_of::()) == 0; - let requires_reorder = byteorder.requires_reorder(); - - match (is_aligned, requires_reorder) { - (true, true) => { - for i in 0..chunk.size.get() { - let val = write_data.add(i).read(); - let val = byteorder.reorder(val); - self.tx_fifo_enqueue_data(val).await; - } - } - (true, false) => { - // This is the case that supports DMA. - // TODO: add DMA. - for i in 0..chunk.size.get() { - let val = write_data.add(i).read(); - self.tx_fifo_enqueue_data(val).await; - } - log::info!("Would support DMA."); - } - (false, true) => { - for i in 0..chunk.size.get() { - let val = write_data.add(i).read_unaligned(); - let val = byteorder.reorder(val); - self.tx_fifo_enqueue_data(val).await; - } - } - (false, false) => { - for i in 0..chunk.size.get() { - let val = write_data.add(i).read_unaligned(); - self.tx_fifo_enqueue_data(val).await; - } - } - } - } - } - } - /// Perform a sequence of transfer actions async fn transfer_unchecked(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { self.flush_unchecked().await?; self.clear_fifos(); + let read_part = &mut self.read_part; + let write_part = &mut self.write_part; + let read_task = async { assert!(!sequence.contains_read_actions()); - self.check_errors() }; let write_task = async { unsafe { @@ -360,50 +175,54 @@ impl<'a, const N: u8> Lpspi<'a, N> { if let Some(phase1) = &sequence.phase1 { for action in phase1.get_write_actions() { if action.len.get() < 4 { - self.write_single_word( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first, - action.is_last && !has_phase_2, - ) - .await + write_part + .write_single_word( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first, + action.is_last && !has_phase_2, + ) + .await } else { - self.write_u32_stream( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first, - action.is_last && !has_phase_2, - ) - .await; + write_part + .write_u32_stream( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first, + action.is_last && !has_phase_2, + ) + .await; } } } if let Some(phase2) = &sequence.phase2 { for action in phase2.get_write_actions() { if action.len.get() < 4 { - self.write_single_word( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first && !has_phase_1, - action.is_last, - ) - .await + write_part + .write_single_word( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first && !has_phase_1, + action.is_last, + ) + .await } else { - self.write_u32_stream( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first && !has_phase_1, - action.is_last, - ) - .await + write_part + .write_u32_stream( + action.buf, + sequence.byteorder, + action.read, + action.len, + action.is_first && !has_phase_1, + action.is_last, + ) + .await } } } diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs new file mode 100644 index 00000000..940fdfe1 --- /dev/null +++ b/src/common/lpspi/read_part.rs @@ -0,0 +1,30 @@ +use super::{ral, LpspiReadPart}; + +impl LpspiReadPart<'_, N> { + fn fifo_read_data_available(&self) -> bool { + ral::read_reg!( + ral::lpspi, + self.data.lpspi.instance(), + RSR, + RXEMPTY == RXEMPTY_0 + ) + } + + async fn wait_for_read_watermark(&self, watermark: u32) { + self.data + .lpspi + .wait_for_rx_watermark(watermark) + .await + .unwrap(); + } + + async fn wait_for_read_data_available(&mut self, at_most: usize) { + if !self.fifo_read_data_available() { + let mut watermark = self.rx_fifo_size / 2; + if let Ok(at_most) = u32::try_from(at_most) { + watermark = watermark.min(at_most); + } + self.wait_for_read_watermark(watermark).await; + } + } +} diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs new file mode 100644 index 00000000..2b2271d7 --- /dev/null +++ b/src/common/lpspi/write_part.rs @@ -0,0 +1,179 @@ +use core::num::NonZeroUsize; + +use eh1::spi::{Phase, Polarity}; + +use super::{ + ral, + transfer_actions::{ByteOrder, ChunkIter}, + LpspiWritePart, MAX_FRAME_SIZE_BITS, MAX_FRAME_SIZE_U32, +}; + +impl LpspiWritePart<'_, N> { + fn fifo_write_space_available(&self) -> bool { + ral::read_reg!( + ral::lpspi, + self.data.lpspi.instance(), + FSR, + TXCOUNT < self.tx_fifo_size + ) + } + + async fn wait_for_write_watermark(&self) { + self.data.lpspi.wait_for_tx_watermark().await.unwrap(); + } + + async fn wait_for_write_space_available(&self) { + if !self.fifo_write_space_available() { + self.wait_for_write_watermark().await; + } + } + + async fn tx_fifo_enqueue_data(&self, val: u32) { + self.wait_for_write_space_available().await; + ral::write_reg!(ral::lpspi, self.data.lpspi.instance(), TDR, val); + } + + async fn start_frame( + &mut self, + reverse_bytes: bool, + is_first_frame: bool, + is_last_frame: bool, + enable_read: bool, + enable_write: bool, + frame_size_bytes: NonZeroUsize, + ) { + let num_bits = frame_size_bytes.get() as u32 * 8; + assert!(num_bits <= MAX_FRAME_SIZE_BITS); + + self.wait_for_write_space_available().await; + ral::write_reg!(ral::lpspi, self.data.lpspi.instance(), TCR, + CPOL: if self.mode.polarity == Polarity::IdleHigh {CPOL_1} else {CPOL_0}, + CPHA: if self.mode.phase == Phase::CaptureOnSecondTransition {CPHA_1} else {CPHA_0}, + PRESCALE: PRESCALE_0, + PCS: PCS_0, + LSBF: LSBF_0, + BYSW: if reverse_bytes {BYSW_0} else {BYSW_1}, + CONT: if is_last_frame {CONT_0} else {CONT_1}, + CONTC: if is_first_frame {CONTC_0} else {CONTC_1}, + RXMSK: if enable_read {RXMSK_0} else {RXMSK_1}, + TXMSK: if enable_write {TXMSK_0} else {TXMSK_1}, + WIDTH: WIDTH_0, + FRAMESZ: num_bits - 1 + ); + } + + pub async unsafe fn write_single_word( + &mut self, + write_data: Option<*const u8>, + byteorder: ByteOrder, + read: bool, + len: NonZeroUsize, + is_first_frame: bool, + is_last_frame: bool, + ) { + assert!(len.get() < 4); + + let reverse_bytes = match byteorder { + ByteOrder::Normal => false, + ByteOrder::WordReversed => true, + ByteOrder::HalfWordReversed => true, + }; + + // This should make sure that at least two words are free to be written + self.start_frame( + false, + is_first_frame, + is_last_frame, + read, + write_data.is_some(), + len, + ) + .await; + + if let Some(data) = write_data { + let mut tx_buffer = [0u8; 4]; + let active_buffer = &mut tx_buffer[(4 - len.get())..]; + if reverse_bytes { + active_buffer + .iter_mut() + .rev() + .enumerate() + .for_each(|(pos, val)| *val = data.add(pos).read()); + } else { + active_buffer + .iter_mut() + .enumerate() + .for_each(|(pos, val)| *val = data.add(pos).read()); + } + + self.tx_fifo_enqueue_data(u32::from_le_bytes(tx_buffer)) + .await; + } + } + + pub async unsafe fn write_u32_stream( + &mut self, + write_data: Option<*const u8>, + byteorder: ByteOrder, + read: bool, + len: NonZeroUsize, + is_first_frame: bool, + is_last_frame: bool, + // TODO: dma + ) { + assert!(len.get() % 4 == 0); + let len = NonZeroUsize::new(len.get() / 4).unwrap(); + let write_data: Option<*const u32> = write_data.map(|p| p.cast()); + + for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { + self.start_frame( + byteorder.requires_flip(), + is_first_frame && chunk.first, + is_last_frame && chunk.last, + read, + write_data.is_some(), + chunk.size.saturating_mul(NonZeroUsize::new_unchecked(4)), + ) + .await; + + if let Some(write_data) = write_data { + let write_data = write_data.add(chunk.position); + + let is_aligned = write_data.align_offset(core::mem::align_of::()) == 0; + let requires_reorder = byteorder.requires_reorder(); + + match (is_aligned, requires_reorder) { + (true, true) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read(); + let val = byteorder.reorder(val); + self.tx_fifo_enqueue_data(val).await; + } + } + (true, false) => { + // This is the case that supports DMA. + // TODO: add DMA. + for i in 0..chunk.size.get() { + let val = write_data.add(i).read(); + self.tx_fifo_enqueue_data(val).await; + } + log::info!("Would support DMA."); + } + (false, true) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read_unaligned(); + let val = byteorder.reorder(val); + self.tx_fifo_enqueue_data(val).await; + } + } + (false, false) => { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read_unaligned(); + self.tx_fifo_enqueue_data(val).await; + } + } + } + } + } + } +} From fd8a53351e1cbeb009eec222a17c87b938b8f75a Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 14:23:27 +0100 Subject: [PATCH 55/73] Add explanatory comment --- src/common/lpspi/read_part.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 940fdfe1..26ff1dbc 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -21,6 +21,11 @@ impl LpspiReadPart<'_, N> { async fn wait_for_read_data_available(&mut self, at_most: usize) { if !self.fifo_read_data_available() { let mut watermark = self.rx_fifo_size / 2; + + // If there are only a couple of bytes left in the current + // transmission, then waiting for rx_fifo_size/2 bytes + // might not wake us, causing a deadlock. + // Therefore dynamically reduce the watermark if required. if let Ok(at_most) = u32::try_from(at_most) { watermark = watermark.min(at_most); } From 84a502beba1e41dff9f88f1fbf0956b3ed3a52ce Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 18:02:36 +0100 Subject: [PATCH 56/73] Refactoring; add ReadActionIter --- src/common/lpspi/bus.rs | 73 +++++++--------------- src/common/lpspi/read_part.rs | 24 ++++++- src/common/lpspi/transfer_actions.rs | 93 ++++++++++++++++++++++++++-- src/common/lpspi/write_part.rs | 34 +++++++++- 4 files changed, 166 insertions(+), 58 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 84dcecf7..b43fa270 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -161,11 +161,23 @@ impl<'a, const N: u8> Lpspi<'a, N> { self.clear_fifos(); + let byteorder = sequence.byteorder; + let read_part = &mut self.read_part; let write_part = &mut self.write_part; let read_task = async { - assert!(!sequence.contains_read_actions()); + unsafe { + if let Some(phase1) = &sequence.phase1 { + let actions = phase1.get_read_actions(); + read_part.perform_read_actions(actions, byteorder).await; + } + if let Some(phase2) = &sequence.phase2 { + if let Some(actions) = phase2.get_read_actions() { + read_part.perform_read_actions(actions, byteorder).await; + } + } + } }; let write_task = async { unsafe { @@ -173,58 +185,16 @@ impl<'a, const N: u8> Lpspi<'a, N> { let has_phase_2 = sequence.phase2.is_some(); if let Some(phase1) = &sequence.phase1 { - for action in phase1.get_write_actions() { - if action.len.get() < 4 { - write_part - .write_single_word( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first, - action.is_last && !has_phase_2, - ) - .await - } else { - write_part - .write_u32_stream( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first, - action.is_last && !has_phase_2, - ) - .await; - } - } + let actions = phase1.get_write_actions(); + write_part + .perform_write_actions(actions, false, has_phase_2, byteorder) + .await; } if let Some(phase2) = &sequence.phase2 { - for action in phase2.get_write_actions() { - if action.len.get() < 4 { - write_part - .write_single_word( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first && !has_phase_1, - action.is_last, - ) - .await - } else { - write_part - .write_u32_stream( - action.buf, - sequence.byteorder, - action.read, - action.len, - action.is_first && !has_phase_1, - action.is_last, - ) - .await - } - } + let actions = phase2.get_write_actions(); + write_part + .perform_write_actions(actions, has_phase_1, false, byteorder) + .await; } } }; @@ -309,6 +279,7 @@ impl<'a, 'b, const N: u8> CleanupOnError<'a, 'b, N> { impl<'a, 'b, const N: u8> Drop for CleanupOnError<'a, 'b, N> { fn drop(&mut self) { if !self.defused { + log::warn!("An LPSPI error happened! Cleaning up ..."); self.driver.clear_fifos(); self.driver.data.lpspi.clear_errors(); } diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 26ff1dbc..61b28c28 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -1,4 +1,8 @@ -use super::{ral, LpspiReadPart}; +use super::{ + ral, + transfer_actions::{ByteOrder, ReadAction}, + LpspiReadPart, +}; impl LpspiReadPart<'_, N> { fn fifo_read_data_available(&self) -> bool { @@ -32,4 +36,22 @@ impl LpspiReadPart<'_, N> { self.wait_for_read_watermark(watermark).await; } } + + pub async unsafe fn perform_read_actions( + &mut self, + actions: impl Iterator, + byteorder: ByteOrder, + ) { + for action in actions { + if action.len.get() < 4 { + todo!(); + // self.read_single_word(action.buf, byteorder, action.len) + // .await + } else { + todo!(); + // self.read_u32_stream(action.buf, byteorder, action.len) + // .await; + } + } + } } diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index eda38c73..fa5eef6e 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -1,4 +1,4 @@ -use core::{iter::FusedIterator, marker::PhantomData, num::NonZeroUsize}; +use core::{marker::PhantomData, num::NonZeroUsize}; #[derive(Debug)] pub(crate) struct DualDirectionActions { @@ -14,6 +14,13 @@ impl DualDirectionActions { len: self.len, }) } + + pub(crate) unsafe fn get_read_actions(&self) -> ReadActionIter { + ReadActionIter::new(ReadActions { + read_buf: self.read_buf, + len: self.len, + }) + } } pub(crate) struct WriteAction { @@ -24,6 +31,11 @@ pub(crate) struct WriteAction { pub(crate) is_last: bool, } +pub(crate) struct ReadAction { + pub(crate) buf: *const u8, + pub(crate) len: NonZeroUsize, +} + pub(crate) struct WriteActionIter { actions: WriteActions, pos: usize, @@ -71,6 +83,38 @@ impl Iterator for WriteActionIter { } } +pub(crate) struct ReadActionIter { + actions: ReadActions, + pos: usize, +} +impl ReadActionIter { + fn new(actions: ReadActions) -> Self { + Self { actions, pos: 0 } + } +} + +impl Iterator for ReadActionIter { + type Item = ReadAction; + + fn next(&mut self) -> Option { + let len = loop { + let mut lengths = self.actions.len.get(self.pos..)?; + if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + break len; + } + self.pos += 1; + lengths = self.actions.len.get(self.pos..)?; + }; + + let buf = self.actions.read_buf; + + self.pos += 1; + self.actions.read_buf = unsafe { self.actions.read_buf.add(len.get()) }; + + Some(ReadAction { buf, len }) + } +} + pub(crate) struct SingleDirectionWriteActionIter { actions: MaybeWriteActions, pos: usize, @@ -145,23 +189,33 @@ pub(crate) enum SingleDirectionActions { impl SingleDirectionActions { pub(crate) fn transfer_direction(&self) -> TransferDirection { match self { - SingleDirectionActions::Read(_) => TransferDirection::Read, - SingleDirectionActions::Write(_) => TransferDirection::Write, + Self::Read(_) => TransferDirection::Read, + Self::Write(_) => TransferDirection::Write, } } pub(crate) unsafe fn get_write_actions(&self) -> SingleDirectionWriteActionIter { SingleDirectionWriteActionIter::new(match self { - SingleDirectionActions::Read(actions) => MaybeWriteActions { + Self::Read(actions) => MaybeWriteActions { write_buf: None, len: actions.len, }, - SingleDirectionActions::Write(actions) => MaybeWriteActions { + Self::Write(actions) => MaybeWriteActions { write_buf: Some(actions.write_buf), len: actions.len, }, }) } + + pub(crate) unsafe fn get_read_actions(&self) -> Option { + match self { + Self::Read(actions) => Some(ReadActionIter::new(ReadActions { + read_buf: actions.read_buf, + len: actions.len, + })), + Self::Write(_) => None, + } + } } /// The order in which the bytes need @@ -378,6 +432,26 @@ mod tests { // - ChunkIter // - Byteorder conversion functions + macro_rules! actions_read_iter_test { + ($len:expr, $expected:expr) => {{ + let actual = ReadActionIter::new(ReadActions { + read_buf: 1000usize as *mut u8, + len: $len, + }) + .map(|val| { + ( + val.buf.unwrap() as usize - 1000, + val.len.get(), + val.is_first, + val.is_last, + ) + }) + .collect::>(); + let expected: &[(usize, usize, bool, bool)] = &$expected; + + assert_eq!(actual, expected); + }}; + } macro_rules! actions_write_iter_test { ($len:expr, $expected:expr) => {{ let actual = WriteActionIter::new(WriteActions { @@ -423,6 +497,15 @@ mod tests { }}; } + #[test] + fn read_actions_iter() { + actions_read_iter_test!([0, 5, 0], [(0, 5)]); + actions_read_iter_test!([2, 3, 4], [(0, 2), (2, 3), (5, 4),]); + actions_read_iter_test!([2, 0, 4], [(0, 2), (2, 4)]); + actions_read_iter_test!([2, 0, 0], [(0, 2)]); + actions_read_iter_test!([0, 0, 4], [(0, 4)]); + } + #[test] fn write_actions_iter() { actions_write_iter_test!([0, 5, 0], [(0, 5, true, true)]); diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs index 2b2271d7..3e9e2c1d 100644 --- a/src/common/lpspi/write_part.rs +++ b/src/common/lpspi/write_part.rs @@ -4,7 +4,7 @@ use eh1::spi::{Phase, Polarity}; use super::{ ral, - transfer_actions::{ByteOrder, ChunkIter}, + transfer_actions::{ByteOrder, ChunkIter, WriteAction}, LpspiWritePart, MAX_FRAME_SIZE_BITS, MAX_FRAME_SIZE_U32, }; @@ -62,6 +62,38 @@ impl LpspiWritePart<'_, N> { ); } + pub async unsafe fn perform_write_actions( + &mut self, + actions: impl Iterator, + has_previous: bool, + has_next: bool, + byteorder: ByteOrder, + ) { + for action in actions { + if action.len.get() < 4 { + self.write_single_word( + action.buf, + byteorder, + action.read, + action.len, + action.is_first && !has_previous, + action.is_last && !has_next, + ) + .await + } else { + self.write_u32_stream( + action.buf, + byteorder, + action.read, + action.len, + action.is_first && !has_previous, + action.is_last && !has_next, + ) + .await; + } + } + } + pub async unsafe fn write_single_word( &mut self, write_data: Option<*const u8>, From 7c4056345b3a333f910a076ed112e126a385e457 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 18:03:46 +0100 Subject: [PATCH 57/73] Update cargo.toml --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4c1988d4..53f0ac5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,9 @@ version = "0.3.11" default-features = false features = ["async-await"] +[dependencies.log] +version = "0.4" + ####################### # imxrt-rs dependencies ####################### From fe44f278ae426673d2351b81ef85464fb1ed79b6 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 18:07:06 +0100 Subject: [PATCH 58/73] Fix tests --- src/common/lpspi/transfer_actions.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index fa5eef6e..caa3b690 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -274,18 +274,6 @@ pub(crate) struct ActionSequence<'a> { _lifetimes: PhantomData<&'a [u8]>, } -impl ActionSequence<'_> { - pub(crate) fn contains_read_actions(&self) -> bool { - if self.phase1.is_some() { - true - } else if let Some(phase2) = &self.phase2 { - phase2.transfer_direction() == TransferDirection::Read - } else { - false - } - } -} - pub trait BufferType: Copy + 'static { fn byte_order() -> ByteOrder; } @@ -438,16 +426,9 @@ mod tests { read_buf: 1000usize as *mut u8, len: $len, }) - .map(|val| { - ( - val.buf.unwrap() as usize - 1000, - val.len.get(), - val.is_first, - val.is_last, - ) - }) + .map(|val| (val.buf as usize - 1000, val.len.get())) .collect::>(); - let expected: &[(usize, usize, bool, bool)] = &$expected; + let expected: &[(usize, usize)] = &$expected; assert_eq!(actual, expected); }}; From 88a5d600bf11113930b1912b3ebd2194ee2dab54 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 20:21:29 +0100 Subject: [PATCH 59/73] Refactor transfer_actions --- src/common/lpspi/transfer_actions.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index caa3b690..2cc0b9e0 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -32,7 +32,7 @@ pub(crate) struct WriteAction { } pub(crate) struct ReadAction { - pub(crate) buf: *const u8, + pub(crate) buf: *mut u8, pub(crate) len: NonZeroUsize, } @@ -51,13 +51,11 @@ impl Iterator for WriteActionIter { fn next(&mut self) -> Option { let is_first = self.pos == 0; - let mut lengths = self.actions.len.get(self.pos..)?; let len = loop { - if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { break len; } self.pos += 1; - lengths = self.actions.len.get(self.pos..)?; }; let buf = self.actions.write_buf; @@ -98,12 +96,10 @@ impl Iterator for ReadActionIter { fn next(&mut self) -> Option { let len = loop { - let mut lengths = self.actions.len.get(self.pos..)?; - if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { break len; } self.pos += 1; - lengths = self.actions.len.get(self.pos..)?; }; let buf = self.actions.read_buf; @@ -130,13 +126,11 @@ impl Iterator for SingleDirectionWriteActionIter { fn next(&mut self) -> Option { let is_first = self.pos == 0; - let mut lengths = self.actions.len.get(self.pos..)?; let len = loop { - if let Some(len) = NonZeroUsize::new(*lengths.first()?) { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { break len; } self.pos += 1; - lengths = self.actions.len.get(self.pos..)?; }; let buf = self.actions.write_buf; From 64ff0c9c5f4b1620f8a2f97990c27095ec6cf37b Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 20:33:51 +0100 Subject: [PATCH 60/73] Attempt to add read_single_word --- src/common/lpspi/read_part.rs | 50 ++++++++++++++++++++++++++++++---- src/common/lpspi/write_part.rs | 8 +++--- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 61b28c28..9be2355f 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -1,3 +1,5 @@ +use core::num::NonZeroUsize; + use super::{ ral, transfer_actions::{ByteOrder, ReadAction}, @@ -5,7 +7,7 @@ use super::{ }; impl LpspiReadPart<'_, N> { - fn fifo_read_data_available(&self) -> bool { + fn fifo_read_data_available(&mut self) -> bool { ral::read_reg!( ral::lpspi, self.data.lpspi.instance(), @@ -14,7 +16,7 @@ impl LpspiReadPart<'_, N> { ) } - async fn wait_for_read_watermark(&self, watermark: u32) { + async fn wait_for_read_watermark(&mut self, watermark: u32) { self.data .lpspi .wait_for_rx_watermark(watermark) @@ -44,9 +46,8 @@ impl LpspiReadPart<'_, N> { ) { for action in actions { if action.len.get() < 4 { - todo!(); - // self.read_single_word(action.buf, byteorder, action.len) - // .await + self.read_single_word(action.buf, byteorder, action.len) + .await } else { todo!(); // self.read_u32_stream(action.buf, byteorder, action.len) @@ -54,4 +55,43 @@ impl LpspiReadPart<'_, N> { } } } + + async fn rx_fifo_read_data(&mut self, num_leftover: usize) -> u32 { + self.wait_for_read_data_available(num_leftover).await; + ral::read_reg!(ral::lpspi, self.data.lpspi.instance(), RDR) + } + + pub async unsafe fn read_single_word( + &mut self, + data: *mut u8, + byteorder: ByteOrder, + len: NonZeroUsize, + ) { + assert!(len.get() < 4); + + let reverse_bytes = match byteorder { + ByteOrder::Normal => false, + ByteOrder::WordReversed => true, + ByteOrder::HalfWordReversed => true, + }; + + log::info!("Receiving ..."); + let value = self.rx_fifo_read_data(1).await; + log::info!("Read: 0x{:02x}", value); + let rx_buffer = value.to_le_bytes(); + + let active_buffer = &rx_buffer[(4 - len.get())..]; + if reverse_bytes { + active_buffer + .iter() + .rev() + .enumerate() + .for_each(|(pos, val)| data.add(pos).write(*val)); + } else { + active_buffer + .iter() + .enumerate() + .for_each(|(pos, val)| data.add(pos).write(*val)); + } + } } diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs index 3e9e2c1d..5b3e414b 100644 --- a/src/common/lpspi/write_part.rs +++ b/src/common/lpspi/write_part.rs @@ -9,7 +9,7 @@ use super::{ }; impl LpspiWritePart<'_, N> { - fn fifo_write_space_available(&self) -> bool { + fn fifo_write_space_available(&mut self) -> bool { ral::read_reg!( ral::lpspi, self.data.lpspi.instance(), @@ -18,17 +18,17 @@ impl LpspiWritePart<'_, N> { ) } - async fn wait_for_write_watermark(&self) { + async fn wait_for_write_watermark(&mut self) { self.data.lpspi.wait_for_tx_watermark().await.unwrap(); } - async fn wait_for_write_space_available(&self) { + async fn wait_for_write_space_available(&mut self) { if !self.fifo_write_space_available() { self.wait_for_write_watermark().await; } } - async fn tx_fifo_enqueue_data(&self, val: u32) { + async fn tx_fifo_enqueue_data(&mut self, val: u32) { self.wait_for_write_space_available().await; ral::write_reg!(ral::lpspi, self.data.lpspi.instance(), TDR, val); } From 3027e05f37147f366c81a94f71696085b294e794 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 20:59:30 +0100 Subject: [PATCH 61/73] Fix read --- src/common/lpspi/read_part.rs | 11 ++++++----- src/common/lpspi/status_watcher.rs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 9be2355f..93c160f8 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -24,17 +24,18 @@ impl LpspiReadPart<'_, N> { .unwrap(); } - async fn wait_for_read_data_available(&mut self, at_most: usize) { + async fn wait_for_read_data_available(&mut self, at_most: NonZeroUsize) { if !self.fifo_read_data_available() { - let mut watermark = self.rx_fifo_size / 2; + let mut watermark = self.rx_fifo_size / 2 - 1; // If there are only a couple of bytes left in the current // transmission, then waiting for rx_fifo_size/2 bytes // might not wake us, causing a deadlock. // Therefore dynamically reduce the watermark if required. - if let Ok(at_most) = u32::try_from(at_most) { + if let Ok(at_most) = u32::try_from(at_most.get() - 1) { watermark = watermark.min(at_most); } + self.wait_for_read_watermark(watermark).await; } } @@ -56,7 +57,7 @@ impl LpspiReadPart<'_, N> { } } - async fn rx_fifo_read_data(&mut self, num_leftover: usize) -> u32 { + async fn rx_fifo_read_data(&mut self, num_leftover: NonZeroUsize) -> u32 { self.wait_for_read_data_available(num_leftover).await; ral::read_reg!(ral::lpspi, self.data.lpspi.instance(), RDR) } @@ -76,7 +77,7 @@ impl LpspiReadPart<'_, N> { }; log::info!("Receiving ..."); - let value = self.rx_fifo_read_data(1).await; + let value = self.rx_fifo_read_data(NonZeroUsize::new(1).unwrap()).await; log::info!("Read: 0x{:02x}", value); let rx_buffer = value.to_le_bytes(); diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs index e6af5436..a769a7fc 100644 --- a/src/common/lpspi/status_watcher.rs +++ b/src/common/lpspi/status_watcher.rs @@ -200,7 +200,9 @@ impl StatusWatcher { } })?; - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark); + interrupt::free(|_| { + ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark); + }); Ok(StatusWatcherFuture::new( self, From 9e5a4271238f73f521d203096bd93b3cde8c06ef Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 21:16:05 +0100 Subject: [PATCH 62/73] Add u32 stream; finish read part --- src/common/lpspi/read_part.rs | 66 +++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 93c160f8..c56133bd 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -2,8 +2,8 @@ use core::num::NonZeroUsize; use super::{ ral, - transfer_actions::{ByteOrder, ReadAction}, - LpspiReadPart, + transfer_actions::{ByteOrder, ChunkIter, ReadAction}, + LpspiReadPart, MAX_FRAME_SIZE_U32, }; impl LpspiReadPart<'_, N> { @@ -50,9 +50,8 @@ impl LpspiReadPart<'_, N> { self.read_single_word(action.buf, byteorder, action.len) .await } else { - todo!(); - // self.read_u32_stream(action.buf, byteorder, action.len) - // .await; + self.read_u32_stream(action.buf, byteorder, action.len) + .await; } } } @@ -76,9 +75,7 @@ impl LpspiReadPart<'_, N> { ByteOrder::HalfWordReversed => true, }; - log::info!("Receiving ..."); let value = self.rx_fifo_read_data(NonZeroUsize::new(1).unwrap()).await; - log::info!("Read: 0x{:02x}", value); let rx_buffer = value.to_le_bytes(); let active_buffer = &rx_buffer[(4 - len.get())..]; @@ -95,4 +92,59 @@ impl LpspiReadPart<'_, N> { .for_each(|(pos, val)| data.add(pos).write(*val)); } } + + pub async unsafe fn read_u32_stream( + &mut self, + read_data: *mut u8, + byteorder: ByteOrder, + len: NonZeroUsize, + // TODO: dma + ) { + assert!(len.get() % 4 == 0); + let len = NonZeroUsize::new(len.get() / 4).unwrap(); + let read_data: *mut u32 = read_data.cast(); + + for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { + let read_data = read_data.add(chunk.position); + + let is_aligned = read_data.align_offset(core::mem::align_of::()) == 0; + let requires_reorder = byteorder.requires_reorder(); + + match (is_aligned, requires_reorder) { + (true, true) => { + for i in 0..chunk.size.get() { + let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + let val = byteorder.reorder(val); + read_data.add(i).write(val); + } + } + (true, false) => { + // This is the case that supports DMA. + // TODO: add DMA. + for i in 0..chunk.size.get() { + let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + read_data.add(i).write(val); + } + log::info!("Would support DMA."); + } + (false, true) => { + for i in 0..chunk.size.get() { + let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + let val = byteorder.reorder(val); + read_data.add(i).write_unaligned(val); + } + } + (false, false) => { + for i in 0..chunk.size.get() { + let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + read_data.add(i).write_unaligned(val); + } + } + } + } + } } From 1a2caabcbc6c5c9fee889463fd6b0eca18913840 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 21:23:10 +0100 Subject: [PATCH 63/73] Redistribute unsafe tags --- src/common/lpspi/bus.rs | 51 ++++++++++++++++---------------- src/common/lpspi/bus/eh1_impl.rs | 34 +++++++++++---------- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index b43fa270..3309e161 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -156,7 +156,10 @@ impl<'a, const N: u8> Lpspi<'a, N> { } /// Perform a sequence of transfer actions - async fn transfer_unchecked(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { + async unsafe fn transfer_unchecked( + &mut self, + sequence: ActionSequence<'_>, + ) -> Result<(), LpspiError> { self.flush_unchecked().await?; self.clear_fifos(); @@ -167,35 +170,31 @@ impl<'a, const N: u8> Lpspi<'a, N> { let write_part = &mut self.write_part; let read_task = async { - unsafe { - if let Some(phase1) = &sequence.phase1 { - let actions = phase1.get_read_actions(); + if let Some(phase1) = &sequence.phase1 { + let actions = phase1.get_read_actions(); + read_part.perform_read_actions(actions, byteorder).await; + } + if let Some(phase2) = &sequence.phase2 { + if let Some(actions) = phase2.get_read_actions() { read_part.perform_read_actions(actions, byteorder).await; } - if let Some(phase2) = &sequence.phase2 { - if let Some(actions) = phase2.get_read_actions() { - read_part.perform_read_actions(actions, byteorder).await; - } - } } }; let write_task = async { - unsafe { - let has_phase_1 = sequence.phase1.is_some(); - let has_phase_2 = sequence.phase2.is_some(); - - if let Some(phase1) = &sequence.phase1 { - let actions = phase1.get_write_actions(); - write_part - .perform_write_actions(actions, false, has_phase_2, byteorder) - .await; - } - if let Some(phase2) = &sequence.phase2 { - let actions = phase2.get_write_actions(); - write_part - .perform_write_actions(actions, has_phase_1, false, byteorder) - .await; - } + let has_phase_1 = sequence.phase1.is_some(); + let has_phase_2 = sequence.phase2.is_some(); + + if let Some(phase1) = &sequence.phase1 { + let actions = phase1.get_write_actions(); + write_part + .perform_write_actions(actions, false, has_phase_2, byteorder) + .await; + } + if let Some(phase2) = &sequence.phase2 { + let actions = phase2.get_write_actions(); + write_part + .perform_write_actions(actions, has_phase_1, false, byteorder) + .await; } }; @@ -205,7 +204,7 @@ impl<'a, const N: u8> Lpspi<'a, N> { } /// Perform a sequence of transfer actions while continuously checking for errors. - async fn transfer(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { + async unsafe fn transfer(&mut self, sequence: ActionSequence<'_>) -> Result<(), LpspiError> { let mut cleanup_on_error = CleanupOnError::new(self); let this = cleanup_on_error.driver(); diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs index aad7ecc9..9f899c92 100644 --- a/src/common/lpspi/bus/eh1_impl.rs +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -15,30 +15,30 @@ where T: crate::lpspi::LpspiWord, { fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - Cassette::new(core::pin::pin!( - self.transfer(create_actions_read_write(words, &[])), - )) + Cassette::new(core::pin::pin!(unsafe { + self.transfer(create_actions_read_write(words, &[])) + },)) .block_on() } fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - Cassette::new(core::pin::pin!( + Cassette::new(core::pin::pin!(unsafe { self.transfer(create_actions_read_write(&mut [], words)) - )) + })) .block_on() } fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - Cassette::new(core::pin::pin!( + Cassette::new(core::pin::pin!(unsafe { self.transfer(create_actions_read_write(read, write)) - )) + })) .block_on() } fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - Cassette::new(core::pin::pin!( + Cassette::new(core::pin::pin!(unsafe { self.transfer(create_actions_read_write_in_place(words)) - )) + })) .block_on() } @@ -54,21 +54,25 @@ where T: crate::lpspi::LpspiWord, { async fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.transfer(create_actions_read_write(words, &[])).await + unsafe { self.transfer(create_actions_read_write(words, &[])).await } } async fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { - self.transfer(create_actions_read_write(&mut [], words)) - .await + unsafe { + self.transfer(create_actions_read_write(&mut [], words)) + .await + } } async fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { - self.transfer(create_actions_read_write(read, write)).await + unsafe { self.transfer(create_actions_read_write(read, write)).await } } async fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { - self.transfer(create_actions_read_write_in_place(words)) - .await + unsafe { + self.transfer(create_actions_read_write_in_place(words)) + .await + } } async fn flush(&mut self) -> Result<(), Self::Error> { From 2ffab655be1bf251a945ec03a8de63da0fcdac2f Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 23:07:14 +0100 Subject: [PATCH 64/73] Fix cleanup procedure --- src/common/lpspi/bus.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 3309e161..13d1440c 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -142,6 +142,43 @@ impl<'a, const N: u8> Lpspi<'a, N> { ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RTF: RTF_1, RRF: RRF_1); } + /// Stops the current transfer without changing its current configuration. + fn reset(&mut self) { + // Restore old registers + cortex_m::interrupt::free(|_| { + // Backup + let ier = ral::read_reg!(ral::lpspi, self.lpspi(), IER); + let der = ral::read_reg!(ral::lpspi, self.lpspi(), DER); + let cfgr0 = ral::read_reg!(ral::lpspi, self.lpspi(), CFGR0); + let cfgr1 = ral::read_reg!(ral::lpspi, self.lpspi(), CFGR1); + let dmr0 = ral::read_reg!(ral::lpspi, self.lpspi(), DMR0); + let dmr1 = ral::read_reg!(ral::lpspi, self.lpspi(), DMR1); + let ccr = ral::read_reg!(ral::lpspi, self.lpspi(), CCR); + let fcr = ral::read_reg!(ral::lpspi, self.lpspi(), FCR); + + // Reset and disable + ral::modify_reg!(ral::lpspi, self.lpspi(), CR, MEN: MEN_0, RST: RST_1); + while ral::read_reg!(ral::lpspi, self.lpspi(), CR, MEN == MEN_1) {} + ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RST: RST_0, RTF: RTF_1, RRF: RRF_1); + + // Reset fifos + ral::modify_reg!(ral::lpspi, self.lpspi(), CR, RTF: RTF_1, RRF: RRF_1); + + // Resore settings + ral::write_reg!(ral::lpspi, self.lpspi(), IER, ier); + ral::write_reg!(ral::lpspi, self.lpspi(), DER, der); + ral::write_reg!(ral::lpspi, self.lpspi(), CFGR0, cfgr0); + ral::write_reg!(ral::lpspi, self.lpspi(), CFGR1, cfgr1); + ral::write_reg!(ral::lpspi, self.lpspi(), DMR0, dmr0); + ral::write_reg!(ral::lpspi, self.lpspi(), DMR1, dmr1); + ral::write_reg!(ral::lpspi, self.lpspi(), CCR, ccr); + ral::write_reg!(ral::lpspi, self.lpspi(), FCR, fcr); + + // Enable + ral::write_reg!(ral::lpspi, self.lpspi(), CR, MEN: MEN_1); + }); + } + /// Returns errors, if any there are any. fn check_errors(&self) -> Result<(), LpspiError> { self.data.lpspi.check_for_errors() @@ -279,7 +316,7 @@ impl<'a, 'b, const N: u8> Drop for CleanupOnError<'a, 'b, N> { fn drop(&mut self) { if !self.defused { log::warn!("An LPSPI error happened! Cleaning up ..."); - self.driver.clear_fifos(); + self.driver.reset(); self.driver.data.lpspi.clear_errors(); } } From 86ef4ed7c07c3b789d759cd98ab6bacc4ec0aa0e Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 23:10:23 +0100 Subject: [PATCH 65/73] Remove finished TODO --- src/common/lpspi/bus.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 13d1440c..8422c3b2 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -247,7 +247,6 @@ impl<'a, const N: u8> Lpspi<'a, N> { let data = this.data; - // TODO: check if error handling actually works let result: Result<(), LpspiError> = futures::select_biased! { res = data.lpspi.watch_for_errors().fuse() => res, res = this.transfer_unchecked(sequence).fuse() => res, From 8bf18b3172dfcbde8395d48cc20c50129053e272 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 23:18:05 +0100 Subject: [PATCH 66/73] Remove lpspi_old driver --- src/common/lpspi_old.rs | 1066 --------------------------------------- 1 file changed, 1066 deletions(-) delete mode 100644 src/common/lpspi_old.rs diff --git a/src/common/lpspi_old.rs b/src/common/lpspi_old.rs deleted file mode 100644 index 04c22216..00000000 --- a/src/common/lpspi_old.rs +++ /dev/null @@ -1,1066 +0,0 @@ -//! Low-power serial peripheral interface. -//! -//! [`Lpspi`] implements select embedded HAL SPI traits for coordinating SPI I/O. -//! When using the trait implementations, make sure that [`set_bit_order`](Lpspi::set_bit_order) -//! is correct for your device. These settings apply when the driver internally defines the transaction. -//! -//! This driver also exposes the peripheral's lower-level, hardware-dependent transaction interface. -//! Create a [`Transaction`], then [`enqueue_transaction`](Lpspi::enqueue_transaction) before -//! sending data with [`enqueue_data`](Lpspi::enqueue_data). When using the transaction interface, -//! you're responsible for serializing your data into `u32` SPI words. -//! -//! # Chip selects (CS) for SPI peripherals -//! -//! The iMXRT SPI peripherals have one or more peripheral-controlled chip selects (CS). Using -//! the peripheral-controlled CS means that you do not need a GPIO to coordinate SPI operations. -//! Blocking full-duplex transfers and writes will observe an asserted chip select while data -//! frames are exchanged / written. -//! -//! This driver generally assumes that you're using the peripheral-controlled chip select. If -//! you instead want to manage chip select in software, you should be able to multiplex your own -//! pins, then construct the driver [`without_pins`](Lpspi::without_pins). -//! -//! # Example -//! -//! Initialize an LPSPI with a 1MHz SCK. To understand how to configure the LPSPI -//! peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. -//! -//! ```no_run -//! use imxrt_hal as hal; -//! use imxrt_ral as ral; -//! # use eh02 as embedded_hal; -//! use embedded_hal::blocking::spi::Transfer; -//! use hal::lpspi::{Lpspi, Pins, SamplePoint}; -//! use ral::lpspi::LPSPI4; -//! -//! let mut pads = // Handle to all processor pads... -//! # unsafe { imxrt_iomuxc::imxrt1060::Pads::new() }; -//! -//! # || -> Option<()> { -//! let spi_pins = Pins { -//! sdo: pads.gpio_b0.p02, -//! sdi: pads.gpio_b0.p01, -//! sck: pads.gpio_b0.p03, -//! pcs0: pads.gpio_b0.p00, -//! }; -//! -//! let mut spi4 = unsafe { LPSPI4::instance() }; -//! let mut spi = Lpspi::new( -//! spi4, -//! spi_pins, -//! ); -//! -//! # const LPSPI_CLK_HZ: u32 = 1; -//! spi.disabled(|spi| { -//! spi.set_clock_hz(LPSPI_CLK_HZ, 1_000_000); -//! spi.set_sample_point(SamplePoint::Edge); -//! }); -//! -//! let mut buffer: [u8; 3] = [1, 2, 3]; -//! spi.transfer(&mut buffer).ok()?; -//! -//! let (spi4, pins) = spi.release(); -//! -//! // Re-construct without pins: -//! let mut spi = Lpspi::without_pins(spi4); -//! # Some(()) }(); -//! ``` -//! -//! # Limitations -//! -//! Due to [a hardware defect][1], this driver does not yet support the EH02 SPI transaction API. -//! An early iteration of this driver reproduced the issue discussed in that forum. This driver may -//! be able to work around the defect in software, but it hasn't been explored. -//! -//! [1]: https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 -//! -//! [`Transaction`] exposes the continuous / continuing flags, so you're free to model advanced -//! transactions. However, keep in mind that disabling the receiver during a continuous transaction -//! may not work as expected. - -#[cfg(feature = "eh1")] -mod lpspi_eh1; - -use crate::iomuxc::{consts, lpspi}; -use crate::ral; - -#[cfg(not(feature = "eh1"))] -pub use eh02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; -#[cfg(feature = "eh1")] -pub use eh1::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; - -/// Data direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - /// Transmit direction (leaving the peripheral). - Tx, - /// Receive direction (entering the peripheral). - Rx, -} - -/// Bit order. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum BitOrder { - /// Data is transferred most significant bit first (default). - #[default] - Msb, - /// Data is transferred least significant bit first. - Lsb, -} - -/// Receive sample point behavior. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SamplePoint { - /// Input data is sampled on SCK edge. - Edge, - /// Input data is sampled on delayed SCK edge. - DelayedEdge, -} - -/// Possible errors when interfacing the LPSPI. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LpspiError { - /// The transaction frame size is incorrect. - /// - /// The frame size, in bits, must be between 8 bits and - /// 4095 bits. - FrameSize, - /// FIFO error in the given direction. - Fifo(Direction), - /// Bus is busy at the start of a transfer. - Busy, - /// Caller provided no data. - NoData, -} - -/// An LPSPI transaction definition. -/// -/// The transaction defines how many bits the driver sends or recieves. -/// It also describes -/// -/// - endianness -/// - bit order -/// - transmit and receive masking -/// - continuous and continuing transfers (default: both disabled) -/// -/// The LPSPI enqueues the transaction data into the transmit -/// FIFO. When it pops the values from the FIFO, the values take -/// effect immediately. This may affect, or abort, any ongoing -/// transactions. Consult the reference manual to understand when -/// you should enqueue transaction definitions, since it may only -/// be supported on word / frame boundaries. -/// -/// Construct `Transaction` with [`new`](Self::new), and supply -/// the number of **bits** to transmit per frame. -/// -/// ``` -/// use imxrt_hal as hal; -/// use hal::lpspi::Transaction; -/// -/// // Send one u32. -/// let mut transaction -/// = Transaction::new(8 * core::mem::size_of::() as u16); -/// ``` -/// -/// Once constructed, manipulate the public members to change the -/// configuration. -/// -/// # Continuous transactions -/// -/// The pseudo-code below shows how to set [`continuous`](Self::continuous) and -/// [`continuing`](Self::continuing) to model a continuous transaction. Keep in -/// mind the hardware limitations; see the [module-level docs](crate::lpspi#limitations) for -/// details. -/// -/// ``` -/// use imxrt_hal as hal; -/// use hal::lpspi::Transaction; -/// -/// // Skipping LPSPI initialization; see module-level example. -/// -/// // Start byte exchange as a continuous transaction. Each frame -/// // exchanges one byte (eight bits) with a device. -/// # || -> Result<(), hal::lpspi::LpspiError> { -/// let mut transaction = Transaction::new(8)?; -/// transaction.continuous = true; -/// // Enqueue transaction with LPSPI... -/// // Enqueue one byte with LPSPI... <-- PCS asserts here. -/// -/// # let buffer: [u8; 5] = [0; 5]; -/// for byte in buffer { -/// // Set 'continuing' to indicate that the next -/// // transaction continues the previous one... -/// transaction.continuing = true; -/// -/// // Enqueue transaction with LPSPI... -/// // Enqueue byte with LPSPI... -/// } -/// -/// transaction.continuous = false; -/// transaction.continuing = false; -/// // Enqueue transaction with LPSPI... <-- PCS de-asserts here. -/// # Ok(()) }().unwrap(); -/// ``` -pub struct Transaction { - /// Enable byte swap. - /// - /// When enabled (`true`), swap bytes with the `u32` word. This allows - /// you to change the endianness of the 32-bit word transfer. The - /// default is `false`. - pub byte_swap: bool, - /// Bit order. - /// - /// See [`BitOrder`] for details. The default is [`BitOrder::Msb`]. - pub bit_order: BitOrder, - /// Mask the received data. - /// - /// If `true`, the peripheral discards received data. Use this - /// when you only care about sending data. The default is `false`; - /// the peripheral puts received data in the receive FIFO. - pub receive_data_mask: bool, - /// Mask the transmit data. - /// - /// If `true`, the peripheral doesn't send any data. Use this when - /// you only care about receiving data. The default is `false`; - /// the peripheral expects to send data using the transmit FIFO. - pub transmit_data_mask: bool, - /// Indicates (`true`) the start of a continuous transfer. - /// - /// If set, the peripherals chip select will remain asserted after - /// exchanging the frame. This allows you to enqueue new commands - /// and data words within the same transaction. Those new commands - /// should have [`continuing`](Self::continuing) set to `true`. - /// - /// The default is `false`; chip select de-asserts after exchanging - /// the frame. To stop a continuous transfer, enqueue a new `Transaction` - /// in which this flag, and `continuing`, is false. - pub continuous: bool, - /// Indicates (`true`) that this command belongs to a previous transaction. - /// - /// Set this to indicate that this new `Transaction` belongs to a previous - /// `Transaction`, one that had [`continuous`](Self::continuous) set. - /// The default value is `false`. - pub continuing: bool, - - frame_size: u16, -} - -impl Transaction { - /// Defines a transaction for a `u32` buffer. - /// - /// After successfully defining a transaction of this buffer, - /// supply it to the LPSPI driver, then start sending the - /// data. - /// - /// Returns an error if any are true: - /// - /// - the buffer is empty. - /// - there's more than 128 elements in the buffer. - pub fn new_u32s(data: &[u32]) -> Result { - Transaction::new_words(data) - } - - fn new_words(data: &[W]) -> Result { - Transaction::new(8 * core::mem::size_of_val(data) as u16) - } - - /// Define a transaction by specifying the frame size, in bits. - /// - /// The frame size describes the number of bits that will be transferred and - /// received during the next transaction. Specifically, it describes the number - /// of bits for which the PCS pin signals a transaction. - /// - /// # Requirements - /// - /// - `frame_size` fits within 12 bits; the implementation enforces this maximum value. - /// - The minimum value for `frame_size` is 8; the implementation enforces this minimum - /// value. - pub fn new(frame_size: u16) -> Result { - const MIN_FRAME_SIZE: u16 = 8; - const MAX_FRAME_SIZE: u16 = 1 << 12; - if (MIN_FRAME_SIZE..MAX_FRAME_SIZE).contains(&frame_size) { - Ok(Self { - byte_swap: false, - bit_order: Default::default(), - receive_data_mask: false, - transmit_data_mask: false, - frame_size: frame_size - 1, - continuing: false, - continuous: false, - }) - } else { - Err(LpspiError::FrameSize) - } - } -} - -/// Sets the clock speed parameters. -/// -/// This should only happen when the LPSPI peripheral is disabled. -fn set_spi_clock(source_clock_hz: u32, spi_clock_hz: u32, reg: &ral::lpspi::RegisterBlock) { - let mut div = source_clock_hz / spi_clock_hz; - - if source_clock_hz / div > spi_clock_hz { - div += 1; - } - - // 0 <= div <= 255, and the true coefficient is really div + 2 - let div = div.saturating_sub(2).clamp(0, 255); - ral::write_reg!( - ral::lpspi, - reg, - CCR, - SCKDIV: div, - // Both of these delays are arbitrary choices, and they should - // probably be configurable by the end-user. - DBT: div / 2, - SCKPCS: 0x1F, - PCSSCK: 0x1F - ); -} - -/// An LPSPI driver. -/// -/// The driver exposes low-level methods for coordinating -/// DMA transfers. However, you may find it easier to use the -/// [`dma`](crate::dma) interface to coordinate DMA transfers. -/// -/// The driver implements `embedded-hal` SPI traits. You should prefer -/// these implementations for their ease of use. -/// -/// See the [module-level documentation](crate::lpspi) for an example -/// of how to construct this driver. -pub struct Lpspi { - lpspi: ral::lpspi::Instance, - pins: P, - bit_order: BitOrder, -} - -/// Pins for a LPSPI device. -/// -/// Consider using type aliases to simplify your usage: -/// -/// ```no_run -/// use imxrt_hal as hal; -/// use imxrt_iomuxc::imxrt1060::gpio_b0::*; -/// -/// // SPI pins used in my application -/// type LpspiPins = hal::lpspi::Pins< -/// GPIO_B0_02, -/// GPIO_B0_01, -/// GPIO_B0_03, -/// GPIO_B0_00, -/// >; -/// -/// // Helper type for your SPI peripheral -/// type Lpspi = hal::lpspi::Lpspi; -/// ``` -pub struct Pins { - /// Serial data out - /// - /// Data travels from the SPI host controller to the SPI device. - pub sdo: SDO, - /// Serial data in - /// - /// Data travels from the SPI device to the SPI host controller. - pub sdi: SDI, - /// Serial clock - pub sck: SCK, - /// Chip select 0 - /// - /// (PCSx) convention matches the hardware. - pub pcs0: PCS0, -} - -impl Lpspi, N> -where - SDO: lpspi::Pin, Signal = lpspi::Sdo>, - SDI: lpspi::Pin, Signal = lpspi::Sdi>, - SCK: lpspi::Pin, Signal = lpspi::Sck>, - PCS0: lpspi::Pin, Signal = lpspi::Pcs0>, -{ - /// Create a new LPSPI driver from the RAL LPSPI instance and a set of pins. - /// - /// When this call returns, the LPSPI pins are configured for their function. - /// The peripheral is enabled after reset. The LPSPI clock speed is unspecified. - /// The mode is [`MODE_0`]. The sample point is [`SamplePoint::DelayedEdge`]. - pub fn new(lpspi: ral::lpspi::Instance, mut pins: Pins) -> Self { - lpspi::prepare(&mut pins.sdo); - lpspi::prepare(&mut pins.sdi); - lpspi::prepare(&mut pins.sck); - lpspi::prepare(&mut pins.pcs0); - Self::init(lpspi, pins) - } -} - -impl Lpspi<(), N> { - /// Create a new LPSPI driver from the RAL LPSPI instance. - /// - /// This is similar to [`new()`](Self::new), but it does not configure - /// pins. You're responsible for configuring pins, and for making sure - /// the pin configuration doesn't change while this driver is in use. - pub fn without_pins(lpspi: ral::lpspi::Instance) -> Self { - Self::init(lpspi, ()) - } -} - -impl Lpspi { - /// The peripheral instance. - pub const N: u8 = N; - - fn init(lpspi: ral::lpspi::Instance, pins: P) -> Self { - let mut spi = Lpspi { - lpspi, - pins, - bit_order: BitOrder::default(), - }; - ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_1); - ral::write_reg!(ral::lpspi, spi.lpspi, CR, RST: RST_0); - ral::write_reg!( - ral::lpspi, - spi.lpspi, - CFGR1, - MASTER: MASTER_1, - SAMPLE: SAMPLE_1 - ); - Disabled::new(&mut spi.lpspi).set_mode(MODE_0); - ral::write_reg!(ral::lpspi, spi.lpspi, FCR, RXWATER: 0xF, TXWATER: 0xF); - ral::write_reg!(ral::lpspi, spi.lpspi, CR, MEN: MEN_1); - spi - } - - /// Indicates if the driver is (`true`) or is not (`false`) enabled. - pub fn is_enabled(&self) -> bool { - ral::read_reg!(ral::lpspi, self.lpspi, CR, MEN == MEN_1) - } - - /// Enable (`true`) or disable (`false`) the peripheral. - pub fn set_enable(&mut self, enable: bool) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: enable as u32) - } - - /// Reset the driver. - /// - /// Note that this may not not reset all peripheral state, like the - /// enabled state. - pub fn reset(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_1); - while ral::read_reg!(ral::lpspi, self.lpspi, CR, RST == RST_1) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RST: RST_0); - } - } - - /// Release the SPI driver components. - /// - /// This does not change any component state; it releases the components as-is. - /// If you need to obtain the registers in a known, good state, consider calling - /// methods like [`reset()`](Self::reset) before releasing the registers. - pub fn release(self) -> (ral::lpspi::Instance, P) { - (self.lpspi, self.pins) - } - - /// Returns the bit order configuration. - /// - /// See notes in [`set_bit_order`](Lpspi::set_bit_order) to - /// understand when this configuration takes effect. - pub fn bit_order(&self) -> BitOrder { - self.bit_order - } - - /// Set the bit order configuration. - /// - /// This applies to all higher-level write and transfer operations. - /// If you're using the [`Transaction`] API with manual word reads - /// and writes, set the configuration as part of the transaction. - pub fn set_bit_order(&mut self, bit_order: BitOrder) { - self.bit_order = bit_order; - } - - /// Temporarily disable the LPSPI peripheral. - /// - /// The handle to a [`Disabled`](crate::lpspi::Disabled) driver lets you modify - /// LPSPI settings that require a fully disabled peripheral. This will clear the transmit - /// and receive FIFOs. - pub fn disabled(&mut self, func: impl FnOnce(&mut Disabled) -> R) -> R { - self.clear_fifos(); - let mut disabled = Disabled::new(&mut self.lpspi); - func(&mut disabled) - } - - /// Read the status register. - pub fn status(&self) -> Status { - Status::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, SR)) - } - - /// Clear the status flags. - /// - /// To clear status flags, set them high, then call `clear_status()`. - /// - /// The implementation will ensure that only the W1C bits are written, so it's - /// OK to supply `Status::all()` to clear all bits. - pub fn clear_status(&self, flags: Status) { - let flags = flags & Status::W1C; - ral::write_reg!(ral::lpspi, self.lpspi, SR, flags.bits()); - } - - /// Read the interrupt enable bits. - pub fn interrupts(&self) -> Interrupts { - Interrupts::from_bits_truncate(ral::read_reg!(ral::lpspi, self.lpspi, IER)) - } - - /// Set the interrupt enable bits. - /// - /// This writes the bits described by `interrupts` as is to the register. - /// To modify the existing interrupts flags, you should first call [`interrupts`](Lpspi::interrupts) - /// to get the current state, then modify that state. - pub fn set_interrupts(&self, interrupts: Interrupts) { - ral::write_reg!(ral::lpspi, self.lpspi, IER, interrupts.bits()); - } - - /// Clear any existing data in the SPI receive or transfer FIFOs. - #[inline] - pub fn clear_fifo(&mut self, direction: Direction) { - match direction { - Direction::Tx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1), - Direction::Rx => ral::modify_reg!(ral::lpspi, self.lpspi, CR, RRF: RRF_1), - } - } - - /// Clear both FIFOs. - pub fn clear_fifos(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, RTF: RTF_1, RRF: RRF_1); - } - - /// Returns the watermark level for the given direction. - #[inline] - pub fn watermark(&self, direction: Direction) -> u8 { - (match direction { - Direction::Rx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, RXWATER), - Direction::Tx => ral::read_reg!(ral::lpspi, self.lpspi, FCR, TXWATER), - }) as u8 - } - - /// Returns the FIFO status. - #[inline] - pub fn fifo_status(&self) -> FifoStatus { - let (rxcount, txcount) = ral::read_reg!(ral::lpspi, self.lpspi, FSR, RXCOUNT, TXCOUNT); - FifoStatus { - rxcount: rxcount as u16, - txcount: txcount as u16, - } - } - - /// Simply read whatever is in the receiver data register. - fn read_data_unchecked(&self) -> u32 { - ral::read_reg!(ral::lpspi, self.lpspi, RDR) - } - - /// Read the data register. - /// - /// Returns `None` if the receive FIFO is empty. Otherwise, returns the complete - /// read of the register. You're reponsible for interpreting the raw value as - /// a data word, depending on the frame size. - pub fn read_data(&mut self) -> Option { - if ral::read_reg!(ral::lpspi, self.lpspi, RSR, RXEMPTY == RXEMPTY_0) { - Some(self.read_data_unchecked()) - } else { - None - } - } - - /// Check for any receiver errors. - fn recv_ok(&self) -> Result<(), LpspiError> { - let status = self.status(); - if status.intersects(Status::RECEIVE_ERROR) { - Err(LpspiError::Fifo(Direction::Rx)) - } else { - Ok(()) - } - } - - /// Place `word` into the transmit FIFO. - /// - /// This will result in the value being sent from the LPSPI. - /// You're responsible for making sure that the transmit FIFO can - /// fit this word. - pub fn enqueue_data(&self, word: u32) { - ral::write_reg!(ral::lpspi, self.lpspi, TDR, word); - } - - pub(crate) fn wait_for_transmit_fifo_space(&mut self) -> Result<(), LpspiError> { - loop { - let status = self.status(); - if status.intersects(Status::TRANSMIT_ERROR) { - return Err(LpspiError::Fifo(Direction::Tx)); - } - let fifo_status = self.fifo_status(); - if !fifo_status.is_full(Direction::Tx) { - return Ok(()); - } - } - } - - /// Place a transaction definition into the transmit FIFO. - /// - /// Once this definition is popped from the transmit FIFO, this may - /// affect, or abort, any ongoing transactions. - /// - /// You're responsible for making sure there's space in the transmit - /// FIFO for this transaction command. - pub fn enqueue_transaction(&mut self, transaction: &Transaction) { - // TODO: This is technically unsafe. TCR is not meant to be read. - // Solution is simple: Write it completely, every time. - ral::modify_reg!(ral::lpspi, self.lpspi, TCR, - LSBF: transaction.bit_order as u32, - BYSW: transaction.byte_swap as u32, - RXMSK: transaction.receive_data_mask as u32, - TXMSK: transaction.transmit_data_mask as u32, - FRAMESZ: transaction.frame_size as u32, - CONT: transaction.continuous as u32, - CONTC: transaction.continuing as u32 - ); - } - - /// Exchanges data with the SPI device. - /// - /// This routine uses continuous transfers to perform the transaction, no matter the - /// primitive type. There's an optimization for &[u32] that we're missing; in this case, - /// we don't necessarily need to use continuous transfers. The frame size could be set to - /// 8 * buffer.len() * sizeof(u32), and we copy user words into the transmit queue as-is. - /// But handling the packing of u8s and u16s into the u32 transmit queue in software is - /// extra work, work that's effectively achieved when we use continuous transfers. - /// We're guessing that the time to pop a transmit command from the queue is much faster - /// than the time taken to pop from the data queue, so the extra queue utilization shouldn't - /// matter. - fn exchange(&mut self, buffer: &mut [W]) -> Result<(), LpspiError> - where - W: Word, - { - if self.status().intersects(Status::BUSY) { - return Err(LpspiError::Busy); - } else if buffer.is_empty() { - return Err(LpspiError::NoData); - } - - self.clear_fifos(); - - let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; - transaction.bit_order = self.bit_order(); - transaction.continuous = true; - - let mut tx_idx = 0usize; - let mut rx_idx = 0usize; - - // Continue looping while there is either tx OR rx remaining - while tx_idx < buffer.len() || rx_idx < buffer.len() { - if tx_idx < buffer.len() { - let word = buffer[tx_idx]; - - // Turn off TCR CONT on last tx as a workaround so that the final - // falling edge comes through: - // https://community.nxp.com/t5/i-MX-RT/RT1050-LPSPI-last-bit-not-completing-in-continuous-mode/m-p/898460 - if tx_idx + 1 == buffer.len() { - transaction.continuous = false; - } - - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - self.wait_for_transmit_fifo_space()?; - self.enqueue_data(word.into()); - transaction.continuing = true; - tx_idx += 1; - } - - if rx_idx < buffer.len() { - self.recv_ok()?; - if let Some(word) = self.read_data() { - buffer[rx_idx] = word.try_into().unwrap_or(W::MAX); - rx_idx += 1; - } - } - } - - Ok(()) - } - - /// Write data to the transmit queue without subsequently reading - /// the receive queue. - /// - /// Use this method when you know that the receiver queue is disabled - /// (RXMASK high in TCR). - /// - /// Similar to `exchange`, this is using continuous transfers for all supported primitives. - fn write_no_read(&mut self, buffer: &[W]) -> Result<(), LpspiError> - where - W: Word, - { - if self.status().intersects(Status::BUSY) { - return Err(LpspiError::Busy); - } else if buffer.is_empty() { - return Err(LpspiError::NoData); - } - - self.clear_fifos(); - - let mut transaction = Transaction::new(8 * core::mem::size_of::() as u16)?; - transaction.bit_order = self.bit_order(); - transaction.continuous = true; - transaction.receive_data_mask = true; - - for word in buffer { - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - self.wait_for_transmit_fifo_space()?; - self.enqueue_data((*word).into()); - transaction.continuing = true; - } - - transaction.continuing = false; - transaction.continuous = false; - - self.wait_for_transmit_fifo_space()?; - self.enqueue_transaction(&transaction); - - Ok(()) - } - - /// Let the peripheral act as a DMA source. - /// - /// After this call, the peripheral will signal to the DMA engine whenever - /// it has data available to read. - pub fn enable_dma_receive(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: 0); // No watermarks; affects DMA signaling - ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 1); - } - - /// Stop the peripheral from acting as a DMA source. - /// - /// See the DMA chapter in the reference manual to understand when this - /// should be called in the DMA transfer lifecycle. - pub fn disable_dma_receive(&mut self) { - while ral::read_reg!(ral::lpspi, self.lpspi, DER, RDDE == 1) { - ral::modify_reg!(ral::lpspi, self.lpspi, DER, RDDE: 0); - } - } - - /// Let the peripheral act as a DMA destination. - /// - /// After this call, the peripheral will signal to the DMA engine whenever - /// it has free space in its transfer buffer. - pub fn enable_dma_transmit(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: 0); // No watermarks; affects DMA signaling - ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 1); - } - - /// Stop the peripheral from acting as a DMA destination. - /// - /// See the DMA chapter in the reference manual to understand when this - /// should be called in the DMA transfer lifecycle. - pub fn disable_dma_transmit(&mut self) { - while ral::read_reg!(ral::lpspi, self.lpspi, DER, TDDE == 1) { - ral::modify_reg!(ral::lpspi, self.lpspi, DER, TDDE: 0); - } - } - - /// Produces a pointer to the receiver data register. - /// - /// You should use this pointer when coordinating a DMA transfer. - /// You're not expected to read from this pointer in software. - pub fn rdr(&self) -> *const ral::RORegister { - core::ptr::addr_of!(self.lpspi.RDR) - } - - /// Produces a pointer to the transfer data register. - /// - /// You should use this pointer when coordinating a DMA transfer. - /// You're not expected to read from this pointer in software. - pub fn tdr(&self) -> *const ral::WORegister { - core::ptr::addr_of!(self.lpspi.TDR) - } -} - -bitflags::bitflags! { - /// Status flags for the LPSPI interface. - pub struct Status : u32 { - /// Module busy flag. - /// - /// This flag is read only. - const BUSY = 1 << 24; - - // - // Start W1C bits. - // - - /// Data match flag. - /// - /// Indicates that received data has matched one or both of the match - /// fields. To clear this flag, write this bit to the status register - /// (W1C). - const DATA_MATCH = 1 << 13; - /// Receive error flag. - /// - /// Set when the receive FIFO has overflowed. Before clearing this bit, - /// empty the receive FIFO. Then, write this bit to clear the flag (W1C). - const RECEIVE_ERROR = 1 << 12; - /// Transmit error flag. - /// - /// Set when the transmit FIFO has underruns. Before clearing this bit, - /// end the transfer. Then, write this bit to clear the flag (W1C). - const TRANSMIT_ERROR = 1 << 11; - /// Transfer complete flag. - /// - /// Set when the LPSPI returns to an idle state, and the transmit FIFO - /// is empty. To clear this flag, write this bit (W1C). - const TRANSFER_COMPLETE = 1 << 10; - /// Frame complete flag. - /// - /// Set at the end of each frame transfer, when PCS negates. To clear this - /// flag, write this bit (W1C). - const FRAME_COMPLETE = 1 << 9; - /// Word complete flag. - /// - /// Set when the last bit of a received word is sampled. To clear this flag, write - /// this bit (W1C). - const WORD_COMPLETE = 1 << 8; - - // - // End W1C bits. - // - - /// Receive data flag. - /// - /// Set when the number of words in the receive FIFO is greater than the watermark. - /// This flag is read only. To clear the flag, exhaust the receive FIFO. - const RECEIVE_DATA = 1 << 1; - /// Transmit data flag. - /// - /// Set when the number of words in the transmit FIFO is less than or equal to the - /// watermark. This flag is read only. TO clear the flag, fill the transmit FIFO. - const TRANSMIT_DATA = 1 << 0; - } -} - -impl Status { - const W1C: Self = Self::from_bits_truncate( - Self::DATA_MATCH.bits() - | Self::RECEIVE_ERROR.bits() - | Self::TRANSMIT_ERROR.bits() - | Self::TRANSFER_COMPLETE.bits() - | Self::FRAME_COMPLETE.bits() - | Self::WORD_COMPLETE.bits(), - ); -} - -/// The number of words in each FIFO. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct FifoStatus { - /// Number of words in the receive FIFO. - pub rxcount: u16, - /// Number of words in the transmit FIFO. - pub txcount: u16, -} - -impl FifoStatus { - /// Indicates if the FIFO is full for the given direction. - #[inline] - pub const fn is_full(self, direction: Direction) -> bool { - /// See PARAM register docs. - const MAX_FIFO_SIZE: u16 = 16; - let count = match direction { - Direction::Tx => self.txcount, - Direction::Rx => self.rxcount, - }; - count >= MAX_FIFO_SIZE - } -} - -bitflags::bitflags! { - /// Interrupt flags. - /// - /// A high bit indicates that the condition generates an interrupt. - /// See the status bits for more information. - pub struct Interrupts : u32 { - /// Data match interrupt enable. - const DATA_MATCH = 1 << 13; - /// Receive error interrupt enable. - const RECEIVE_ERROR = 1 << 12; - /// Transmit error interrupt enable. - const TRANSMIT_ERROR = 1 << 11; - /// Transmit complete interrupt enable. - const TRANSMIT_COMPLETE = 1 << 10; - /// Frame complete interrupt enable. - const FRAME_COMPLETE = 1 << 9; - /// Word complete interrupt enable. - const WORD_COMPLETE = 1 << 8; - - /// Receive data interrupt enable. - const RECEIVE_DATA = 1 << 1; - /// Transmit data interrupt enable. - const TRANSMIT_DATA = 1 << 0; - } -} - -/// An LPSPI peripheral which is temporarily disabled. -pub struct Disabled<'a, const N: u8> { - lpspi: &'a ral::lpspi::Instance, - men: bool, -} - -impl<'a, const N: u8> Disabled<'a, N> { - fn new(lpspi: &'a mut ral::lpspi::Instance) -> Self { - let men = ral::read_reg!(ral::lpspi, lpspi, CR, MEN == MEN_1); - ral::modify_reg!(ral::lpspi, lpspi, CR, MEN: MEN_0); - Self { lpspi, men } - } - - /// Set the SPI mode for the peripheral - pub fn set_mode(&mut self, mode: Mode) { - // This could probably be changed when we're not disabled. - // However, there's rules about when you can read TCR. - // Specifically, reading TCR while it's being loaded from - // the transmit FIFO could result in an incorrect reading. - // Only permitting this when we're disabled might help - // us avoid something troublesome. - ral::modify_reg!( - ral::lpspi, - self.lpspi, - TCR, - CPOL: ((mode.polarity == Polarity::IdleHigh) as u32), - CPHA: ((mode.phase == Phase::CaptureOnSecondTransition) as u32) - ); - } - - /// Set the LPSPI clock speed (Hz). - /// - /// `source_clock_hz` is the LPSPI peripheral clock speed. To specify the - /// peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation. - pub fn set_clock_hz(&mut self, source_clock_hz: u32, clock_hz: u32) { - set_spi_clock(source_clock_hz, clock_hz, self.lpspi); - } - - /// Set the watermark level for a given direction. - /// - /// Returns the watermark level committed to the hardware. This may be different - /// than the supplied `watermark`, since it's limited by the hardware. - /// - /// When `direction == Direction::Rx`, the receive data flag is set whenever the - /// number of words in the receive FIFO is greater than `watermark`. - /// - /// When `direction == Direction::Tx`, the transmit data flag is set whenever the - /// the number of words in the transmit FIFO is less than, or equal, to `watermark`. - #[inline] - pub fn set_watermark(&mut self, direction: Direction, watermark: u8) -> u8 { - let max_watermark = match direction { - Direction::Rx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, RXFIFO), - Direction::Tx => 1 << ral::read_reg!(ral::lpspi, self.lpspi, PARAM, TXFIFO), - }; - - let watermark = watermark.min(max_watermark - 1); - - match direction { - Direction::Rx => { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, RXWATER: watermark as u32) - } - Direction::Tx => { - ral::modify_reg!(ral::lpspi, self.lpspi, FCR, TXWATER: watermark as u32) - } - } - - watermark - } - - /// Set the sampling point of the LPSPI peripheral. - /// - /// When set to `SamplePoint::DelayedEdge`, the LPSPI will sample the input data - /// on a delayed LPSPI_SCK edge, which improves the setup time when sampling data. - #[inline] - pub fn set_sample_point(&mut self, sample_point: SamplePoint) { - match sample_point { - SamplePoint::Edge => ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_0), - SamplePoint::DelayedEdge => { - ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, SAMPLE: SAMPLE_1) - } - } - } -} - -impl Drop for Disabled<'_, N> { - fn drop(&mut self) { - ral::modify_reg!(ral::lpspi, self.lpspi, CR, MEN: self.men as u32); - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u8]) -> Result<&'a [u8], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u16]) -> Result<&'a [u16], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Transfer for Lpspi { - type Error = LpspiError; - - fn transfer<'a>(&mut self, words: &'a mut [u32]) -> Result<&'a [u32], Self::Error> { - self.exchange(words)?; - Ok(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u16]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -impl eh02::blocking::spi::Write for Lpspi { - type Error = LpspiError; - - fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { - self.write_no_read(words) - } -} - -// Not supporting WriteIter right now. Since we don't know how many bytes we're -// going to write, we can't specify the frame size. There might be ways around -// this by playing with CONTC and CONT bits, but we can evaluate that later. - -/// Describes SPI words that can participate in transactions. -trait Word: Copy + Into + TryFrom { - const MAX: Self; -} - -impl Word for u8 { - const MAX: u8 = u8::MAX; -} - -impl Word for u16 { - const MAX: u16 = u16::MAX; -} - -impl Word for u32 { - const MAX: u32 = u32::MAX; -} From 7492ba20d34ab37f97299894a3e4d31b7c8a04f3 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 23:20:41 +0100 Subject: [PATCH 67/73] Remove unnecessary pubs --- src/common/lpspi/read_part.rs | 4 ++-- src/common/lpspi/write_part.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index c56133bd..dfc354e6 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -61,7 +61,7 @@ impl LpspiReadPart<'_, N> { ral::read_reg!(ral::lpspi, self.data.lpspi.instance(), RDR) } - pub async unsafe fn read_single_word( + async unsafe fn read_single_word( &mut self, data: *mut u8, byteorder: ByteOrder, @@ -93,7 +93,7 @@ impl LpspiReadPart<'_, N> { } } - pub async unsafe fn read_u32_stream( + async unsafe fn read_u32_stream( &mut self, read_data: *mut u8, byteorder: ByteOrder, diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs index 5b3e414b..7eaf97bc 100644 --- a/src/common/lpspi/write_part.rs +++ b/src/common/lpspi/write_part.rs @@ -94,7 +94,7 @@ impl LpspiWritePart<'_, N> { } } - pub async unsafe fn write_single_word( + async unsafe fn write_single_word( &mut self, write_data: Option<*const u8>, byteorder: ByteOrder, @@ -143,7 +143,7 @@ impl LpspiWritePart<'_, N> { } } - pub async unsafe fn write_u32_stream( + async unsafe fn write_u32_stream( &mut self, write_data: Option<*const u8>, byteorder: ByteOrder, From b75ede322266481e4f76f20cbab832f5c0632a58 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Fri, 15 Dec 2023 23:55:07 +0100 Subject: [PATCH 68/73] Add TODO comments --- src/common/lpspi/read_part.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index dfc354e6..44f2a1c3 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -68,6 +68,7 @@ impl LpspiReadPart<'_, N> { len: NonZeroUsize, ) { assert!(len.get() < 4); + // TODO: test byte order let reverse_bytes = match byteorder { ByteOrder::Normal => false, @@ -100,6 +101,7 @@ impl LpspiReadPart<'_, N> { len: NonZeroUsize, // TODO: dma ) { + // TODO: test byte order assert!(len.get() % 4 == 0); let len = NonZeroUsize::new(len.get() / 4).unwrap(); let read_data: *mut u32 = read_data.cast(); From 9cbd05ec9acaee06b2c2380101cc770a066ac353 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 17 Dec 2023 15:13:58 +0100 Subject: [PATCH 69/73] Simplify read part --- src/common/lpspi/read_part.rs | 68 +++++++++++++++++------------------ 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs index 44f2a1c3..e04ec4b7 100644 --- a/src/common/lpspi/read_part.rs +++ b/src/common/lpspi/read_part.rs @@ -106,45 +106,41 @@ impl LpspiReadPart<'_, N> { let len = NonZeroUsize::new(len.get() / 4).unwrap(); let read_data: *mut u32 = read_data.cast(); - for chunk in ChunkIter::new(len, MAX_FRAME_SIZE_U32 as usize) { - let read_data = read_data.add(chunk.position); - - let is_aligned = read_data.align_offset(core::mem::align_of::()) == 0; - let requires_reorder = byteorder.requires_reorder(); - - match (is_aligned, requires_reorder) { - (true, true) => { - for i in 0..chunk.size.get() { - let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); - let val = self.rx_fifo_read_data(num_leftover).await; - let val = byteorder.reorder(val); - read_data.add(i).write(val); - } + let is_aligned = read_data.align_offset(core::mem::align_of::()) == 0; + let requires_reorder = byteorder.requires_reorder(); + + match (is_aligned, requires_reorder) { + (true, true) => { + for i in 0..len.get() { + let num_leftover = NonZeroUsize::new(len.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + let val = byteorder.reorder(val); + read_data.add(i).write(val); } - (true, false) => { - // This is the case that supports DMA. - // TODO: add DMA. - for i in 0..chunk.size.get() { - let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); - let val = self.rx_fifo_read_data(num_leftover).await; - read_data.add(i).write(val); - } - log::info!("Would support DMA."); + } + (true, false) => { + // This is the case that supports DMA. + // TODO: add DMA. + for i in 0..len.get() { + let num_leftover = NonZeroUsize::new(len.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + read_data.add(i).write(val); } - (false, true) => { - for i in 0..chunk.size.get() { - let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); - let val = self.rx_fifo_read_data(num_leftover).await; - let val = byteorder.reorder(val); - read_data.add(i).write_unaligned(val); - } + log::info!("Would support DMA."); + } + (false, true) => { + for i in 0..len.get() { + let num_leftover = NonZeroUsize::new(len.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + let val = byteorder.reorder(val); + read_data.add(i).write_unaligned(val); } - (false, false) => { - for i in 0..chunk.size.get() { - let num_leftover = NonZeroUsize::new(chunk.size.get() - i).unwrap(); - let val = self.rx_fifo_read_data(num_leftover).await; - read_data.add(i).write_unaligned(val); - } + } + (false, false) => { + for i in 0..len.get() { + let num_leftover = NonZeroUsize::new(len.get() - i).unwrap(); + let val = self.rx_fifo_read_data(num_leftover).await; + read_data.add(i).write_unaligned(val); } } } From c70ea1a1a1e67ee5da2e5a6cb10769949382f630 Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 17 Dec 2023 16:14:22 +0100 Subject: [PATCH 70/73] Prepare write DMA --- src/common/lpspi/bus.rs | 26 ++++++++++++++++++++-- src/common/lpspi/transfer_actions.rs | 14 ++++++++++++ src/common/lpspi/write_part.rs | 32 ++++++++++++++++++++++------ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/common/lpspi/bus.rs b/src/common/lpspi/bus.rs index 8422c3b2..1ba7bfdc 100644 --- a/src/common/lpspi/bus.rs +++ b/src/common/lpspi/bus.rs @@ -7,6 +7,7 @@ use super::{ }; use crate::{ iomuxc::{consts, lpspi}, + lpspi::transfer_actions::TransferDirection, ral, }; @@ -206,6 +207,15 @@ impl<'a, const N: u8> Lpspi<'a, N> { let read_part = &mut self.read_part; let write_part = &mut self.write_part; + let (mut write_dma, mut read_dma) = match &mut self.dma { + LpspiDma::Disabled => (None, None), + LpspiDma::Partial(dma) => match sequence.recommended_dma_direction() { + TransferDirection::Read => (None, Some(dma)), + TransferDirection::Write => (Some(dma), None), + }, + LpspiDma::Full(dma1, dma2) => (Some(dma1), Some(dma2)), + }; + let read_task = async { if let Some(phase1) = &sequence.phase1 { let actions = phase1.get_read_actions(); @@ -224,13 +234,25 @@ impl<'a, const N: u8> Lpspi<'a, N> { if let Some(phase1) = &sequence.phase1 { let actions = phase1.get_write_actions(); write_part - .perform_write_actions(actions, false, has_phase_2, byteorder) + .perform_write_actions( + actions, + false, + has_phase_2, + byteorder, + write_dma.as_deref_mut(), + ) .await; } if let Some(phase2) = &sequence.phase2 { let actions = phase2.get_write_actions(); write_part - .perform_write_actions(actions, has_phase_1, false, byteorder) + .perform_write_actions( + actions, + has_phase_1, + false, + byteorder, + write_dma.as_deref_mut(), + ) .await; } }; diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs index 2cc0b9e0..6f70320d 100644 --- a/src/common/lpspi/transfer_actions.rs +++ b/src/common/lpspi/transfer_actions.rs @@ -268,6 +268,20 @@ pub(crate) struct ActionSequence<'a> { _lifetimes: PhantomData<&'a [u8]>, } +impl ActionSequence<'_> { + pub(crate) fn recommended_dma_direction(&self) -> TransferDirection { + if let Some(phase2) = &self.phase2 { + phase2.transfer_direction() + } else { + // If both both directions are equal in size, choose the + // read direction for DMA, it might be slightly more efficient. + // (Because the tx FIFO needs to be filled manually with the + // command words anyway) + TransferDirection::Read + } + } +} + pub trait BufferType: Copy + 'static { fn byte_order() -> ByteOrder; } diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs index 7eaf97bc..af0a0415 100644 --- a/src/common/lpspi/write_part.rs +++ b/src/common/lpspi/write_part.rs @@ -1,6 +1,7 @@ use core::num::NonZeroUsize; use eh1::spi::{Phase, Polarity}; +use imxrt_dma::channel::Channel; use super::{ ral, @@ -33,6 +34,20 @@ impl LpspiWritePart<'_, N> { ral::write_reg!(ral::lpspi, self.data.lpspi.instance(), TDR, val); } + async unsafe fn tx_fifo_enqueue_data_dma( + &mut self, + channel: &mut Channel, + data: *const u32, + len: NonZeroUsize, + ) { + log::info!("DMA!"); + + for i in 0..len.get() { + let val = data.add(i).read(); + self.tx_fifo_enqueue_data(val).await; + } + } + async fn start_frame( &mut self, reverse_bytes: bool, @@ -68,6 +83,7 @@ impl LpspiWritePart<'_, N> { has_previous: bool, has_next: bool, byteorder: ByteOrder, + mut dma: Option<&mut Channel>, ) { for action in actions { if action.len.get() < 4 { @@ -88,6 +104,7 @@ impl LpspiWritePart<'_, N> { action.len, action.is_first && !has_previous, action.is_last && !has_next, + dma.as_deref_mut(), ) .await; } @@ -151,7 +168,7 @@ impl LpspiWritePart<'_, N> { len: NonZeroUsize, is_first_frame: bool, is_last_frame: bool, - // TODO: dma + mut dma: Option<&mut Channel>, ) { assert!(len.get() % 4 == 0); let len = NonZeroUsize::new(len.get() / 4).unwrap(); @@ -184,12 +201,15 @@ impl LpspiWritePart<'_, N> { } (true, false) => { // This is the case that supports DMA. - // TODO: add DMA. - for i in 0..chunk.size.get() { - let val = write_data.add(i).read(); - self.tx_fifo_enqueue_data(val).await; + if let Some(dma) = dma.as_deref_mut() { + self.tx_fifo_enqueue_data_dma(dma, write_data, chunk.size) + .await; + } else { + for i in 0..chunk.size.get() { + let val = write_data.add(i).read(); + self.tx_fifo_enqueue_data(val).await; + } } - log::info!("Would support DMA."); } (false, true) => { for i in 0..chunk.size.get() { From 0c5dcae19cf6d676fc70ed64dfd6fe1394743d4e Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 17 Dec 2023 16:15:06 +0100 Subject: [PATCH 71/73] Add TODO --- src/common/lpspi/write_part.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs index af0a0415..ba5244b3 100644 --- a/src/common/lpspi/write_part.rs +++ b/src/common/lpspi/write_part.rs @@ -41,7 +41,7 @@ impl LpspiWritePart<'_, N> { len: NonZeroUsize, ) { log::info!("DMA!"); - + // TODO: implement for i in 0..len.get() { let val = data.add(i).read(); self.tx_fifo_enqueue_data(val).await; From 0cfe3a7cd7456f44fcdf117b80e10c36b7b871ed Mon Sep 17 00:00:00 2001 From: Finomnis Date: Sun, 17 Dec 2023 16:26:03 +0100 Subject: [PATCH 72/73] Adjust visibility of DMA mappings --- src/chip/dma.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chip/dma.rs b/src/chip/dma.rs index 0a9e2940..de0449e0 100644 --- a/src/chip/dma.rs +++ b/src/chip/dma.rs @@ -63,8 +63,8 @@ mod mappings { pub(super) const LPUART_DMA_RX_MAPPING: [u32; 8] = [3, 67, 5, 69, 7, 71, 9, 73]; pub(super) const LPUART_DMA_TX_MAPPING: [u32; 8] = [2, 66, 4, 68, 6, 70, 8, 72]; - pub(super) const LPSPI_DMA_RX_MAPPING: [u32; 4] = [13, 77, 15, 79]; - pub(super) const LPSPI_DMA_TX_MAPPING: [u32; 4] = [14, 78, 16, 80]; + pub(crate) const LPSPI_DMA_RX_MAPPING: [u32; 4] = [13, 77, 15, 79]; + pub(crate) const LPSPI_DMA_TX_MAPPING: [u32; 4] = [14, 78, 16, 80]; pub(super) const ADC_DMA_RX_MAPPING: [u32; 2] = [24, 88]; } @@ -75,8 +75,8 @@ mod mappings { pub(super) const LPUART_DMA_TX_MAPPING: [u32; 12] = [8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]; - pub(super) const LPSPI_DMA_RX_MAPPING: [u32; 6] = [36, 38, 40, 42, 44, 46]; - pub(super) const LPSPI_DMA_TX_MAPPING: [u32; 6] = [37, 39, 41, 43, 45, 47]; + pub(crate) const LPSPI_DMA_RX_MAPPING: [u32; 6] = [36, 38, 40, 42, 44, 46]; + pub(crate) const LPSPI_DMA_TX_MAPPING: [u32; 6] = [37, 39, 41, 43, 45, 47]; } use mappings::*; From 649748dbbfff021e45c893664f8184367635c3fb Mon Sep 17 00:00:00 2001 From: Finomnis Date: Thu, 21 Dec 2023 21:45:36 +0100 Subject: [PATCH 73/73] Check in latest example version, does not work yet. --- examples/rtic_spi.rs | 164 ++++++++++++++++++++++--------------------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/examples/rtic_spi.rs b/examples/rtic_spi.rs index 4dac258c..bcfe7ac5 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -1,111 +1,113 @@ -//! Demonstrates an interrupt-driven SPI device. -//! -//! Connect SDI to SDO. The example uses the LPSPI interrupt to -//! schedule transfers, and to receive data. You can observe the -//! I/O with a scope / logic analyzer. The SPI CLK runs at 1MHz, -//! and the frame size is 64 bits. -//! -//! TODO: update description - -#![no_std] +#![deny(warnings)] #![no_main] -// Required for RTIC 2 (for now) +#![no_std] #![feature(type_alias_impl_trait)] -#[rtic::app(device = board, peripherals = false, dispatchers = [BOARD_SWTASK0])] -mod app { +use teensy4_bsp::pins::common::{P0, P1}; +imxrt_uart_panic::register!(LPUART6, P1, P0, 115200, teensy4_panic::sos); - use embedded_hal_bus::spi::ExclusiveDevice; - use imxrt_hal as hal; +use teensy4_bsp as bsp; - use eh1::spi::Operation; - use eh1::spi::SpiDevice; - use hal::lpspi::LpspiDma; - use rtic_monotonics::systick::*; +use bsp::board; +use bsp::hal; +use bsp::logging; - use board::{SpiBus, SpiCsPin, SpiInterruptHandler}; +use eh1::serial::Write; - #[local] - struct Local { - spi_device: ExclusiveDevice, - spi_interrupt_handler: SpiInterruptHandler, - } +use rtic_monotonics::imxrt::Gpt1 as Mono; +use rtic_monotonics::imxrt::*; +use rtic_monotonics::Monotonic; - #[shared] - struct Shared {} +#[rtic::app(device = teensy4_bsp, dispatchers = [LPSPI1])] +mod app { + use super::*; - #[init(local = [ - spi_systick: Option = None, - ])] - fn init(cx: init::Context) -> (Shared, Local) { - let ( - board::Common { mut dma, .. }, - board::Specifics { - spi: (mut spi_bus, spi_cs_pin), - .. - }, - ) = board::new(); + const LOG_POLL_INTERVAL: u32 = board::PERCLK_FREQUENCY / 100; + const LOG_DMA_CHANNEL: usize = 0; - // Init monotonic - let systick_token = rtic_monotonics::create_systick_token!(); - Systick::start( - cx.core.SYST, - 600_000_000, /* TODO: fix */ - systick_token, - ); + #[shared] + struct Shared {} - // Init DMA - let mut chan_a = dma[board::BOARD_DMA_A_INDEX].take().unwrap(); - chan_a.set_disable_on_completion(true); + #[local] + struct Local { + led: board::Led, + poll_log: hal::pit::Pit<3>, + log_poller: logging::Poller, + } - let mut chan_b = dma[board::BOARD_DMA_B_INDEX].take().unwrap(); - chan_b.set_disable_on_completion(true); + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + let board::Resources { + mut dma, + pit: (_, _, _, mut poll_log), + pins, + lpuart6, + mut gpio2, + mut gpt1, + .. + } = board::t40(cx.device); + + // Logging + let log_dma = dma[LOG_DMA_CHANNEL].take().unwrap(); + let mut log_uart = board::lpuart(lpuart6, pins.p1, pins.p0, 115200); + for &ch in "\r\n===== Teensy4 Rtic Blinky =====\r\n\r\n".as_bytes() { + nb::block!(log_uart.write(ch)).unwrap(); + } + nb::block!(log_uart.flush()).unwrap(); + let log_poller = + logging::log::lpuart(log_uart, log_dma, logging::Interrupts::Enabled).unwrap(); + poll_log.set_interrupt_enable(true); + poll_log.set_load_timer_value(LOG_POLL_INTERVAL); + poll_log.enable(); - // Configure SPI - spi_bus.set_dma(LpspiDma::Full(chan_a, chan_b)); - let spi_interrupt_handler = spi_bus.enable_interrupts(); + // Initialize Monotonic + gpt1.set_clock_source(hal::gpt::ClockSource::PeripheralClock); + let gpt1_mono_token = rtic_monotonics::create_imxrt_gpt1_token!(); + Mono::start(board::PERCLK_FREQUENCY, gpt1.release(), gpt1_mono_token); - // Create SPI device - let spi_device = ExclusiveDevice::new(spi_bus, spi_cs_pin, Systick); + // Setup LED + let led = board::led(&mut gpio2, pins.p13); + led.set(); - demo::spawn().unwrap(); + // Schedule the blinking task + blink::spawn().ok(); ( Shared {}, Local { - spi_device, - spi_interrupt_handler, + log_poller, + poll_log, + led, }, ) } - #[task(priority = 1, local = [spi_device])] - async fn demo(cx: demo::Context) { - let demo::LocalResources { spi_device, .. } = cx.local; + #[task(local = [led])] + async fn blink(cx: blink::Context) { + let blink::LocalResources { led, .. } = cx.local; + + let mut next_update = Mono::now(); loop { - Systick::delay(1000.millis()).await; - - // To demonstrate normal operation - spi_device - .transaction(&mut [ - Operation::DelayUs(100), - Operation::Write(&[12345u16]), - Operation::DelayUs(10), - Operation::Write(&[420, 69, 42]), - Operation::Write(&[0xFFFF]), - Operation::DelayUs(50), - ]) - .unwrap(); - - // To demonstrate larger, DMA based transfers - let mut buf = [0xf5u32; 512]; - spi_device.transfer_in_place(&mut buf).unwrap(); + led.toggle(); + log::info!("Time: {}", Mono::now()); + next_update += 1000.millis(); + Mono::delay_until(next_update).await; } } - #[task(priority = 2, binds = BOARD_SPI, local = [spi_interrupt_handler])] - fn spi_interrupt(cx: spi_interrupt::Context) { - cx.local.spi_interrupt_handler.on_interrupt(); + #[task(binds = PIT, priority = 1, local = [poll_log, log_poller])] + fn logger(cx: logger::Context) { + let logger::LocalResources { + poll_log, + log_poller, + .. + } = cx.local; + + if poll_log.is_elapsed() { + poll_log.clear_elapsed(); + + log_poller.poll(); + } } }