diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 30c525521..4abbf8d69 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1091,6 +1091,27 @@ fn main() { (("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)), (("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)), (("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)), + (("hspi", "IO0"), quote!(crate::hspi::D0Pin)), + (("hspi", "IO1"), quote!(crate::hspi::D1Pin)), + (("hspi", "IO2"), quote!(crate::hspi::D2Pin)), + (("hspi", "IO3"), quote!(crate::hspi::D3Pin)), + (("hspi", "IO4"), quote!(crate::hspi::D4Pin)), + (("hspi", "IO5"), quote!(crate::hspi::D5Pin)), + (("hspi", "IO6"), quote!(crate::hspi::D6Pin)), + (("hspi", "IO7"), quote!(crate::hspi::D7Pin)), + (("hspi", "IO8"), quote!(crate::hspi::D8Pin)), + (("hspi", "IO9"), quote!(crate::hspi::D9Pin)), + (("hspi", "IO10"), quote!(crate::hspi::D10Pin)), + (("hspi", "IO11"), quote!(crate::hspi::D11Pin)), + (("hspi", "IO12"), quote!(crate::hspi::D12Pin)), + (("hspi", "IO13"), quote!(crate::hspi::D13Pin)), + (("hspi", "IO14"), quote!(crate::hspi::D14Pin)), + (("hspi", "IO15"), quote!(crate::hspi::D15Pin)), + (("hspi", "DQS0"), quote!(crate::hspi::DQS0Pin)), + (("hspi", "DQS1"), quote!(crate::hspi::DQS1Pin)), + (("hspi", "NCS"), quote!(crate::hspi::NSSPin)), + (("hspi", "CLK"), quote!(crate::hspi::SckPin)), + (("hspi", "NCLK"), quote!(crate::hspi::NckPin)), (("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)), (("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)), (("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)), @@ -1275,6 +1296,7 @@ fn main() { (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), (("octospi", "OCTOSPI1"), quote!(crate::ospi::OctoDma)), + (("hspi", "HSPI1"), quote!(crate::hspi::HspiDma)), (("dac", "CH1"), quote!(crate::dac::DacDma1)), (("dac", "CH2"), quote!(crate::dac::DacDma2)), (("timer", "UP"), quote!(crate::timer::UpDma)), diff --git a/embassy-stm32/src/hspi/enums.rs b/embassy-stm32/src/hspi/enums.rs new file mode 100644 index 000000000..351fc9ec6 --- /dev/null +++ b/embassy-stm32/src/hspi/enums.rs @@ -0,0 +1,411 @@ +//! Enums used in Hspi configuration. + +#[allow(dead_code)] +#[derive(Copy, Clone, defmt::Format)] +pub(crate) enum HspiMode { + IndirectWrite, + IndirectRead, + AutoPolling, + MemoryMapped, +} + +impl Into for HspiMode { + fn into(self) -> u8 { + match self { + HspiMode::IndirectWrite => 0b00, + HspiMode::IndirectRead => 0b01, + HspiMode::AutoPolling => 0b10, + HspiMode::MemoryMapped => 0b11, + } + } +} + +/// Hspi lane width +#[allow(dead_code)] +#[derive(Copy, Clone, defmt::Format)] +pub enum HspiWidth { + /// None + NONE, + /// Single lane + SING, + /// Dual lanes + DUAL, + /// Quad lanes + QUAD, + /// Eight lanes + OCTO, + /// Sixteen lanes + HEXADECA, +} + +impl Into for HspiWidth { + fn into(self) -> u8 { + match self { + HspiWidth::NONE => 0b00, + HspiWidth::SING => 0b01, + HspiWidth::DUAL => 0b10, + HspiWidth::QUAD => 0b11, + HspiWidth::OCTO => 0b100, + HspiWidth::HEXADECA => 0b101, + } + } +} + +/// Flash bank selection +#[allow(dead_code)] +#[derive(Copy, Clone, defmt::Format)] +pub enum FlashSelection { + /// Bank 1 + Flash1, + /// Bank 2 + Flash2, +} + +impl Into for FlashSelection { + fn into(self) -> bool { + match self { + FlashSelection::Flash1 => false, + FlashSelection::Flash2 => true, + } + } +} + +/// Wrap Size +#[allow(dead_code)] +#[allow(missing_docs)] +#[derive(Copy, Clone, defmt::Format)] +pub enum WrapSize { + None, + _16Bytes, + _32Bytes, + _64Bytes, + _128Bytes, +} + +impl Into for WrapSize { + fn into(self) -> u8 { + match self { + WrapSize::None => 0x00, + WrapSize::_16Bytes => 0x02, + WrapSize::_32Bytes => 0x03, + WrapSize::_64Bytes => 0x04, + WrapSize::_128Bytes => 0x05, + } + } +} + +/// Memory Type +#[allow(missing_docs)] +#[allow(dead_code)] +#[derive(Copy, Clone, defmt::Format)] +pub enum MemoryType { + Micron, + Macronix, + Standard, + MacronixRam, + HyperBusMemory, + HyperBusRegister, +} + +impl Into for MemoryType { + fn into(self) -> u8 { + match self { + MemoryType::Micron => 0x00, + MemoryType::Macronix => 0x01, + MemoryType::Standard => 0x02, + MemoryType::MacronixRam => 0x03, + MemoryType::HyperBusMemory => 0x04, + MemoryType::HyperBusRegister => 0x04, + } + } +} + +/// Hspi memory size. +#[allow(missing_docs)] +#[derive(Copy, Clone, defmt::Format)] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 6, + MemorySize::_2KiB => 7, + MemorySize::_4KiB => 8, + MemorySize::_8KiB => 9, + MemorySize::_16KiB => 10, + MemorySize::_32KiB => 11, + MemorySize::_64KiB => 12, + MemorySize::_128KiB => 13, + MemorySize::_256KiB => 14, + MemorySize::_512KiB => 15, + MemorySize::_1MiB => 16, + MemorySize::_2MiB => 17, + MemorySize::_4MiB => 18, + MemorySize::_8MiB => 19, + MemorySize::_16MiB => 20, + MemorySize::_32MiB => 21, + MemorySize::_64MiB => 22, + MemorySize::_128MiB => 23, + MemorySize::_256MiB => 24, + MemorySize::_512MiB => 25, + MemorySize::_1GiB => 26, + MemorySize::_2GiB => 27, + MemorySize::_4GiB => 28, + MemorySize::Other(val) => val, + } + } +} + +/// Hspi Address size +#[derive(Copy, Clone, defmt::Format)] +pub enum AddressSize { + /// 8-bit address + _8Bit, + /// 16-bit address + _16Bit, + /// 24-bit address + _24Bit, + /// 32-bit address + _32Bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8Bit => 0b00, + AddressSize::_16Bit => 0b01, + AddressSize::_24Bit => 0b10, + AddressSize::_32Bit => 0b11, + } + } +} + +/// Time the Chip Select line stays high. +#[allow(missing_docs)] +#[derive(Copy, Clone, defmt::Format)] +pub enum ChipSelectHighTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHighTime { + fn into(self) -> u8 { + match self { + ChipSelectHighTime::_1Cycle => 0, + ChipSelectHighTime::_2Cycle => 1, + ChipSelectHighTime::_3Cycle => 2, + ChipSelectHighTime::_4Cycle => 3, + ChipSelectHighTime::_5Cycle => 4, + ChipSelectHighTime::_6Cycle => 5, + ChipSelectHighTime::_7Cycle => 6, + ChipSelectHighTime::_8Cycle => 7, + } + } +} + +/// FIFO threshold. +#[allow(missing_docs)] +#[derive(Copy, Clone, defmt::Format)] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +/// Dummy cycle count +#[allow(missing_docs)] +#[derive(Copy, Clone, defmt::Format)] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} + +/// Functional mode +#[allow(missing_docs)] +#[allow(dead_code)] +#[derive(Copy, Clone, defmt::Format)] +pub enum FunctionalMode { + IndirectWrite, + IndirectRead, + AutoStatusPolling, + MemoryMapped, +} + +impl Into for FunctionalMode { + fn into(self) -> u8 { + match self { + FunctionalMode::IndirectWrite => 0x00, + FunctionalMode::IndirectRead => 0x01, + FunctionalMode::AutoStatusPolling => 0x02, + FunctionalMode::MemoryMapped => 0x03, + } + } +} diff --git a/embassy-stm32/src/hspi/mod.rs b/embassy-stm32/src/hspi/mod.rs new file mode 100644 index 000000000..97e1993dd --- /dev/null +++ b/embassy-stm32/src/hspi/mod.rs @@ -0,0 +1,1008 @@ +//! HSPI Serial Peripheral Interface +//! + +// NOTE: This is a partial implementation of the HSPI driver. +// It implements only Single and Octal SPI modes, but additional +// modes can be added as needed following the same pattern and +// using ospi/mod.rs as a reference. + +#![macro_use] + +pub mod enums; + +use core::marker::PhantomData; + +use embassy_embedded_hal::{GetConfig, SetConfig}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use enums::*; + +use crate::dma::{word, ChannelAndRequest}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::hspi::Hspi as Regs; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +/// HSPI driver config. +#[derive(Clone, Copy, defmt::Format)] +pub struct Config { + /// Fifo threshold used by the peripheral to generate the interrupt indicating data + /// or space is available in the FIFO + pub fifo_threshold: FIFOThresholdLevel, + /// Indicates the type of external device connected + pub memory_type: MemoryType, // Need to add an additional enum to provide this public interface + /// Defines the size of the external device connected to the HSPI corresponding + /// to the number of address bits required to access the device + pub device_size: MemorySize, + /// Sets the minimum number of clock cycles that the chip select signal must be held high + /// between commands + pub chip_select_high_time: ChipSelectHighTime, + /// Enables the free running clock + pub free_running_clock: bool, + /// Sets the clock level when the device is not selected + pub clock_mode: bool, + /// Indicates the wrap size corresponding to the external device configuration + pub wrap_size: WrapSize, + /// Specified the prescaler factor used for generating the external clock based + /// on the AHB clock + pub clock_prescaler: u8, + /// Allows the delay of 1/2 cycle the data sampling to account for external + /// signal delays + pub sample_shifting: bool, + /// Allows hold to 1/4 cycle the data + pub delay_hold_quarter_cycle: bool, + /// Enables the transaction boundary feature and defines the boundary to release + /// the chip select + pub chip_select_boundary: u8, + /// Enables the delay block bypass so the sampling is not affected by the delay block + pub delay_block_bypass: bool, + /// Enables communication regulation feature. Chip select is released when the other + /// HSPI requests access to the bus + pub max_transfer: u8, + /// Enables the refresh feature, chip select is released every refresh + 1 clock cycles + pub refresh: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::Other(0), + chip_select_high_time: ChipSelectHighTime::_5Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, // Acceptable range 0 to 31 + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + } + } +} + +/// HSPI transfer configuration. +pub struct TransferConfig { + /// Instruction width (IMODE) + pub iwidth: HspiWidth, + /// Instruction Id + pub instruction: Option, + /// Number of Instruction Bytes + pub isize: AddressSize, + /// Instruction Double Transfer rate enable + pub idtr: bool, + + /// Address width (ADMODE) + pub adwidth: HspiWidth, + /// Device memory address + pub address: Option, + /// Number of Address Bytes + pub adsize: AddressSize, + /// Address Double Transfer rate enable + pub addtr: bool, + + /// Alternate bytes width (ABMODE) + pub abwidth: HspiWidth, + /// Alternate Bytes + pub alternate_bytes: Option, + /// Number of Alternate Bytes + pub absize: AddressSize, + /// Alternate Bytes Double Transfer rate enable + pub abdtr: bool, + + /// Data width (DMODE) + pub dwidth: HspiWidth, + /// Data buffer + pub ddtr: bool, + + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: HspiWidth::NONE, + instruction: None, + isize: AddressSize::_8Bit, + idtr: false, + + adwidth: HspiWidth::NONE, + address: None, + adsize: AddressSize::_8Bit, + addtr: false, + + abwidth: HspiWidth::NONE, + alternate_bytes: None, + absize: AddressSize::_8Bit, + abdtr: false, + + dwidth: HspiWidth::NONE, + ddtr: false, + + dummy: DummyCycles::_0, + } + } +} + +/// Error used for HSPI implementation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HspiError { + /// Peripheral configuration is invalid + InvalidConfiguration, + /// Operation configuration is invalid + InvalidCommand, + /// Size zero buffer passed to instruction + EmptyBuffer, +} + +/// HSPI driver. +pub struct Hspi<'d, T: Instance, M: PeriMode> { + _peri: PeripheralRef<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + nss: Option>, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + _phantom: PhantomData, + config: Config, + width: HspiWidth, +} + +impl<'d, T: Instance, M: PeriMode> Hspi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), HspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + // Set writing configurations, there are separate registers for write configurations in memory mapped mode + T::REGS.wccr().modify(|w| { + w.set_imode(write_config.iwidth.into()); + w.set_idtr(write_config.idtr); + w.set_isize(write_config.isize.into()); + + w.set_admode(write_config.adwidth.into()); + w.set_addtr(write_config.idtr); + w.set_adsize(write_config.adsize.into()); + + w.set_dmode(write_config.dwidth.into()); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(write_config.abwidth.into()); + w.set_dqse(true); + }); + + T::REGS.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + T::REGS.cr().modify(|r| { + r.set_fmode(FunctionalMode::MemoryMapped.into()); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + T::REGS.cr().modify(|r| { + r.set_fmode(FunctionalMode::IndirectWrite.into()); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + T::REGS.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable HSPI + T::REGS.cr().modify(|r| { + r.set_en(true); + }); + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + sck: Option>, + nss: Option>, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + config: Config, + width: HspiWidth, + dual_memory_mode: bool, + ) -> Self { + into_ref!(peri); + + // System configuration + rcc::enable_and_reset::(); + + // Call this function just to check that the clock for HSPI1 is properly setup + let _ = T::frequency(); + + while T::REGS.sr().read().busy() {} + + Self::configure_registers(&config, Some(dual_memory_mode)); + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + nss, + dqs0, + dqs1, + dma, + _phantom: PhantomData, + config, + width, + } + } + + fn configure_registers(config: &Config, dual_memory_mode: Option) { + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_mtyp(config.memory_type.into()); + w.set_devsize(config.device_size.into()); + w.set_csht(config.chip_select_high_time.into()); + w.set_frck(false); + w.set_ckmode(config.clock_mode); + w.set_dlybyp(config.delay_block_bypass); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(config.wrap_size.into()); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(config.chip_select_boundary); + w.set_maxtran(config.max_transfer); + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(config.refresh); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(config.fifo_threshold.into()); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + // The configuration of clock prescaler trigger automatically a calibration process + // So it is necessary to wait the calibration is complete + while T::REGS.sr().read().busy() {} + + if let Some(dual_memory_mode) = dual_memory_mode { + T::REGS.cr().modify(|w| { + w.set_dmm(dual_memory_mode); + }); + } + + T::REGS.tcr().modify(|w| { + w.set_sshift(config.sample_shifting); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + } + + // Function to configure the peripheral for the requested command + fn configure_command(&mut self, command: &TransferConfig, data_len: Option) -> Result<(), HspiError> { + // Check that transaction doesn't use more than hardware initialized pins + if >::into(command.iwidth) > >::into(self.width) + || >::into(command.adwidth) > >::into(self.width) + || >::into(command.abwidth) > >::into(self.width) + || >::into(command.dwidth) > >::into(self.width) + { + return Err(HspiError::InvalidCommand); + } + + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_fmode(0.into()); + }); + + // Configure alternate bytes + if let Some(ab) = command.alternate_bytes { + T::REGS.abr().write(|v| v.set_alternate(ab)); + T::REGS.ccr().modify(|w| { + w.set_abmode(command.abwidth.into()); + w.set_abdtr(command.abdtr); + w.set_absize(command.absize.into()); + }) + } + + // Configure dummy cycles + T::REGS.tcr().modify(|w| { + w.set_dcyc(command.dummy.into()); + }); + + // Configure data + if let Some(data_length) = data_len { + T::REGS.dlr().write(|v| { + v.set_dl((data_length - 1) as u32); + }) + } else { + T::REGS.dlr().write(|v| { + v.set_dl((0) as u32); + }) + } + + // Configure instruction/address/data modes + T::REGS.ccr().modify(|w| { + w.set_imode(command.iwidth.into()); + w.set_idtr(command.idtr); + w.set_isize(command.isize.into()); + + w.set_admode(command.adwidth.into()); + w.set_addtr(command.addtr); + w.set_adsize(command.adsize.into()); + + w.set_dmode(command.dwidth.into()); + w.set_ddtr(command.ddtr); + }); + + // Configure DQS + T::REGS.ccr().modify(|w| { + w.set_dqse(command.ddtr && command.instruction.unwrap_or(0) != 0x12ED); + }); + + // Set information required to initiate transaction + if let Some(instruction) = command.instruction { + if let Some(address) = command.address { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + } + } else { + if let Some(address) = command.address { + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // The only single phase transaction supported is instruction only + return Err(HspiError::InvalidCommand); + } + } + + Ok(()) + } + + /// Function used to control or configure the target device without data transfer + pub fn blocking_command(&mut self, command: &TransferConfig) -> Result<(), HspiError> { + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Need additional validation that command configuration doesn't have data set + self.configure_command(command, None)?; + + // Transaction initiated by setting final configuration, i.e the instruction register + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|w| { + w.set_ctcf(true); + }); + + Ok(()) + } + + /// Blocking read with byte by byte data transfer + pub fn blocking_read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Ensure DMA is not enabled for this transaction + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + for idx in 0..buf.len() { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut W).read_volatile() }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Blocking write with byte by byte data transfer + pub fn blocking_write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + for idx in 0..buf.len() { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut W).write_volatile(buf[idx]) }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Set new bus configuration + pub fn set_config(&mut self, config: &Config) { + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + // Disable DMA channel while configuring the peripheral + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + Self::configure_registers(config, None); + + self.config = *config; + } + + /// Get current configuration + pub fn get_config(&self) -> Config { + self.config + } +} + +impl<'d, T: Instance> Hspi<'d, T, Blocking> { + /// Create new blocking HSPI driver for single spi external chip + pub fn new_blocking_singlespi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + config, + HspiWidth::SING, + false, + ) + } + + /// Create new blocking HSPI driver for octospi external chip + pub fn new_blocking_octospi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dqs0: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_pin!(dqs0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + config, + HspiWidth::OCTO, + false, + ) + } +} + +impl<'d, T: Instance> Hspi<'d, T, Async> { + /// Create new HSPI driver for a single spi external chip + pub fn new_singlespi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + new_dma!(dma), + config, + HspiWidth::SING, + false, + ) + } + + /// Create new HSPI driver for octospi external chip + pub fn new_octospi( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + d4: impl Peripheral

> + 'd, + d5: impl Peripheral

> + 'd, + d6: impl Peripheral

> + 'd, + d7: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dqs0: impl Peripheral

> + 'd, + dma: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_pin!(dqs0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + new_dma!(dma), + config, + HspiWidth::OCTO, + false, + ) + } + + /// Blocking read with DMA transfer + pub fn blocking_read_dma(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Blocking write with DMA transfer + pub fn blocking_write_dma(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous read from external device + pub async fn read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous write to external device + pub async fn write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> Drop for Hspi<'d, T, M> { + fn drop(&mut self) { + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.d0.as_ref().map(|x| x.set_as_disconnected()); + self.d1.as_ref().map(|x| x.set_as_disconnected()); + self.d2.as_ref().map(|x| x.set_as_disconnected()); + self.d3.as_ref().map(|x| x.set_as_disconnected()); + self.d4.as_ref().map(|x| x.set_as_disconnected()); + self.d5.as_ref().map(|x| x.set_as_disconnected()); + self.d6.as_ref().map(|x| x.set_as_disconnected()); + self.d7.as_ref().map(|x| x.set_as_disconnected()); + self.d8.as_ref().map(|x| x.set_as_disconnected()); + self.d9.as_ref().map(|x| x.set_as_disconnected()); + self.d10.as_ref().map(|x| x.set_as_disconnected()); + self.d11.as_ref().map(|x| x.set_as_disconnected()); + self.d12.as_ref().map(|x| x.set_as_disconnected()); + self.d13.as_ref().map(|x| x.set_as_disconnected()); + self.d14.as_ref().map(|x| x.set_as_disconnected()); + self.d15.as_ref().map(|x| x.set_as_disconnected()); + self.nss.as_ref().map(|x| x.set_as_disconnected()); + self.dqs0.as_ref().map(|x| x.set_as_disconnected()); + self.dqs1.as_ref().map(|x| x.set_as_disconnected()); + + rcc::disable::(); + } +} + +fn finish_dma(regs: Regs) { + while !regs.sr().read().tcf() {} + regs.fcr().write(|v| v.set_ctcf(true)); + + regs.cr().modify(|w| { + w.set_dmaen(false); + }); +} + +/// HSPI instance trait. +pub(crate) trait SealedInstance { + const REGS: Regs; +} + +/// HSPI instance trait. +#[allow(private_bounds)] +pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(NckPin, Instance); +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(DQS0Pin, Instance); +pin_trait!(DQS1Pin, Instance); +pin_trait!(NSSPin, Instance); +dma_trait!(HspiDma, Instance); + +foreach_peripheral!( + (hspi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +impl<'d, T: Instance, M: PeriMode> SetConfig for Hspi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> GetConfig for Hspi<'d, T, M> { + type Config = Config; + fn get_config(&self) -> Self::Config { + self.get_config() + } +} + +/// Word sizes usable for HSPI. +#[allow(private_bounds)] +pub trait Word: word::Word {} + +macro_rules! impl_word { + ($T:ty) => { + impl Word for $T {} + }; +} + +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 61da754c3..c37908dbc 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -83,6 +83,8 @@ pub mod hash; pub mod hrtim; #[cfg(hsem)] pub mod hsem; +#[cfg(hspi)] +pub mod hspi; #[cfg(i2c)] pub mod i2c; #[cfg(any(all(spi_v1, rcc_f4), spi_v3))] diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 33e19f4f8..e35d51c91 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -52,7 +52,7 @@ pub struct Config { /// Enables the transaction boundary feature and defines the boundary to release /// the chip select pub chip_select_boundary: u8, - /// Enbales the delay block bypass so the sampling is not affected by the delay block + /// Enables the delay block bypass so the sampling is not affected by the delay block pub delay_block_bypass: bool, /// Enables communication regulation feature. Chip select is released when the other /// OctoSpi requests access to the bus diff --git a/examples/stm32u5/src/bin/hspi_memory_mapped.rs b/examples/stm32u5/src/bin/hspi_memory_mapped.rs new file mode 100644 index 000000000..9fef4855e --- /dev/null +++ b/examples/stm32u5/src/bin/hspi_memory_mapped.rs @@ -0,0 +1,455 @@ +#![no_main] +#![no_std] + +// Tested on an STM32U5G9J-DK2 demo board using the on-board MX66LM1G45G flash memory +// The flash is connected to the HSPI1 port as an OCTA-DTR device +// +// Use embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ" + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::hspi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Hspi, HspiWidth, Instance, MemorySize, + MemoryType, TransferConfig, WrapSize, +}; +use embassy_stm32::mode::Async; +use embassy_stm32::rcc; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start hspi_memory_mapped"); + + // RCC config + let mut config = embassy_stm32::Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll2 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, + mul: rcc::PllMul::MUL66, + divp: None, + divq: Some(rcc::PllDiv::DIV2), + divr: None, + }); + config.rcc.mux.hspi1sel = rcc::mux::Hspisel::PLL2_Q; // 132 MHz + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let flash_config = embassy_stm32::hspi::Config { + fifo_threshold: FIFOThresholdLevel::_4Bytes, + memory_type: MemoryType::Macronix, + device_size: MemorySize::_1GiB, + chip_select_high_time: ChipSelectHighTime::_2Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: false, + max_transfer: 0, + refresh: 0, + }; + + let use_dma = true; + + info!("Testing flash in OCTA DTR mode and memory mapped mode"); + + let hspi = Hspi::new_octospi( + p.HSPI1, + p.PI3, + p.PH10, + p.PH11, + p.PH12, + p.PH13, + p.PH14, + p.PH15, + p.PI0, + p.PI1, + p.PH9, + p.PI2, + p.GPDMA1_CH7, + flash_config, + ); + + let mut flash = OctaDtrFlashMemory::new(hspi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + + let mut rd_buf = [0u8; 16]; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + + flash.erase_sector(0).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0xFF); + assert_eq!(rd_buf[15], 0xFF); + + let mut wr_buf = [0u8; 16]; + for i in 0..wr_buf.len() { + wr_buf[i] = i as u8; + } + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + flash.write_memory(0, &wr_buf, use_dma).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0x00); + assert_eq!(rd_buf[15], 0x0F); + + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0xA0000000 as *const u32) }; + info!("first_u32: 0x{=u32:X}", first_u32); + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0xA0000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + info!("second_u32: 0x{=u32:X}", second_u32); + + let first_u8 = unsafe { *(0xA0000000 as *const u8) }; + assert_eq!(first_u8, 00); + info!("first_u8: 0x{=u8:X}", first_u8); + + let second_u8 = unsafe { *(0xA0000001 as *const u8) }; + assert_eq!(second_u8, 0x01); + info!("second_u8: 0x{=u8:X}", second_u8); + + let third_u8 = unsafe { *(0xA0000002 as *const u8) }; + assert_eq!(third_u8, 0x02); + info!("third_u8: 0x{=u8:X}", third_u8); + + let fourth_u8 = unsafe { *(0xA0000003 as *const u8) }; + assert_eq!(fourth_u8, 0x03); + info!("fourth_u8: 0x{=u8:X}", fourth_u8); + + info!("DONE"); +} + +// Custom implementation for MX66UW1G45G NOR flash memory from Macronix. +// Chip commands are hardcoded as they depend on the chip used. +// This implementation enables Octa I/O (OPI) and Double Transfer Rate (DTR) + +pub struct OctaDtrFlashMemory<'d, I: Instance> { + hspi: Hspi<'d, I, Async>, +} + +impl<'d, I: Instance> OctaDtrFlashMemory<'d, I> { + const MEMORY_PAGE_SIZE: usize = 256; + + const CMD_READ_OCTA_DTR: u16 = 0xEE11; + const CMD_PAGE_PROGRAM_OCTA_DTR: u16 = 0x12ED; + + const CMD_READ_ID_OCTA_DTR: u16 = 0x9F60; + + const CMD_RESET_ENABLE: u8 = 0x66; + const CMD_RESET_ENABLE_OCTA_DTR: u16 = 0x6699; + const CMD_RESET: u8 = 0x99; + const CMD_RESET_OCTA_DTR: u16 = 0x9966; + + const CMD_WRITE_ENABLE: u8 = 0x06; + const CMD_WRITE_ENABLE_OCTA_DTR: u16 = 0x06F9; + + const CMD_SECTOR_ERASE_OCTA_DTR: u16 = 0x21DE; + const CMD_BLOCK_ERASE_OCTA_DTR: u16 = 0xDC23; + + const CMD_READ_SR: u8 = 0x05; + const CMD_READ_SR_OCTA_DTR: u16 = 0x05FA; + + const CMD_READ_CR2: u8 = 0x71; + const CMD_WRITE_CR2: u8 = 0x72; + + const CR2_REG1_ADDR: u32 = 0x00000000; + const CR2_OCTA_DTR: u8 = 0x02; + + const CR2_REG3_ADDR: u32 = 0x00000300; + const CR2_DC_6_CYCLES: u8 = 0x07; + + pub async fn new(hspi: Hspi<'d, I, Async>) -> Self { + let mut memory = Self { hspi }; + + memory.reset_memory().await; + memory.enable_octa_dtr().await; + memory + } + + async fn enable_octa_dtr(&mut self) { + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG3_ADDR, Self::CR2_DC_6_CYCLES); + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG1_ADDR, Self::CR2_OCTA_DTR); + } + + pub async fn enable_mm(&mut self) { + let read_config = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: HspiWidth::OCTO, + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.hspi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + async fn exec_command_spi(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(cmd as u32), + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command_octa_dtr(&mut self, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + fn wait_write_finish_spi(&mut self) { + while (self.read_sr_spi() & 0x01) != 0 {} + } + + fn wait_write_finish_octa_dtr(&mut self) { + while (self.read_sr_octa_dtr() & 0x01) != 0 {} + } + + pub async fn reset_memory(&mut self) { + // servono entrambi i comandi? + self.exec_command_octa_dtr(Self::CMD_RESET_ENABLE_OCTA_DTR).await; + self.exec_command_octa_dtr(Self::CMD_RESET_OCTA_DTR).await; + self.exec_command_spi(Self::CMD_RESET_ENABLE).await; + self.exec_command_spi(Self::CMD_RESET).await; + self.wait_write_finish_spi(); + } + + async fn write_enable_spi(&mut self) { + self.exec_command_spi(Self::CMD_WRITE_ENABLE).await; + } + + async fn write_enable_octa_dtr(&mut self) { + self.exec_command_octa_dtr(Self::CMD_WRITE_ENABLE_OCTA_DTR).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 6]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_ID_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + info!("Reading flash id: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + [buffer[0], buffer[2], buffer[4]] + } + + pub async fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + if use_dma { + self.hspi.read(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_read(buffer, transaction).unwrap(); + } + } + + async fn perform_erase_octa_dtr(&mut self, addr: u32, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + self.hspi.blocking_command(&transaction).unwrap(); + self.wait_write_finish_octa_dtr(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + info!("Erasing 4K sector at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_SECTOR_ERASE_OCTA_DTR).await; + } + + pub async fn erase_block(&mut self, addr: u32) { + info!("Erasing 64K block at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_BLOCK_ERASE_OCTA_DTR).await; + } + + async fn write_page_octa_dtr(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= Self::MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_PAGE_PROGRAM_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + if use_dma { + self.hspi.write(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish_octa_dtr(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = Self::MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page_octa_dtr(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + pub fn read_sr_spi(&mut self) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_SR as u32), + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_sr_octa_dtr(&mut self) -> u8 { + let mut buffer = [0; 2]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_SR_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_cr2_spi(&mut self, addr: u32) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G CR2[0x{:X}] register: 0x{:x}", addr, buffer[0]); + buffer[0] + } + + pub fn write_cr2_spi(&mut self, addr: u32, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_WRITE_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_write(&buffer, transaction).unwrap(); + } +}