From 75010b3d9384ae7c40049dd807ca343071122e11 Mon Sep 17 00:00:00 2001 From: astapleton Date: Fri, 8 Sep 2023 16:39:37 -0700 Subject: [PATCH] rcc: calculate PLL clocks and implement core clock configuration logic --- src/rcc.rs | 671 ++++++++++++++++++++++++++++++++++++++++++++++++- src/rcc/pll.rs | 572 +++++++++++++++++++++++++++++++++++++++++ src/rcc/rec.rs | 1 - 3 files changed, 1241 insertions(+), 3 deletions(-) create mode 100644 src/rcc/pll.rs diff --git a/src/rcc.rs b/src/rcc.rs index 810057f..6c194ef 100644 --- a/src/rcc.rs +++ b/src/rcc.rs @@ -1,17 +1,156 @@ //! Reset and Clock Control //! +//! This module configures the RCC unit to provide set frequencies for +//! the input to the `sys_ck`, the High-performance Bus (AHB) `hclk`, +//! the Peripheral (APB) Buses `pclkN` and the peripheral clock `per_ck`. +//! +//! See Figure 31 "Clock tree" in Reference Manual +//! RM0492 for more information (p 250). +//! +//! HSI is 64 MHz. +//! CSI is 4 MHz. +//! HSI48 is 48MHz. +//! +//! # Usage +//! +//! This peripheral must be used alongside the +//! [`PWR`](../pwr/index.html) peripheral to freeze voltage scaling of the +//! device. +//! +//! A builder pattern is used to specify the state and frequency of +//! possible clocks. The `freeze` method configures the RCC peripheral +//! in a best-effort attempt to generate these clocks. The actual +//! clocks configured are returned in `ccdr.clocks`. +//! +//! No clock specification overrides another. However supplying some +//! clock specifications may influence multiple resulting clocks, +//! including those corresponding to other clock specifications. This +//! is particularly the case for PLL clocks, where the frequencies of +//! adjacent 'P', 'Q, and 'R' clock outputs must have a simple integer +//! fraction relationship. +//! +//! Some clock specifications imply other clock specifications, as follows: +//! +//! * `use_hse(a)` implies `sys_ck(a)` +//! +//! * `sys_ck(b)` implies `pll1_p_ck(b)` unless `b` equals HSI or +//! `use_hse(b)` was specified +//! +//! * `pll1_p_ck(c)` implies `pll1_r_ck(c/2)`, including when +//! `pll1_p_ck` was implied by `sys_ck(c)` or `mco2_from_pll1_p_ck(c)`. +//! +//! Implied clock specifications can always be overridden by explicitly +//! specifying that clock. If this results in a configuration that cannot +//! be achieved by hardware, `freeze` will panic. +//! +//! # Examples +//! +//! - [Simple RCC example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/rcc.rs). +//! - [Fractional PLL configuration](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/fractional-pll.rs) +//! - [MCO example](https://github.com/stm32-rs/stm32h7xx-hal/blob/master/examples/mco.rs) +//! +//! Simple example: +//! +//! ```rust +//! let dp = pac::Peripherals::take().unwrap(); +//! +//! let pwr = dp.PWR.constrain(); +//! let pwrcfg = pwr.freeze(); +//! +//! let rcc = dp.RCC.constrain(); +//! let ccdr = rcc +//! .sys_ck(96.MHz()) +//! .pclk1(48.MHz()) +//! .freeze(pwrcfg, &dp.SBS); +//! ``` +//! +//! A more complex example, involving the PLL: +//! +//! ```rust +//! let dp = pac::Peripherals::take().unwrap(); +//! +//! let pwr = dp.PWR.constrain(); +//! let pwrcfg = pwr.freeze(); +//! +//! let rcc = dp.RCC.constrain(); +//! let ccdr = rcc +//! .sys_ck(200.MHz()) // Implies pll1_p_ck +//! // For non-integer values, round up. `freeze` will never +//! // configure a clock faster than that specified. +//! .pll1_q_ck(33_333_334.hz()) +//! .freeze(pwrcfg, &dp.SBS); +//! ``` +//! +//! A much more complex example, indicative of real usage with a +//! significant fraction of the STM32H5's capabilities. +//! +//! ```rust +//! let dp = pac::Peripherals::take().unwrap(); +//! +//! let pwr = dp.PWR.constrain(); +//! let pwrcfg = pwr.freeze(); +//! +//! let rcc = dp.RCC.constrain(); +//! let ccdr = rcc +//! .use_hse(25.MHz()) // XTAL X1 +//! .sys_ck(250.MHz()) +//! .pll1_r_ck(50.MHz()) +//! .pll1_q_ck(100.MHz()) +//! .hclk(250.MHz()) +//! .pll2_strategy(PllConfigStrategy::Fractional) +//! .pll2_p_ck(240.MHz()) +//! .pll2_q_ck(48.MHz()) +//! .pll2_r_ck(26_666_667.Hz()) +//! .freeze(pwrcfg, &dp.SBS); +//!``` +//! +//! # Peripherals +//! +//! The `freeze()` method returns a [Core Clocks Distribution and Reset +//! (CCDR)](struct.Ccdr.html) object. This singleton tells you how the core +//! clocks were actually configured (in [CoreClocks](struct.CoreClocks.html)) +//! and allows you to configure the remaining peripherals (see +//! [PeripheralREC](crate::rcc::rec::struct.PeripheralREC.html)). +//! +//!```rust +//! let ccdr = ...; // Returned by `freeze()`, see examples above +//! +//! // Runtime confirmation that hclk really is 200MHz +//! assert_eq!(ccdr.clocks.hclk().raw(), 200_000_000); +//! +//! // Panics if pll1_q_ck is not running +//! let _ = ccdr.clocks.pll1_q_ck().unwrap(); +//! +//! // Enable the clock to a peripheral and reset it +//! ccdr.peripheral.FDCAN.enable().reset(); +//!``` +//! +//! The [PeripheralREC](struct.PeripheralREC.html) members implement move +//! semantics, so once you have passed them to a constructor they cannot be +//! modified again in safe Rust. +//! +#![deny(missing_docs)] -use crate::stm32::RCC; +use crate::pwr::PowerConfiguration; +use crate::pwr::VoltageScale as Voltage; +use crate::stm32::rcc::{ + ccipr5::CKPERSEL, cfgr1::SW, cfgr1::TIMPRE, cfgr2::HPRE, + cfgr2::PPRE1 as PPRE, pll1cfgr::PLL1SRC, pll2cfgr::PLL2SRC, +}; +use crate::stm32::{RCC, SBS}; use crate::time::Hertz; #[cfg(feature = "log")] use log::debug; mod core_clocks; -mod rec; +mod pll; +pub mod rec; mod reset_reason; pub use core_clocks::CoreClocks; +pub use pll::{PllConfig, PllConfigStrategy}; +pub use rec::{LowPowerMode, PeripheralREC, ResetEnable}; pub use reset_reason::ResetReason; /// Configuration of the core clocks @@ -28,6 +167,10 @@ pub struct Config { rcc_pclk3: Option, #[cfg(feature = "rm0481")] rcc_pclk4: Option, + pll1: PllConfig, + pll2: PllConfig, + #[cfg(feature = "rm0481")] + pll3: PllConfig, } /// Extension trait that constrains the `RCC` peripheral @@ -53,6 +196,10 @@ impl RccExt for RCC { rcc_pclk3: None, #[cfg(feature = "rm0481")] rcc_pclk4: None, + pll1: PllConfig::default(), + pll2: PllConfig::default(), + #[cfg(feature = "rm0481")] + pll3: PllConfig::default(), }, rb: self, } @@ -89,8 +236,16 @@ impl Rcc { pub struct Ccdr { /// A record of the frozen core clock frequencies pub clocks: CoreClocks, + + /// Peripheral reset / enable / kernel clock control + pub peripheral: PeripheralREC, } +const HSI: u32 = 32_000_000; // Hz +const CSI: u32 = 4_000_000; // Hz +const HSI48: u32 = 48_000_000; // Hz +const LSI: u32 = 32_000; // Hz + const MAX_SYSCLK_FREQ_HZ: u32 = 250_000_000; /// Setter defintion for pclk 1 - 4 @@ -110,6 +265,38 @@ macro_rules! pclk_setter { }; } +/// Setter definition for pll 1 - 3 p, q, r +macro_rules! pll_setter { + ($($pll:ident: [ $($name:ident: $ck:ident,)+ ],)+) => { + $( + $( + /// Set the target clock frequency for PLL output + #[must_use] + pub fn $name(mut self, freq: Hertz) -> Self { + self.config.$pll.$ck = Some(freq.raw()); + self + } + )+ + )+ + }; +} + +/// Setter definition for pll 1 - 3 strategy +macro_rules! pll_strategy_setter { + ($($pll:ident: $name:ident,)+) => { + $( + /// Set the PLL divider strategy to be used when the PLL + /// is configured + #[must_use] + pub fn $name(mut self, strategy: PllConfigStrategy) -> Self + { + self.config.$pll.strategy = strategy; + self + } + )+ + } +} + impl Rcc { /// Uses HSE (external oscillator) instead of HSI (internal RC /// oscillator) as the clock source. Will result in a hang if an @@ -186,4 +373,484 @@ impl Rcc { pclk_setter! { pclk4: rcc_pclk4, } + + pll_setter! { + pll1: [ + pll1_p_ck: p_ck, + pll1_q_ck: q_ck, + pll1_r_ck: r_ck, + ], + pll2: [ + pll2_p_ck: p_ck, + pll2_q_ck: q_ck, + pll2_r_ck: r_ck, + ], + } + + pll_strategy_setter! { + pll1: pll1_strategy, + pll2: pll2_strategy, + } +} + +/// Divider calculator for pclk 1 - 4 +/// +/// Also calulate tim[xy]_ker_clk if there are timers on this bus +macro_rules! ppre_calculate { + ($(($ppre:ident, $bits:ident): ($self: ident, $hclk: ident, $pclk: ident + $(,$rcc_tim_ker_clk:ident, $timpre:ident)*),)+) => { + $( + // Get intended rcc_pclkN frequency + let $pclk: u32 = $self.config + .$pclk + .unwrap_or($hclk); + + // Calculate suitable divider + let ($bits, $ppre) = match ($hclk + $pclk - 1) / $pclk + { + 0 => unreachable!(), + 1 => (PPRE::Div1, 1 as u8), + 2 => (PPRE::Div2, 2), + 3..=5 => (PPRE::Div4, 4), + 6..=11 => (PPRE::Div8, 8), + _ => (PPRE::Div16, 16), + }; + + // Calculate real APBn clock + let $pclk = $hclk / u32::from($ppre); + + $( + let $rcc_tim_ker_clk = match ($bits, &$timpre) + { + (PPRE::Div4, TIMPRE::DefaultX2) => $hclk / 2, + (PPRE::Div8, TIMPRE::DefaultX4) => $hclk / 2, + (PPRE::Div8, TIMPRE::DefaultX2) => $hclk / 4, + (PPRE::Div16, TIMPRE::DefaultX4) => $hclk / 4, + (PPRE::Div16, TIMPRE::DefaultX2) => $hclk / 8, + _ => $hclk, + }; + )* + )+ + }; +} + +impl Rcc { + fn flash_setup(rcc_hclk: u32, vos: Voltage) { + use crate::stm32::FLASH; + // ACLK in MHz, round down and subtract 1 from integers. eg. + // 61_999_999 -> 61MHz + // 62_000_000 -> 61MHz + // 62_000_001 -> 62MHz + let rcc_hclk_mhz = Hertz::from_raw(rcc_hclk - 1).to_MHz(); + + // See RM00492 Table 20 + let (wait_states, progr_delay) = match vos { + // VOS 0 range VCORE 1.25V - 1.35V + Voltage::Scale0 => match rcc_hclk_mhz { + 0..=41 => (0, 0), + 42..=83 => (1, 0), + 84..=125 => (2, 1), + 126..=167 => (3, 1), + 168..=209 => (4, 2), + 210..=250 => (5, 2), + _ => (7, 3), + }, + // VOS 1 range VCORE 1.15V - 1.25V + Voltage::Scale1 => match rcc_hclk_mhz { + 0..=33 => (0, 0), + 34..=67 => (1, 0), + 68..=101 => (2, 1), + 102..=135 => (3, 1), + 136..=169 => (4, 2), + 170..=200 => (5, 2), + _ => (7, 3), + }, + // VOS 2 range VCORE 1.05V - 1.15V + Voltage::Scale2 => match rcc_hclk_mhz { + 0..=29 => (0, 0), + 30..=59 => (1, 0), + 60..=89 => (2, 1), + 90..=119 => (3, 1), + 120..=150 => (4, 2), + _ => (7, 3), + }, + // VOS 3 range VCORE 0.95V - 1.05V + Voltage::Scale3 => match rcc_hclk_mhz { + 0..=19 => (0, 0), + 20..=39 => (1, 0), + 40..=59 => (2, 1), + 60..=79 => (3, 1), + 80..=100 => (4, 2), + _ => (7, 3), + }, + }; + + let flash = unsafe { &(*FLASH::ptr()) }; + // Adjust flash wait states + flash.acr().write(|w| unsafe { + w.wrhighfreq().bits(progr_delay).latency().bits(wait_states) + }); + while flash.acr().read().latency().bits() != wait_states {} + } + + /// Setup sys_ck + /// Returns sys_ck frequency, and a pll1_p_ck + fn sys_ck_setup(&mut self) -> (Hertz, bool) { + // Compare available with wanted clocks + let srcclk = self.config.hse.unwrap_or(HSI); // Available clocks + let sys_ck = self.config.sys_ck.unwrap_or(srcclk); + + if sys_ck != srcclk { + // The requested system clock is not the immediately available + // HSE/HSI clock. Perhaps there are other ways of obtaining + // the requested system clock (such as `HSIDIV`) but we will + // ignore those for now. + // + // Therefore we must use pll1_p_ck + let pll1_p_ck = match self.config.pll1.p_ck { + Some(p_ck) => { + assert!(p_ck == sys_ck, + "Error: Cannot set pll1_p_ck independently as it must be used to generate sys_ck"); + Some(p_ck) + } + None => Some(sys_ck), + }; + self.config.pll1.p_ck = pll1_p_ck; + + (Hertz::from_raw(sys_ck), true) + } else { + // sys_ck is derived directly from a source clock + // (HSE/HSI). pll1_p_ck can be as requested + (Hertz::from_raw(sys_ck), false) + } + } + + /// Freeze the core clocks, returning a Core Clocks Distribution + /// and Reset (CCDR) structure. The actual frequency of the clocks + /// configured is returned in the `clocks` member of the CCDR + /// structure. + /// + /// Note that `freeze` will never result in a clock _faster_ than + /// that specified. It may result in a clock that is a factor of [1, + /// 2) slower. + /// + /// `sbs` is required to enable the I/O compensation cell. + /// + /// # Panics + /// + /// If a clock specification cannot be achieved within the + /// hardware specification then this function will panic. This + /// function may also panic if a clock specification can be + /// achieved, but the mechanism for doing so is not yet + /// implemented here. + pub fn freeze(mut self, pwrcfg: PowerConfiguration, sbs: &SBS) -> Ccdr { + // We do not reset RCC here. This routine must assert when + // the previous state of the RCC peripheral is unacceptable. + + // sys_ck from PLL if needed, else HSE or HSI + let (sys_ck, sys_use_pll1_p) = self.sys_ck_setup(); + + // self is now immutable ---------------------------------------- + let rcc = &self.rb; + + // Configure PLL1 + let (pll1_p_ck, pll1_q_ck, pll1_r_ck) = + self.pll1_setup(rcc, &self.config.pll1); + // Configure PLL2 + let (pll2_p_ck, pll2_q_ck, pll2_r_ck) = + self.pll2_setup(rcc, &self.config.pll2); + + let sys_ck = if sys_use_pll1_p { + pll1_p_ck.unwrap() // Must have been set by sys_ck_setup + } else { + sys_ck + }; + + // hsi_ck = HSI. This routine does not support HSIDIV != 1. To + // do so it would need to ensure all PLLxON bits are clear + // before changing the value of HSIDIV + let hsi = HSI; + assert!( + rcc.cr().read().hsion().is_on(), + "HSI oscillator must be on!" + ); + assert!( + rcc.cr().read().hsidiv().is_div2(), + "HSI oscillator divider is not 2: {:?}", + rcc.cr().read().hsidiv().variant() + ); + + let csi = CSI; + let hsi48 = HSI48; + + // Enable LSI for RTC, IWDG, AWU, or MCO2 + let lsi = LSI; + rcc.bdcr().modify(|_, w| w.lsion().enabled()); + while rcc.bdcr().read().lsirdy().is_not_ready() {} + + // per_ck from HSI by default + let (per_ck, ckpersel) = + match (self.config.per_ck == self.config.hse, self.config.per_ck) { + (true, Some(hse)) => (hse, CKPERSEL::Hse), // HSE + (_, Some(CSI)) => (csi, CKPERSEL::CsiKer), // CSI + _ => (hsi, CKPERSEL::HsiKer), // HSI + }; + + // Timer prescaler selection + let timpre = TIMPRE::DefaultX2; + + // Get AHB clock or sensible default + let rcc_hclk = self.config.rcc_hclk.unwrap_or(sys_ck.raw()); + + // Estimate divisor + let (hpre_bits, hpre_div) = + match (sys_ck.raw() + rcc_hclk - 1) / rcc_hclk { + 0 => unreachable!(), + 1 => (HPRE::Div1, 1), + 2 => (HPRE::Div2, 2), + 3..=5 => (HPRE::Div4, 4), + 6..=11 => (HPRE::Div8, 8), + 12..=39 => (HPRE::Div16, 16), + 40..=95 => (HPRE::Div64, 64), + 96..=191 => (HPRE::Div128, 128), + 192..=383 => (HPRE::Div256, 256), + _ => (HPRE::Div512, 512), + }; + + // Calculate real AHB clock + let rcc_hclk = sys_ck.raw() / hpre_div; + + // Calculate ppreN dividers and real rcc_pclkN frequencies + ppre_calculate! { + (ppre1, ppre1_bits): (self, rcc_hclk, rcc_pclk1, rcc_timx_ker_ck, timpre), + (ppre2, ppre2_bits): (self, rcc_hclk, rcc_pclk2, rcc_timy_ker_ck, timpre), + (ppre3, ppre3_bits): (self, rcc_hclk, rcc_pclk3), + } + + // Start switching clocks here! ---------------------------------------- + + // Flash setup + Self::flash_setup(rcc_hclk, pwrcfg.vos); + + // Ensure CSI is on and stable + rcc.cr().modify(|_, w| w.csion().on()); + while rcc.cr().read().csirdy().is_not_ready() {} + + // Ensure HSI48 is on and stable + rcc.cr().modify(|_, w| w.hsi48on().on()); + while rcc.cr().read().hsi48rdy().is_not_ready() {} + + // HSE + let hse_ck = match self.config.hse { + Some(hse) => { + // Ensure HSE is on and stable + rcc.cr().modify(|_, w| { + w.hseon().on().hsebyp().bit(self.config.bypass_hse) + }); + while rcc.cr().read().hserdy().is_not_ready() {} + + Some(Hertz::from_raw(hse)) + } + None => None, + }; + + let lse_ck = self.config.lse.map(Hertz::from_raw); + + let audio_ck = self.config.audio_ck.map(Hertz::from_raw); + + // PLL + let (pll1src, pll2src) = if self.config.hse.is_some() { + (PLL1SRC::Hse, PLL2SRC::Hse) + } else { + (PLL1SRC::Hsi, PLL2SRC::Hsi) + }; + rcc.pll1cfgr().modify(|_, w| w.pll1src().variant(pll1src)); + rcc.pll2cfgr().modify(|_, w| w.pll2src().variant(pll2src)); + + // PLL1 + if pll1_p_ck.is_some() { + // Enable PLL and wait for it to stabilise + rcc.cr().modify(|_, w| w.pll1on().on()); + while rcc.cr().read().pll1rdy().is_not_ready() {} + } + + // PLL2 + if pll2_p_ck.is_some() { + // Enable PLL and wait for it to stabilise + rcc.cr().modify(|_, w| w.pll2on().on()); + while rcc.cr().read().pll2rdy().is_not_ready() {} + } + + // Core Prescaler / AHB Prescaler / APBx Prescalers + rcc.cfgr2().modify(|_, w| { + w.hpre() + .variant(hpre_bits) + .ppre1() + .variant(ppre1_bits) + .ppre2() + .variant(ppre2_bits) + .ppre3() + .variant(ppre3_bits) + }); + + // Ensure core prescaler value is valid before future lower + // core voltage + while rcc.cfgr2().read().hpre().variant() != Some(hpre_bits) {} + + // Peripheral Clock (per_ck) + rcc.ccipr5().modify(|_, w| w.ckpersel().variant(ckpersel)); + + // Set timer clocks prescaler setting + rcc.cfgr1().modify(|_, w| w.timpre().variant(timpre)); + + // Select system clock source + let swbits = match (sys_use_pll1_p, self.config.hse.is_some()) { + (true, _) => SW::Pll1, + (false, true) => SW::Hse, + _ => SW::Hsi, + }; + rcc.cfgr1().modify(|_, w| w.sw().variant(swbits)); + while rcc.cfgr1().read().sws().bits() != swbits.into() {} + + // IO compensation cell - Requires CSI clock and SYSCFG + assert!(rcc.cr().read().csirdy().is_ready()); + rcc.apb3enr().modify(|_, w| w.sbsen().enabled()); + + // Enable the compensation cell, using back-bias voltage code + // provide by the cell. + + // PAC note: using the unmodified SBS field definitions as they're not defined for the + // rm0481 MCUs at this time + sbs.cccsr().modify(|_, w| { + w.en1() + .set_bit() + .cs1() + .clear_bit() + .en2() + .set_bit() + .cs2() + .clear_bit() + }); + while sbs.cccsr().read().rdy1().bit_is_clear() + || sbs.cccsr().read().rdy2().bit_is_clear() + {} + + // This section prints the final register configuration for the main RCC registers: + // - System Clock and PLL Source MUX + // - PLL configuration + // - System Prescalers + // Does not include peripheral/MCO/RTC clock MUXes + #[cfg(feature = "log")] + { + debug!("--- RCC register settings"); + + let cfgr1 = rcc.cfgr1().read(); + debug!( + "CFGR1 register: SWS (System Clock Mux)={:?}", + cfgr1.sws().variant() + ); + + let cfgr2 = rcc.cfgr2().read(); + debug!( + "CFGR2 register: HPRE={:?} PPRE1={:?} PPRE2={:?} PPRE3={:?}", + cfgr2.hpre().variant().unwrap(), + cfgr2.ppre1().variant().unwrap(), + cfgr2.ppre2().variant().unwrap(), + cfgr2.ppre3().variant().unwrap(), + ); + + let pll1cfgr = rcc.pll1cfgr().read(); + debug!( + "PLL1CFGR register: PLL1SRC={:?} PLL1RGE={:?} PLL1FRACEN={:?} PLL1VCOSEL={:?} PLL1M={:#x} PLL1PEN={:?} PLL1QEN={:?} PLL1REN={:?}", + pll1cfgr.pll1src().variant(), + pll1cfgr.pll1rge().variant(), + pll1cfgr.pll1fracen().variant(), + pll1cfgr.pll1vcosel().variant(), + pll1cfgr.pll1m().bits(), + pll1cfgr.pll1pen().variant(), + pll1cfgr.pll1qen().variant(), + pll1cfgr.pll1ren().variant(), + ); + + let pll2cfgr = rcc.pll2cfgr().read(); + debug!( + "PLL2CFGR register: PLL2SRC={:?} PLL2RGE={:?} PLL2FRACEN={:?} PLL2VCOSEL={:?} PLL2M={:#x} PLL2PEN={:?} PLL2QEN={:?} PLL2REN={:?}", + pll2cfgr.pll2src().variant(), + pll2cfgr.pll2rge().variant(), + pll2cfgr.pll2fracen().variant(), + pll2cfgr.pll2vcosel().variant(), + pll2cfgr.pll2m().bits(), + pll2cfgr.pll2pen().variant(), + pll2cfgr.pll2qen().variant(), + pll2cfgr.pll2ren().variant(), + ); + + let pll1divr = rcc.pll1divr().read(); + debug!( + "PLL1DIVR register: PLL1N={:#x} PLL1P={:#x} PLL1Q={:#x} PLL1R={:#x}", + pll1divr.pll1n().bits(), + pll1divr.pll1p().bits(), + pll1divr.pll1q().bits(), + pll1divr.pll1r().bits(), + ); + + let pll1fracr = rcc.pll1fracr().read(); + debug!( + "PLL1FRACR register: FRACN1={:#x}", + pll1fracr.pll1fracn().bits(), + ); + + let pll2divr = rcc.pll2divr().read(); + debug!( + "PLL2DIVR register: PLL2N={:#x} PLL2P={:#x} PLL2Q={:#x} PLL2R={:#x}", + pll2divr.pll2n().bits(), + pll2divr.pll2p().bits(), + pll2divr.pll2q().bits(), + pll2divr.pll2r().bits(), + ); + + let pll2fracr = rcc.pll2fracr().read(); + debug!( + "PLL2FRACR register: FRACN2={:#x}", + pll2fracr.pll2fracn().bits(), + ); + } + + // Return frozen clock configuration + Ccdr { + clocks: CoreClocks { + hclk: Hertz::from_raw(rcc_hclk), + pclk1: Hertz::from_raw(rcc_pclk1), + pclk2: Hertz::from_raw(rcc_pclk2), + pclk3: Hertz::from_raw(rcc_pclk3), + ppre1, + ppre2, + ppre3, + csi_ck: Some(Hertz::from_raw(csi)), + hsi_ck: Some(Hertz::from_raw(hsi)), + hsi48_ck: Some(Hertz::from_raw(hsi48)), + lsi_ck: Some(Hertz::from_raw(lsi)), + per_ck: Some(Hertz::from_raw(per_ck)), + hse_ck, + lse_ck, + audio_ck, + mco1_ck: None, + mco2_ck: None, + pll1_p_ck, + pll1_q_ck, + pll1_r_ck, + pll2_p_ck, + pll2_q_ck, + pll2_r_ck, + timx_ker_ck: Hertz::from_raw(rcc_timx_ker_ck), + timy_ker_ck: Hertz::from_raw(rcc_timy_ker_ck), + sys_ck, + }, + peripheral: unsafe { + // unsafe: we consume self which was a singleton, hence + // we can safely create a singleton here + PeripheralREC::new_singleton() + }, + } + } } diff --git a/src/rcc/pll.rs b/src/rcc/pll.rs new file mode 100644 index 0000000..7b67d3e --- /dev/null +++ b/src/rcc/pll.rs @@ -0,0 +1,572 @@ +//! Phase Locked Loop Configuration + +use core::ops::RangeInclusive; + +use super::{Rcc, HSI}; +use crate::stm32::rcc::pll1cfgr::PLL1VCOSEL as VCOSEL; +use crate::stm32::RCC; +use crate::time::Hertz; + +const FRACN_DIVISOR: f32 = 8192.0; // 2 ** 13 +const FRACN_MAX: f32 = FRACN_DIVISOR - 1.0; + +const PLL_N_MIN: u32 = 4; +const PLL_N_MAX: u32 = 512; +const PLL_OUT_DIV_MAX: u32 = 128; +const PLL_M_MAX: u32 = 63; + +/// Strategies for configuring a Phase Locked Loop (PLL) +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum PllConfigStrategy { + /// No fractional component + Integer, + /// PLL configured with fractional divider + Fractional, +} + +/// Configuration of a Phase Locked Loop (PLL) +pub struct PllConfig { + pub(super) strategy: PllConfigStrategy, + pub(super) p_ck: Option, + pub(super) q_ck: Option, + pub(super) r_ck: Option, +} +impl Default for PllConfig { + fn default() -> PllConfig { + PllConfig { + strategy: PllConfigStrategy::Integer, + p_ck: None, + q_ck: None, + r_ck: None, + } + } +} + +#[derive(Debug)] +struct VcoRange { + output_range: RangeInclusive, + input_range: RangeInclusive, + vcosel: VCOSEL, +} + +const VCO_RANGE_MEDIUM: VcoRange = VcoRange { + output_range: 150_000_000..=420_000_000, + input_range: 1_000_000..=2_000_000, + vcosel: VCOSEL::MediumVco, +}; +const VCO_RANGE_WIDE: VcoRange = VcoRange { + output_range: 128_000_000..=560_000_000, + input_range: 2_000_000..=16_000_000, + vcosel: VCOSEL::WideVco, +}; + +#[derive(Debug)] +struct PllOutput { + ck: u32, + div: u32, +} + +#[derive(Debug)] +struct PllSetup { + vco_range: VcoRange, + vco_out_target: u32, + ref_ck: u32, + pll_m: u32, + pll_p: Option, + pll_q: Option, + pll_r: Option, +} + +/// Calculate VCO output divider (p-divider). Choose the highest VCO +/// frequency to give specified output. +/// +/// Returns *target* VCO frequency and p-divider +/// +fn vco_output_divider_setup( + pllsrc: u32, + pllcfg: &PllConfig, + p_odd_allowed: bool, +) -> PllSetup { + let outputs = [ + (pllcfg.p_ck, p_odd_allowed), + (pllcfg.q_ck, true), + (pllcfg.r_ck, true), + ] + .into_iter() + .filter(|it| it.0.is_some()) + // Multiply output for PLL where P cannot be odd, so that the max output frequency + // considered is PLLP * 2 + .map(|it| { + if it.1 { + it.0.unwrap() + } else { + it.0.unwrap() * 2 + } + }); + + let (min_output, max_output): (u32, u32) = + outputs.fold((0, 0), |minmax, ck| { + let min = if minmax.0 == 0 { ck } else { minmax.0.min(ck) }; + let max = minmax.1.max(ck); + (min, max) + }); + + let vco_ranges = [VCO_RANGE_MEDIUM, VCO_RANGE_WIDE]; + + // Use max output frequency required for VCO out to minimize dividers + assert!( + min_output >= (max_output / PLL_OUT_DIV_MAX), + "VCO cannot be configured for given PLL spec" + ); + + let range = vco_ranges + .into_iter() + .find(|range| range.output_range.end() > &max_output) + .expect("Target frequency is higher than PLL max frequency"); + + let vco_max: u32 = *range.output_range.end(); + let min_div = vco_max / max_output; + let vco_out_target = max_output * min_div; + + let vco_out_target = if (vco_out_target / min_output) > PLL_OUT_DIV_MAX { + let f = ((vco_out_target / min_output) + PLL_OUT_DIV_MAX - 1) + / PLL_OUT_DIV_MAX; + vco_out_target / f + } else { + vco_out_target + }; + assert!(range.output_range.contains(&vco_out_target)); + + let pll_x_p = pllcfg.p_ck.map(|ck| PllOutput { + ck, + div: vco_out_target / ck, + }); + let pll_x_q = pllcfg.q_ck.map(|ck| PllOutput { + ck, + div: vco_out_target / ck, + }); + let pll_x_r = pllcfg.r_ck.map(|ck| PllOutput { + ck, + div: vco_out_target / ck, + }); + + // Input divisor, resulting in a reference clock in the + // range 2 to 16 MHz. + let pll_x_m_min = + (pllsrc + range.input_range.end() - 1) / range.input_range.end(); + let pll_x_m_max = (pllsrc / range.input_range.start()).min(PLL_M_MAX); + + // Iterative search for the lowest m value that minimizes + // the difference between requested and actual VCO frequency + let pll_x_m = (pll_x_m_min..=pll_x_m_max) + .min_by_key(|pll_x_m| { + let ref_x_ck = pllsrc / pll_x_m; + + // Feedback divider. Integer only + let pll_x_n = vco_out_target / ref_x_ck; + + vco_out_target as i32 - (ref_x_ck * pll_x_n) as i32 + }) + .unwrap(); + + assert!(pll_x_m <= PLL_M_MAX); + + // Calculate resulting reference clock + let ref_ck = pllsrc / pll_x_m; + assert!(range.input_range.contains(&ref_ck)); + + PllSetup { + vco_range: range, + vco_out_target, + ref_ck, + pll_m: pll_x_m, + pll_p: pll_x_p, + pll_q: pll_x_q, + pll_r: pll_x_r, + } +} + +/// Setup PFD input frequency and VCO output frequency +/// +macro_rules! vco_setup { + ($pllsrc:ident, $pllcfg:expr, $rcc:ident, $pllX:ident, $odd_allowed:expr) + => { paste::item! {{ + + // VCO output frequency limits + let pll_setup = vco_output_divider_setup($pllsrc, $pllcfg, $odd_allowed); + + // Configure VCO + $rcc.[<$pllX cfgr>]().modify(|_, w| { + match pll_setup.vco_range.vcosel { + VCOSEL::MediumVco => w.[<$pllX vcosel>]().medium_vco(), + VCOSEL::WideVco => w.[<$pllX vcosel>]().wide_vco(), + } + }); + $rcc.[<$pllX cfgr>]().modify(|_, w| { + match pll_setup.ref_ck { + 1_000_000 ..= 1_999_999=> w.[<$pllX rge>]().range1(), + 2_000_000 ..= 3_999_999 => w.[<$pllX rge>]().range2(), + 4_000_000 ..= 7_999_999 => w.[<$pllX rge>]().range4(), + _ => w.[<$pllX rge>]().range8(), + } + }); + + pll_setup + }}}; +} + +macro_rules! pll_divider_setup { + (($pllX:ident, $rcc:expr, $pll:expr, $vco_ck:expr) $(,$d:ident: $pll_x_d:expr)*) => { + paste::item! {{ + $( + let [<$d _ck_hz>] = if let Some(pll_x_d) = $pll_x_d { + // Bounds check + + // Setup divider + $rcc.[<$pllX divr>]().modify(|_, w| + w.[<$pllX $d>]().variant((pll_x_d.div - 1) as u8) + ); + $rcc.[<$pllX cfgr>]().modify(|_, w| w.[<$pllX $d en>]().enabled()); + Some(Hertz::from_raw($vco_ck / pll_x_d.div)) + } else { + $rcc.[< $pllX cfgr>]().modify(|_, w| w.[<$pllX $d en>]().disabled()); + None + }; + )* + ($([<$d _ck_hz>],)*) + }} + }; +} + +macro_rules! pll_setup { + ($pllX:ident, $odd_allowed:expr) => { + paste::item! { + /// PLL Setup + /// Returns (Option(pllX_p_ck), Option(pllX_q_ck), Option(pllX_r_ck)) + + pub(super) fn [< $pllX _setup >] ( + &self, + rcc: &RCC, + pll: &PllConfig, + ) -> (Option, Option, Option) { + // PLL sourced from either HSE or HSI + let pllsrc = self.config.hse.unwrap_or(HSI); + assert!(pllsrc > 0); + + // PLL output. Use P, Q or R ck in that order of preference + if pll.p_ck.or(pll.q_ck.or(pll.r_ck)).is_none() { + return (None, None, None); + } + + let pll_setup = vco_setup!(pllsrc, pll, rcc, $pllX, $odd_allowed); + + // Feedback divider. Integer only + let pll_x_n = pll_setup.vco_out_target / pll_setup.ref_ck; + + // Write dividers + rcc.[< $pllX cfgr >]().modify(|_, w| + w.[< $pllX m >]() + .variant(pll_setup.pll_m as u8)); // ref prescaler + + // unsafe as not all values are permitted: see RM0492 + assert!(pll_x_n >= PLL_N_MIN); + assert!(pll_x_n <= PLL_N_MAX); + rcc.[<$pllX divr>]().modify(|_, w| w.[<$pllX n>]().variant((pll_x_n - 1) as u16)); + + let pll_x = pll_setup.pll_p.as_ref().or(pll_setup.pll_q.as_ref().or(pll_setup.pll_r.as_ref())).unwrap(); + + // Configure N divider. Returns the resulting VCO frequency + let vco_ck = match pll.strategy { + PllConfigStrategy::Fractional => { + // Calculate FRACN + let pll_x_fracn = calc_fracn(pll_setup.ref_ck as f32, pll_x_n as f32, pll_x.div as f32, pll_x.ck as f32); + //RCC_PLL1FRACR + rcc.[<$pllX fracr>]().modify(|_, w| w.[<$pllX fracn>]().variant(pll_x_fracn)); + // Latch FRACN by resetting and setting it + rcc.[<$pllX cfgr>]().modify(|_, w| w.[< $pllX fracen>]().reset() ); + rcc.[<$pllX cfgr>]().modify(|_, w| w.[< $pllX fracen>]().set() ); + + calc_vco_ck(pll_setup.ref_ck, pll_x_n, pll_x_fracn) + }, + // Iterative + _ => { + pll_setup.ref_ck * pll_x_n + }, + }; + + pll_divider_setup! {($pllX, rcc, pll, vco_ck), + p: &pll_setup.pll_p, + q: &pll_setup.pll_q, + r: &pll_setup.pll_r } + } + } + }; +} + +/// Calcuate the Fractional-N part of the divider +/// +/// ref_clk - Frequency at the PFD input +/// pll_n - Integer-N part of the divider +/// pll_p - P-divider +/// output - Wanted output frequency +fn calc_fracn(ref_clk: f32, pll_n: f32, pll_p: f32, output: f32) -> u16 { + // VCO output frequency = Fref1_ck x (DIVN1 + (FRACN1 / 2^13)), + let pll_fracn = FRACN_DIVISOR * (((output * pll_p) / ref_clk) - pll_n); + assert!(pll_fracn >= 0.0); + assert!(pll_fracn <= FRACN_MAX); + // Rounding down by casting gives up the lowest without going over + pll_fracn as u16 +} + +/// Calculates the VCO output frequency +/// +/// ref_clk - Frequency at the PFD input +/// pll_n - Integer-N part of the divider +/// pll_fracn - Fractional-N part of the divider +fn calc_vco_ck(ref_ck: u32, pll_n: u32, pll_fracn: u16) -> u32 { + (ref_ck as f32 * (pll_n as f32 + (pll_fracn as f32 / FRACN_DIVISOR))) as u32 +} + +impl Rcc { + pll_setup! {pll1, false} + pll_setup! {pll2, true} +} + +#[cfg(test)] +mod tests { + use super::VCOSEL; + use crate::rcc::{ + pll::{ + calc_fracn, calc_vco_ck, vco_output_divider_setup, + PllConfigStrategy, PLL_OUT_DIV_MAX, + }, + PllConfig, + }; + + macro_rules! dummy_method { + ($($name:ident),+) => ( + $( + fn $name(self) -> Self { + self + } + )+ + ) + } + + // Mock PLL CFGR + struct WPllCfgr {} + impl WPllCfgr { + dummy_method! { pll1vcosel, medium_vco, wide_vco } + dummy_method! { pll1rge, range1, range2, range4, range8 } + } + struct MockPllCfgr {} + impl MockPllCfgr { + // Modify mock registers + fn modify(&self, func: F) + where + F: FnOnce((), WPllCfgr) -> WPllCfgr, + { + func((), WPllCfgr {}); + } + } + + // Mock RCC + struct MockRcc { + pll1cfgr: MockPllCfgr, + } + impl MockRcc { + pub fn new() -> Self { + MockRcc { + pll1cfgr: MockPllCfgr {}, + } + } + + fn pll1cfgr(&self) -> &MockPllCfgr { + &self.pll1cfgr + } + } + + #[test] + fn vco_setup_integer() { + let rcc = MockRcc::new(); + + let pllsrc = 25_000_000; // PLL source frequency eg. 25MHz crystal + let pll_p_target = 242_000_000; // PLL output frequency (P_CK) + let pll_q_target = 120_900_000; // PLL output frequency (Q_CK) + let pll_r_target = 30_200_000; // PLL output frequency (R_CK) + let pllcfg = PllConfig { + strategy: PllConfigStrategy::Integer, + p_ck: Some(pll_p_target), + q_ck: Some(pll_q_target), + r_ck: Some(pll_r_target), + }; + println!( + "PLL2/3 {} MHz -> {} MHz", + pllsrc as f32 / 1e6, + pll_p_target as f32 / 1e6 + ); + + // ---------------------------------------- + + // VCO Setup + let pll_setup = vco_setup! { + pllsrc, &pllcfg, rcc, pll1, true + }; + + println!("\nPLL setup: {pll_setup:?}\n"); + // Feedback divider. Integer only + let pll_x_n = pll_setup.vco_out_target / pll_setup.ref_ck; + // Resulting achieved vco_ck + let vco_ck_achieved = calc_vco_ck(pll_setup.ref_ck, pll_x_n, 0); + + // ---------------------------------------- + + // Input + println!("M Divider {}", pll_setup.pll_m); + let input = pllsrc as f32 / pll_setup.pll_m as f32; + println!("==> Input {} MHz", input / 1e6); + println!(); + assert!((input >= 1e6) && (input < 2e6), "input: {}", input); + + println!( + "VCO CK Target {} MHz", + pll_setup.vco_out_target as f32 / 1e6 + ); + println!("VCO CK Achieved {} MHz", vco_ck_achieved as f32 / 1e6); + println!(); + + // Output + let pll_x_p = pll_setup.pll_p.unwrap().div; + println!("P Divider {}", pll_x_p); + let output_p = vco_ck_achieved as f32 / pll_x_p as f32; + println!("==> Output {} MHz", output_p / 1e6); + + let error = output_p - pll_p_target as f32; + println!( + "Error {} {}", + f32::abs(error), + (pll_p_target as f32 / 100.0) + ); + assert!(f32::abs(error) < (pll_p_target as f32 / 100.0)); // < ±1% error + println!(); + + let pll_x_q = pll_setup.pll_q.unwrap().div; + let output_q = vco_ck_achieved as f32 / pll_x_q as f32; + println!("Q Divider {}", pll_x_q); + println!("==> Output Q {} MHz", output_q / 1e6); + println!(); + let error = output_q - pll_q_target as f32; + assert!(f32::abs(error) < (pll_q_target as f32 / 100.0)); // < ±1% error + + let pll_x_r = pll_setup.pll_r.unwrap().div; + let output_r = vco_ck_achieved as f32 / pll_x_r as f32; + println!("R Divider {}", pll_x_r); + println!("==> Output Q {} MHz", output_r / 1e6); + println!(); + let error = output_r - pll_r_target as f32; + assert!(f32::abs(error) < (pll_r_target as f32 / 100.0)); // < ±1% error + } + + #[test] + fn vco_setup_fractional() { + let rcc = MockRcc::new(); + + let pllsrc = 16_000_000; // PLL source frequency eg. 16MHz crystal + let pll_p_target = 48_000 * 256; // Target clock = 12.288MHz + let pll_q_target = 48_000 * 128; // Target clock = 6.144MHz + let pll_r_target = 48_000 * 63; // Target clock = 3.024MHz + let pllcfg = PllConfig { + strategy: PllConfigStrategy::Integer, + p_ck: Some(pll_p_target), + q_ck: Some(pll_q_target), + r_ck: Some(pll_r_target), + }; + let output = pll_p_target; // PLL output frequency (P_CK) + println!( + "PLL2/3 {} MHz -> {} MHz", + pllsrc as f32 / 1e6, + output as f32 / 1e6 + ); + + // ---------------------------------------- + + // VCO Setup + let pll_setup = vco_setup! { + pllsrc, &pllcfg, rcc, pll1, true + }; + let input = pllsrc as f32 / pll_setup.pll_m as f32; + + println!("\nPLL setup: {pll_setup:?}\n"); + // Feedback divider. Integer only + let pll_x_n = pll_setup.vco_out_target / pll_setup.ref_ck; + let pll_x_p = pll_setup.pll_p.unwrap().div; + let pll_x_fracn = calc_fracn( + input as f32, + pll_x_n as f32, + pll_x_p as f32, + output as f32, + ); + println!("FRACN Divider {}", pll_x_fracn); + // Resulting achieved vco_ck + let vco_ck_achieved = + calc_vco_ck(pll_setup.ref_ck, pll_x_n, pll_x_fracn); + + // Calulate additional output dividers + let pll_x_q = pll_setup.pll_q.unwrap().div; + let pll_x_r = pll_setup.pll_r.unwrap().div; + assert!(pll_x_p <= PLL_OUT_DIV_MAX); + assert!(pll_x_q <= PLL_OUT_DIV_MAX); + assert!(pll_x_r <= PLL_OUT_DIV_MAX); + + // ---------------------------------------- + + // Input + println!("M Divider {}", pll_setup.pll_m); + println!("==> Input {} MHz", input / 1e6); + println!(); + + println!( + "VCO CK Target {} MHz", + pll_setup.vco_out_target as f32 / 1e6 + ); + println!("VCO CK Achieved {} MHz", vco_ck_achieved as f32 / 1e6); + println!(); + + // Output + let output_p = vco_ck_achieved as f32 / pll_x_p as f32; + println!("P Divider {}", pll_x_p); + println!("==> Output P {} MHz", output_p / 1e6); + println!(); + + // The P_CK should be very close to the target with a finely tuned FRACN + // + // The other clocks accuracy will vary depending on how close + // they are to an integer fraction of the P_CK + assert!(output_p <= pll_p_target as f32); + let error_p = output_p - pll_p_target as f32; + assert!( + f32::abs(error_p) < (pll_p_target as f32 / 500_000.0), + "P error too large: {error_p}" + ); // < ±.0002% = 2ppm error + + let output_q = vco_ck_achieved as f32 / pll_x_q as f32; + let error_q = output_q - pll_q_target as f32; + assert!( + f32::abs(error_q) < (pll_q_target as f32 / 100.0), + "Q error too large: {error_q}" + ); // < ±1% error + println!("Q Divider {}", pll_x_q); + println!("==> Output Q {} MHz", output_q / 1e6); + println!(); + + let output_r = vco_ck_achieved as f32 / pll_x_r as f32; + let error_r = output_r - pll_r_target as f32; + assert!( + f32::abs(error_r) < (pll_r_target as f32 / 100.0), + "R error too large: {error_r}" + ); // < ±1% error + println!("R Divider {}", pll_x_r); + println!("==> Output R {} MHz", output_r / 1e6); + println!(); + } +} diff --git a/src/rcc/rec.rs b/src/rcc/rec.rs index f20fe66..dcfcbdd 100644 --- a/src/rcc/rec.rs +++ b/src/rcc/rec.rs @@ -78,7 +78,6 @@ use cortex_m::interrupt; use paste; /// A trait for Resetting, Enabling and Disabling a single peripheral -#[allow(dead_code)] pub trait ResetEnable { /// Enable this peripheral #[allow(clippy::return_self_not_must_use)]