From 1c2b27dcadf6256dca6ddf7b3d8e5650fe8cb99d Mon Sep 17 00:00:00 2001 From: Will Glynn Date: Fri, 27 May 2022 21:15:38 -0500 Subject: [PATCH] embassy-stm32: g0: add PLL clock source STM32G0 SYSCLK can be sourced from PLLRCLK. Given that the HSI runs at 16 MHz and the HSE range is 4-48 MHz, the PLL is the only way to reach 64 MHz. This commit adds `ClockSrc::PLL`. The PLL sources from either HSI16 or HSE, divides it by `m`, and locks its VCO to multiple `n`. It then divides the VCO by `r`, `p`, and `q` to produce up to three associated clock signals: * PLLRCLK is one of the inputs on the SYSCLK mux. This is the main reason the user will configure the PLL, so `r` is mandatory and the output is enabled unconditionally. * PLLPCLK is available as a clock source for the ADC and I2S peripherals, so `p` is optional and the output is conditional. * PLLQCLK exists only on STM32G0B0xx, and exists only to feed the MCO and MCO2 peripherals, so `q` is optional and the output is conditional. When the user specifies `ClockSrc::PLL(PllConfig)`, `rcc::init()` calls `PllConfig::init()` which initializes the PLL per [RM0454]. It disables the PLL, waits for it to stop, enables the source oscillator, configures the PLL, waits for it to lock, and then enables the appropriate outputs. `rcc::init()` then switches the clock source to PLLRCLK. `rcc::init()` is now also resonsible for calculating and setting flash wait states. SYSCLCK < 24 MHz is fine in the reset state, but 24-48 MHz requires waiting 1 cycle and 48-64 MHz requires waiting 2 cycles. (This was likely a blocker for anyone using HSE >= 24 MHz, with or without the PLL.) Flash accesses are now automatically slowed down as needed before changing the clock source, and sped up as permitted after changing the clock source. The number of flash wait states also determines if flash prefetching will be profitable, so that is now handled automatically too. [RM0454]: https://www.st.com/resource/en/reference_manual/rm0454-stm32g0x0-advanced-armbased-32bit-mcus-stmicroelectronics.pdf --- embassy-stm32/src/rcc/g0.rs | 287 +++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index ad5a661d..be049729 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -1,5 +1,6 @@ -use crate::pac::rcc::vals::{Hpre, Hsidiv, Ppre, Sw}; -use crate::pac::{PWR, RCC}; +use crate::pac::flash::vals::Latency; +use crate::pac::rcc::vals::{self, Hpre, Hsidiv, Ppre, Sw}; +use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; use crate::time::U32Ext; @@ -15,6 +16,7 @@ pub const LSI_FREQ: u32 = 32_000; pub enum ClockSrc { HSE(Hertz), HSI16(HSI16Prescaler), + PLL(PllConfig), LSI, } @@ -45,6 +47,132 @@ impl Into for HSI16Prescaler { } } +/// The PLL configuration. +/// +/// * `VCOCLK = source / m * n` +/// * `PLLRCLK = VCOCLK / r` +/// * `PLLQCLK = VCOCLK / q` +/// * `PLLPCLK = VCOCLK / p` +#[derive(Clone, Copy)] +pub struct PllConfig { + /// The source from which the PLL receives a clock signal + pub source: PllSrc, + /// The initial divisor of that clock signal + pub m: Pllm, + /// The PLL VCO multiplier, which must be in the range `8..=86`. + pub n: u8, + /// The final divisor for `PLLRCLK` output which drives the system clock + pub r: Pllr, + + /// The divisor for the `PLLQCLK` output, if desired + pub q: Option, + + /// The divisor for the `PLLPCLK` output, if desired + pub p: Option, +} + +impl Default for PllConfig { + #[inline] + fn default() -> PllConfig { + // HSI16 / 1 * 8 / 2 = 64 MHz + PllConfig { + source: PllSrc::HSI16, + m: Pllm::Div1, + n: 8, + r: Pllr::Div2, + q: None, + p: None, + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum PllSrc { + HSI16, + HSE(Hertz), +} + +#[derive(Clone, Copy)] +pub enum Pllm { + Div1, + Div2, + Div3, + Div4, + Div5, + Div6, + Div7, + Div8, +} + +impl From for u8 { + fn from(v: Pllm) -> Self { + match v { + Pllm::Div1 => 0b000, + Pllm::Div2 => 0b001, + Pllm::Div3 => 0b010, + Pllm::Div4 => 0b011, + Pllm::Div5 => 0b100, + Pllm::Div6 => 0b101, + Pllm::Div7 => 0b110, + Pllm::Div8 => 0b111, + } + } +} + +impl From for u32 { + fn from(v: Pllm) -> Self { + match v { + Pllm::Div1 => 1, + Pllm::Div2 => 2, + Pllm::Div3 => 3, + Pllm::Div4 => 4, + Pllm::Div5 => 5, + Pllm::Div6 => 6, + Pllm::Div7 => 7, + Pllm::Div8 => 8, + } + } +} + +#[derive(Clone, Copy)] +pub enum Pllr { + Div2, + Div3, + Div4, + Div5, + Div6, + Div7, + Div8, +} + +impl From for u8 { + fn from(v: Pllr) -> Self { + match v { + Pllr::Div2 => 0b000, + Pllr::Div3 => 0b001, + Pllr::Div4 => 0b010, + Pllr::Div5 => 0b011, + Pllr::Div6 => 0b101, + Pllr::Div7 => 0b110, + Pllr::Div8 => 0b111, + } + } +} + +impl From for u32 { + fn from(v: Pllr) -> Self { + match v { + Pllr::Div2 => 2, + Pllr::Div3 => 3, + Pllr::Div4 => 4, + Pllr::Div5 => 5, + Pllr::Div6 => 6, + Pllr::Div7 => 7, + Pllr::Div8 => 8, + } + } +} + /// AHB prescaler #[derive(Clone, Copy, PartialEq)] pub enum AHBPrescaler { @@ -117,6 +245,95 @@ impl Default for Config { } } +impl PllConfig { + pub(crate) unsafe fn init(self) -> u32 { + assert!(self.n >= 8 && self.n <= 86); + let (src, input_freq) = match self.source { + PllSrc::HSI16 => (vals::Pllsrc::HSI16, HSI_FREQ), + PllSrc::HSE(freq) => (vals::Pllsrc::HSE, freq.0), + }; + + let m_freq = input_freq / u32::from(self.m); + // RM0454 § 5.4.4: + // > Caution: The software must set these bits so that the PLL input frequency after the + // > /M divider is between 2.66 and 16 MHz. + debug_assert!(m_freq >= 2_660_000 && m_freq <= 16_000_000); + + let n_freq = m_freq * self.n as u32; + // RM0454 § 5.4.4: + // > Caution: The software must set these bits so that the VCO output frequency is between + // > 64 and 344 MHz. + debug_assert!(n_freq >= 64_000_000 && n_freq <= 344_000_000); + + let r_freq = n_freq / u32::from(self.r); + // RM0454 § 5.4.4: + // > Caution: The software must set this bitfield so as not to exceed 64 MHz on this clock. + debug_assert!(r_freq <= 64_000_000); + + // RM0454 § 5.2.3: + // > To modify the PLL configuration, proceed as follows: + // > 1. Disable the PLL by setting PLLON to 0 in Clock control register (RCC_CR). + RCC.cr().modify(|w| w.set_pllon(false)); + + // > 2. Wait until PLLRDY is cleared. The PLL is now fully stopped. + while RCC.cr().read().pllrdy() {} + + // > 3. Change the desired parameter. + // Enable whichever clock source we're using, and wait for it to become ready + match self.source { + PllSrc::HSI16 => { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + } + PllSrc::HSE(_) => { + RCC.cr().write(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + } + } + + // Configure PLLSYSCFGR + RCC.pllsyscfgr().modify(|w| { + w.set_pllr(u8::from(self.r)); + w.set_pllren(false); + + if let Some(q) = self.q { + w.set_pllq(u8::from(q)); + } + w.set_pllqen(false); + + if let Some(p) = self.p { + w.set_pllp(u8::from(p)); + } + w.set_pllpen(false); + + w.set_plln(self.n); + + w.set_pllm(self.m as u8); + + w.set_pllsrc(src) + }); + + // > 4. Enable the PLL again by setting PLLON to 1. + RCC.cr().modify(|w| w.set_pllon(true)); + + // Wait for the PLL to become ready + while !RCC.cr().read().pllrdy() {} + + // > 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, and PLLREN in PLL + // > configuration register (RCC_PLLCFGR). + RCC.pllsyscfgr().modify(|w| { + // We'll use R for system clock, so enable that unconditionally + w.set_pllren(true); + + // We may also use Q or P + w.set_pllqen(self.q.is_some()); + w.set_pllpen(self.p.is_some()); + }); + + r_freq + } +} + pub(crate) unsafe fn init(config: Config) { let (sys_clk, sw) = match config.mux { ClockSrc::HSI16(div) => { @@ -137,6 +354,10 @@ pub(crate) unsafe fn init(config: Config) { (freq.0, Sw::HSE) } + ClockSrc::PLL(pll) => { + let freq = pll.init(); + (freq, Sw::PLLRCLK) + } ClockSrc::LSI => { // Enable LSI RCC.csr().write(|w| w.set_lsion(true)); @@ -145,12 +366,66 @@ pub(crate) unsafe fn init(config: Config) { } }; - RCC.cfgr().modify(|w| { - w.set_sw(sw.into()); - w.set_hpre(config.ahb_pre.into()); - w.set_ppre(config.apb_pre.into()); + // Determine the flash latency implied by the target clock speed + // RM0454 § 3.3.4: + let target_flash_latency = if sys_clk <= 24_000_000 { + Latency::WS0 + } else if sys_clk <= 48_000_000 { + Latency::WS1 + } else { + Latency::WS2 + }; + + // Increase the number of cycles we wait for flash if the new value is higher + // There's no harm in waiting a little too much before the clock change, but we'll + // crash immediately if we don't wait enough after the clock change + let mut set_flash_latency_after = false; + FLASH.acr().modify(|w| { + // Is the current flash latency less than what we need at the new SYSCLK? + if w.latency().0 <= target_flash_latency.0 { + // We must increase the number of wait states now + w.set_latency(target_flash_latency) + } else { + // We may decrease the number of wait states later + set_flash_latency_after = true; + } + + // RM0454 § 3.3.5: + // > Prefetch is enabled by setting the PRFTEN bit of the FLASH access control register + // > (FLASH_ACR). This feature is useful if at least one wait state is needed to access the + // > Flash memory. + // + // Enable flash prefetching if we have at least one wait state, and disable it otherwise. + w.set_prften(target_flash_latency.0 > 0); }); + if !set_flash_latency_after { + // Spin until the effective flash latency is compatible with the clock change + while FLASH.acr().read().latency().0 < target_flash_latency.0 {} + } + + // Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once + let (sw, hpre, ppre) = (sw.into(), config.ahb_pre.into(), config.apb_pre.into()); + RCC.cfgr().modify(|w| { + w.set_sw(sw); + w.set_hpre(hpre); + w.set_ppre(ppre); + }); + + if set_flash_latency_after { + // We can make the flash require fewer wait states + // Spin until the SYSCLK changes have taken effect + loop { + let cfgr = RCC.cfgr().read(); + if cfgr.sw() == sw && cfgr.hpre() == hpre && cfgr.ppre() == ppre { + break; + } + } + + // Set the flash latency to require fewer wait states + FLASH.acr().modify(|w| w.set_latency(target_flash_latency)); + } + let ahb_div = match config.ahb_pre { AHBPrescaler::NotDivided => 1, AHBPrescaler::Div2 => 2,