From c82db162fe60df5d5de72b4b40345ffef4f94e81 Mon Sep 17 00:00:00 2001 From: x-yl Date: Fri, 26 May 2023 12:03:42 +0100 Subject: [PATCH] Add a basic blocking QSPI interface This module implements the QuadSPI interface which allows high speed communication with external flash memory. Limitations - Interrupts are not supported. - Status polling mode is not supported. --- CHANGELOG.md | 2 + src/lib.rs | 2 + src/qspi.rs | 783 ++++++++++++++++++++++++++++++++++++++++++++++ src/rcc/enable.rs | 8 + 4 files changed, 795 insertions(+) create mode 100644 src/qspi.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a35aaa..9dc77cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Extended 64-bit monotonic timer [#640] + - Basic blocking QSPI interface [#645] ### Fixed @@ -26,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). [#635]: https://github.com/stm32-rs/stm32f4xx-hal/pull/635 [#636]: https://github.com/stm32-rs/stm32f4xx-hal/pull/636 [#640]: https://github.com/stm32-rs/stm32f4xx-hal/pull/640 +[#645]: https://github.com/stm32-rs/stm32f4xx-hal/pull/645 ## [v0.16.0] - 2023-05-07 diff --git a/src/lib.rs b/src/lib.rs index ef12a548..6904face 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,6 +154,8 @@ pub mod prelude; #[cfg(feature = "device-selected")] pub mod qei; #[cfg(feature = "device-selected")] +pub mod qspi; +#[cfg(feature = "device-selected")] pub mod rcc; #[cfg(feature = "device-selected")] pub mod rtc; diff --git a/src/qspi.rs b/src/qspi.rs new file mode 100644 index 00000000..d9990e86 --- /dev/null +++ b/src/qspi.rs @@ -0,0 +1,783 @@ +//! QSPI interface +//! +//! This module implements the QuadSPI interface which allows high speed +//! communication with external flash memory. +//! +//! Limitations: +//! - Interrupts are not supported. +//! - Status polling mode is not supported. + +// Based on work by Unizippro in stm32l4xx-hal. + +use core::{any::TypeId, marker::PhantomData}; + +use crate::{ + gpio::alt::{self, QuadSpiBank}, + pac::QUADSPI, + rcc::Enable, +}; + +pub trait QspiPins { + type Pins; +} + +impl QspiPins for T +where + T: QuadSpiBank, +{ + type Pins = (T::Ncs, T::Io0, T::Io1, T::Io2, T::Io3, alt::quadspi::Clk); +} + +pub struct DualFlash; + +impl QspiPins for DualFlash { + type Pins = ( + alt::quadspi::Bk1Ncs, + alt::quadspi::Bk2Ncs, + alt::quadspi::Bk1Io0, + alt::quadspi::Bk1Io1, + alt::quadspi::Bk1Io2, + alt::quadspi::Bk1Io3, + alt::quadspi::Bk2Io0, + alt::quadspi::Bk2Io1, + alt::quadspi::Bk2Io2, + alt::quadspi::Bk2Io3, + alt::quadspi::Clk, + ); +} + +pub struct Qspi { + qspi: QUADSPI, + config: QspiConfig, + _marker: PhantomData, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct QspiConfig { + /// This field defines the scaler factor for generating CLK based on the AHB clock + /// (value+1). + clock_prescaler: u8, + /// Number of bytes in Flash memory = 2^[FSIZE+1] + flash_size: FlashSize, + address_size: AddressSize, + /// This bit indicates the level that CLK takes between commands Mode 0(low) / mode 3(high) + clock_mode: ClockMode, + /// FIFO threshold level (Activates FTF, QUADSPI_SR[2]) 0-15. + fifo_threshold: u8, + sample_shift: SampleShift, + /// CSHT+1 defines the minimum number of CLK cycles which the chip select (nCS) must + /// remain high between commands issued to the Flash memory. + chip_select_high_time: u8, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct FlashSize { + // actual flash size in bytes is 2^[inner+1] + inner: u8, +} + +impl FlashSize { + pub const fn new_raw(inner: u8) -> Self { + Self { inner } + } + + pub const fn from_megabytes(megabytes: u32) -> Self { + let pow = 32 - megabytes.leading_zeros(); + Self::new_raw((pow + 18) as u8) + } +} + +impl Default for QspiConfig { + fn default() -> QspiConfig { + QspiConfig { + clock_prescaler: 0, + flash_size: FlashSize { inner: 0 }, + address_size: AddressSize::Addr24Bit, + clock_mode: ClockMode::Mode0, + fifo_threshold: 1, + sample_shift: SampleShift::HalfACycle, + chip_select_high_time: 1, + } + } +} + +impl QspiConfig { + pub fn clock_prescaler(mut self, clock_prescaler: u8) -> Self { + self.clock_prescaler = clock_prescaler; + self + } + + pub fn flash_size(mut self, flash_size: FlashSize) -> Self { + self.flash_size = flash_size; + self + } + + pub fn address_size(mut self, address_size: AddressSize) -> Self { + self.address_size = address_size; + self + } + + pub fn clock_mode(mut self, clock_mode: ClockMode) -> Self { + self.clock_mode = clock_mode; + self + } + + pub fn fifo_threshold(mut self, fifo_threshold: u8) -> Self { + self.fifo_threshold = fifo_threshold; + self + } + + pub fn sample_shift(mut self, sample_shift: SampleShift) -> Self { + self.sample_shift = sample_shift; + self + } + + pub fn chip_select_high_time(mut self, chip_select_high_time: u8) -> Self { + self.chip_select_high_time = chip_select_high_time; + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum QspiMode { + SingleChannel = 0b01, + DualChannel = 0b10, + QuadChannel = 0b11, +} + +impl Default for QspiMode { + fn default() -> Self { + QspiMode::SingleChannel + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum AddressSize { + Addr8Bit = 0b00, + Addr16Bit = 0b01, + Addr24Bit = 0b10, + Addr32Bit = 0b11, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SampleShift { + None, + HalfACycle, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ClockMode { + Mode0, + Mode3, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum QspiError { + Busy, + Address, + Unknown, + IllegalArgument, +} + +impl Qspi { + /// Enable the QSPI peripheral with the given configuration. + pub fn new(qspi: QUADSPI, _pins: PINS::Pins, config: QspiConfig) -> Self { + // Enable quad SPI in the clocks. + unsafe { + QUADSPI::enable_unchecked(); + } + + // Disable QUADSPI before configuring it. + qspi.cr.modify(|_, w| w.en().clear_bit()); + + // Clear all pending flags. + qspi.fcr.write(|w| { + w.ctof() + .set_bit() + .csmf() + .set_bit() + .ctcf() + .set_bit() + .ctef() + .set_bit() + }); + + let mut unit = Qspi { + qspi, + config, + _marker: PhantomData, + }; + unit.apply_config(config); + unit + } + + pub fn is_busy(&self) -> bool { + self.qspi.sr.read().busy().bit_is_set() + } + + /// Aborts any ongoing transaction + /// Note can cause problems if aborting writes to flash satus register + pub fn abort_transmission(&mut self) { + self.qspi.cr.modify(|_, w| w.abort().set_bit()); + while self.qspi.sr.read().busy().bit_is_set() {} + } + + pub fn apply_config(&mut self, config: QspiConfig) { + if self.qspi.sr.read().busy().bit_is_set() { + self.abort_transmission(); + } + + self.qspi + .cr + .modify(|_, w| unsafe { w.fthres().bits(config.fifo_threshold as u8) }); + + while self.qspi.sr.read().busy().bit_is_set() {} + + self.qspi.cr.modify(|_, w| unsafe { + w.prescaler() + .bits(config.clock_prescaler as u8) + .sshift() + .bit(config.sample_shift == SampleShift::HalfACycle) + .fsel() + .bit(TypeId::of::() == TypeId::of::()) + .dfm() + .bit(TypeId::of::() == TypeId::of::()) + }); + while self.is_busy() {} + + // Modify DCR with flash size, CSHT and clock mode + self.qspi.dcr.modify(|_, w| unsafe { + w.fsize() + .bits(config.flash_size.inner) + .csht() + .bits(config.chip_select_high_time as u8) + .ckmode() + .bit(config.clock_mode == ClockMode::Mode3) + }); + while self.is_busy() {} + + // Enable QSPI + self.qspi.cr.modify(|_, w| w.en().set_bit()); + while self.is_busy() {} + + self.config = config; + } + + /// Perform an indirect read operation with the given command. + pub fn indirect_read(&mut self, command: QspiReadCommand) -> Result<(), QspiError> { + let buffer = command.data.0; + if buffer.len() == 0 { + // Illegal to perform an indirect read with no buffer. + return Err(QspiError::IllegalArgument); + } + + if self.is_busy() { + return Err(QspiError::Busy); + } + + // If double data rate change shift + if command.double_data_rate { + self.qspi.cr.modify(|_, w| w.sshift().bit(false)); + } + while self.is_busy() {} + + // Clear the transfer complete flag. + self.qspi.fcr.modify(|_, w| w.ctcf().set_bit()); + + let dmode: u8 = command.data.1 as u8; + let mut instruction: u8 = 0; + let mut imode: u8 = 0; + let mut admode: u8 = 0; + let mut adsize: u8 = 0; + let mut abmode: u8 = 0; + let mut absize: u8 = 0; + + // Write the length and format of data + self.qspi + .dlr + .write(|w| unsafe { w.dl().bits(buffer.len() as u32 - 1) }); + + // Write instruction mode + if let Some((inst, mode)) = command.instruction { + imode = mode as u8; + instruction = inst; + } + + // Note Address mode + if let Some((_, mode)) = command.address { + admode = mode as u8; + adsize = self.config.address_size as u8; + } + + // Write alternate bytes + if let Some((a_bytes, mode)) = command.alternate_bytes { + abmode = mode as u8; + absize = a_bytes.len() as u8 - 1; + + self.qspi.abr.write(|w| { + let mut reg_byte: u32 = 0; + for (i, element) in a_bytes.iter().rev().enumerate() { + reg_byte |= (*element as u32) << (i * 8); + } + unsafe { w.alternate().bits(reg_byte) } + }); + } + + // Write CCR register with instruction etc. + self.qspi.ccr.modify(|_, w| unsafe { + w.fmode() + .bits(0b01 /* Indirect read */) + .admode() + .bits(admode) + .adsize() + .bits(adsize) + .abmode() + .bits(abmode) + .absize() + .bits(absize) + .ddrm() + .bit(command.double_data_rate) + .dcyc() + .bits(command.dummy_cycles) + .dmode() + .bits(dmode) + .imode() + .bits(imode) + .instruction() + .bits(instruction) + }); + + // Write address, triggers send + if let Some((addr, _)) = command.address { + self.qspi.ar.write(|w| unsafe { w.address().bits(addr) }); + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Address); + } + } + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Unknown); + } + + // Read data from the buffer + let mut b = buffer.iter_mut(); + while self.qspi.sr.read().tcf().bit_is_clear() { + if self.qspi.sr.read().ftf().bit_is_set() { + if let Some(v) = b.next() { + unsafe { + *v = core::ptr::read_volatile(self.qspi.dr.as_ptr() as *const u8); + } + } else { + // OVERFLOW + self.abort_transmission(); + break; + } + } + } + // When transfer complete, empty fifo buffer + while self.qspi.sr.read().flevel().bits() > 0 { + if let Some(v) = b.next() { + unsafe { + *v = core::ptr::read_volatile(self.qspi.dr.as_ptr() as *const u8); + } + } else { + // OVERFLOW + self.abort_transmission(); + break; + } + } + // If double data rate set shift back to original and if busy abort. + if command.double_data_rate { + if self.is_busy() { + self.abort_transmission(); + } + self.qspi.cr.modify(|_, w| { + w.sshift() + .bit(self.config.sample_shift == SampleShift::HalfACycle) + }); + } + while self.is_busy() {} + self.qspi.fcr.write(|w| w.ctcf().set_bit()); + Ok(()) + } + + /// Perform an indirect write with the given command. + pub fn indirect_write(&mut self, command: QspiWriteCommand) -> Result<(), QspiError> { + if self.is_busy() { + return Err(QspiError::Busy); + } + // Clear the transfer complete flag. + self.qspi.fcr.modify(|_, w| w.ctcf().set_bit()); + + let mut dmode: u8 = 0; + let mut instruction: u8 = 0; + let mut imode: u8 = 0; + let mut admode: u8 = 0; + let mut adsize: u8 = 0; + let mut abmode: u8 = 0; + let mut absize: u8 = 0; + + // Write the length and format of data + if let Some((data, mode)) = command.data { + self.qspi + .dlr + .write(|w| unsafe { w.dl().bits(data.len() as u32 - 1) }); + dmode = mode as u8; + } + + // Write instruction mode + if let Some((inst, mode)) = command.instruction { + imode = mode as u8; + instruction = inst; + } + + // Note Address mode + if let Some((_, mode)) = command.address { + admode = mode as u8; + adsize = self.config.address_size as u8; + } + + // Write alternate bytes + if let Some((a_bytes, mode)) = command.alternate_bytes { + abmode = mode as u8; + + absize = a_bytes.len() as u8 - 1; + + self.qspi.abr.write(|w| { + let mut reg_byte: u32 = 0; + for (i, element) in a_bytes.iter().rev().enumerate() { + reg_byte |= (*element as u32) << (i * 8); + } + unsafe { w.alternate().bits(reg_byte) } + }); + } + + if command.double_data_rate { + self.qspi.cr.modify(|_, w| w.sshift().bit(false)); + } + + // Write CCR register with instruction etc. + self.qspi.ccr.modify(|_, w| unsafe { + w.fmode() + .bits(0b00 /* Indirect write mode */) + .admode() + .bits(admode) + .adsize() + .bits(adsize) + .abmode() + .bits(abmode) + .absize() + .bits(absize) + .ddrm() + .bit(command.double_data_rate) + .dcyc() + .bits(command.dummy_cycles) + .dmode() + .bits(dmode) + .imode() + .bits(imode) + .instruction() + .bits(instruction) + }); + + // Write address, triggers send + if let Some((addr, _)) = command.address { + self.qspi.ar.write(|w| unsafe { w.address().bits(addr) }); + } + + // Transfer error + if self.qspi.sr.read().tef().bit_is_set() { + return Err(QspiError::Unknown); + } + + // Write data to the FIFO + if let Some((data, _)) = command.data { + for byte in data { + while self.qspi.sr.read().ftf().bit_is_clear() {} + unsafe { + core::ptr::write_volatile(self.qspi.dr.as_ptr() as *mut u8, *byte); + } + } + } + + while self.qspi.sr.read().tcf().bit_is_clear() {} + + self.qspi.fcr.write(|w| w.ctcf().set_bit()); + + if command.double_data_rate { + self.qspi.cr.modify(|_, w| { + w.sshift() + .bit(self.config.sample_shift == SampleShift::HalfACycle) + }); + } + Ok(()) + } + + /// Put the QSPI peripheral into memory mapped mode. Returns a slice to the + /// memory mapped region. + /// Provide the command for the read operation for your flash chip. + pub fn memory_mapped<'a>( + &'a mut self, + command: QspiMemoryMappedConfig, + ) -> Result, QspiError> { + if self.is_busy() { + return Err(QspiError::Busy); + } + + // If double data rate change shift + if command.double_data_rate { + self.qspi.cr.modify(|_, w| w.sshift().bit(false)); + } + while self.is_busy() {} + + // Clear the transfer complete flag. + self.qspi.fcr.modify(|_, w| w.ctcf().set_bit()); + + let mut abmode: u8 = 0; + let mut absize: u8 = 0; + let mut imode: u8 = 0; + let mut instruction: u8 = 0; + + if let Some((inst, mode)) = command.instruction { + imode = mode as u8; + instruction = inst; + } + + // Write alternate bytes + if let Some((a_bytes, mode)) = command.alternate_bytes { + abmode = mode as u8; + absize = a_bytes.len() as u8 - 1; + + self.qspi.abr.write(|w| { + let mut reg_byte: u32 = 0; + for (i, element) in a_bytes.iter().rev().enumerate() { + reg_byte |= (*element as u32) << (i * 8); + } + unsafe { w.alternate().bits(reg_byte) } + }); + } + + self.qspi.ccr.modify(|_, w| unsafe { + w.fmode() + .bits(0b11 /* Memory mapped mode */) + .admode() + .bits(command.address_mode as u8) + .adsize() + .bits(self.config.address_size as u8) + .abmode() + .bits(abmode) + .absize() + .bits(absize) + .ddrm() + .bit(command.double_data_rate) + .dcyc() + .bits(command.dummy_cycles) + .dmode() + .bits(command.data_mode as u8) + .imode() + .bits(imode) + .instruction() + .bits(instruction) + }); + + let buffer = unsafe { + core::slice::from_raw_parts( + 0x9000_0000 as *const u8, + 2usize.pow(self.config.flash_size.inner as u32 + 1), + ) + }; + + Ok(MemoryMapped { + qspi: self, + buffer, + }) + } +} + +/// This struct is used to configure a write command for the QSPI peripheral. +/// Specify None for any phase to skip it in the transaction. +/// Each phase requires a mode to be specified. +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub struct QspiWriteCommand<'a> { + instruction: Option<(u8, QspiMode)>, + address: Option<(u32, QspiMode)>, + alternate_bytes: Option<(&'a [u8], QspiMode)>, + dummy_cycles: u8, + data: Option<(&'a [u8], QspiMode)>, + double_data_rate: bool, +} + +impl<'a> QspiWriteCommand<'a> { + pub fn new() -> Self { + Self::default() + } + + pub fn instruction(mut self, instruction: u8, instruction_mode: QspiMode) -> Self { + self.instruction = Some((instruction, instruction_mode)); + self + } + + pub fn address(mut self, address: u32, address_mode: QspiMode) -> Self { + self.address = Some((address, address_mode)); + self + } + + pub fn alternate_bytes( + mut self, + alternate_bytes: &'a [u8], + alternate_bytes_mode: QspiMode, + ) -> Self { + self.alternate_bytes = Some((alternate_bytes, alternate_bytes_mode)); + self + } + + pub fn dummy_cycles(mut self, dummy_cycles: u8) -> Self { + self.dummy_cycles = dummy_cycles; + self + } + + pub fn data(mut self, data: &'a [u8], data_mode: QspiMode) -> Self { + self.data = Some((data, data_mode)); + self + } + + pub fn double_data_rate(mut self, double_data_rate: bool) -> Self { + self.double_data_rate = double_data_rate; + self + } +} + +/// QSPI Memory mapped mode configuration +/// The configuration is used by the QSPI peripheral to access the external memory. +/// Specify the read command for your external memory. +#[derive(Copy, Clone, Debug, PartialEq, Default)] +pub struct QspiMemoryMappedConfig<'a> { + instruction: Option<(u8, QspiMode)>, + address_mode: QspiMode, + alternate_bytes: Option<(&'a [u8], QspiMode)>, + dummy_cycles: u8, + data_mode: QspiMode, + double_data_rate: bool, +} + +impl<'a> QspiMemoryMappedConfig<'a> { + pub fn new() -> Self { + Self::default() + } + + pub fn instruction(mut self, instruction: u8, instruction_mode: QspiMode) -> Self { + self.instruction = Some((instruction, instruction_mode)); + self + } + + pub fn address_mode(mut self, address_mode: QspiMode) -> Self { + self.address_mode = address_mode; + self + } + + pub fn alternate_bytes( + mut self, + alternate_bytes: &'a [u8], + alternate_bytes_mode: QspiMode, + ) -> Self { + self.alternate_bytes = Some((alternate_bytes, alternate_bytes_mode)); + self + } + + pub fn dummy_cycles(mut self, dummy_cycles: u8) -> Self { + self.dummy_cycles = dummy_cycles; + self + } + + pub fn data_mode(mut self, data_mode: QspiMode) -> Self { + self.data_mode = data_mode; + self + } + + pub fn double_data_rate(mut self, double_data_rate: bool) -> Self { + self.double_data_rate = double_data_rate; + self + } +} + +/// This struct is used to configure a read command for the QSPI peripheral. +/// Specify None for any phase to skip it in the transaction. +/// Each phase requires a mode to be specified. +#[derive(Debug, PartialEq)] +pub struct QspiReadCommand<'a> { + instruction: Option<(u8, QspiMode)>, + address: Option<(u32, QspiMode)>, + alternate_bytes: Option<(&'a [u8], QspiMode)>, + dummy_cycles: u8, + data: (&'a mut [u8], QspiMode), + double_data_rate: bool, +} + +impl<'a> QspiReadCommand<'a> { + pub fn new(data: &'a mut [u8], data_mode: QspiMode) -> Self { + Self { + instruction: None, + address: None, + alternate_bytes: None, + dummy_cycles: 0, + data: (data, data_mode), + double_data_rate: false, + } + } + + pub fn instruction(mut self, instruction: u8, instruction_mode: QspiMode) -> Self { + self.instruction = Some((instruction, instruction_mode)); + self + } + + pub fn address(mut self, address: u32, address_mode: QspiMode) -> Self { + self.address = Some((address, address_mode)); + self + } + + pub fn alternate_bytes( + mut self, + alternate_bytes: &'a [u8], + alternate_bytes_mode: QspiMode, + ) -> Self { + self.alternate_bytes = Some((alternate_bytes, alternate_bytes_mode)); + self + } + + pub fn dummy_cycles(mut self, dummy_cycles: u8) -> Self { + self.dummy_cycles = dummy_cycles; + self + } + + pub fn data(mut self, data: &'a mut [u8], data_mode: QspiMode) -> Self { + self.data = (data, data_mode); + self + } + + pub fn double_data_rate(mut self, double_data_rate: bool) -> Self { + self.double_data_rate = double_data_rate; + self + } +} + +pub struct MemoryMapped<'a, PINS: QspiPins + 'static> { + qspi: &'a mut Qspi, + buffer: &'a [u8], +} + +impl<'a, PINS: QspiPins> MemoryMapped<'a, PINS> { + pub fn buffer(&self) -> &[u8] { + return self.buffer; + } +} + +impl<'a, PINS: QspiPins> Drop for MemoryMapped<'a, PINS> { + fn drop(&mut self) { + self.qspi.abort_transmission(); + } +} diff --git a/src/rcc/enable.rs b/src/rcc/enable.rs index 6064ca71..c4b0a33a 100644 --- a/src/rcc/enable.rs +++ b/src/rcc/enable.rs @@ -69,6 +69,14 @@ macro_rules! bus { } } +impl crate::Sealed for crate::pac::QUADSPI {} +impl RccBus for crate::pac::QUADSPI { + type Bus = AHB3; +} + +bus_enable! { QUADSPI => 1 } +bus_reset! { QUADSPI => 1 } + bus! { CRC => (AHB1, 12), DMA1 => (AHB1, 21),