From 6a802c47083e4f6bb7b7c6c06fd2ef3e291a5212 Mon Sep 17 00:00:00 2001 From: Mateusz Butkiewicz Date: Wed, 22 Mar 2023 08:44:58 +0100 Subject: [PATCH] feat(stm32:qspi): add support for QSPI in stm32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented with help of Tomasz GrzeĊ› . --- embassy-stm32/build.rs | 7 + embassy-stm32/src/lib.rs | 3 +- embassy-stm32/src/qspi/mod.rs | 342 ++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 embassy-stm32/src/qspi/mod.rs diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index dbfc1370d..3780c5a40 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -427,6 +427,12 @@ fn main() { (("sdmmc", "D6"), quote!(crate::sdmmc::D6Pin)), (("sdmmc", "D6"), quote!(crate::sdmmc::D7Pin)), (("sdmmc", "D8"), quote!(crate::sdmmc::D8Pin)), + (("quadspi", "BK1_IO0"), quote!(crate::qspi::D0Pin)), + (("quadspi", "BK1_IO1"), quote!(crate::qspi::D1Pin)), + (("quadspi", "BK1_IO2"), quote!(crate::qspi::D2Pin)), + (("quadspi", "BK1_IO3"), quote!(crate::qspi::D3Pin)), + (("quadspi", "CLK"), quote!(crate::qspi::SckPin)), + (("quadspi", "BK1_NCS"), quote!(crate::qspi::NSSPin)), ].into(); for p in METADATA.peripherals { @@ -507,6 +513,7 @@ fn main() { (("dcmi", "PSSI"), quote!(crate::dcmi::FrameDma)), // SDMMCv1 uses the same channel for both directions, so just implement for RX (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), + (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), ] .into(); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index eeaa04f67..8dc4df2dc 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -48,6 +48,8 @@ pub mod crc; ))] pub mod flash; pub mod pwm; +#[cfg(quadspi)] +pub mod qspi; #[cfg(rng)] pub mod rng; #[cfg(sdmmc)] @@ -60,7 +62,6 @@ pub mod usart; pub mod usb; #[cfg(otg)] pub mod usb_otg; - #[cfg(iwdg)] pub mod wdg; diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs new file mode 100644 index 000000000..f375d1b46 --- /dev/null +++ b/embassy-stm32/src/qspi/mod.rs @@ -0,0 +1,342 @@ +#![macro_use] + +use embassy_hal_common::{into_ref, PeripheralRef}; + +use crate::dma::TransferOptions; +use crate::gpio::sealed::AFType; +use crate::gpio::AnyPin; +use crate::pac::quadspi::Quadspi as Regs; +use crate::rcc::RccPeripheral; +use crate::{peripherals, Peripheral}; + +pub struct QspiWidth; + +#[allow(dead_code)] +impl QspiWidth { + pub const NONE: u8 = 0b00; + pub const SING: u8 = 0b01; + pub const DUAL: u8 = 0b10; + pub const QUAD: u8 = 0b11; +} + +struct QspiMode; + +#[allow(dead_code)] +impl QspiMode { + pub const INDIRECT_WRITE: u8 = 0b00; + pub const INDIRECT_READ: u8 = 0b01; + pub const AUTO_POLLING: u8 = 0b10; + pub const MEMORY_MAPPED: u8 = 0b11; +} + +pub struct QspiTransaction { + pub iwidth: u8, + pub awidth: u8, + pub dwidth: u8, + pub instruction: u8, + pub address: Option, + pub dummy: u8, + pub data_len: Option, +} + +impl Default for QspiTransaction { + fn default() -> Self { + Self { + iwidth: QspiWidth::NONE, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: 0, + address: None, + dummy: 0, + data_len: None, + } + } +} + +pub struct Config { + pub memory_size: u8, + pub address_size: u8, + pub prescaler: u8, + pub fifo_threshold: u8, + pub cs_high_time: u8, +} + +impl Default for Config { + fn default() -> Self { + Self { + memory_size: 0, + address_size: 2, + prescaler: 128, + fifo_threshold: 16, + cs_high_time: 4, + } + } +} + +#[allow(dead_code)] +pub struct Qspi<'d, T: Instance, Dma> { + _peri: PeripheralRef<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + nss: Option>, + dma: PeripheralRef<'d, Dma>, + config: Config, +} + +impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> { + pub fn new( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, d0, d1, d2, d3, sck, nss); + + unsafe { + sck.set_as_af(sck.af_num(), AFType::OutputPushPull); + sck.set_speed(crate::gpio::Speed::VeryHigh); + nss.set_as_af(nss.af_num(), AFType::OutputPushPull); + nss.set_speed(crate::gpio::Speed::VeryHigh); + d0.set_as_af(d0.af_num(), AFType::OutputPushPull); + d0.set_speed(crate::gpio::Speed::VeryHigh); + d1.set_as_af(d1.af_num(), AFType::OutputPushPull); + d1.set_speed(crate::gpio::Speed::VeryHigh); + d2.set_as_af(d2.af_num(), AFType::OutputPushPull); + d2.set_speed(crate::gpio::Speed::VeryHigh); + d3.set_as_af(d3.af_num(), AFType::OutputPushPull); + d3.set_speed(crate::gpio::Speed::VeryHigh); + } + + Self::new_inner( + peri, + Some(d0.map_into()), + Some(d1.map_into()), + Some(d2.map_into()), + Some(d3.map_into()), + Some(sck.map_into()), + Some(nss.map_into()), + dma, + config, + ) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + sck: Option>, + nss: Option>, + dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + + T::enable(); + unsafe { + T::REGS.cr().write(|w| w.set_fthres(config.fifo_threshold)); + + while T::REGS.sr().read().busy() {} + + T::REGS.cr().write(|w| { + w.set_prescaler(config.prescaler); + w.set_en(true); + }); + T::REGS.dcr().write(|w| { + w.set_fsize(config.memory_size); + w.set_csht(config.cs_high_time); + w.set_ckmode(false); + }); + } + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + nss, + dma, + config, + } + } + + pub fn command(&mut self, transaction: QspiTransaction) { + unsafe { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction); + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + } + + pub fn read(&mut self, buf: &mut [u8], transaction: QspiTransaction) { + unsafe { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction); + + if let Some(len) = transaction.data_len { + let current_ar = T::REGS.ar().read().address(); + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::INDIRECT_READ); + }); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + for idx in 0..len { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = *(T::REGS.dr().ptr() as *mut u8); + } + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + } + + pub fn write(&mut self, buf: &[u8], transaction: QspiTransaction) { + unsafe { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction); + + if let Some(len) = transaction.data_len { + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::INDIRECT_WRITE); + }); + + for idx in 0..len { + while !T::REGS.sr().read().ftf() {} + *(T::REGS.dr().ptr() as *mut u8) = buf[idx]; + } + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + } + + pub fn read_dma(&mut self, buf: &mut [u8], transaction: QspiTransaction) + where + Dma: QuadDma, + { + unsafe { + self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction); + + let request = self.dma.request(); + let options = TransferOptions::default(); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::INDIRECT_READ); + }); + let current_ar = T::REGS.ar().read().address(); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + self.dma + .start_read(request, T::REGS.dr().ptr() as *mut u8, buf, options); + + T::REGS.cr().modify(|v| v.set_dmaen(true)); + + while self.dma.is_running() {} + } + } + + pub fn write_dma(&mut self, buf: &[u8], transaction: QspiTransaction) + where + Dma: QuadDma, + { + unsafe { + self.setup_transaction(QspiMode::INDIRECT_WRITE, &transaction); + + let request = self.dma.request(); + let options = TransferOptions::default(); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::INDIRECT_WRITE); + }); + + self.dma + .start_write(request, buf, T::REGS.dr().ptr() as *mut u8, options); + + T::REGS.cr().modify(|v| v.set_dmaen(true)); + + while self.dma.is_running() {} + } + } + + fn setup_transaction(&mut self, fmode: u8, transaction: &QspiTransaction) { + unsafe { + T::REGS.fcr().modify(|v| { + v.set_csmf(true); + v.set_ctcf(true); + v.set_ctef(true); + v.set_ctof(true); + }); + + while T::REGS.sr().read().busy() {} + + if let Some(len) = transaction.data_len { + T::REGS.dlr().write(|v| v.set_dl(len as u32 - 1)); + } + + T::REGS.ccr().write(|v| { + v.set_fmode(fmode); + v.set_imode(transaction.iwidth); + v.set_instruction(transaction.instruction); + v.set_admode(transaction.awidth); + v.set_adsize(self.config.address_size); + v.set_dmode(transaction.dwidth); + v.set_abmode(QspiWidth::NONE); + v.set_dcyc(transaction.dummy); + }); + + if let Some(addr) = transaction.address { + T::REGS.ar().write(|v| { + v.set_address(addr); + }); + } + } + } +} + +pub(crate) mod sealed { + use super::*; + + pub trait Instance { + const REGS: Regs; + } +} + +pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(NSSPin, Instance); + +dma_trait!(QuadDma, Instance); + +foreach_peripheral!( + (quadspi, $inst:ident) => { + impl sealed::Instance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +);