diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 00ccaf27..1f523344 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -6,17 +6,12 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - 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 +31,9 @@ jobs: - imxrt-ral/imxrt1176_cm7 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable components: clippy - uses: actions-rs/clippy-check@v1 name: Lint imxrt-hal @@ -64,18 +57,16 @@ jobs: - imxrt-ral/imxrt1176_cm7,imxrt1170 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - profile: minimal - 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 +95,18 @@ 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 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - 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 +nightly build ${{ matrix.config }} --target=thumbv7em-none-eabihf --release # Run unit, integration tests. # @@ -140,16 +126,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 +stable test --features=${{ matrix.chips }} --tests --package=imxrt-hal --package=imxrt-log # Ensures that documentation builds, and that links are valid docs: @@ -158,14 +138,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 +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 diff --git a/Cargo.toml b/Cargo.toml index cfd28125..53f0ac5f 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.4" @@ -19,7 +22,7 @@ version = "1.0" version = "1.2" [dependencies.fugit] -version = "0.3" +version = "0.3.7" # For EH02 CountDown. [dependencies.void] @@ -33,11 +36,34 @@ version = "1" package = "embedded-hal" version = "0.2" +[dependencies.eh1] +package = "embedded-hal" +version = "1.0.0-rc.2" + +[dependencies.eh1-async] +package = "embedded-hal-async" +version = "1.0.0-rc.2" +optional = true + [dependencies.rand_core] -version = "0.5" +version = "0.6" default-features = false optional = true +[dependencies.cortex-m] +version = "0.7" + +[dependencies.cassette] +version = "0.2.3" + +[dependencies.futures] +version = "0.3.11" +default-features = false +features = ["async-await"] + +[dependencies.log] +version = "0.4" + ####################### # imxrt-rs dependencies ####################### @@ -70,6 +96,7 @@ imxrt1020 = ["imxrt-iomuxc/imxrt1020"] imxrt1060 = ["imxrt-iomuxc/imxrt1060"] imxrt1064 = ["imxrt-iomuxc/imxrt1060"] imxrt1170 = ["imxrt-iomuxc/imxrt1170"] +async = ["dep:eh1-async"] ################ # Extra features @@ -80,16 +107,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" @@ -124,10 +152,9 @@ 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" @@ -148,7 +175,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 97e42293..1f8a4794 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -49,16 +49,27 @@ 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"))] /// Activate the `"spi"` feature to configure the SPI peripheral. -pub type Spi = (); +mod lpspi_types { + pub type SpiBus = (); + pub type SpiCsPin = (); + pub type SpiInterruptHandler = (); +} #[cfg(feature = "spi")] /// SPI peripheral. -pub type Spi = hal::lpspi::Lpspi; +mod lpspi_types { + + use super::*; + pub type SpiBus = hal::lpspi::Lpspi<'static, 4>; + pub type SpiCsPin = hal::gpio::Output; + pub type SpiInterruptHandler = hal::lpspi::LpspiInterruptHandler<'static, 4>; +} + +pub use lpspi_types::*; pub type I2cPins = hal::lpi2c::Pins< iomuxc::gpio_ad_b1::GPIO_AD_B1_07, // SCL, P16 @@ -110,7 +121,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, @@ -153,17 +164,25 @@ 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); - spi.disabled(|spi| { - spi.set_clock_hz(super::LPSPI_CLK_FREQUENCY, super::SPI_BAUD_RATE_FREQUENCY); + 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 }, + super::LPSPI_CLK_FREQUENCY, + ); + spi.disabled(|bus| { + bus.set_clock_hz(super::SPI_BAUD_RATE_FREQUENCY); }); - spi + + (spi, cs_pin) }; #[cfg(not(feature = "spi"))] #[allow(clippy::let_unit_value)] - let spi = (); + let spi = ((), ()); let lpi2c3 = unsafe { ral::lpi2c::LPI2C3::instance() }; let i2c = I2c::new( @@ -218,7 +237,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/examples/rtic_spi.rs b/examples/rtic_spi.rs index 29fe5faa..bcfe7ac5 100644 --- a/examples/rtic_spi.rs +++ b/examples/rtic_spi.rs @@ -1,67 +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. - -#![no_std] +#![deny(warnings)] #![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use teensy4_bsp::pins::common::{P0, P1}; +imxrt_uart_panic::register!(LPUART6, P1, P0, 115200, teensy4_panic::sos); + +use teensy4_bsp as bsp; + +use bsp::board; +use bsp::hal; +use bsp::logging; -#[rtic::app(device = board, peripherals = false)] +use eh1::serial::Write; + +use rtic_monotonics::imxrt::Gpt1 as Mono; +use rtic_monotonics::imxrt::*; +use rtic_monotonics::Monotonic; + +#[rtic::app(device = teensy4_bsp, dispatchers = [LPSPI1])] mod app { + use super::*; + + const LOG_POLL_INTERVAL: u32 = board::PERCLK_FREQUENCY / 100; + const LOG_DMA_CHANNEL: usize = 0; - use hal::lpspi::{Direction, Interrupts, Status, Transaction}; - use imxrt_hal as hal; + #[shared] + struct Shared {} #[local] struct Local { - spi: board::Spi, + led: board::Led, + poll_log: hal::pit::Pit<3>, + log_poller: logging::Poller, } - #[shared] - struct Shared {} - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - 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 }, init::Monotonics()) - } + 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); - #[task(binds = BOARD_SPI, local = [spi])] - fn spi_interrupt(cx: spi_interrupt::Context) { - let spi_interrupt::LocalResources { spi } = cx.local; + // 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(); + + // 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); - let status = spi.status(); - spi.clear_status(Status::TRANSMIT_DATA | Status::RECEIVE_DATA); + // Setup LED + let led = board::led(&mut gpio2, pins.p13); + led.set(); - if status.intersects(Status::TRANSMIT_DATA) { - // This write clears TRANSMIT_DATA. - spi.set_interrupts(Interrupts::RECEIVE_DATA); + // Schedule the blinking task + blink::spawn().ok(); - // 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); + ( + Shared {}, + Local { + log_poller, + poll_log, + led, + }, + ) + } + + #[task(local = [led])] + async fn blink(cx: blink::Context) { + let blink::LocalResources { led, .. } = cx.local; + + let mut next_update = Mono::now(); + + loop { + led.toggle(); + log::info!("Time: {}", Mono::now()); + next_update += 1000.millis(); + Mono::delay_until(next_update).await; + } + } - 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); + #[task(binds = PIT, priority = 1, local = [poll_log, log_poller])] + fn logger(cx: logger::Context) { + let logger::LocalResources { + poll_log, + log_poller, + .. + } = cx.local; - assert!(spi.fifo_status().rxcount == 2); + if poll_log.is_elapsed() { + poll_log.clear_elapsed(); - while let Some(_) = spi.read_data() {} + log_poller.poll(); } } } 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/chip/dma.rs b/src/chip/dma.rs index 4fa17b68..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::*; @@ -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/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 802abe0b..2a42fa9d 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -1,356 +1,49 @@ -//! 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. +//! TODO + +pub use eh1::spi::Mode; +use imxrt_dma::channel::Channel; -use crate::iomuxc::{consts, lpspi}; use crate::ral; -pub use eh02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +mod bus; +mod disabled; +mod error; +mod read_part; +mod status_watcher; +mod transfer_actions; +mod write_part; -/// Data direction. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - /// Transmit direction (leaving the peripheral). - Tx, - /// Receive direction (entering the peripheral). - Rx, -} +use status_watcher::StatusWatcher; -/// 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, -} +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; -/// 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, +/// 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 { - /// 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, -} - -/// 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 { +/// TODO +pub struct Pins { /// Serial data out /// /// Data travels from the SPI host controller to the SPI device. @@ -361,698 +54,52 @@ 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; - - // - // 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; - } +/// Static shared data allocated by the user +pub struct LpspiData { + lpspi: StatusWatcher, } -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(), - ); +struct LpspiReadPart<'a, const N: u8> { + data: &'a LpspiData, + rx_fifo_size: u32, } -/// 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 LpspiWritePart<'a, const N: u8> { + data: &'a LpspiData, + tx_fifo_size: u32, + 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 - } -} - -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; - } +/// TODO +pub struct Lpspi<'a, const N: u8> { + dma: LpspiDma, + source_clock_hz: u32, + data: &'a LpspiData, + read_part: LpspiReadPart<'a, N>, + write_part: LpspiWritePart<'a, N>, } /// An LPSPI peripheral which is temporarily disabled. -pub struct Disabled<'a, const N: u8> { - lpspi: &'a ral::lpspi::Instance, +pub struct Disabled<'a, 'b, const N: u8> { + bus: &'a mut Lpspi<'b, N>, 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); - } +/// TODO +pub struct LpspiInterruptHandler<'a, const N: u8> { + status_watcher: &'a StatusWatcher, } - -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 LpspiInterruptHandler<'_, N> { + /// TODO + pub fn on_interrupt(&mut self) { + self.status_watcher.on_interrupt(); } } -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; -} +/// 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 new file mode 100644 index 00000000..1ba7bfdc --- /dev/null +++ b/src/common/lpspi/bus.rs @@ -0,0 +1,344 @@ +use eh1::spi::MODE_0; +use futures::FutureExt; + +use super::{ + transfer_actions::ActionSequence, Disabled, Lpspi, LpspiData, LpspiDma, LpspiError, + LpspiInterruptHandler, LpspiReadPart, LpspiWritePart, Pins, StatusWatcher, +}; +use crate::{ + iomuxc::{consts, lpspi}, + lpspi::transfer_actions::TransferDirection, + ral, +}; + +mod eh1_impl; + +impl<'a, const N: u8> Lpspi<'a, N> { + /// The peripheral instance. + pub const N: u8 = N; + + /// 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: &'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 (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), + }; + + let data = data_storage.insert(data); + + let mut this = Self { + source_clock_hz, + dma: LpspiDma::Disabled, + 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)"); + + // 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) {} + 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.lpspi(), + CFGR1, + MASTER: MASTER_1, + SAMPLE: SAMPLE_1 + ); + + // Set sane default parameters + this.disabled(|bus| { + bus.set_clock_hz(1_000_000); + }); + + // Configure pins + lpspi::prepare(&mut pins.sdo); + lpspi::prepare(&mut pins.sdi); + lpspi::prepare(&mut pins.sck); + + // Configure watermarks + ral::write_reg!(ral::lpspi, this.lpspi(), FCR, + 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 + 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. + 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); + func(&mut disabled) + } + + /// Switches the SPI bus to interrupt based operation. + /// + /// 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. + pub fn enable_interrupts(&mut self) -> LpspiInterruptHandler<'a, N> { + self.data.lpspi.enable_interrupts(); + LpspiInterruptHandler { + status_watcher: &self.data.lpspi, + } + } + + /// 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 + #[inline] + fn lpspi(&self) -> &ral::lpspi::Instance { + self.data.lpspi.instance() + } + + /// Clear both FIFOs. + fn clear_fifos(&mut self) { + 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() + } + + 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}, + TDDE: if dma_write {TDDE_1} else {TDDE_0} + ); + } + + /// Perform a sequence of transfer actions + async unsafe fn transfer_unchecked( + &mut self, + sequence: ActionSequence<'_>, + ) -> Result<(), LpspiError> { + self.flush_unchecked().await?; + + self.clear_fifos(); + + let byteorder = sequence.byteorder; + + 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(); + 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 { + 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, + 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, + write_dma.as_deref_mut(), + ) + .await; + } + }; + + futures::join!(read_task, write_task); + + self.check_errors() + } + + /// Perform a sequence of transfer actions while continuously checking for errors. + async unsafe 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_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(); + } + 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 { + log::warn!("An LPSPI error happened! Cleaning up ..."); + self.driver.reset(); + self.driver.data.lpspi.clear_errors(); + } + } +} diff --git a/src/common/lpspi/bus/eh1_impl.rs b/src/common/lpspi/bus/eh1_impl.rs new file mode 100644 index 00000000..9f899c92 --- /dev/null +++ b/src/common/lpspi/bus/eh1_impl.rs @@ -0,0 +1,81 @@ +use cassette::Cassette; + +use crate::lpspi::transfer_actions::{ + create_actions_read_write, create_actions_read_write_in_place, +}; + +use super::{Lpspi, LpspiError}; + +impl eh1::spi::ErrorType for Lpspi<'_, N> { + type Error = LpspiError; +} + +impl eh1::spi::SpiBus for Lpspi<'_, N> +where + T: crate::lpspi::LpspiWord, +{ + fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { + 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!(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!(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!(unsafe { + self.transfer(create_actions_read_write_in_place(words)) + })) + .block_on() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Cassette::new(core::pin::pin!(self.flush())).block_on() + } +} + +// Async only makes sense for DMA; DMA only supports u32. +#[cfg(feature = "async")] +impl eh1_async::spi::SpiBus for Lpspi<'_, N> +where + T: crate::lpspi::LpspiWord, +{ + async fn read(&mut self, words: &mut [T]) -> Result<(), Self::Error> { + unsafe { self.transfer(create_actions_read_write(words, &[])).await } + } + + async fn write(&mut self, words: &[T]) -> Result<(), Self::Error> { + unsafe { + self.transfer(create_actions_read_write(&mut [], words)) + .await + } + } + + async fn transfer(&mut self, read: &mut [T], write: &[T]) -> Result<(), Self::Error> { + unsafe { self.transfer(create_actions_read_write(read, write)).await } + } + + async fn transfer_in_place(&mut self, words: &mut [T]) -> Result<(), Self::Error> { + unsafe { + self.transfer(create_actions_read_write_in_place(words)) + .await + } + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.flush().await + } +} diff --git a/src/common/lpspi/disabled.rs b/src/common/lpspi/disabled.rs new file mode 100644 index 00000000..f96ce89b --- /dev/null +++ b/src/common/lpspi/disabled.rs @@ -0,0 +1,51 @@ +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 { + 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, bus.data.lpspi.instance(), CR, MEN == MEN_1) {} + Self { bus, men } + } + + /// 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. + 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 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); + + 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: if self.men {MEN_1} else {MEN_0}); + } +} diff --git a/src/common/lpspi/error.rs b/src/common/lpspi/error.rs new file mode 100644 index 00000000..00910ab5 --- /dev/null +++ b/src/common/lpspi/error.rs @@ -0,0 +1,13 @@ +use super::LpspiError; + +use eh1::spi::{Error, ErrorKind}; + +impl Error for LpspiError { + fn kind(&self) -> ErrorKind { + match self { + LpspiError::Busy => ErrorKind::Other, + LpspiError::ReceiveFifo => ErrorKind::Overrun, + LpspiError::TransmitFifo => ErrorKind::Other, + } + } +} diff --git a/src/common/lpspi/read_part.rs b/src/common/lpspi/read_part.rs new file mode 100644 index 00000000..e04ec4b7 --- /dev/null +++ b/src/common/lpspi/read_part.rs @@ -0,0 +1,148 @@ +use core::num::NonZeroUsize; + +use super::{ + ral, + transfer_actions::{ByteOrder, ChunkIter, ReadAction}, + LpspiReadPart, MAX_FRAME_SIZE_U32, +}; + +impl LpspiReadPart<'_, N> { + fn fifo_read_data_available(&mut self) -> bool { + ral::read_reg!( + ral::lpspi, + self.data.lpspi.instance(), + RSR, + RXEMPTY == RXEMPTY_0 + ) + } + + async fn wait_for_read_watermark(&mut self, watermark: u32) { + self.data + .lpspi + .wait_for_rx_watermark(watermark) + .await + .unwrap(); + } + + 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 - 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.get() - 1) { + watermark = watermark.min(at_most); + } + + 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 { + self.read_single_word(action.buf, byteorder, action.len) + .await + } else { + self.read_u32_stream(action.buf, byteorder, action.len) + .await; + } + } + } + + 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) + } + + async unsafe fn read_single_word( + &mut self, + data: *mut u8, + byteorder: ByteOrder, + len: NonZeroUsize, + ) { + assert!(len.get() < 4); + // TODO: test byte order + + let reverse_bytes = match byteorder { + ByteOrder::Normal => false, + ByteOrder::WordReversed => true, + ByteOrder::HalfWordReversed => true, + }; + + let value = self.rx_fifo_read_data(NonZeroUsize::new(1).unwrap()).await; + 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)); + } + } + + async unsafe fn read_u32_stream( + &mut self, + read_data: *mut u8, + byteorder: ByteOrder, + 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(); + + 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..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); + } + 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..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); + } + } + } + } +} diff --git a/src/common/lpspi/status_watcher.rs b/src/common/lpspi/status_watcher.rs new file mode 100644 index 00000000..a769a7fc --- /dev/null +++ b/src/common/lpspi/status_watcher.rs @@ -0,0 +1,325 @@ +use core::{ + cell::RefCell, + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use cortex_m::interrupt::{self, Mutex}; + +use super::{ral, LpspiError}; + +struct StatusWatcherInner { + transfer_complete_happened: bool, + 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, +} + +pub(crate) struct StatusWatcher { + inner: Mutex>>, + lpspi: ral::lpspi::Instance, +} + +impl StatusWatcherInner { + pub fn check_and_reset(&mut self, lpspi: &ral::lpspi::Instance) { + 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; + if let Some(waker) = self.transfer_complete_waker.take() { + 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(); + } + } + + 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(); + } + } + } +} + +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, + 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, + } + } + + pub fn enable_interrupts(&self) { + 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; + }); + } + + #[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 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; + + 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 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, watermark: u32) -> 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(()) + } + })?; + + interrupt::free(|_| { + 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(()), + |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, I, D> +where + C: Fn(&mut StatusWatcherInner) -> Option, + W: Fn(&mut StatusWatcherInner) -> &mut Option, + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), +{ + watcher: &'a StatusWatcher, + condition: C, + waker: W, + interrupt_enable: I, + on_drop: D, +} + +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, + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), +{ + fn new( + watcher: &'a StatusWatcher, + condition: C, + waker: W, + interrupt_enable: I, + on_drop: D, + ) -> Self { + Self { + watcher, + condition, + waker, + interrupt_enable, + on_drop, + } + } +} + +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, + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + 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 (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) = (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. + // + // 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() + } + } + } + + // If interrupts are disabled, notify right away to provoke busy-waiting + if !inner.interrupts_enabled { + if let Some(waker) = (self.waker)(inner).take() { + waker.wake(); + } + } else { + (self.interrupt_enable)(inner); + } + + Poll::Pending + } + }) + } +} + +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, + I: Fn(&mut StatusWatcherInner), + D: Fn(&mut StatusWatcherInner), +{ + fn drop(&mut self) { + self.watcher + .with_check_and_reset(|inner| (self.on_drop)(inner)); + } +} diff --git a/src/common/lpspi/transfer_actions.rs b/src/common/lpspi/transfer_actions.rs new file mode 100644 index 00000000..6f70320d --- /dev/null +++ b/src/common/lpspi/transfer_actions.rs @@ -0,0 +1,555 @@ +use core::{marker::PhantomData, num::NonZeroUsize}; + +#[derive(Debug)] +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) unsafe fn get_read_actions(&self) -> ReadActionIter { + ReadActionIter::new(ReadActions { + read_buf: self.read_buf, + len: self.len, + }) + } +} + +pub(crate) struct WriteAction { + pub(crate) buf: Option<*const u8>, + pub(crate) read: bool, + pub(crate) len: NonZeroUsize, + pub(crate) is_first: bool, + pub(crate) is_last: bool, +} + +pub(crate) struct ReadAction { + pub(crate) buf: *mut u8, + pub(crate) len: NonZeroUsize, +} + +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 len = loop { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { + break len; + } + self.pos += 1; + }; + + 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: Some(buf), + read: true, + len, + is_first, + is_last, + }) + } +} + +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 { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { + break len; + } + self.pos += 1; + }; + + 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, +} +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 len = loop { + if let Some(len) = NonZeroUsize::new(*self.actions.len.get(self.pos)?) { + break len; + } + self.pos += 1; + }; + + 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 + .get(self.pos..) + .into_iter() + .flatten() + .all(|&val| val == 0); + + Some(WriteAction { + buf, + read: buf.is_none(), + len, + is_first, + is_last, + }) + } +} + +#[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), +} + +impl SingleDirectionActions { + pub(crate) fn transfer_direction(&self) -> TransferDirection { + match self { + Self::Read(_) => TransferDirection::Read, + Self::Write(_) => TransferDirection::Write, + } + } + + pub(crate) unsafe fn get_write_actions(&self) -> SingleDirectionWriteActionIter { + SingleDirectionWriteActionIter::new(match self { + Self::Read(actions) => MaybeWriteActions { + write_buf: None, + len: actions.len, + }, + 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 +/// to be transferred on the bus +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub 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, +} + +impl ByteOrder { + pub(crate) fn reorder(self, val: u32) -> u32 { + match self { + ByteOrder::Normal => val, + 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)] +pub(crate) enum TransferDirection { + Read, + Write, +} + +#[derive(Debug)] +pub(crate) struct ActionSequence<'a> { + pub(crate) phase1: Option, + pub(crate) phase2: Option, + pub(crate) byteorder: ByteOrder, + _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; +} + +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, + } +} + +#[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_read_iter_test { + ($len:expr, $expected:expr) => {{ + let actual = ReadActionIter::new(ReadActions { + read_buf: 1000usize as *mut u8, + len: $len, + }) + .map(|val| (val.buf as usize - 1000, val.len.get())) + .collect::>(); + let expected: &[(usize, usize)] = &$expected; + + assert_eq!(actual, expected); + }}; + } + macro_rules! actions_write_iter_test { + ($len:expr, $expected:expr) => {{ + let actual = WriteActionIter::new(WriteActions { + write_buf: 1000usize as *const 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_single_direction_write_iter_test { + ($write:expr, $len:expr, $expected:expr) => {{ + let actual = SingleDirectionWriteActionIter::new(MaybeWriteActions { + write_buf: if $write { + Some(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: &[(Option, usize, bool, bool)] = &$expected; + + assert_eq!(actual, expected); + }}; + } + + #[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)]); + 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)]); + } + + #[test] + fn single_direction_write_actions_iter_write() { + 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], + [ + (Some(0), 2, true, false), + (Some(2), 3, false, false), + (Some(5), 4, false, true), + ] + ); + actions_single_direction_write_iter_test!( + true, + [2, 0, 4], + [(Some(0), 2, true, false), (Some(2), 4, false, 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_single_direction_write_iter_test!(false, [0, 5, 0], [(None, 5, true, true)]); + actions_single_direction_write_iter_test!( + false, + [2, 3, 4], + [ + (None, 2, true, false), + (None, 3, false, false), + (None, 4, false, true), + ] + ); + actions_single_direction_write_iter_test!( + false, + [2, 0, 4], + [(None, 2, true, false), (None, 4, false, 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)]); + } +} diff --git a/src/common/lpspi/write_part.rs b/src/common/lpspi/write_part.rs new file mode 100644 index 00000000..ba5244b3 --- /dev/null +++ b/src/common/lpspi/write_part.rs @@ -0,0 +1,231 @@ +use core::num::NonZeroUsize; + +use eh1::spi::{Phase, Polarity}; +use imxrt_dma::channel::Channel; + +use super::{ + ral, + transfer_actions::{ByteOrder, ChunkIter, WriteAction}, + LpspiWritePart, MAX_FRAME_SIZE_BITS, MAX_FRAME_SIZE_U32, +}; + +impl LpspiWritePart<'_, N> { + fn fifo_write_space_available(&mut self) -> bool { + ral::read_reg!( + ral::lpspi, + self.data.lpspi.instance(), + FSR, + TXCOUNT < self.tx_fifo_size + ) + } + + async fn wait_for_write_watermark(&mut self) { + self.data.lpspi.wait_for_tx_watermark().await.unwrap(); + } + + 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.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!"); + // TODO: implement + 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, + 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 perform_write_actions( + &mut self, + actions: impl Iterator, + has_previous: bool, + has_next: bool, + byteorder: ByteOrder, + mut dma: Option<&mut Channel>, + ) { + 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, + dma.as_deref_mut(), + ) + .await; + } + } + } + + 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; + } + } + + 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, + mut dma: Option<&mut Channel>, + ) { + 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. + 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; + } + } + } + (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; + } + } + } + } + } + } +}