diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index fae23c7e0d..2523ad3b99 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -5,6 +5,8 @@ use stm32_metapac::rcc::vals::Xspisel; use crate::pac; #[cfg(stm32h7rs)] +use crate::pac::flash::regs::Optkeyr; +#[cfg(stm32h7rs)] pub use crate::pac::rcc::vals::Plldivst as PllDivSt; pub use crate::pac::rcc::vals::{ Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Sw as Sysclk, @@ -238,6 +240,28 @@ pub struct Config { /// Per-peripheral kernel clock selection muxes pub mux: super::mux::ClockMux, + + /// Enable HSLV mode for XSPI1. + /// CAUTION: enabling when VDD_XSPI1 > 2.7 V may be destructive! + #[cfg(stm32h7rs)] + pub hslv_xspi1: bool, + /// Enable HSLV mode for XSPI2. + /// CAUTION: enabling when VDD_XSPI2 > 2.7 V may be destructive! + #[cfg(stm32h7rs)] + pub hslv_xspi2: bool, + /// Enable HSLV mode for I/O pins. + /// CAUTION: enabling when VDD > 2.7 V may be destructive! + #[cfg(stm32h7rs)] + pub hslv_io: bool, + + /// Enable the compensation cell for XSPI1. + /// Enabling with no active device connected will fail with time-out. + #[cfg(stm32h7rs)] + pub comp_xspi1: bool, + /// Enable the compensation cell for XSPI2. + /// Enabling with no active device connected will fail with time-out. + #[cfg(stm32h7rs)] + pub comp_xspi2: bool, } impl Config { @@ -279,6 +303,18 @@ impl Config { supply_config: SupplyConfig::LDO, mux: super::mux::ClockMux::default(), + + #[cfg(stm32h7rs)] + hslv_xspi1: false, + #[cfg(stm32h7rs)] + hslv_xspi2: false, + #[cfg(stm32h7rs)] + hslv_io: false, + + #[cfg(stm32h7rs)] + comp_xspi1: false, + #[cfg(stm32h7rs)] + comp_xspi2: false, } } } @@ -711,8 +747,8 @@ pub(crate) unsafe fn init(config: Config) { super::disable_hsi48(); } - // IO compensation cell - Requires CSI clock and SYSCFG - #[cfg(any(stm32h7))] // TODO h5, h7rs + // IO compensation cell(s) - Requires CSI clock and SYSCFG + #[cfg(any(stm32h7))] // TODO h5 if csi.is_some() { // Enable the compensation cell, using back-bias voltage code // provide by the cell. @@ -725,6 +761,110 @@ pub(crate) unsafe fn init(config: Config) { }); while !pac::SYSCFG.cccsr().read().rdy() {} } + #[cfg(any(stm32h7rs))] + if csi.is_some() { + // The CSI oscillator must be enabled for this to work. + // (RM0477, p.362, Ch. 7.5.2 "Oscillators description".) + + // SBS peripheral clock is required by the compensation cells. + RCC.apb4enr().modify(|w| w.set_syscfgen(true)); + + // Unlock flash OPTCR, when required, to be able to configure HSLV + // (high-speed, low-voltage) mode for the required I/O domains. + // The original state of the lock bits will be restored when done. + // Note: this also avoids double-unlocking, which is not permitted. + // (RM0477, p.581, Ch. 10.3.16: High-speed low-voltage mode) + let original_lock_state = FLASH.optcr().read().optlock(); + let original_opt_pg_state = FLASH.optcr().read().pg_opt(); + if original_lock_state { + FLASH.optkeyr().write_value(Optkeyr(0x08192A3B)); + FLASH.optkeyr().write_value(Optkeyr(0x4C5D6E7F)); + } + if !original_opt_pg_state { + // Enable write access to the option bits. + FLASH.optcr().modify(|w| w.set_pg_opt(true)); + while FLASH.sr().read().busy() {} + } + + // Set the HSLV option bits to enable HSLV mode configuratrion for all + // three domains. (RM0477, p.272, 5.9.33 "FLASH option byte word 1 + // status register programming") + FLASH.obw1srp().modify(|w| { + w.set_octo1_hslv(true); + w.set_octo2_hslv(true); + w.set_vddio_hslv(true); + }); + while FLASH.sr().read().busy() {} + + // Restore the original state of the optlock and pg_opt bits. This is + // effectively a no-op if no state change was originally required. + FLASH.optcr().modify(|w| { + w.set_pg_opt(original_opt_pg_state); + w.set_optlock(original_lock_state); + }); + while FLASH.sr().read().busy() {} + + // Configure the HSLV mode domains and I/O compensation cells. + pac::SYSCFG.cccsr().modify(|w| { + w.set_octo1_iohslv(config.hslv_xspi1); + w.set_octo2_iohslv(config.hslv_xspi2); + w.set_iohslv(config.hslv_io); + + // Enable the compensation cells, using automatic compensation at + // first. Note that an errata applies, which is handled below. + w.set_octo1_comp_codesel(false); + w.set_octo2_comp_codesel(false); + w.set_comp_codesel(false); + + w.set_octo1_comp_en(config.comp_xspi1); + w.set_octo2_comp_en(config.comp_xspi2); + w.set_comp_en(true); + }); + + // Wait for the compensation cells to stabilize. + // Note: this may never happen for XSPI ports that have no active + // device connected to them. A basic polling time-out is added + // as fall-back, with a diagnostic message. + const COMP_POLL_MAX: u32 = 10000; // Enough for maxed-out clocks. + let mut poll_cnt = 0; + while config.comp_xspi1 && !pac::SYSCFG.cccsr().read().octo1_comp_rdy() && poll_cnt < COMP_POLL_MAX { + poll_cnt += 1; + } + if poll_cnt == COMP_POLL_MAX { + warn!("XSPI1 compensation cell ready-flag time-out."); + } + poll_cnt = 0; + while config.comp_xspi2 && !pac::SYSCFG.cccsr().read().octo2_comp_rdy() && poll_cnt < COMP_POLL_MAX { + poll_cnt += 1 + } + if poll_cnt == COMP_POLL_MAX { + warn!("XSPI2 compensation cell ready-flag time-out."); + } + while !pac::SYSCFG.cccsr().read().comp_rdy() {} + + // Now the I/O compensation and HSLV mode are configured, there are + // still issues with the automatic tuning of the I/O compensation, when + // operating over a wider temperature range. A work-around is required, + // as explained in the errata: + // - Read the auto-tune values at around 30°C ambient and store in non- + // volatile storage. The values should have a correction applied. + // - Apply these stored, correct values at boot. + // + // The fix is applied here, assuming a sufficiently-constant ambient + // temperature, but the user should still do the calibration step and + // apply the correct values, before attempting high-speed peripheral + // access in real-world applications. + // + // (ES0596, p. 12, Ch 2.2.15 "I/O compensation could alter duty-cycle of high-frequency output signal") + // + // + // Note: applying the errata to the GPIO compensation cell, as is done + // here, seems to improve this as well, judging by a signal on + // the MCO output pin, although it is not explicitly stated in + // the errata. + let ccv = get_corrected_comp_vals(); + set_and_enable_comp_vals(&ccv); + } config.mux.init(); @@ -1123,3 +1263,69 @@ fn flash_setup(clk: Hertz, vos: VoltageScale) { }); while FLASH.acr().read().latency() != latency {} } + +/// Compensation cell calibration values. The N-MOS and P-MOS transistors slew +/// rate compensation factors are stored for the three different compensation +/// cells available in the STM32H7RS family. +#[cfg(stm32h7rs)] +pub struct CompVals { + /// XSPI1 N-MOS transistors slew-rate compensation value [0-15]. + pub octo1_nsrc: u8, + /// XSPI1 P-MOS transistors slew-rate compensation value [0-15]. + pub octo1_psrc: u8, + /// XSPI2 N-MOS transistors slew-rate compensation value [0-15]. + pub octo2_nsrc: u8, + /// XSPI2 P-MOS transistors slew-rate compensation value [0-15]. + pub octo2_psrc: u8, + /// GPIO N-MOS transistors slew-rate compensation value [0-15]. + pub io_nsrc: u8, + /// GPIO P-MOS transistors slew-rate compensation value [0-15]. + pub io_psrc: u8, +} + +/// Obtain the auto-tuned, slew-rate compensation values for the different +/// compensation cells. The errata corrections are applied. Following the +/// errata, these values should be obtained once during production, around +/// 30°C MCU temperature, for each individual board, and stored in non-volatile +/// memory for future use. The stored values should then be applied at power-up +/// to guarantee stable, high-speed operation of the XSPI busses, and other +/// high-speed I/O. While ST does not discuss the application to the GPIO pins +/// in general in the errata, applying the errata compensation to those as well +/// seems to improve the waveform symmetry (eg: MCO). +/// (ES0596, p. 12, Ch 2.2.15 "I/O compensation could alter duty-cycle of +/// high-frequency output signal") +#[cfg(stm32h7rs)] +pub fn get_corrected_comp_vals() -> CompVals { + let ccvalr = pac::SYSCFG.ccvalr().read(); + return CompVals { + octo1_nsrc: ccvalr.octo1_nsrc().saturating_add(2), + octo1_psrc: ccvalr.octo1_psrc().saturating_sub(2), + octo2_nsrc: ccvalr.octo2_nsrc().saturating_add(2), + octo2_psrc: ccvalr.octo2_psrc().saturating_sub(2), + io_nsrc: ccvalr.octo2_nsrc().saturating_add(2), + io_psrc: ccvalr.octo2_psrc().saturating_sub(2), + }; +} + +/// Apply static slew-rate compensation values to all compensation cells, and +/// enable them. These should be the corrected values outlined in the errata. +/// (ES0596, p. 12, Ch 2.2.15 "I/O compensation could alter duty-cycle of +/// high-frequency output signal") +#[cfg(stm32h7rs)] +pub fn set_and_enable_comp_vals(cv: &CompVals) { + pac::SYSCFG.ccswvalr().modify(|w| { + // Set the corrected, constant compensation values manually. + w.set_octo1_sw_nsrc(cv.octo1_nsrc); + w.set_octo1_sw_psrc(cv.octo1_psrc); + w.set_octo2_sw_nsrc(cv.octo2_nsrc); + w.set_octo2_sw_psrc(cv.octo2_psrc); + w.set_sw_nsrc(cv.io_nsrc); + w.set_sw_psrc(cv.io_psrc); + }); + pac::SYSCFG.cccsr().modify(|w| { + // Switch to use the constant, manual compensation values. + w.set_octo1_comp_codesel(true); + w.set_octo2_comp_codesel(true); + w.set_comp_codesel(true); + }); +} diff --git a/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs b/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs index b4b2ede17b..26a050cc27 100644 --- a/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs +++ b/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs @@ -1,8 +1,15 @@ #![no_main] #![no_std] -//! For Nucleo STM32H7S3L8 MB1737, has MX25UW25645GXDI00 +//! This example is intended for the Nucleo STM32H7S3L8 MB1737, which has a +//! Macronix MX25UW25645GXDI00 flash chip installed on XSPI2, powered at 1.8 V. //! +//! IMPORTANT: this example use HSLV (high-speed, low-voltage) mode for XSPI2, +//! to which the flash is connected, to achieve the maximum flash +//! bus speeds in STR communication mode. This is safe on the Nucleo +//! board, which is factory-wired for a fixed 1.8 V on VDD_XSPI2. +//! For other boards, this may not be so, and may be destructive! +//! See the notes in the RCC config below for more details. use core::cmp::min; @@ -11,6 +18,8 @@ use embassy_executor::Spawner; use embassy_stm32::Config; use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_stm32::mode::Blocking; +use embassy_stm32::rcc::mux::Xspisel; +use embassy_stm32::rcc::{get_corrected_comp_vals, set_and_enable_comp_vals}; use embassy_stm32::time::Hertz; use embassy_stm32::xspi::{ AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, TransferConfig, @@ -25,20 +34,52 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); { use embassy_stm32::rcc::*; + + // CSI is required for HSLV and the compensation cells. + config.rcc.csi = true; + + // Run from the external, 24 MHz crystal oscillator. config.rcc.hse = Some(Hse { freq: Hertz(24_000_000), mode: HseMode::Oscillator, }); + config.rcc.pll1 = Some(Pll { source: PllSource::Hse, prediv: PllPreDiv::Div3, - mul: PllMul::Mul150, - divp: Some(PllDiv::Div2), + mul: PllMul::Mul150, // 24 MHz / 3 * 150 = 1200 MHz. + divp: Some(PllDiv::Div2), // 600 MHz PLLCLK for SYSCLK. divq: None, divr: None, divs: None, divt: None, }); + + // PLL2_S will be used as the clock source for XSPI2. + // A signal with a 50 % duty-cycle is required. The PLL always has a + // 50% duty-cycle output on post-dividers divs and divt when VCOH is + // selected (PLLxVCOSEL = 0). This should be so, for the given + // configuration. However, the uncertainty can be readily avoided by + // generating double the required frequency, and then post-dividing + // by 2 in the XSPI peripheral. + // For bus clk of 133 MHZ: 24 MHz / 4 * 133 / 3 = 266 MHz (and then /2 in XSPI) + // 200 MHz: 24 MHz / 6 * 200 / 2 = 400 MHz (and then /2 in XSPI) + config.rcc.pll2 = Some(Pll { + source: PllSource::Hse, + prediv: PllPreDiv::Div6, + mul: PllMul::Mul200, // 24 MHz / 6 * 200 = 800 MHz + divp: None, + divq: None, + divr: None, + divs: Some(PllDivSt::Div2), // 400 MHz for XSPI2. + divt: None, + }); + + // XSPI2 uses PLL2S as its clock source. This allows for more flexible + // clock rates. + config.rcc.mux.xspi2sel = Xspisel::Pll2S; + + // Run the core and internal busses at full speed. config.rcc.sys = Sysclk::Pll1P; // 600 Mhz config.rcc.ahb_pre = AHBPrescaler::Div2; // 300 Mhz config.rcc.apb1_pre = APBPrescaler::Div2; // 150 Mhz @@ -46,6 +87,33 @@ async fn main(_spawner: Spawner) { config.rcc.apb4_pre = APBPrescaler::Div2; // 150 Mhz config.rcc.apb5_pre = APBPrescaler::Div2; // 150 Mhz config.rcc.voltage_scale = VoltageScale::High; + + // Enable HSLV mode to allow high-speed operation at low supply voltage. + // The STM32H7RS has three power domains where HSLV mode is applicable: + // - XSPI1 I/Os supplied from VDDXSPI1 + // - XSPI2 I/Os supplied from VDDXSPI2. + // - General I/Os supplied from VDD. + // + // IMPORTANT: Only enable HSLV mode when the specific power domain VDD is + // below 2.7 V. Enabling when VDD is > 2.7 V may be destructive! + // (RM0477, p.551, Ch. 8.5.10 "SBS I/O compensation cell control + // and status register (SBS_CCCSR)") + // + // IMPORTANT: JP5 selects all VDD domains EXCEPT XSPI. (Assuming the + // factory-default solder bridges are installed.) + // Make sure the jumper is in the correct position BEFORE + // enabling hslv_io! + config.rcc.hslv_xspi1 = false; // NUCLEO-H7S3L8: SB20 to VDD (default). + config.rcc.hslv_xspi2 = true; // NUCLEO-H7S3L8: SB34 to 1.8 V (default). + config.rcc.hslv_io = false; // NUCLEO-H7S3L8: JP5 (user VDD selection) to 1.8 V or 3.3 V. + + // Selectively enable the compensation cell for the XSPI peripherals. + // - The XSPI compensation cell auto-tune will fail (and time-out) if no + // active device is connected to the selected XSPI port. + // - The GPIO compensation cell is always enabled, as it can auto-tune + // irregardless of connected devices. + config.rcc.comp_xspi1 = false; // NUCLEO-H7S3L8: not connected. + config.rcc.comp_xspi2 = true; // NUCLEO-H7S3L8: MX25UW25645GXDI00 flash. } // Initialize peripherals @@ -60,11 +128,12 @@ async fn main(_spawner: Spawner) { free_running_clock: false, clock_mode: false, wrap_size: WrapSize::None, - // 300 MHz clock / (3 + 1) = 75 MHz. This is above the max for READ instructions so the - // FAST READ must be used. The nucleo board's flash can run at up to 133 MHz in SPI mode - // and 200 MHz in OPI mode. This clock prescaler must be even otherwise the clock will not - // have symmetric high and low times. - // The clock can also be fed by one of the PLLs to allow for more flexible clock rates. + // 400 MHz clock / (3 + 1) = 100 MHz. This is above the maximum for + // READ instructions so the FAST READ must be used. The nucleo board's + // flash can run at up to 133 MHz in SPI mode and 200 MHz in OPI mode. + // This clock prescaler must be even, otherwise the clock will not have + // symmetric high and low times. (Note: one is internally added to the + // clock_prescaler value defined here.) clock_prescaler: 3, sample_shifting: false, chip_select_boundary: 0, @@ -79,11 +148,41 @@ async fn main(_spawner: Spawner) { // Note: Enabling data cache can cause issues with DMA transfers. cor.SCB.enable_dcache(&mut cor.CPUID); - let xspi = embassy_stm32::xspi::Xspi::new_blocking_xspi( - p.XSPI2, p.PN6, p.PN2, p.PN3, p.PN4, p.PN5, p.PN8, p.PN9, p.PN10, p.PN11, p.PN1, spi_config, + // Work-around the errata isse with automatic compensation cell tuning. + // - Read the corrected, auto-tune values at around 30°C ambient and store + // in non-volatile memory. + // - Apply these values at boot, before attempting high-speed operations. + // + // ES0596, p. 12, Ch 2.2.15 "I/O compensation could alter duty-cycle of high-frequency output signal" + // + // + // Note: applying the errata to the GPIO compensation cell, as is done here, + // seems to improve this as well, judging by a signal on the MCO output + // pin, although it is not explicitly stated in the errata. + // + // Note: these steps are already done in the RCC initialization code. + // They are made explicit here, to show how to use the convenience + // functions, and to remind the user that this must be implemented + // in real-world projects that operate at high I/O speeds, over wider + // temperature ranges. + let cv = get_corrected_comp_vals(); + info!("Compensation Cells - Corrected Tuning Values:"); + info!("- XSPI1: NMOS {:#04X} PMOS {:#04X}", cv.octo1_nsrc, cv.octo1_psrc); + info!("- XSPI2: NNOS {:#04X} PMOS {:#04X}", cv.octo2_nsrc, cv.octo2_psrc); + info!("- GPIO: NMOS {:#04X} PMOS {:#04X}", cv.io_nsrc, cv.io_psrc); + set_and_enable_comp_vals(&cv); + + let xspi = embassy_stm32::xspi::Xspi::new_blocking_xspi_dqs( + p.XSPI2, p.PN6, p.PN2, p.PN3, p.PN4, p.PN5, p.PN8, p.PN9, p.PN10, p.PN11, p.PN1, p.PN0, spi_config, ); let mut flash = SpiFlashMemory::new(xspi); + info!("Starting in SPI mode with 100 MHz XSPI bus clock."); + + // With higher flash clock speeds, the first read_id() returns all zeros, + // or a hardfault occurs. A short wait (>15µs) resolves this. (This may be + // due to the flash initialization not having completed yet.) + Timer::after_micros(50).await; let flash_id = flash.read_id(); info!("FLASH ID: {=[u8]:x}", flash_id); @@ -91,7 +190,8 @@ async fn main(_spawner: Spawner) { // Erase the first sector flash.erase_sector(0); - // Write some data into the flash. This writes more than one page to test that functionality. + // Write some data into the flash. This writes more than one page to test + // that functionality. let mut wr_buf = [0u8; 512]; let base_number: u8 = 0x90; for i in 0..512 { @@ -127,7 +227,14 @@ async fn main(_spawner: Spawner) { let flash_id = flash.read_id(); info!("FLASH ID: {=[u8]:x}", flash_id); + // Change to OPI mode. This allows for higher XSPI bus clock speeds. let mut flash = flash.into_octo(); + info!("Entered OPI mode."); + + // Change the bus clock to 200 MHz. + // 400 MHz / (1 + 1) = 200 MHz (Even ratio: 1+1 = 2) + flash.xspi.set_clock_prescaler(1); + info!("XSPI clock prescaler set to /2 -> 200 MHz bus clock"); Timer::after_millis(100).await; @@ -161,15 +268,22 @@ async fn main(_spawner: Spawner) { flash.disable_mm(); info!("Disabled memory mapped mode in OPI mode"); - // Reset back to SPI mode + // Change the bus clock to 100 MHz in preparation for re-entering SPI mode. + // 400 MHz / (3 + 1) = 100 MHz (Even ratio: 3+1 = 4) + flash.xspi.set_clock_prescaler(3); + info!("XSPI clock prescaler set to /4 -> 100 MHz bus clock"); + + // Reset back to SPI mode. let mut flash = flash.into_spi(); + info!("Re-entered SPI mode"); + let flash_id = flash.read_id(); info!("FLASH ID back in SPI mode: {=[u8]:x}", flash_id); info!("DONE"); - // Output pin PE3 - let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + // Output pin PD10 = LD1 (green). + let mut led = Output::new(p.PD10, Level::Low, Speed::Low); loop { led.toggle();