From 64e7bc508a27d34f091d0ea703f98803da64b4b8 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:18:30 +0200 Subject: [PATCH 1/9] Initial Octo SPI support Much of the functionality shared with QuadSPI --- .github/workflows/ci.yml | 2 +- .github/workflows/nightly.yml | 2 +- CHANGELOG.md | 2 + Cargo.toml | 4 +- examples/qspi.rs | 2 +- src/lib.rs | 10 +- src/prelude.rs | 4 +- src/qspi.rs | 791 ---------------------------------- src/xspi/mod.rs | 541 +++++++++++++++++++++++ src/xspi/octospi.rs | 344 +++++++++++++++ src/xspi/qspi.rs | 328 ++++++++++++++ 11 files changed, 1225 insertions(+), 805 deletions(-) delete mode 100644 src/qspi.rs create mode 100644 src/xspi/mod.rs create mode 100644 src/xspi/octospi.rs create mode 100644 src/xspi/qspi.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 574fb24c..7f3196bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - stm32h7b3 - stm32h7b0 env: # Peripheral Feature flags - FLAGS: rt,quadspi,sdmmc,fmc,usb_hs,rtc,ethernet,ltdc,crc + FLAGS: rt,xspi,sdmmc,fmc,usb_hs,rtc,ethernet,ltdc,crc steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 74884d46..42a86bc9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -19,7 +19,7 @@ jobs: - log-semihost - log-rtt env: # Peripheral Feature flags - FLAGS: rt,quadspi,sdmmc,fmc,usb_hs,rtc,ethernet,ltdc,crc + FLAGS: rt,xspi,sdmmc,fmc,usb_hs,rtc,ethernet,ltdc,crc steps: - uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index e22536c2..580039e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +* **Breaking**: Rename the `quadspi` flag to `xspi` + ## [v0.9.0] 2021-03-12 * Updates `cortex-m` to v0.7.1. `cortex-m` v0.6.5+ [are forward compatible with diff --git a/Cargo.toml b/Cargo.toml index e5ed96f6..32ed252b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,7 +84,7 @@ cm4 = [] cm7 = [] smps = [] ltdc = ["embedded-display-controller"] -quadspi = [] +xspi = [] fmc = ["stm32-fmc"] sdmmc = ["sdio-host"] ethernet = ["smoltcp"] @@ -141,7 +141,7 @@ required-features = ["fmc"] [[example]] name = "qspi" -required-features = ["quadspi", "rm0433"] +required-features = ["xspi", "rm0433"] [[example]] name = "sdmmc" diff --git a/examples/qspi.rs b/examples/qspi.rs index 7fda99c3..11c23e4f 100644 --- a/examples/qspi.rs +++ b/examples/qspi.rs @@ -6,7 +6,7 @@ mod utilities; use cortex_m_rt::entry; -use stm32h7xx_hal::{pac, prelude::*, qspi::QspiMode}; +use stm32h7xx_hal::{pac, prelude::*, xspi::QspiMode}; use log::info; diff --git a/src/lib.rs b/src/lib.rs index 841e6705..363084fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ //! * [Serial Peripheral Interface (SPI)](crate::spi) //! * [Serial Data (USART/UART)](crate::serial) //! * [Serial Audio Interface](crate::sai) -//! * [Quad SPI](crate::qspi) Feature gate `qspi` +//! * [Quad or Octo SPI](crate::xspi) Feature gate `xspi` //! * [Ethernet](crate::ethernet) Feature gate `ethernet` //! * [USB HS](crate::usb_hs) Feature gate `usb_hs` //! @@ -166,12 +166,6 @@ pub mod pwm; pub mod pwr; #[cfg(feature = "device-selected")] pub mod qei; -#[cfg(all( - feature = "device-selected", - feature = "quadspi", - not(feature = "rm0455") -))] -pub mod qspi; #[cfg(feature = "device-selected")] pub mod rcc; #[cfg(feature = "device-selected")] @@ -196,3 +190,5 @@ pub mod timer; pub mod usb_hs; #[cfg(feature = "device-selected")] pub mod watchdog; +#[cfg(all(feature = "device-selected", feature = "xspi"))] +pub mod xspi; diff --git a/src/prelude.rs b/src/prelude.rs index 638bb18c..44b5cd0c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,8 +15,6 @@ pub use crate::i2c::I2cExt as _stm32h7xx_hal_i2c_I2cExt; pub use crate::pwm::PwmAdvExt as _stm32_hal_pwm_PwmAdvExt; pub use crate::pwm::PwmExt as _stm32_hal_pwm_PwmExt; pub use crate::pwr::PwrExt as _stm32h7xx_hal_pwr_PwrExt; -#[cfg(all(feature = "quadspi", not(feature = "rm0455")))] -pub use crate::qspi::QspiExt as _stm32h7xx_hal_qspi_QspiExt; pub use crate::rcc::RccExt as _stm32h7xx_hal_rcc_RccExt; pub use crate::rng::RngCore as _stm32h7xx_hal_rng_RngCore; pub use crate::rng::RngExt as _stm32h7xx_hal_rng_RngExt; @@ -27,3 +25,5 @@ pub use crate::serial::SerialExt as _stm32h7xx_hal_serial_SerialExt; pub use crate::spi::SpiExt as _stm32h7xx_hal_spi_SpiExt; pub use crate::time::U32Ext as _stm32h7xx_hal_time_U32Ext; pub use crate::timer::TimerExt as _stm32h7xx_hal_timer_TimerExt; +#[cfg(all(feature = "xspi"))] +pub use crate::xspi::XspiExt as _stm32h7xx_hal_xspi_XspiExt; diff --git a/src/qspi.rs b/src/qspi.rs deleted file mode 100644 index 5791d0a2..00000000 --- a/src/qspi.rs +++ /dev/null @@ -1,791 +0,0 @@ -//! Quad SPI (QSPI) bus -//! -//! The QSPI peripheral supports a SPI interface operating over 1, 2, or 4 IO lines. -//! -//! # Usage -//! -//! This driver supports using the QSPI peripheral in indirect mode. This allows the peripheral to -//! be used to read and write from an address over a quad-SPI interface. -//! -//! The SPI can be configured to operate on either of the two available banks on the board. In the -//! simplest case, this can be accomplished with just the peripheral and the GPIO pins. -//! -//! ``` -//! use stm32h7xx_hal::qspi; -//! -//! // Get the device peripherals and instantiate IO pins. -//! let dp = ...; -//! let (sck, io0, io1, io2, io3) = ...; -//! -//! let mut qspi = dp.QUADSPI.bank1((sck, io0, io1, io2, io3), 3.mhz(), &ccdr.clocks, -//! ccdr.peripheral.QSPI); -//! -//! // Configure QSPI to operate in 4-bit mode. -//! qspi.configure_mode(qspi::QspiMode::FourBit).unwrap(); -//! -//! // Write data to address 0x00 on the QSPI interface. -//! qspi.write(0x00, &[0xAB, 0xCD]).unwrap(); -//! ``` -//! -//! # Limitations -//! -//! This driver currently only supports indirect operation mode of the QSPI -//! interface. Automatic polling or memory-mapped modes are not supported. This -//! driver support either bank 1 or bank 2 as well as a dual flash bank (in -//! which all 8 IOs are used for the interface). -use crate::{ - gpio::{ - gpioa::PA1, - gpiob::PB2, - gpioc::{PC10, PC9}, - gpiod::{PD11, PD12, PD13}, - gpioe::{PE10, PE2, PE7, PE8, PE9}, - gpiof::{PF10, PF6, PF7, PF8, PF9}, - gpiog::{PG14, PG9}, - gpioh::{PH2, PH3}, - Alternate, AF10, AF9, - }, - rcc::{rec, CoreClocks, ResetEnable}, - stm32, - time::Hertz, -}; - -use core::{marker::PhantomData, ptr}; - -/// Represents operation modes of the QSPI interface. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum QspiMode { - /// Only a single IO line (IO0) is used for transmit and a separate line (IO1) is used for receive. - OneBit, - - /// Two IO lines (IO0 and IO1) are used for transmit/receive. - TwoBit, - - /// All four IO lines are used for transmit/receive. - FourBit, -} -impl QspiMode { - pub(self) fn reg_value(&self) -> u8 { - match self { - QspiMode::OneBit => 1, - QspiMode::TwoBit => 2, - QspiMode::FourBit => 3, - } - } -} - -/// Address sizes used by the QSPI interface -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum AddressSize { - EightBit, - SixteenBit, - TwentyFourBit, - ThirtyTwoBit, -} - -/// Sampling mode for the QSPI interface -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum SamplingEdge { - Falling, - Rising, -} - -/// Indicates an error with the QSPI peripheral. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum QspiError { - Busy, - Underflow, -} - -/// Indicates a specific QSPI bank to use. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Bank { - One, - Two, - Dual, -} - -/// A structure for specifying the QSPI configuration. -/// -/// This structure uses builder semantics to generate the configuration. -/// -/// ``` -/// let config = Config::new().dummy_cycles(1); -/// ``` -#[derive(Copy, Clone)] -pub struct Config { - mode: QspiMode, - frequency: Hertz, - address_size: AddressSize, - dummy_cycles: u8, - sampling_edge: SamplingEdge, - fifo_threshold: u8, -} - -impl Config { - /// Create a default configuration for the QSPI interface. - /// - /// * Bus in 1-bit Mode - /// * 8-bit Address - /// * No dummy cycle - /// * Sample on falling edge - pub fn new>(freq: T) -> Self { - Config { - mode: QspiMode::OneBit, - frequency: freq.into(), - address_size: AddressSize::EightBit, - dummy_cycles: 0, - sampling_edge: SamplingEdge::Falling, - fifo_threshold: 1, - } - } - - /// Specify the operating mode of the QSPI bus. Can be a 1-bit, 2-bit or - /// 4-bit width. - /// - /// The operating mode can also be changed using the - /// [`configure_mode`](Qspi#method.configure_mode) method - pub fn mode(mut self, mode: QspiMode) -> Self { - self.mode = mode; - self - } - - /// Specify the size of the address phase - pub fn address_size(mut self, address_size: AddressSize) -> Self { - self.address_size = address_size; - self - } - - /// Specify the number of dummy cycles in between the address and data - /// phases. - /// - /// Hardware supports 0-31 dummy cycles. - /// - /// # Note - /// - /// With zero dummy cycles, the QSPI peripheral will erroneously drive the - /// output pins for an extra half clock cycle before IO is swapped from - /// output to input. Refer to - /// https://github.com/quartiq/stabilizer/issues/101 for more information. - pub fn dummy_cycles(mut self, cycles: u8) -> Self { - debug_assert!(cycles < 32, "Hardware only supports 0-31 dummy cycles"); - - self.dummy_cycles = cycles; - self - } - - /// Specify the sampling edge for the QSPI receiver. - /// - /// # Note - /// - /// If zero dummy cycles are used, during read operations the QSPI - /// peripheral will erroneously drive the output pins for an extra half - /// clock cycle before IO is swapped from output to input. Refer to - /// https://github.com/quartiq/stabilizer/issues/101 for more information. - /// - /// In this case it is recommended to sample on the falling edge. Although - /// this doesn't stop the possible bus contention, delaying the sampling - /// point by an extra half cycle results in a sampling point after the bus - /// contention. - pub fn sampling_edge(mut self, sampling_edge: SamplingEdge) -> Self { - self.sampling_edge = sampling_edge; - self - } - - /// Specify the number of bytes in the FIFO that will set the FIFO threshold - /// flag. Must be in the range 1-32 inclusive. - /// - /// In indirect write mode, this is the number of free bytes that will raise - /// the FIFO threshold flag. - /// - /// In indirect read mode, this is the number of valid pending bytes that - /// will raise the FIFO threshold flag. - pub fn fifo_threshold(mut self, threshold: u8) -> Self { - debug_assert!(threshold > 0 && threshold <= 32); - - self.fifo_threshold = threshold; - self - } -} - -impl> From for Config { - fn from(frequency: T) -> Self { - Self::new(frequency) - } -} - -/// Used to indicate that an IO pin is not used by the QSPI interface. -pub struct NoIo {} - -/// Indicates a set of pins can be used for the QSPI interface on bank 1. -pub trait PinsBank1 {} -pub trait PinIo0Bank1 {} -pub trait PinIo1Bank1 {} -pub trait PinIo2Bank1 {} -pub trait PinIo3Bank1 {} - -/// Indicates a set of pins can be used for the QSPI interface on bank 2. -pub trait PinsBank2 {} -pub trait PinSckBank2 {} -pub trait PinIo0Bank2 {} -pub trait PinIo1Bank2 {} -pub trait PinIo2Bank2 {} -pub trait PinIo3Bank2 {} - -pub trait PinSck {} - -impl PinsBank1 for (SCK, IO0, IO1, IO2, IO3) -where - SCK: PinSck, - IO0: PinIo0Bank1, - IO1: PinIo1Bank1, - IO2: PinIo2Bank1, - IO3: PinIo3Bank1, -{ -} - -impl PinsBank2 for (SCK, IO0, IO1, IO2, IO3) -where - SCK: PinSck, - IO0: PinIo0Bank2, - IO1: PinIo1Bank2, - IO2: PinIo2Bank2, - IO3: PinIo3Bank2, -{ -} - -macro_rules! pins { - (Bank1: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { - $( - impl PinIo0Bank1 for $IO0 {} - )* - $( - impl PinIo1Bank1 for $IO1 {} - )* - $( - impl PinIo2Bank1 for $IO2 {} - )* - $( - impl PinIo3Bank1 for $IO3 {} - )* - }; - - (Bank2: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { - $( - impl PinIo0Bank2 for $IO0 {} - )* - $( - impl PinIo1Bank2 for $IO1 {} - )* - $( - impl PinIo2Bank2 for $IO2 {} - )* - $( - impl PinIo3Bank2 for $IO3 {} - )* - }; - - (SCK: [$($SCK:ty),*], Bank1: $bank1:tt, Bank2: $bank2:tt) => { - $( - impl PinSck for $SCK {} - )* - pins!(Bank1: $bank1); - pins!(Bank2: $bank2); - }; -} - -pins! { - SCK: [ - PB2>, - PF10> - ], - Bank1: [ - IO0: [ - PC9>, - PD11>, - PF8> - ] - IO1: [ - PC10>, - PD12>, - PF9>, - NoIo - ] - IO2: [ - PE2>, - PF7>, - NoIo - ] - IO3: [ - PA1>, - PD13>, - PF6>, - NoIo - ] - ], - Bank2: [ - IO0: [ - PE7>, - PF8>, - PH2> - ] - IO1: [ - PE8>, - PF9>, - PH3>, - NoIo - ] - IO2: [ - PE9>, - PG9>, - NoIo - ] - IO3: [ - PE10>, - PG14>, - NoIo - ] - ] -} - -pub trait QspiExt { - fn bank1( - self, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into, - PINS: PinsBank1; - - fn bank2( - self, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into, - PINS: PinsBank2; - - fn qspi_unchecked( - self, - config: CONFIG, - bank: Bank, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into; -} - -/// Interrupt events -#[derive(Copy, Clone, PartialEq)] -pub enum Event { - /// FIFO Threashold - FIFOThreashold, - /// Transfer complete - Complete, - /// Tranfer error - Error, -} - -pub struct Qspi { - rb: stm32::QUADSPI, -} - -impl Qspi { - pub fn bank1( - regs: stm32::QUADSPI, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Self - where - CONFIG: Into, - PINS: PinsBank1, - { - Self::qspi_unchecked(regs, config, Bank::One, clocks, prec) - } - - pub fn bank2( - regs: stm32::QUADSPI, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Self - where - CONFIG: Into, - PINS: PinsBank2, - { - Self::qspi_unchecked(regs, config, Bank::Two, clocks, prec) - } - - pub fn qspi_unchecked( - regs: stm32::QUADSPI, - config: CONFIG, - bank: Bank, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Self - where - CONFIG: Into, - { - prec.enable(); - - // Disable QUADSPI before configuring it. - regs.cr.write(|w| w.en().clear_bit()); - - let spi_kernel_ck = match Self::get_clock(clocks) { - Some(freq_hz) => freq_hz.0, - _ => panic!("QSPI kernel clock not running!"), - }; - - while regs.sr.read().busy().bit_is_set() {} - - let config: Config = config.into(); - - // Configure the FSIZE to maximum. It appears that even when addressing is not used, the - // flash size violation may still trigger. - regs.dcr.write(|w| unsafe { w.fsize().bits(0x1F) }); - - // Clear all pending flags. - regs.fcr.write(|w| { - w.ctof() - .set_bit() - .csmf() - .set_bit() - .ctcf() - .set_bit() - .ctef() - .set_bit() - }); - - // Configure the communication method for QSPI. - regs.ccr.write(|w| unsafe { - w.fmode() - .bits(0) // indirect mode - .dmode() - .bits(config.mode.reg_value()) - .admode() - .bits(config.mode.reg_value()) - .adsize() - .bits(config.address_size as u8) - .imode() - .bits(0) // No instruction phase - .dcyc() - .bits(config.dummy_cycles) - }); - - let spi_frequency = config.frequency.0; - let divisor = match (spi_kernel_ck + spi_frequency - 1) / spi_frequency - { - divisor @ 1..=256 => divisor - 1, - _ => panic!("Invalid QSPI frequency requested"), - }; - - // Write the prescaler and the SSHIFT bit. - // - // Note that we default to setting SSHIFT (sampling on the falling - // edge). This is because it appears that the QSPI may have signal - // contention issues when reading with zero dummy cycles. Setting SSHIFT - // forces the read to occur on the falling edge instead of the rising - // edge. Refer to https://github.com/quartiq/stabilizer/issues/101 for - // more information - // - // SSHIFT must not be set in DDR mode. - regs.cr.write(|w| unsafe { - w.prescaler() - .bits(divisor as u8) - .sshift() - .bit(config.sampling_edge == SamplingEdge::Falling) - .fthres() - .bits(config.fifo_threshold - 1) - }); - - match bank { - Bank::One => regs.cr.modify(|_, w| w.fsel().clear_bit()), - Bank::Two => regs.cr.modify(|_, w| w.fsel().set_bit()), - Bank::Dual => regs.cr.modify(|_, w| w.dfm().set_bit()), - } - - // Enable ther peripheral - regs.cr.modify(|_, w| w.en().set_bit()); - - Qspi { rb: regs } - } - - /// Deconstructs the QSPI HAL and returns the component parts - pub fn free(self) -> (stm32::QUADSPI, rec::Qspi) { - ( - self.rb, - rec::Qspi { - _marker: PhantomData, - }, - ) - } - - /// Returns a reference to the inner peripheral - pub fn inner(&self) -> &stm32::QUADSPI { - &self.rb - } - - /// Returns a mutable reference to the inner peripheral - pub fn inner_mut(&mut self) -> &mut stm32::QUADSPI { - &mut self.rb - } - - /// Check if the QSPI peripheral is currently busy with a transaction - pub fn is_busy(&self) -> bool { - self.rb.sr.read().busy().bit_is_set() - } - - /// Enable interrupts for the given `event` - pub fn listen(&mut self, event: Event) { - self.rb.cr.modify(|_, w| match event { - Event::FIFOThreashold => w.ftie().set_bit(), - Event::Complete => w.tcie().set_bit(), - Event::Error => w.teie().set_bit(), - }); - } - - /// Disable interrupts for the given `event` - pub fn unlisten(&mut self, event: Event) { - self.rb.cr.modify(|_, w| match event { - Event::FIFOThreashold => w.ftie().clear_bit(), - Event::Complete => w.tcie().clear_bit(), - Event::Error => w.teie().clear_bit(), - }); - let _ = self.rb.cr.read(); - let _ = self.rb.cr.read(); // Delay 2 peripheral clocks - } - - fn get_clock(clocks: &CoreClocks) -> Option { - let d1ccipr = unsafe { (*stm32::RCC::ptr()).d1ccipr.read() }; - - match d1ccipr.qspisel().variant() { - stm32::rcc::d1ccipr::QSPISEL_A::RCC_HCLK3 => Some(clocks.hclk()), - stm32::rcc::d1ccipr::QSPISEL_A::PLL1_Q => clocks.pll1_q_ck(), - stm32::rcc::d1ccipr::QSPISEL_A::PLL2_R => clocks.pll2_r_ck(), - stm32::rcc::d1ccipr::QSPISEL_A::PER => clocks.per_ck(), - } - } - - /// Configure the operational mode of the QSPI interface. - /// - /// # Args - /// * `mode` - The newly desired mode of the interface. - /// - /// # Errors - /// Returns QspiError::Busy if an operation is ongoing - pub fn configure_mode(&mut self, mode: QspiMode) -> Result<(), QspiError> { - if self.is_busy() { - return Err(QspiError::Busy); - } - - self.rb.ccr.modify(|_, w| unsafe { - w.admode() - .bits(mode.reg_value()) - .dmode() - .bits(mode.reg_value()) - }); - - Ok(()) - } - - /// Begin a write over the QSPI interface. This is mostly useful for use with - /// DMA or if you are managing the read yourself. If you want to complete a - /// whole transaction, see the [`write`](#method.write) method. - /// - /// # Args - /// * `addr` - The address to write data to. If the address size is less - /// than 32-bit, then unused bits are discarded. - /// * `length` - The length of the write operation in bytes - pub fn begin_write( - &mut self, - addr: u32, - length: usize, - ) -> Result<(), QspiError> { - if self.is_busy() { - return Err(QspiError::Busy); - } - - // Clear the transfer complete flag. - self.rb.fcr.modify(|_, w| w.ctcf().set_bit()); - - // Write the length - self.rb - .dlr - .write(|w| unsafe { w.dl().bits(length as u32 - 1) }); - - // Configure the mode to indirect write. - self.rb.ccr.modify(|_, w| unsafe { w.fmode().bits(0b00) }); - - self.rb.ar.write(|w| unsafe { w.address().bits(addr) }); - - Ok(()) - } - - /// Write data over the QSPI interface. - /// - /// # Args - /// * `addr` - The address to write data to. If the address size is less - /// than 32-bit, then unused bits are discarded. - /// * `data` - An array of data to transfer over the QSPI interface. - /// - /// # Panics - /// - /// Panics if the length of `data` is greater than the size of the QSPI - /// hardware FIFO (32 bytes). - pub fn write(&mut self, addr: u32, data: &[u8]) -> Result<(), QspiError> { - assert!( - data.len() <= 32, - "Transactions larger than the QSPI FIFO are currently unsupported" - ); - - self.begin_write(addr, data.len())?; - - // Write data to the FIFO in a byte-wise manner. - unsafe { - for byte in data { - ptr::write_volatile(&self.rb.dr as *const _ as *mut u8, *byte); - } - } - - // Wait for the transaction to complete - while self.rb.sr.read().tcf().bit_is_clear() {} - - // Wait for the peripheral to indicate it is no longer busy. - while self.is_busy() {} - - Ok(()) - } - - /// Begin a read over the QSPI interface. This is mostly useful for use with - /// DMA or if you are managing the read yourself. If you want to complete a - /// whole transaction, see the [`read`](#method.read) method. - /// - /// # Args - /// * `addr` - The address to read data from. If the address size is less - /// than 32-bit, then unused bits are discarded. - /// * `length` - The length of the read operation in bytes - pub fn begin_read( - &mut self, - addr: u32, - length: usize, - ) -> Result<(), QspiError> { - if self.is_busy() { - return Err(QspiError::Busy); - } - - // Clear the transfer complete flag. - self.rb.fcr.modify(|_, w| w.ctcf().set_bit()); - - // Write the length that should be read. - self.rb - .dlr - .write(|w| unsafe { w.dl().bits(length as u32 - 1) }); - - // Configure the mode to indirect read. - self.rb.ccr.modify(|_, w| unsafe { w.fmode().bits(0b01) }); - - // Write the address to force the read to start. - self.rb.ar.write(|w| unsafe { w.address().bits(addr) }); - - Ok(()) - } - - /// Read data over the QSPI interface. - /// - /// # Args - /// * `addr` - The address to read data from. If the address size is less - /// than 32-bit, then unused bits are discarded. - /// * `dest` - An array to store the result of the read into. - /// - /// # Panics - /// - /// Panics if the length of `data` is greater than the size of the QSPI - /// hardware FIFO (32 bytes). - pub fn read( - &mut self, - addr: u32, - dest: &mut [u8], - ) -> Result<(), QspiError> { - assert!( - dest.len() <= 32, - "Transactions larger than the QSPI FIFO are currently unsupported" - ); - - // Begin the read operation - self.begin_read(addr, dest.len())?; - - // Wait for the transaction to complete - while self.rb.sr.read().tcf().bit_is_clear() {} - - // Check for underflow on the FIFO. - if (self.rb.sr.read().flevel().bits() as usize) < dest.len() { - return Err(QspiError::Underflow); - } - - // Read data from the FIFO in a byte-wise manner. - unsafe { - for location in dest { - *location = - ptr::read_volatile(&self.rb.dr as *const _ as *const u8); - } - } - - // Wait for the peripheral to indicate it is no longer busy. - while self.is_busy() {} - - Ok(()) - } -} - -impl QspiExt for stm32::QUADSPI { - fn bank1( - self, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into, - PINS: PinsBank1, - { - Qspi::qspi_unchecked(self, config, Bank::One, clocks, prec) - } - - fn bank2( - self, - _pins: PINS, - config: CONFIG, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into, - PINS: PinsBank2, - { - Qspi::qspi_unchecked(self, config, Bank::Two, clocks, prec) - } - - fn qspi_unchecked( - self, - config: CONFIG, - bank: Bank, - clocks: &CoreClocks, - prec: rec::Qspi, - ) -> Qspi - where - CONFIG: Into, - { - Qspi::qspi_unchecked(self, config, bank, clocks, prec) - } -} diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs new file mode 100644 index 00000000..92518aa5 --- /dev/null +++ b/src/xspi/mod.rs @@ -0,0 +1,541 @@ +//! Quad or Octo SPI bus +//! +//! STM32H7 parts support either Quad or Octo SPI using dedicated peripheral(s). +//! +//! | Interface | Parts | # IO lines | +//! | --- | --- | --- | +//! | Quad Spi | H742/743/750/753/747/757 | 1-bit, 2-bit or 4-bit | +//! | Octo Spi | H725/735/7a3/7b0/7b3 | 1-bit, 2-bit, 4-bit or 8-bit | +//! +//! # Usage +//! +//! This driver supports using the xSPI peripheral in indirect mode. This allows +//! the peripheral to be used to read and write from an address over a +//! SPI/QUADSPI/OCTOSPI interface. +//! +//! The QUADSPI interface can be configured to operate on either of the two +//! available banks. +//! +//! ``` +//! use stm32h7xx_hal::xspi; +//! +//! // Get the device peripherals and instantiate IO pins. +//! let dp = ...; +//! let (sck, io0, io1, io2, io3) = ...; +//! +//! let mut qspi = dp.QUADSPI.bank1((sck, io0, io1, io2, io3), 3.mhz(), &ccdr.clocks, +//! ccdr.peripheral.QSPI); +//! +//! // Configure QSPI to operate in 4-bit mode. +//! qspi.configure_mode(xspi::QspiMode::FourBit).unwrap(); +//! +//! // Write data to address 0x00 on the QSPI interface. +//! qspi.write(0x00, &[0xAB, 0xCD]).unwrap(); +//! ``` +//! +//! # Limitations +//! +//! This driver currently only supports indirect operation mode of the xSPI +//! interface. Automatic polling or memory-mapped modes are not supported. + +// Parts of the Quad and Octo SPI support are shared (this file), but they are +// different enough to require different initialisation routines and pin +// allocations (for example). The Octospi peripheral appears to be 'loosly' +// based upon the (older?) Quadspi peripheral + +// Quadspi +#[cfg(any(feature = "rm0433", feature = "rm0399"))] +mod qspi; +#[cfg(any(feature = "rm0433", feature = "rm0399"))] +pub use common::{ + Bank, Xspi as Qspi, XspiError as QspiError, XspiMode as QspiMode, +}; +#[cfg(any(feature = "rm0433", feature = "rm0399"))] +pub use qspi::QspiExt as XspiExt; + +// Octospi +#[cfg(any(feature = "rm0455", feature = "rm0468"))] +mod octospi; +#[cfg(any(feature = "rm0455", feature = "rm0468"))] +pub use common::{ + Xspi as Octospi, XspiError as OctospiError, XspiMode as OctospiMode, +}; +#[cfg(any(feature = "rm0455", feature = "rm0468"))] +pub use octospi::OctospiExt as XspiExt; + +// Both +pub use common::{AddressSize, Config, Event, SamplingEdge}; + +/// This modulate contains functionality common to both Quad and Octo SPI +mod common { + use crate::{ + rcc::{rec, CoreClocks}, + stm32, + time::Hertz, + }; + use core::{marker::PhantomData, ptr}; + + /// Represents operation modes of the XSPI interface. + #[derive(Debug, Copy, Clone, PartialEq)] + pub enum XspiMode { + /// Only a single IO line (IO0) is used for transmit and a separate line + /// (IO1) is used for receive. + OneBit, + + /// Two IO lines (IO0 and IO1) are used for transmit/receive. + TwoBit, + + /// Four IO lines are used for transmit/receive. + FourBit, + + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + /// Eight IO lines are used for transmit/receive. + EightBit, + } + impl XspiMode { + pub(super) fn reg_value(&self) -> u8 { + match self { + XspiMode::OneBit => 1, + XspiMode::TwoBit => 2, + XspiMode::FourBit => 3, + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + XspiMode::EightBit => 4, + } + } + } + /// Indicates an error with the XSPI peripheral. + #[derive(Debug, Copy, Clone, PartialEq)] + pub enum XspiError { + Busy, + Underflow, + } + + /// Address sizes used by the XSPI interface + #[derive(Debug, Copy, Clone, PartialEq)] + pub enum AddressSize { + EightBit, + SixteenBit, + TwentyFourBit, + ThirtyTwoBit, + } + + /// Sampling mode for the XSPI interface + #[derive(Debug, Copy, Clone, PartialEq)] + pub enum SamplingEdge { + Falling, + Rising, + } + + /// Interrupt events + #[derive(Copy, Clone, PartialEq)] + pub enum Event { + /// FIFO Threashold + FIFOThreashold, + /// Transfer complete + Complete, + /// Tranfer error + Error, + } + + /// Indicates a specific QUADSPI bank to use + #[derive(Debug, Copy, Clone, PartialEq)] + pub enum Bank { + One, + Two, + Dual, + } + // Banks are not supported by the Octospi peripheral (there's two Octospi + // peripherals instead) + + /// A structure for specifying the XSPI configuration. + /// + /// This structure uses builder semantics to generate the configuration. + /// + /// ``` + /// let config = Config::new().dummy_cycles(1); + /// ``` + #[derive(Copy, Clone)] + pub struct Config { + pub(super) mode: XspiMode, + pub(super) frequency: Hertz, + pub(super) address_size: AddressSize, + pub(super) dummy_cycles: u8, + pub(super) sampling_edge: SamplingEdge, + pub(super) fifo_threshold: u8, + } + + impl Config { + /// Create a default configuration for the XSPI interface. + /// + /// * Bus in 1-bit Mode + /// * 8-bit Address + /// * No dummy cycle + /// * Sample on falling edge + pub fn new>(freq: T) -> Self { + Config { + mode: XspiMode::OneBit, + frequency: freq.into(), + address_size: AddressSize::EightBit, + dummy_cycles: 0, + sampling_edge: SamplingEdge::Falling, + fifo_threshold: 1, + } + } + + /// Specify the operating mode of the XSPI bus. Can be 1-bit, 2-bit or + /// 4-bit for Quadspi; 1-bit, 2-bit, 4-bit or 8-bit for Octospi. + /// + /// The operating mode can also be changed using the + /// [`configure_mode`](Xspi#method.configure_mode) method + pub fn mode(mut self, mode: XspiMode) -> Self { + self.mode = mode; + self + } + + /// Specify the size of the address phase + pub fn address_size(mut self, address_size: AddressSize) -> Self { + self.address_size = address_size; + self + } + + /// Specify the number of dummy cycles in between the address and data + /// phases. + /// + /// Hardware supports 0-31 dummy cycles. + /// + /// # Note + /// + /// With zero dummy cycles, the QUADSPI peripheral will erroneously drive the + /// output pins for an extra half clock cycle before IO is swapped from + /// output to input. Refer to + /// https://github.com/quartiq/stabilizer/issues/101 for more information. + pub fn dummy_cycles(mut self, cycles: u8) -> Self { + debug_assert!( + cycles < 32, + "Hardware only supports 0-31 dummy cycles" + ); + + self.dummy_cycles = cycles; + self + } + + /// Specify the sampling edge for the XSPI receiver. + /// + /// # Note + /// + /// If zero dummy cycles are used, during read operations the QUADSPI + /// peripheral will erroneously drive the output pins for an extra half + /// clock cycle before IO is swapped from output to input. Refer to + /// https://github.com/quartiq/stabilizer/issues/101 for more information. + /// + /// In this case it is recommended to sample on the falling edge. Although + /// this doesn't stop the possible bus contention, delaying the sampling + /// point by an extra half cycle results in a sampling point after the bus + /// contention. + pub fn sampling_edge(mut self, sampling_edge: SamplingEdge) -> Self { + self.sampling_edge = sampling_edge; + self + } + + /// Specify the number of bytes in the FIFO that will set the FIFO threshold + /// flag. Must be in the range 1-32 inclusive. + /// + /// In indirect write mode, this is the number of free bytes that will raise + /// the FIFO threshold flag. + /// + /// In indirect read mode, this is the number of valid pending bytes that + /// will raise the FIFO threshold flag. + pub fn fifo_threshold(mut self, threshold: u8) -> Self { + debug_assert!(threshold > 0 && threshold <= 32); + + self.fifo_threshold = threshold; + self + } + } + + impl> From for Config { + fn from(frequency: T) -> Self { + Self::new(frequency) + } + } + + /// Generic type for Quad or Octo SPI + pub struct Xspi { + pub(super) rb: XSPI, + } + + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + macro_rules! fmode_reg { + ($e:expr) => { + $e.rb.ccr + }; + } + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + macro_rules! fmode_reg { + ($e:expr) => { + $e.rb.cr + }; + } + + // This macro uses the paste::item! macro to create identifiers. + // + // https://crates.io/crates/paste + macro_rules! xspi_impl { + ($peripheral:expr, $rec:expr, $ccip:ident) => { + paste::item! { + impl Xspi<$peripheral> { + /// Deconstructs the XSPI HAL and returns the component parts + pub fn free(self) -> ($peripheral, $rec) { + ( + self.rb, + $rec { + _marker: PhantomData, + }, + ) + } + + /// Returns a reference to the inner peripheral + pub fn inner(&self) -> &$peripheral { + &self.rb + } + + /// Returns a mutable reference to the inner peripheral + pub fn inner_mut(&mut self) -> &mut $peripheral { + &mut self.rb + } + + /// Check if the XSPI peripheral is currently busy with a + /// transaction + pub fn is_busy(&self) -> bool { + self.rb.sr.read().busy().bit_is_set() + } + + /// Enable interrupts for the given `event` + pub fn listen(&mut self, event: Event) { + self.rb.cr.modify(|_, w| match event { + Event::FIFOThreashold => w.ftie().set_bit(), + Event::Complete => w.tcie().set_bit(), + Event::Error => w.teie().set_bit(), + }); + } + + /// Disable interrupts for the given `event` + pub fn unlisten(&mut self, event: Event) { + self.rb.cr.modify(|_, w| match event { + Event::FIFOThreashold => w.ftie().clear_bit(), + Event::Complete => w.tcie().clear_bit(), + Event::Error => w.teie().clear_bit(), + }); + let _ = self.rb.cr.read(); + let _ = self.rb.cr.read(); // Delay 2 peripheral clocks + } + + pub(super) fn get_clock(clocks: &CoreClocks) -> Option { + let d1ccipr = unsafe { (*stm32::RCC::ptr()).d1ccipr.read() }; + + match d1ccipr.$ccip().variant() { + stm32::rcc::d1ccipr::[< $ccip:upper _A >]::RCC_HCLK3 => Some(clocks.hclk()), + stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PLL1_Q => clocks.pll1_q_ck(), + stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PLL2_R => clocks.pll2_r_ck(), + stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PER => clocks.per_ck(), + } + } + + /// Configure the operational mode of the XSPI interface. + /// + /// # Args + /// * `mode` - The newly desired mode of the interface. + /// + /// # Errors + /// Returns XspiError::Busy if an operation is ongoing + pub fn configure_mode(&mut self, mode: XspiMode) -> Result<(), XspiError> { + if self.is_busy() { + return Err(XspiError::Busy); + } + + self.rb.ccr.modify(|_, w| unsafe { + w.admode() + .bits(mode.reg_value()) + .dmode() + .bits(mode.reg_value()) + }); + + // if mode == XspiMode::EightBit { + // // TODO + // self.rb.ccr.modify(|_, w| unsafe { + // w.admode() + // .bits(mode.reg_value()) + // .adsize() + // .bits(3) // 32-bit + // .isize() + // .bits(1) // 16-bit + // .imode() + // .bits(mode.reg_value()) + // .dmode() + // .bits(mode.reg_value()) + // }); + // self.rb.tcr.write(|w| unsafe { w.dcyc().bits(4) }); + // } + + Ok(()) + } + + /// Begin a write over the XSPI interface. This is mostly useful for use with + /// DMA or if you are managing the read yourself. If you want to complete a + /// whole transaction, see the [`write`](#method.write) method. + /// + /// # Args + /// * `addr` - The address to write data to. If the address size is less + /// than 32-bit, then unused bits are discarded. + /// * `length` - The length of the write operation in bytes + pub fn begin_write( + &mut self, + addr: u32, + length: usize, + ) -> Result<(), XspiError> { + if self.is_busy() { + return Err(XspiError::Busy); + } + + // Clear the transfer complete flag. + self.rb.fcr.write(|w| w.ctcf().set_bit()); + + // Write the length + self.rb + .dlr + .write(|w| unsafe { w.dl().bits(length as u32 - 1) }); + + // Configure the mode to indirect write. + fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b00) }); + + // Write the address to force the write to start + self.rb.ar.write(|w| unsafe { w.address().bits(addr) }); + + Ok(()) + } + + /// Write data over the XSPI interface. + /// + /// # Args + /// * `addr` - The address to write data to. If the address size is less + /// than 32-bit, then unused bits are discarded. + /// * `data` - An array of data to transfer over the XSPI interface. + /// + /// # Panics + /// + /// Panics if the length of `data` is greater than the size of the XSPI + /// hardware FIFO (32 bytes). + pub fn write(&mut self, addr: u32, data: &[u8]) -> Result<(), XspiError> { + assert!( + data.len() <= 32, + "Transactions larger than the XSPI FIFO are currently unsupported" + ); + + self.begin_write(addr, data.len())?; + + // Write data to the FIFO in a byte-wise manner. + unsafe { + for byte in data { + ptr::write_volatile(&self.rb.dr as *const _ as *mut u8, *byte); + } + } + + // Wait for the transaction to complete + while self.rb.sr.read().tcf().bit_is_clear() {} + + // Wait for the peripheral to indicate it is no longer busy. + while self.is_busy() {} + + Ok(()) + } + + /// Begin a read over the XSPI interface. This is mostly useful for use with + /// DMA or if you are managing the read yourself. If you want to complete a + /// whole transaction, see the [`read`](#method.read) method. + /// + /// # Args + /// * `addr` - The address to read data from. If the address size is less + /// than 32-bit, then unused bits are discarded. + /// * `length` - The length of the read operation in bytes + pub fn begin_read( + &mut self, + addr: u32, + length: usize, + ) -> Result<(), XspiError> { + if self.is_busy() { + return Err(XspiError::Busy); + } + + // Clear the transfer complete flag. + self.rb.fcr.write(|w| w.ctcf().set_bit()); + + // Write the length that should be read. + self.rb + .dlr + .write(|w| unsafe { w.dl().bits(length as u32 - 1) }); + + // Configure the mode to indirect read. + fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b01) }); + + // Write the address to force the read to start. + self.rb.ar.write(|w| unsafe { w.address().bits(addr) }); + + Ok(()) + } + + /// Read data over the XSPI interface. + /// + /// # Args + /// * `addr` - The address to read data from. If the address size is less + /// than 32-bit, then unused bits are discarded. + /// * `dest` - An array to store the result of the read into. + /// + /// # Panics + /// + /// Panics if the length of `data` is greater than the size of the XSPI + /// hardware FIFO (32 bytes). + pub fn read( + &mut self, + addr: u32, + dest: &mut [u8], + ) -> Result<(), XspiError> { + assert!( + dest.len() <= 32, + "Transactions larger than the XSPI FIFO are currently unsupported" + ); + + // Begin the read operation + self.begin_read(addr, dest.len())?; + + // Wait for the transaction to complete + while self.rb.sr.read().tcf().bit_is_clear() {} + + // Check for underflow on the FIFO. + if (self.rb.sr.read().flevel().bits() as usize) < dest.len() { + return Err(XspiError::Underflow); + } + + // Read data from the FIFO in a byte-wise manner. + unsafe { + for location in dest { + *location = + ptr::read_volatile(&self.rb.dr as *const _ as *const u8); + } + } + + // Wait for the peripheral to indicate it is no longer busy. + while self.is_busy() {} + + Ok(()) + } + }}} + } + + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + xspi_impl! { stm32::QUADSPI, rec::Qspi, qspisel } + + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + xspi_impl! { stm32::OCTOSPI1, rec::Octospi1, octospisel } + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + xspi_impl! { stm32::OCTOSPI2, rec::Octospi2, octospisel } +} diff --git a/src/xspi/octospi.rs b/src/xspi/octospi.rs new file mode 100644 index 00000000..bebc08d7 --- /dev/null +++ b/src/xspi/octospi.rs @@ -0,0 +1,344 @@ +//! Octo SPI (OCTOSPI) bus +//! +//! See the parent module for documentation + +use crate::{ + gpio::{ + gpioa::{PA1, PA2, PA3, PA6, PA7}, + gpiob::{PB0, PB1, PB10, PB12, PB13, PB2, PB6}, + gpioc::{PC1, PC10, PC11, PC2, PC3, PC5, PC9}, + gpiod::{PD11, PD12, PD13, PD4, PD5, PD6, PD7}, + gpioe::{PE10, PE11, PE2, PE7, PE8, PE9}, + gpiof::{ + PF0, PF1, PF10, PF11, PF12, PF2, PF3, PF4, PF5, PF6, PF7, PF8, PF9, + }, + gpiog::{PG0, PG1, PG10, PG11, PG12, PG14, PG15, PG6, PG7, PG9}, + gpioh::{PH2, PH3}, + Alternate, AF10, AF9, + }, + rcc::{rec, CoreClocks, ResetEnable}, + stm32, +}; + +use super::{Config, Octospi, SamplingEdge}; + +pub trait PinClk {} +pub trait PinNclk {} +pub trait PinDQS {} +pub trait PinNCS {} +pub trait PinIo0 {} +pub trait PinIo1 {} +pub trait PinIo2 {} +pub trait PinIo3 {} +pub trait PinIo4 {} +pub trait PinIo5 {} +pub trait PinIo6 {} +pub trait PinIo7 {} + +// impl PinsQuad +// for (CLK, NCS, IO0, IO1, IO2, IO3) +// where +// CLK: PinClk, +// NCS: PinNCS, +// IO0: PinIo0, +// IO1: PinIo1, +// IO2: PinIo2, +// IO3: PinIo3, +// { +// } + +// macro_rules! pins { +// (Bank1: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { +// $( +// impl PinIo0Bank1 for $IO0 {} +// )* +// $( +// impl PinIo1Bank1 for $IO1 {} +// )* +// $( +// impl PinIo2Bank1 for $IO2 {} +// )* +// $( +// impl PinIo3Bank1 for $IO3 {} +// )* +// }; + +// (Bank2: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { +// $( +// impl PinIo0Bank2 for $IO0 {} +// )* +// $( +// impl PinIo1Bank2 for $IO1 {} +// )* +// $( +// impl PinIo2Bank2 for $IO2 {} +// )* +// $( +// impl PinIo3Bank2 for $IO3 {} +// )* +// }; + +// (SCK: [$($SCK:ty),*], Bank1: $bank1:tt, Bank2: $bank2:tt) => { +// $( +// impl PinSck for $SCK {} +// )* +// pins!(Bank1: $bank1); +// pins!(Bank2: $bank2); +// }; +// } + +// pins! { +// OCTOSPIM_P1: +// CLK: [ +// PA3>, +// PB2>, +// PF10> +// ] +// NCLK: [ +// PB12>, +// PF11> +// ] +// DQS: [ +// PA1>, +// PB2>, +// PC5> +// ] +// NCS: [ +// PB6>, +// PB10>, +// PC11>, +// PE11>, +// PG6> +// ] +// IO0: [ +// PA2>, +// PB1>, +// PB12>, +// PC3>, +// PC3>, +// PC9>, +// PD11>, +// PF8> +// ] +// IO1: [ +// PB0>, +// PC10>, +// PD12>, +// PF9> +// ] +// IO2: [ +// PA3>, +// PA7>, +// PB13>, +// PC2>, +// PC2>, +// PE2>, +// PF7> +// ] +// IO3: [ +// PA1>, +// PA6>, +// PD13>, +// PF6> +// ] +// IO4: [ +// PC1>, +// PD4>, +// PE7>, +// PH2> +// ] +// IO5: [ +// PC2>, +// PC2>, +// PD5>, +// PE8>, +// PH3> +// ] +// IO6: [ +// PC3>, +// PC3>, +// PD6>, +// PE9>, +// PG9> +// ] +// IO7: [ +// PD7>, +// PE10>, +// PG14> +// ] +// OCTOSPIM_P2: +// CLK: [ +// PF4> +// ] +// NCLK: [ +// PF5> +// ] +// DQS: [ +// PF12>, +// PG7>, +// PG15> +// ] +// NCS: [ +// PG12> +// ] +// IO0: [ +// PF0> +// ] +// IO1: [ +// PF1> +// ] +// IO2: [ +// PF2> +// ] +// IO3: [ +// PF3> +// ] +// IO4: [ +// PG0> +// ] +// IO5: [ +// PG1> +// ] +// IO6: [ +// PG10> +// ] +// IO7: [ +// PG11> +// ] +// } + +pub trait OctospiExt: Sized { + /// The `ResetEnable` singleton for this peripheral + type Rec: ResetEnable; + + /// Create and enable the Octospi peripheral + fn octospi_unchecked( + self, + config: CONFIG, + clocks: &CoreClocks, + prec: Self::Rec, + ) -> Octospi + where + CONFIG: Into; +} + +macro_rules! octospi_impl { + ($name:ident, $peripheral:ty, $rec:ty) => { + impl Octospi<$peripheral> { + pub fn $name( + regs: $peripheral, + config: CONFIG, + clocks: &CoreClocks, + prec: $rec, + ) -> Self + where + CONFIG: Into, + { + prec.enable(); + + // Disable OCTOSPI before configuring it. + regs.cr.write(|w| w.en().clear_bit()); + + let spi_kernel_ck = match Self::get_clock(clocks) { + Some(freq_hz) => freq_hz.0, + _ => panic!("OCTOSPI kernel clock not running!"), + }; + + while regs.sr.read().busy().bit_is_set() {} + + let config: Config = config.into(); + + // Clear all pending flags. + regs.fcr.write(|w| { + w.ctof() + .set_bit() + .csmf() + .set_bit() + .ctcf() + .set_bit() + .ctef() + .set_bit() + }); + + // Configure the communication method for OCTOSPI. + regs.cr.write(|w| unsafe { + w.fmode() + .bits(0) // indirect mode + .fthres() + .bits(config.fifo_threshold - 1) + }); + + regs.dcr1.write(|w| unsafe { + w.mtyp() + .bits(2) // standard mode + // Configure the FSIZE to maximum. It appears that even when addressing + // is not used, the flash size violation may still trigger. + .devsize() + .bits(0x1F) + }); + + // // Communications configuration register + // regs.ccr.write(|w| unsafe { + // w.dmode().bits(1) // Data on a single line + + // .dmode() + // .bits(config.mode.reg_value()) + // .admode() + // .bits(config.mode.reg_value()) + // .adsize() + // .bits(config.address_size as u8) + // .imode() + // .bits(0) // No instruction phase + + // Prescaler + let spi_frequency = config.frequency.0; + let divisor = + match (spi_kernel_ck + spi_frequency - 1) / spi_frequency { + divisor @ 1..=256 => divisor - 1, + _ => panic!("Invalid OCTOSPI frequency requested"), + }; + regs.dcr2 + .write(|w| unsafe { w.prescaler().bits(divisor as u8) }); + + // Note that we default to setting SSHIFT (sampling on the falling + // edge). This is because it appears that the QSPI may have signal + // contention issues when reading with zero dummy cycles. Setting SSHIFT + // forces the read to occur on the falling edge instead of the rising + // edge. Refer to https://github.com/quartiq/stabilizer/issues/101 for + // more information + // + // SSHIFT must not be set in DDR mode. + regs.tcr.write(|w| unsafe { + w.sshift() + .bit(config.sampling_edge == SamplingEdge::Falling) + .dcyc() + .bits(config.dummy_cycles) + }); + + // Enable the peripheral + regs.cr.modify(|_, w| w.en().set_bit()); + + Octospi { rb: regs } + } + } + + impl OctospiExt<$peripheral> for $peripheral { + type Rec = $rec; + + fn octospi_unchecked( + self, + config: CONFIG, + clocks: &CoreClocks, + prec: Self::Rec, + ) -> Octospi<$peripheral> + where + CONFIG: Into, + { + Octospi::$name(self, config, clocks, prec) + } + } + }; +} + +octospi_impl! { octospi1_unchecked, stm32::OCTOSPI1, rec::Octospi1 } +octospi_impl! { octospi2_unchecked, stm32::OCTOSPI2, rec::Octospi2 } diff --git a/src/xspi/qspi.rs b/src/xspi/qspi.rs new file mode 100644 index 00000000..e0d17c8b --- /dev/null +++ b/src/xspi/qspi.rs @@ -0,0 +1,328 @@ +//! Quad SPI (QSPI) bus +//! +//! See the parent module for documentation + +use crate::{ + gpio::{ + gpioa::PA1, + gpiob::PB2, + gpioc::{PC10, PC9}, + gpiod::{PD11, PD12, PD13}, + gpioe::{PE10, PE2, PE7, PE8, PE9}, + gpiof::{PF10, PF6, PF7, PF8, PF9}, + gpiog::{PG14, PG9}, + gpioh::{PH2, PH3}, + Alternate, AF10, AF9, + }, + rcc::{rec, CoreClocks, ResetEnable}, + stm32, +}; + +use super::{Bank, Config, Qspi, SamplingEdge}; + +/// Used to indicate that an IO pin is not used by the QSPI interface. +pub struct NoIo {} + +/// Indicates a set of pins can be used for the QSPI interface on bank 1. +pub trait PinsBank1 {} +pub trait PinIo0Bank1 {} +pub trait PinIo1Bank1 {} +pub trait PinIo2Bank1 {} +pub trait PinIo3Bank1 {} + +/// Indicates a set of pins can be used for the QSPI interface on bank 2. +pub trait PinsBank2 {} +pub trait PinSckBank2 {} +pub trait PinIo0Bank2 {} +pub trait PinIo1Bank2 {} +pub trait PinIo2Bank2 {} +pub trait PinIo3Bank2 {} + +pub trait PinSck {} + +impl PinsBank1 for (SCK, IO0, IO1, IO2, IO3) +where + SCK: PinSck, + IO0: PinIo0Bank1, + IO1: PinIo1Bank1, + IO2: PinIo2Bank1, + IO3: PinIo3Bank1, +{ +} + +impl PinsBank2 for (SCK, IO0, IO1, IO2, IO3) +where + SCK: PinSck, + IO0: PinIo0Bank2, + IO1: PinIo1Bank2, + IO2: PinIo2Bank2, + IO3: PinIo3Bank2, +{ +} + +macro_rules! pins { + (Bank1: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { + $( + impl PinIo0Bank1 for $IO0 {} + )* + $( + impl PinIo1Bank1 for $IO1 {} + )* + $( + impl PinIo2Bank1 for $IO2 {} + )* + $( + impl PinIo3Bank1 for $IO3 {} + )* + }; + + (Bank2: [IO0: [$($IO0:ty),*] IO1: [$($IO1:ty),*] IO2: [$($IO2:ty),*] IO3: [$($IO3:ty),*]]) => { + $( + impl PinIo0Bank2 for $IO0 {} + )* + $( + impl PinIo1Bank2 for $IO1 {} + )* + $( + impl PinIo2Bank2 for $IO2 {} + )* + $( + impl PinIo3Bank2 for $IO3 {} + )* + }; + + (SCK: [$($SCK:ty),*], Bank1: $bank1:tt, Bank2: $bank2:tt) => { + $( + impl PinSck for $SCK {} + )* + pins!(Bank1: $bank1); + pins!(Bank2: $bank2); + }; +} + +pins! { + SCK: [ + PB2>, + PF10> + ], + Bank1: [ + IO0: [ + PC9>, + PD11>, + PF8> + ] + IO1: [ + PC10>, + PD12>, + PF9>, + NoIo + ] + IO2: [ + PE2>, + PF7>, + NoIo + ] + IO3: [ + PA1>, + PD13>, + PF6>, + NoIo + ] + ], + Bank2: [ + IO0: [ + PE7>, + PF8>, + PH2> + ] + IO1: [ + PE8>, + PF9>, + PH3>, + NoIo + ] + IO2: [ + PE9>, + PG9>, + NoIo + ] + IO3: [ + PE10>, + PG14>, + NoIo + ] + ] +} + +pub trait QspiExt { + fn bank1( + self, + _pins: PINS, + config: CONFIG, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into, + PINS: PinsBank1; + + fn bank2( + self, + _pins: PINS, + config: CONFIG, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into, + PINS: PinsBank2; + + fn qspi_unchecked( + self, + config: CONFIG, + bank: Bank, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into; +} + +impl Qspi { + pub fn qspi_unchecked( + regs: stm32::QUADSPI, + config: CONFIG, + bank: Bank, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Self + where + CONFIG: Into, + { + prec.enable(); + + // Disable QUADSPI before configuring it. + regs.cr.write(|w| w.en().clear_bit()); + + let spi_kernel_ck = match Self::get_clock(clocks) { + Some(freq_hz) => freq_hz.0, + _ => panic!("QSPI kernel clock not running!"), + }; + + while regs.sr.read().busy().bit_is_set() {} + + let config: Config = config.into(); + + // Configure the FSIZE to maximum. It appears that even when addressing is not used, the + // flash size violation may still trigger. + regs.dcr.write(|w| unsafe { w.fsize().bits(0x1F) }); + + // Clear all pending flags. + regs.fcr.write(|w| { + w.ctof() + .set_bit() + .csmf() + .set_bit() + .ctcf() + .set_bit() + .ctef() + .set_bit() + }); + + // Configure the communication method for QSPI. + regs.ccr.write(|w| unsafe { + w.fmode() + .bits(0) // indirect mode + .dmode() + .bits(config.mode.reg_value()) + .admode() + .bits(config.mode.reg_value()) + .adsize() + .bits(config.address_size as u8) + .imode() + .bits(0) // No instruction phase + .dcyc() + .bits(config.dummy_cycles) + }); + + let spi_frequency = config.frequency.0; + let divisor = match (spi_kernel_ck + spi_frequency - 1) / spi_frequency + { + divisor @ 1..=256 => divisor - 1, + _ => panic!("Invalid QSPI frequency requested"), + }; + + // Write the prescaler and the SSHIFT bit. + // + // Note that we default to setting SSHIFT (sampling on the falling + // edge). This is because it appears that the QSPI may have signal + // contention issues when reading with zero dummy cycles. Setting SSHIFT + // forces the read to occur on the falling edge instead of the rising + // edge. Refer to https://github.com/quartiq/stabilizer/issues/101 for + // more information + // + // SSHIFT must not be set in DDR mode. + regs.cr.write(|w| unsafe { + w.prescaler() + .bits(divisor as u8) + .sshift() + .bit(config.sampling_edge == SamplingEdge::Falling) + .fthres() + .bits(config.fifo_threshold - 1) + }); + + match bank { + Bank::One => regs.cr.modify(|_, w| w.fsel().clear_bit()), + Bank::Two => regs.cr.modify(|_, w| w.fsel().set_bit()), + Bank::Dual => regs.cr.modify(|_, w| w.dfm().set_bit()), + } + + // Enable ther peripheral + regs.cr.modify(|_, w| w.en().set_bit()); + + Qspi { rb: regs } + } +} + +impl QspiExt for stm32::QUADSPI { + fn bank1( + self, + _pins: PINS, + config: CONFIG, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into, + PINS: PinsBank1, + { + Qspi::qspi_unchecked(self, config, Bank::One, clocks, prec) + } + + fn bank2( + self, + _pins: PINS, + config: CONFIG, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into, + PINS: PinsBank2, + { + Qspi::qspi_unchecked(self, config, Bank::Two, clocks, prec) + } + + fn qspi_unchecked( + self, + config: CONFIG, + bank: Bank, + clocks: &CoreClocks, + prec: rec::Qspi, + ) -> Qspi + where + CONFIG: Into, + { + Qspi::qspi_unchecked(self, config, bank, clocks, prec) + } +} From b0b6a4f4b0fce18691e6666b324ed23ffbb8a81e Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Wed, 16 Jun 2021 20:03:23 +0200 Subject: [PATCH 2/9] Remove address_size from config, add write/read_extended methods `write_extended` and `read_extended` methods allow more complicated transcations, such as those needed to read/write from flash memories. These new methods allow the address size to be configured. Remove this same functionality from config to avoid too many redundant ways to achieve the same thing. --- src/xspi/mod.rs | 373 ++++++++++++++++++++++++++++++++++++++------ src/xspi/octospi.rs | 28 ++-- src/xspi/qspi.rs | 7 +- 3 files changed, 346 insertions(+), 62 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index 92518aa5..67d17a2e 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -37,6 +37,11 @@ //! //! This driver currently only supports indirect operation mode of the xSPI //! interface. Automatic polling or memory-mapped modes are not supported. +//! +//! Using different operational modes (1-bit/2-bit/4-bit etc.) for different +//! phases of a single transaction is not supported. It is possible to change +//! operational mode between transactions by calling +//! [`configure_mode`](#method.configure_mode). // Parts of the Quad and Octo SPI support are shared (this file), but they are // different enough to require different initialisation routines and pin @@ -49,6 +54,7 @@ mod qspi; #[cfg(any(feature = "rm0433", feature = "rm0399"))] pub use common::{ Bank, Xspi as Qspi, XspiError as QspiError, XspiMode as QspiMode, + XspiWord as QspiWord, }; #[cfg(any(feature = "rm0433", feature = "rm0399"))] pub use qspi::QspiExt as XspiExt; @@ -59,12 +65,13 @@ mod octospi; #[cfg(any(feature = "rm0455", feature = "rm0468"))] pub use common::{ Xspi as Octospi, XspiError as OctospiError, XspiMode as OctospiMode, + XspiWord as OctospiWord, }; #[cfg(any(feature = "rm0455", feature = "rm0468"))] pub use octospi::OctospiExt as XspiExt; // Both -pub use common::{AddressSize, Config, Event, SamplingEdge}; +pub use common::{Config, Event, SamplingEdge}; /// This modulate contains functionality common to both Quad and Octo SPI mod common { @@ -93,6 +100,7 @@ mod common { EightBit, } impl XspiMode { + #[inline(always)] pub(super) fn reg_value(&self) -> u8 { match self { XspiMode::OneBit => 1, @@ -108,15 +116,49 @@ mod common { pub enum XspiError { Busy, Underflow, + + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + /// The specified XspiWord does not fit in the available register + WordTooLarge, } - /// Address sizes used by the XSPI interface + /// Instruction, Address or Alternate Byte word used by the XSPI interface #[derive(Debug, Copy, Clone, PartialEq)] - pub enum AddressSize { - EightBit, - SixteenBit, - TwentyFourBit, - ThirtyTwoBit, + pub enum XspiWord { + None, + U8(u8), + U16(u16), + U24(u32), + U32(u32), + } + impl XspiWord { + #[inline(always)] + fn size(&self) -> u8 { + match self { + XspiWord::U16(_) => 1, + XspiWord::U24(_) => 2, + XspiWord::U32(_) => 3, + _ => 0, // 8-bit + } + } + #[inline(always)] + fn bits(self) -> u32 { + match self { + XspiWord::None => 0, + XspiWord::U8(x) => x as u32, + XspiWord::U16(x) => x as u32, + XspiWord::U24(x) | XspiWord::U32(x) => x, + } + } + #[inline(always)] + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + fn bits_u8(self) -> Result { + match self { + XspiWord::None => Ok(0), + XspiWord::U8(x) => Ok(x), + _ => Err(XspiError::WordTooLarge), + } + } } /// Sampling mode for the XSPI interface @@ -158,7 +200,6 @@ mod common { pub struct Config { pub(super) mode: XspiMode, pub(super) frequency: Hertz, - pub(super) address_size: AddressSize, pub(super) dummy_cycles: u8, pub(super) sampling_edge: SamplingEdge, pub(super) fifo_threshold: u8, @@ -168,14 +209,12 @@ mod common { /// Create a default configuration for the XSPI interface. /// /// * Bus in 1-bit Mode - /// * 8-bit Address /// * No dummy cycle /// * Sample on falling edge pub fn new>(freq: T) -> Self { Config { mode: XspiMode::OneBit, frequency: freq.into(), - address_size: AddressSize::EightBit, dummy_cycles: 0, sampling_edge: SamplingEdge::Falling, fifo_threshold: 1, @@ -192,12 +231,6 @@ mod common { self } - /// Specify the size of the address phase - pub fn address_size(mut self, address_size: AddressSize) -> Self { - self.address_size = address_size; - self - } - /// Specify the number of dummy cycles in between the address and data /// phases. /// @@ -262,6 +295,11 @@ mod common { /// Generic type for Quad or Octo SPI pub struct Xspi { pub(super) rb: XSPI, + + /// We store the current mode here because for extended transactions + /// various phases may be removed. Therefore we need to restore them + /// after each transaction. + pub(super) mode: XspiMode, } #[cfg(any(feature = "rm0433", feature = "rm0399"))] @@ -306,8 +344,12 @@ mod common { /// Check if the XSPI peripheral is currently busy with a /// transaction - pub fn is_busy(&self) -> bool { - self.rb.sr.read().busy().bit_is_set() + pub fn is_busy(&self) -> Result<(), XspiError> { + if self.rb.sr.read().busy().bit_is_set() { + Err(XspiError::Busy) + } else { + Ok(()) + } } /// Enable interrupts for the given `event` @@ -341,7 +383,8 @@ mod common { } } - /// Configure the operational mode of the XSPI interface. + /// Configure the operational mode (number of bits) of the XSPI + /// interface. /// /// # Args /// * `mode` - The newly desired mode of the interface. @@ -349,15 +392,27 @@ mod common { /// # Errors /// Returns XspiError::Busy if an operation is ongoing pub fn configure_mode(&mut self, mode: XspiMode) -> Result<(), XspiError> { - if self.is_busy() { - return Err(XspiError::Busy); - } + self.is_busy()?; + self.mode = mode; + self.set_mode_address_data_only(); + + Ok(()) + } + /// Sets the interface to 8-bit address and data only using the + /// current operational mode (number of bits). + fn set_mode_address_data_only(&mut self) { self.rb.ccr.modify(|_, w| unsafe { - w.admode() - .bits(mode.reg_value()) - .dmode() - .bits(mode.reg_value()) + w.imode() // NO instruction phase + .bits(0) + .admode() // address phase + .bits(self.mode.reg_value()) + .adsize() + .bits(0) // 8-bit address + .abmode() // NO alternate-bytes phase + .bits(0) + .dmode() // data phase + .bits(self.mode.reg_value()) }); // if mode == XspiMode::EightBit { @@ -375,9 +430,49 @@ mod common { // .bits(mode.reg_value()) // }); // self.rb.tcr.write(|w| unsafe { w.dcyc().bits(4) }); - // } + } - Ok(()) + /// Sets the interface in extended mode + /// + /// # Args + /// * `instruction` - The word to be used for the instruction phase. + /// * `address` - The word to be used for the address phase. + /// * `alternate_bytes` - The word to be used for the alternate-bytes phase. + /// * `dummy_cycles` - The number of dummy cycles between the alternate-bytes + /// and the data phase. + /// * `data` - true is there is a data phase, false for no data phase. + fn setup_extended(&mut self, instruction: XspiWord, address: XspiWord, + alternate_bytes: XspiWord, dummy_cycles: u8, data: bool) { + + let mode = self.mode.reg_value(); + let imode = if instruction != XspiWord::None { mode } else { 0 }; + let admode = if address != XspiWord::None { mode } else { 0 }; + let abmode = if alternate_bytes != XspiWord::None { mode } else { 0 }; + let dmode = if data { mode } else { 0 }; + + self.rb.ccr.modify(|_, w| unsafe { + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + let w = w.dcyc().bits(dummy_cycles); + + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + let w = w.isize().bits(instruction.size()); + + w.imode() + .bits(imode) + .admode() + .bits(admode) + .adsize() + .bits(address.size()) + .abmode() + .bits(abmode) + .absize() + .bits(alternate_bytes.size()) + .dmode() + .bits(dmode) + }); + + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + self.rb.tcr.write(|w| unsafe { w.dcyc().bits(dummy_cycles) }); } /// Begin a write over the XSPI interface. This is mostly useful for use with @@ -387,15 +482,17 @@ mod common { /// # Args /// * `addr` - The address to write data to. If the address size is less /// than 32-bit, then unused bits are discarded. - /// * `length` - The length of the write operation in bytes + /// * `length` - The length of the write operation in bytes. Must be greater + /// than zero pub fn begin_write( &mut self, addr: u32, length: usize, ) -> Result<(), XspiError> { - if self.is_busy() { - return Err(XspiError::Busy); - } + self.is_busy()?; + + // Exit extended mode + self.set_mode_address_data_only(); // Clear the transfer complete flag. self.rb.fcr.write(|w| w.ctcf().set_bit()); @@ -408,7 +505,9 @@ mod common { // Configure the mode to indirect write. fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b00) }); - // Write the address to force the write to start + // Write the address. The transaction starts on the next write + // to DATA, unless there is no DATA phase configured, in which + // case it starts here. self.rb.ar.write(|w| unsafe { w.address().bits(addr) }); Ok(()) @@ -417,21 +516,24 @@ mod common { /// Write data over the XSPI interface. /// /// # Args - /// * `addr` - The address to write data to. If the address size is less - /// than 32-bit, then unused bits are discarded. + /// * `addr` - The address to write data to /// * `data` - An array of data to transfer over the XSPI interface. /// /// # Panics /// /// Panics if the length of `data` is greater than the size of the XSPI /// hardware FIFO (32 bytes). - pub fn write(&mut self, addr: u32, data: &[u8]) -> Result<(), XspiError> { + pub fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), XspiError> { assert!( data.len() <= 32, "Transactions larger than the XSPI FIFO are currently unsupported" ); + assert!( + !data.is_empty(), + "Must have a non-zero number of data cycles" + ); - self.begin_write(addr, data.len())?; + self.begin_write(addr as u32, data.len())?; // Write data to the FIFO in a byte-wise manner. unsafe { @@ -444,7 +546,88 @@ mod common { while self.rb.sr.read().tcf().bit_is_clear() {} // Wait for the peripheral to indicate it is no longer busy. - while self.is_busy() {} + while self.is_busy().is_err() {} + + Ok(()) + } + + /// Write data over the XSPI interface, using an extended + /// transaction that may contain instruction, address, alternate-bytes + /// and data phases with various sizes. + /// + /// # Args + /// * `instruction` - The word to be used for the instruction phase. + /// * `address` - The word to be used for the address phase. + /// * `alternate_bytes` - The word to be used for the alternate-bytes phase. + /// * `data` - An array of data to transfer over the XSPI interface. Use + /// and empty slice to remove the data phase entirely. + /// + /// # Panics + /// + /// Panics if the length of `data` is greater than the size of the XSPI + /// hardware FIFO (32 bytes). + pub fn write_extended(&mut self, + instruction: XspiWord, + address: XspiWord, + alternate_bytes: XspiWord, + data: &[u8]) -> Result<(), XspiError> { + assert!( + data.len() <= 32, + "Transactions larger than the XSPI FIFO are currently unsupported" + ); + + self.is_busy()?; + + // Setup extended mode. Typically no dummy cycles in write mode + self.setup_extended(instruction, address, alternate_bytes, 0, !data.is_empty()); + + // Clear the transfer complete flag. + self.rb.fcr.write(|w| w.ctcf().set_bit()); + + // Data length + if !data.is_empty() { + self.rb + .dlr + .write(|w| unsafe { w.dl().bits(data.len() as u32 - 1) }); + } + + // Configure the mode to indirect write. + fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b00) }); + + // Write alternate-bytes + self.rb.abr.write(|w| unsafe { + w.alternate().bits(alternate_bytes.bits()) + }); + + // Write instruction. If there is no address or data phase, the + // transaction starts here. + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + { + let ir = instruction.bits_u8()?; + self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); + } + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + self.rb.ir.write(|w| unsafe { + w.instruction().bits(instruction.bits()) + }); + + // Write the address. The transaction starts on the next write + // to DATA, unless there is no DATA phase configured, in which + // case it starts here. + self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + + // Write data to the FIFO in a byte-wise manner. + unsafe { + for byte in data { + ptr::write_volatile(&self.rb.dr as *const _ as *mut u8, *byte); + } + } + + // Wait for the transaction to complete + while self.rb.sr.read().tcf().bit_is_clear() {} + + // Wait for the peripheral to indicate it is no longer busy. + while self.is_busy().is_err() {} Ok(()) } @@ -456,15 +639,17 @@ mod common { /// # Args /// * `addr` - The address to read data from. If the address size is less /// than 32-bit, then unused bits are discarded. - /// * `length` - The length of the read operation in bytes + /// * `length` - The length of the read operation in bytes. Must be greater + /// than zero pub fn begin_read( &mut self, addr: u32, length: usize, ) -> Result<(), XspiError> { - if self.is_busy() { - return Err(XspiError::Busy); - } + self.is_busy()?; + + // Exit extended mode + self.set_mode_address_data_only(); // Clear the transfer complete flag. self.rb.fcr.write(|w| w.ctcf().set_bit()); @@ -486,8 +671,7 @@ mod common { /// Read data over the XSPI interface. /// /// # Args - /// * `addr` - The address to read data from. If the address size is less - /// than 32-bit, then unused bits are discarded. + /// * `addr` - The address to read data from /// * `dest` - An array to store the result of the read into. /// /// # Panics @@ -496,7 +680,7 @@ mod common { /// hardware FIFO (32 bytes). pub fn read( &mut self, - addr: u32, + addr: u8, dest: &mut [u8], ) -> Result<(), XspiError> { assert!( @@ -505,7 +689,102 @@ mod common { ); // Begin the read operation - self.begin_read(addr, dest.len())?; + self.begin_read(addr as u32, dest.len())?; + + // Wait for the transaction to complete + while self.rb.sr.read().tcf().bit_is_clear() {} + + // Check for underflow on the FIFO. + if (self.rb.sr.read().flevel().bits() as usize) < dest.len() { + return Err(XspiError::Underflow); + } + + // Read data from the FIFO in a byte-wise manner. + unsafe { + for location in dest { + *location = + ptr::read_volatile(&self.rb.dr as *const _ as *const u8); + } + } + + // Wait for the peripheral to indicate it is no longer busy. + while self.is_busy().is_err() {} + + Ok(()) + } + + /// Read data over the XSPI interface, using an extended transaction + /// that may contain instruction, address, alternate-bytes and data + /// phases with various sizes. + /// + /// # Args + /// * `instruction` - The word to be used for the instruction phase. + /// * `address` - The word to be used for the address phase. + /// * `alternate_bytes` - The word to be used for the alternate-bytes phase. + /// * `dummy_cycles` - 0 to 31 clock cycles between the alternate-bytes + /// and the data phases. + /// * `data` - An array of data to transfer over the XSPI interface + /// + /// # Panics + /// + /// Panics if the length of `dest` is greater than the size of the + /// XSPI hardware FIFO (32 bytes). Panics if the length of `dest` is + /// zero. Panics if the number of dummy cycles is not 0 - 31 inclusive. + pub fn read_extended(&mut self, + instruction: XspiWord, + address: XspiWord, + alternate_bytes: XspiWord, + dummy_cycles: u8, + dest: &mut [u8]) -> Result<(), XspiError> { + assert!( + dest.len() <= 32, + "Transactions larger than the XSPI FIFO are currently unsupported" + ); + assert!( + !dest.is_empty(), + "Must have a non-zero number of data cycles (otherwise use a write operation!)" + ); + assert!( + dummy_cycles < 32, + "Hardware only supports 0-31 dummy cycles" + ); + + self.is_busy()?; + + // Setup extended mode. Read operations always have a data phase. + self.setup_extended(instruction, address, alternate_bytes, + dummy_cycles, true); + + // Clear the transfer complete flag. + self.rb.fcr.write(|w| w.ctcf().set_bit()); + + // Write the length that should be read. + self.rb + .dlr + .write(|w| unsafe { w.dl().bits(dest.len() as u32 - 1) }); + + // Configure the mode to indirect read. + fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b01) }); + + // Write alternate-bytes + self.rb.abr.write(|w| unsafe { + w.alternate().bits(alternate_bytes.bits()) + }); + + // Write instruction. If there is no address phase, the + // transaction starts here. + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + { + let ir = instruction.bits_u8()?; + self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); + } + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + self.rb.ir.write(|w| unsafe { + w.instruction().bits(instruction.bits()) + }); + + // Write the address. Transaction starts here. + self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); // Wait for the transaction to complete while self.rb.sr.read().tcf().bit_is_clear() {} @@ -524,7 +803,7 @@ mod common { } // Wait for the peripheral to indicate it is no longer busy. - while self.is_busy() {} + while self.is_busy().is_err() {} Ok(()) } diff --git a/src/xspi/octospi.rs b/src/xspi/octospi.rs index bebc08d7..620cd679 100644 --- a/src/xspi/octospi.rs +++ b/src/xspi/octospi.rs @@ -277,18 +277,17 @@ macro_rules! octospi_impl { .bits(0x1F) }); - // // Communications configuration register - // regs.ccr.write(|w| unsafe { - // w.dmode().bits(1) // Data on a single line - - // .dmode() - // .bits(config.mode.reg_value()) - // .admode() - // .bits(config.mode.reg_value()) - // .adsize() - // .bits(config.address_size as u8) - // .imode() - // .bits(0) // No instruction phase + // Communications configuration register + regs.ccr.write(|w| unsafe { + w.dmode() + .bits(config.mode.reg_value()) + .admode() + .bits(config.mode.reg_value()) + .adsize() + .bits(0) // Eight-bit address + .imode() + .bits(0) // No instruction phase + }); // Prescaler let spi_frequency = config.frequency.0; @@ -318,7 +317,10 @@ macro_rules! octospi_impl { // Enable the peripheral regs.cr.modify(|_, w| w.en().set_bit()); - Octospi { rb: regs } + Octospi { + rb: regs, + mode: config.mode, + } } } diff --git a/src/xspi/qspi.rs b/src/xspi/qspi.rs index e0d17c8b..1cca081d 100644 --- a/src/xspi/qspi.rs +++ b/src/xspi/qspi.rs @@ -238,7 +238,7 @@ impl Qspi { .admode() .bits(config.mode.reg_value()) .adsize() - .bits(config.address_size as u8) + .bits(0) // Eight-bit address .imode() .bits(0) // No instruction phase .dcyc() @@ -280,7 +280,10 @@ impl Qspi { // Enable ther peripheral regs.cr.modify(|_, w| w.en().set_bit()); - Qspi { rb: regs } + Qspi { + rb: regs, + mode: config.mode, + } } } From 2da85dfdd9352ffee4e48efce25e08046572b6c1 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Wed, 16 Jun 2021 20:04:01 +0200 Subject: [PATCH 3/9] Add example for OCTOSPI Tested on a STM32H735G-DK with a Macronix MX25LM51245GXDI00 --- examples/octospi.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 examples/octospi.rs diff --git a/examples/octospi.rs b/examples/octospi.rs new file mode 100644 index 00000000..523c78fa --- /dev/null +++ b/examples/octospi.rs @@ -0,0 +1,98 @@ +//! OCTOSPI peripheral example in indirect mode +//! +//! Tested on a STM32H735G-DK with a Macronix MX25LM51245GXDI00 + +#![deny(warnings)] +#![no_main] +#![no_std] + +#[macro_use] +mod utilities; + +use cortex_m_rt::entry; +use stm32h7xx_hal::rcc::rec::{OctospiClkSel, OctospiClkSelGetter}; +use stm32h7xx_hal::{ + pac, prelude::*, xspi::OctospiMode, xspi::OctospiWord as XW, +}; + +use log::info; + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let dp = pac::Peripherals::take().unwrap(); + + // Constrain and Freeze power + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // Constrain and Freeze clock + let rcc = dp.RCC.constrain(); + let ccdr = rcc.sys_ck(96.mhz()).freeze(pwrcfg, &dp.SYSCFG); + + // Octospi from HCLK at 48MHz + assert_eq!(ccdr.clocks.hclk().0, 48_000_000); + assert_eq!( + ccdr.peripheral.OCTOSPI1.get_kernel_clk_mux(), + OctospiClkSel::RCC_HCLK3 + ); + + // Acquire the GPIO peripherals. This also enables the clock for + // the GPIOs in the RCC register. + let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); + + let _ncs = gpiog.pg6.into_alternate_af10(); + let _clk = gpiof.pf10.into_alternate_af9(); + let _io0 = gpiod.pd11.into_alternate_af9(); + let _io1 = gpiod.pd12.into_alternate_af9(); + let _io2 = gpioe.pe2.into_alternate_af9(); + let _io3 = gpiod.pd13.into_alternate_af9(); + let _io4 = gpiod.pd4.into_alternate_af10(); + let _io5 = gpiod.pd5.into_alternate_af10(); + let _io6 = gpiog.pg9.into_alternate_af9(); + let _io7 = gpiod.pd7.into_alternate_af10(); + + info!(""); + info!("stm32h7xx-hal example - OCTOSPI"); + info!(""); + + // Initialise the OCTOSPI peripheral. + let mut octospi = dp.OCTOSPI1.octospi_unchecked( + 12.mhz(), + &ccdr.clocks, + ccdr.peripheral.OCTOSPI1, + ); + + octospi.configure_mode(OctospiMode::OneBit).unwrap(); + + // RDID Read Identification. Abuses address as instruction phase, but that + // works in SPI mode. + let mut read: [u8; 3] = [0; 3]; + octospi.read(0x9F, &mut read).unwrap(); + info!("Read with instruction 0x9F : {:x?}", read); + + // Switch Macronix MX25LM51245GXDI00 to SDR OPI + // Set WREN bit + octospi + .write_extended(XW::U8(0x06), XW::None, XW::None, &[]) + .unwrap(); + // Write Configuration Register 2 + octospi + .write_extended(XW::U8(0x72), XW::U32(0), XW::None, &[1]) + .unwrap(); + // Change bus mode + octospi.configure_mode(OctospiMode::EightBit).unwrap(); + + // RDID Read Identification + let mut read: [u8; 3] = [0; 3]; + octospi + .read_extended(XW::U16(0x9F60), XW::U32(0), XW::None, 4, &mut read) + .unwrap(); + + info!("Read with instruction 0x9F60 : {:x?}", read); + + loop {} +} From f9025db93e0036f793eb4294654db23de7d77503 Mon Sep 17 00:00:00 2001 From: Alex Norman Date: Fri, 9 Jul 2021 14:03:46 -0700 Subject: [PATCH 4/9] qspi read seems to be working --- src/xspi/mod.rs | 75 ++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index 67d17a2e..f52fe512 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -452,10 +452,10 @@ mod common { self.rb.ccr.modify(|_, w| unsafe { #[cfg(any(feature = "rm0433", feature = "rm0399"))] - let w = w.dcyc().bits(dummy_cycles); - - #[cfg(any(feature = "rm0455", feature = "rm0468"))] - let w = w.isize().bits(instruction.size()); + let w = { + let ir = instruction.bits_u8().unwrap(); + w.dcyc().bits(dummy_cycles).instruction().bits(ir).fmode().bits(0b01) + }; w.imode() .bits(imode) @@ -599,22 +599,26 @@ mod common { w.alternate().bits(alternate_bytes.bits()) }); - // Write instruction. If there is no address or data phase, the - // transaction starts here. - #[cfg(any(feature = "rm0433", feature = "rm0399"))] - { - let ir = instruction.bits_u8()?; - self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); + if instruction != XspiWord::None { + // Write instruction. If there is no address or data phase, the + // transaction starts here. + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + { + let ir = instruction.bits_u8()?; + self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); + } + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + self.rb.ir.write(|w| unsafe { + w.instruction().bits(instruction.bits()) + }); } - #[cfg(any(feature = "rm0455", feature = "rm0468"))] - self.rb.ir.write(|w| unsafe { - w.instruction().bits(instruction.bits()) - }); - // Write the address. The transaction starts on the next write - // to DATA, unless there is no DATA phase configured, in which - // case it starts here. - self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + if address != XspiWord::None { + // Write the address. The transaction starts on the next write + // to DATA, unless there is no DATA phase configured, in which + // case it starts here. + self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + } // Write data to the FIFO in a byte-wise manner. unsafe { @@ -751,10 +755,6 @@ mod common { self.is_busy()?; - // Setup extended mode. Read operations always have a data phase. - self.setup_extended(instruction, address, alternate_bytes, - dummy_cycles, true); - // Clear the transfer complete flag. self.rb.fcr.write(|w| w.ctcf().set_bit()); @@ -763,25 +763,30 @@ mod common { .dlr .write(|w| unsafe { w.dl().bits(dest.len() as u32 - 1) }); - // Configure the mode to indirect read. - fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b01) }); + // Setup extended mode. Read operations always have a data phase. + self.setup_extended(instruction, address, alternate_bytes, + dummy_cycles, true); // Write alternate-bytes self.rb.abr.write(|w| unsafe { w.alternate().bits(alternate_bytes.bits()) }); - // Write instruction. If there is no address phase, the - // transaction starts here. - #[cfg(any(feature = "rm0433", feature = "rm0399"))] - { - let ir = instruction.bits_u8()?; - self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); - } - #[cfg(any(feature = "rm0455", feature = "rm0468"))] - self.rb.ir.write(|w| unsafe { - w.instruction().bits(instruction.bits()) - }); + /* + * p894 + * + * If neither the address register (QUADSPI_AR) nor the data register (QUADSPI_DR) + * need to be updated for a particular command, then the command sequence starts as + * soon as QUADSPI_CCR is written. This is the case when both ADMODE and DMODE are + * 00, or if just ADMODE = 00 when in indirect read mode (FMODE = 01). + * + * When an address is required (ADMODE is not 00) and the data register does not + * need to be written (when FMODE = 01 or DMODE = 00), the command sequence starts + * as soon as the address is updated with a write to QUADSPI_AR. + * + * In case of data transmission (FMODE = 00 and DMODE! = 00), the communication + * start is triggered by a write in the FIFO through QUADSPI_DR. + */ // Write the address. Transaction starts here. self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); From 75df8b5a2758223b627912103eef110b389d0798 Mon Sep 17 00:00:00 2001 From: Alex Norman Date: Fri, 9 Jul 2021 14:26:33 -0700 Subject: [PATCH 5/9] enable fmode in setup, only write address if needed --- src/xspi/mod.rs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index f52fe512..7d5ebc02 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -442,19 +442,22 @@ mod common { /// and the data phase. /// * `data` - true is there is a data phase, false for no data phase. fn setup_extended(&mut self, instruction: XspiWord, address: XspiWord, - alternate_bytes: XspiWord, dummy_cycles: u8, data: bool) { + alternate_bytes: XspiWord, dummy_cycles: u8, data: bool, read: bool) { + let fmode = if read { 0b01 } else { 0b00 }; let mode = self.mode.reg_value(); let imode = if instruction != XspiWord::None { mode } else { 0 }; let admode = if address != XspiWord::None { mode } else { 0 }; let abmode = if alternate_bytes != XspiWord::None { mode } else { 0 }; let dmode = if data { mode } else { 0 }; + //writing to ccr will trigger the start of a transaction if there is no address or + //data rm0433 pg 894, so we do it all in one go self.rb.ccr.modify(|_, w| unsafe { #[cfg(any(feature = "rm0433", feature = "rm0399"))] let w = { let ir = instruction.bits_u8().unwrap(); - w.dcyc().bits(dummy_cycles).instruction().bits(ir).fmode().bits(0b01) + w.dcyc().bits(dummy_cycles).instruction().bits(ir).fmode().bits(fmode) }; w.imode() @@ -472,7 +475,10 @@ mod common { }); #[cfg(any(feature = "rm0455", feature = "rm0468"))] - self.rb.tcr.write(|w| unsafe { w.dcyc().bits(dummy_cycles) }); + { + self.rb.tcr.write(|w| unsafe { w.dcyc().bits(dummy_cycles) }); + self.rb.cr.modify(|_, w| unsafe { w.fmode().bits(fmode) }); + } } /// Begin a write over the XSPI interface. This is mostly useful for use with @@ -578,9 +584,6 @@ mod common { self.is_busy()?; - // Setup extended mode. Typically no dummy cycles in write mode - self.setup_extended(instruction, address, alternate_bytes, 0, !data.is_empty()); - // Clear the transfer complete flag. self.rb.fcr.write(|w| w.ctcf().set_bit()); @@ -591,9 +594,8 @@ mod common { .write(|w| unsafe { w.dl().bits(data.len() as u32 - 1) }); } - // Configure the mode to indirect write. - fmode_reg!(self).modify(|_, w| unsafe { w.fmode().bits(0b00) }); - + // Setup extended mode. Typically no dummy cycles in write mode + self.setup_extended(instruction, address, alternate_bytes, 0, !data.is_empty(), false); // Write alternate-bytes self.rb.abr.write(|w| unsafe { w.alternate().bits(alternate_bytes.bits()) @@ -763,9 +765,10 @@ mod common { .dlr .write(|w| unsafe { w.dl().bits(dest.len() as u32 - 1) }); - // Setup extended mode. Read operations always have a data phase. + // Setup extended mode. Read operations always have a data phase. if there is no + // address, this will start the transaction self.setup_extended(instruction, address, alternate_bytes, - dummy_cycles, true); + dummy_cycles, true, true); // Write alternate-bytes self.rb.abr.write(|w| unsafe { @@ -788,8 +791,10 @@ mod common { * start is triggered by a write in the FIFO through QUADSPI_DR. */ - // Write the address. Transaction starts here. - self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + // Write the address if there is one, transaction starts here + if address != XspiWord::None { + self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + } // Wait for the transaction to complete while self.rb.sr.read().tcf().bit_is_clear() {} From aa20cebc25a9074e8516be8a07048d01b355ad55 Mon Sep 17 00:00:00 2001 From: Alex Norman Date: Fri, 9 Jul 2021 14:38:41 -0700 Subject: [PATCH 6/9] consolidate setup --- src/xspi/mod.rs | 78 ++++++++++++++++--------------------------------- 1 file changed, 25 insertions(+), 53 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index 7d5ebc02..a9525831 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -460,6 +460,9 @@ mod common { w.dcyc().bits(dummy_cycles).instruction().bits(ir).fmode().bits(fmode) }; + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + let w = w.isize().bits(instruction.size()); + w.imode() .bits(imode) .admode() @@ -479,6 +482,25 @@ mod common { self.rb.tcr.write(|w| unsafe { w.dcyc().bits(dummy_cycles) }); self.rb.cr.modify(|_, w| unsafe { w.fmode().bits(fmode) }); } + + // Write alternate-bytes + self.rb.abr.write(|w| unsafe { + w.alternate().bits(alternate_bytes.bits()) + }); + + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + if instruction != XspiWord::None { + self.rb.ir.write(|w| unsafe { + w.instruction().bits(instruction.bits()) + }); + } + + if address != XspiWord::None { + // Write the address. The transaction starts on the next write + // to DATA, unless there is no DATA phase configured, in which + // case it starts here. + self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); + } } /// Begin a write over the XSPI interface. This is mostly useful for use with @@ -596,33 +618,9 @@ mod common { // Setup extended mode. Typically no dummy cycles in write mode self.setup_extended(instruction, address, alternate_bytes, 0, !data.is_empty(), false); - // Write alternate-bytes - self.rb.abr.write(|w| unsafe { - w.alternate().bits(alternate_bytes.bits()) - }); - - if instruction != XspiWord::None { - // Write instruction. If there is no address or data phase, the - // transaction starts here. - #[cfg(any(feature = "rm0433", feature = "rm0399"))] - { - let ir = instruction.bits_u8()?; - self.rb.ccr.modify(|_, w| unsafe { w.instruction().bits(ir) }); - } - #[cfg(any(feature = "rm0455", feature = "rm0468"))] - self.rb.ir.write(|w| unsafe { - w.instruction().bits(instruction.bits()) - }); - } - - if address != XspiWord::None { - // Write the address. The transaction starts on the next write - // to DATA, unless there is no DATA phase configured, in which - // case it starts here. - self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); - } // Write data to the FIFO in a byte-wise manner. + // Transaction starts here unsafe { for byte in data { ptr::write_volatile(&self.rb.dr as *const _ as *mut u8, *byte); @@ -765,37 +763,11 @@ mod common { .dlr .write(|w| unsafe { w.dl().bits(dest.len() as u32 - 1) }); - // Setup extended mode. Read operations always have a data phase. if there is no - // address, this will start the transaction + // Setup extended mode. Read operations always have a data phase. + // Transaction starts here self.setup_extended(instruction, address, alternate_bytes, dummy_cycles, true, true); - // Write alternate-bytes - self.rb.abr.write(|w| unsafe { - w.alternate().bits(alternate_bytes.bits()) - }); - - /* - * p894 - * - * If neither the address register (QUADSPI_AR) nor the data register (QUADSPI_DR) - * need to be updated for a particular command, then the command sequence starts as - * soon as QUADSPI_CCR is written. This is the case when both ADMODE and DMODE are - * 00, or if just ADMODE = 00 when in indirect read mode (FMODE = 01). - * - * When an address is required (ADMODE is not 00) and the data register does not - * need to be written (when FMODE = 01 or DMODE = 00), the command sequence starts - * as soon as the address is updated with a write to QUADSPI_AR. - * - * In case of data transmission (FMODE = 00 and DMODE! = 00), the communication - * start is triggered by a write in the FIFO through QUADSPI_DR. - */ - - // Write the address if there is one, transaction starts here - if address != XspiWord::None { - self.rb.ar.write(|w| unsafe { w.address().bits(address.bits()) }); - } - // Wait for the transaction to complete while self.rb.sr.read().tcf().bit_is_clear() {} From d6cadcbed9c0780592cb046068fb3bc12f7ffe79 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Mon, 12 Jul 2021 20:37:43 +0200 Subject: [PATCH 7/9] Octospi support for RM0455 parts, mostly commented out for now REC and peripheral names are wrong on master, already fixed on the stm32h725_735_730 branch --- src/xspi/mod.rs | 26 +++++++++++++++++--------- src/xspi/octospi.rs | 5 +++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index a9525831..505ea82c 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -373,13 +373,21 @@ mod common { } pub(super) fn get_clock(clocks: &CoreClocks) -> Option { - let d1ccipr = unsafe { (*stm32::RCC::ptr()).d1ccipr.read() }; - - match d1ccipr.$ccip().variant() { - stm32::rcc::d1ccipr::[< $ccip:upper _A >]::RCC_HCLK3 => Some(clocks.hclk()), - stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PLL1_Q => clocks.pll1_q_ck(), - stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PLL2_R => clocks.pll2_r_ck(), - stm32::rcc::d1ccipr::[< $ccip:upper _A >]::PER => clocks.per_ck(), + #[cfg(not(feature = "rm0455"))] + use stm32::rcc::d1ccipr as ccipr; + #[cfg(feature = "rm0455")] + use stm32::rcc::cdccipr as ccipr; + + #[cfg(not(feature = "rm0455"))] + let ccipr = unsafe { (*stm32::RCC::ptr()).d1ccipr.read() }; + #[cfg(feature = "rm0455")] + let ccipr = unsafe { (*stm32::RCC::ptr()).cdccipr.read() }; + + match ccipr.$ccip().variant() { + ccipr::[< $ccip:upper _A >]::RCC_HCLK3 => Some(clocks.hclk()), + ccipr::[< $ccip:upper _A >]::PLL1_Q => clocks.pll1_q_ck(), + ccipr::[< $ccip:upper _A >]::PLL2_R => clocks.pll2_r_ck(), + ccipr::[< $ccip:upper _A >]::PER => clocks.per_ck(), } } @@ -795,8 +803,8 @@ mod common { #[cfg(any(feature = "rm0433", feature = "rm0399"))] xspi_impl! { stm32::QUADSPI, rec::Qspi, qspisel } - #[cfg(any(feature = "rm0455", feature = "rm0468"))] + #[cfg(any(feature = "rm0468"))] // TODO feature = "rm0455", xspi_impl! { stm32::OCTOSPI1, rec::Octospi1, octospisel } - #[cfg(any(feature = "rm0455", feature = "rm0468"))] + #[cfg(any(feature = "rm0468"))] // TODO feature = "rm0455", xspi_impl! { stm32::OCTOSPI2, rec::Octospi2, octospisel } } diff --git a/src/xspi/octospi.rs b/src/xspi/octospi.rs index 620cd679..84ce9c1b 100644 --- a/src/xspi/octospi.rs +++ b/src/xspi/octospi.rs @@ -342,5 +342,6 @@ macro_rules! octospi_impl { }; } -octospi_impl! { octospi1_unchecked, stm32::OCTOSPI1, rec::Octospi1 } -octospi_impl! { octospi2_unchecked, stm32::OCTOSPI2, rec::Octospi2 } +// TODO +//octospi_impl! { octospi1_unchecked, stm32::OCTOSPI1, rec::Octospi1 } +//octospi_impl! { octospi2_unchecked, stm32::OCTOSPI2, rec::Octospi2 } From 01aa7528ce9b58911082ea9bd412c9d4a83c53e6 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Mon, 12 Jul 2021 20:54:14 +0200 Subject: [PATCH 8/9] Test Octospi example on a part that has octospi --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 32ed252b..976fc6eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,6 +143,10 @@ required-features = ["fmc"] name = "qspi" required-features = ["xspi", "rm0433"] +[[example]] +name = "octospi" +required-features = ["xspi", "rm0468"] + [[example]] name = "sdmmc" required-features = ["sdmmc", "rm0433"] From ad7d8b509caf9a101a3d37bd60ebfe0e9844e8bb Mon Sep 17 00:00:00 2001 From: Alex Norman Date: Tue, 27 Jul 2021 19:44:10 -0700 Subject: [PATCH 9/9] Option for instruction on platforms that only support 8bit or none --- src/xspi/mod.rs | 54 +++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/xspi/mod.rs b/src/xspi/mod.rs index a9525831..ec4d5d59 100644 --- a/src/xspi/mod.rs +++ b/src/xspi/mod.rs @@ -52,29 +52,40 @@ #[cfg(any(feature = "rm0433", feature = "rm0399"))] mod qspi; #[cfg(any(feature = "rm0433", feature = "rm0399"))] -pub use common::{ - Bank, Xspi as Qspi, XspiError as QspiError, XspiMode as QspiMode, - XspiWord as QspiWord, +pub use { + common::{ + Bank, Xspi as Qspi, XspiError as QspiError, XspiInst as QspiInst, + XspiMode as QspiMode, XspiWord as QspiWord, + }, + qspi::QspiExt as XspiExt, }; -#[cfg(any(feature = "rm0433", feature = "rm0399"))] -pub use qspi::QspiExt as XspiExt; // Octospi #[cfg(any(feature = "rm0455", feature = "rm0468"))] mod octospi; + #[cfg(any(feature = "rm0455", feature = "rm0468"))] -pub use common::{ - Xspi as Octospi, XspiError as OctospiError, XspiMode as OctospiMode, - XspiWord as OctospiWord, +pub use { + common::{ + Xspi as Octospi, XspiError as OctospiError, XspiInst as OctospiInst, + XspiMode as OctospiMode, XspiWord as OctospiWord, + }, + octospi::OctospiExt as XspiExt, }; -#[cfg(any(feature = "rm0455", feature = "rm0468"))] -pub use octospi::OctospiExt as XspiExt; // Both pub use common::{Config, Event, SamplingEdge}; /// This modulate contains functionality common to both Quad and Octo SPI mod common { + /// Instruction word used by the XSPI interface + #[cfg(any(feature = "rm0455", feature = "rm0468"))] + pub type XspiInst = XspiWord; + + /// Instruction word used by the XSPI interface + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + pub type XspiInst = Option; + use crate::{ rcc::{rec, CoreClocks}, stm32, @@ -122,7 +133,7 @@ mod common { WordTooLarge, } - /// Instruction, Address or Alternate Byte word used by the XSPI interface + /// Address or Alternate Byte word used by the XSPI interface #[derive(Debug, Copy, Clone, PartialEq)] pub enum XspiWord { None, @@ -150,15 +161,6 @@ mod common { XspiWord::U24(x) | XspiWord::U32(x) => x, } } - #[inline(always)] - #[cfg(any(feature = "rm0433", feature = "rm0399"))] - fn bits_u8(self) -> Result { - match self { - XspiWord::None => Ok(0), - XspiWord::U8(x) => Ok(x), - _ => Err(XspiError::WordTooLarge), - } - } } /// Sampling mode for the XSPI interface @@ -441,12 +443,16 @@ mod common { /// * `dummy_cycles` - The number of dummy cycles between the alternate-bytes /// and the data phase. /// * `data` - true is there is a data phase, false for no data phase. - fn setup_extended(&mut self, instruction: XspiWord, address: XspiWord, + fn setup_extended(&mut self, instruction: XspiInst, address: XspiWord, alternate_bytes: XspiWord, dummy_cycles: u8, data: bool, read: bool) { let fmode = if read { 0b01 } else { 0b00 }; let mode = self.mode.reg_value(); + #[cfg(any(feature = "rm0433", feature = "rm0399"))] + let imode = if instruction.is_some() { mode } else { 0 }; + #[cfg(any(feature = "rm0455", feature = "rm0468"))] let imode = if instruction != XspiWord::None { mode } else { 0 }; + let admode = if address != XspiWord::None { mode } else { 0 }; let abmode = if alternate_bytes != XspiWord::None { mode } else { 0 }; let dmode = if data { mode } else { 0 }; @@ -456,7 +462,7 @@ mod common { self.rb.ccr.modify(|_, w| unsafe { #[cfg(any(feature = "rm0433", feature = "rm0399"))] let w = { - let ir = instruction.bits_u8().unwrap(); + let ir = instruction.unwrap_or(0); w.dcyc().bits(dummy_cycles).instruction().bits(ir).fmode().bits(fmode) }; @@ -595,7 +601,7 @@ mod common { /// Panics if the length of `data` is greater than the size of the XSPI /// hardware FIFO (32 bytes). pub fn write_extended(&mut self, - instruction: XspiWord, + instruction: XspiInst, address: XspiWord, alternate_bytes: XspiWord, data: &[u8]) -> Result<(), XspiError> { @@ -735,7 +741,7 @@ mod common { /// XSPI hardware FIFO (32 bytes). Panics if the length of `dest` is /// zero. Panics if the number of dummy cycles is not 0 - 31 inclusive. pub fn read_extended(&mut self, - instruction: XspiWord, + instruction: XspiInst, address: XspiWord, alternate_bytes: XspiWord, dummy_cycles: u8,