diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c51b8c..b15bff4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Allow specification of ADC reference voltage in ADC configuraton structure - Added support for hardware-based CRC32 functionality - Add `MonoTimer` and `Instant` structs for basic time measurement. +- Added support for I2S and SAI clocks ### Fixed diff --git a/src/rcc.rs b/src/rcc.rs deleted file mode 100644 index e37f689a..00000000 --- a/src/rcc.rs +++ /dev/null @@ -1,496 +0,0 @@ -use crate::stm32::rcc::cfgr::{HPRE_A, SW_A}; -use crate::stm32::RCC; - -use crate::time::Hertz; - -/// Extension trait that constrains the `RCC` peripheral -pub trait RccExt { - /// Constrains the `RCC` peripheral so it plays nicely with the other abstractions - fn constrain(self) -> Rcc; -} - -impl RccExt for RCC { - fn constrain(self) -> Rcc { - Rcc { - cfgr: CFGR { - hse: None, - hclk: None, - pclk1: None, - pclk2: None, - sysclk: None, - pll48clk: false, - }, - } - } -} - -/// Constrained RCC peripheral -pub struct Rcc { - pub cfgr: CFGR, -} - -/// Built-in high speed clock frequency -pub const HSI: u32 = 16_000_000; // Hz - -#[cfg(any( - feature = "stm32f401", - feature = "stm32f405", - feature = "stm32f407", - feature = "stm32f410", - feature = "stm32f411", - feature = "stm32f412", - feature = "stm32f413", - feature = "stm32f415", - feature = "stm32f417", - feature = "stm32f423", - feature = "stm32f427", - feature = "stm32f429", - feature = "stm32f437", - feature = "stm32f439", - feature = "stm32f469", - feature = "stm32f479" -))] -/// Minimum system clock frequency -pub const SYSCLK_MIN: u32 = 24_000_000; - -#[cfg(any(feature = "stm32f446"))] -/// Minimum system clock frequency -pub const SYSCLK_MIN: u32 = 12_500_000; - -#[cfg(feature = "stm32f401")] -/// Maximum system clock frequency -pub const SYSCLK_MAX: u32 = 84_000_000; - -#[cfg(any( - feature = "stm32f405", - feature = "stm32f407", - feature = "stm32f415", - feature = "stm32f417" -))] -/// Maximum system clock frequency -pub const SYSCLK_MAX: u32 = 168_000_000; - -#[cfg(any( - feature = "stm32f410", - feature = "stm32f411", - feature = "stm32f412", - feature = "stm32f413", - feature = "stm32f423" -))] -/// Maximum system clock frequency -pub const SYSCLK_MAX: u32 = 100_000_000; - -#[cfg(any( - feature = "stm32f427", - feature = "stm32f429", - feature = "stm32f437", - feature = "stm32f439", - feature = "stm32f446", - feature = "stm32f469", - feature = "stm32f479" -))] -/// Maximum system clock frequency -pub const SYSCLK_MAX: u32 = 180_000_000; - -#[cfg(any( - feature = "stm32f401", - feature = "stm32f410", - feature = "stm32f411", - feature = "stm32f412", - feature = "stm32f413", - feature = "stm32f423" -))] -/// Maximum APB2 peripheral clock frequency -pub const PCLK2_MAX: u32 = SYSCLK_MAX; - -#[cfg(not(any( - feature = "stm32f401", - feature = "stm32f410", - feature = "stm32f411", - feature = "stm32f412", - feature = "stm32f413", - feature = "stm32f423" -)))] -/// Maximum APB2 peripheral clock frequency -pub const PCLK2_MAX: u32 = SYSCLK_MAX / 2; - -/// Maximum APB1 peripheral clock frequency -pub const PCLK1_MAX: u32 = PCLK2_MAX / 2; - -pub struct CFGR { - hse: Option, - hclk: Option, - pclk1: Option, - pclk2: Option, - sysclk: Option, - pll48clk: bool, -} - -impl CFGR { - /// Uses HSE (external oscillator) instead of HSI (internal RC oscillator) as the clock source. - /// Will result in a hang if an external oscillator is not connected or it fails to start. - pub fn use_hse(mut self, freq: F) -> Self - where - F: Into, - { - self.hse = Some(freq.into().0); - self - } - - pub fn hclk(mut self, freq: F) -> Self - where - F: Into, - { - self.hclk = Some(freq.into().0); - self - } - - pub fn pclk1(mut self, freq: F) -> Self - where - F: Into, - { - self.pclk1 = Some(freq.into().0); - self - } - - pub fn pclk2(mut self, freq: F) -> Self - where - F: Into, - { - self.pclk2 = Some(freq.into().0); - self - } - - pub fn sysclk(mut self, freq: F) -> Self - where - F: Into, - { - self.sysclk = Some(freq.into().0); - self - } - - pub fn require_pll48clk(mut self) -> Self { - self.pll48clk = true; - self - } - - #[inline(always)] - fn pll_setup(&self) -> (bool, bool, u32, Option) { - let pllsrcclk = self.hse.unwrap_or(HSI); - let sysclk = self.sysclk.unwrap_or(pllsrcclk); - let sysclk_on_pll = sysclk != pllsrcclk; - if !sysclk_on_pll && !self.pll48clk { - return (false, false, sysclk, None); - } - - // Sysclk output divisor must be one of 2, 4, 6 or 8 - let sysclk_div = core::cmp::min(8, (432_000_000 / sysclk) & !1); - - // Input divisor from PLL source clock, must result to frequency in - // the range from 1 to 2 MHz - let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000; - let pllm_max = pllsrcclk / 1_000_000; - - let target_freq = if self.pll48clk { - 48_000_000 - } else { - sysclk * sysclk_div - }; - - // Find the lowest pllm value that minimize the difference between - // target frequency and the real vco_out frequency. - let pllm = (pllm_min..=pllm_max) - .min_by_key(|pllm| { - let vco_in = pllsrcclk / pllm; - let plln = target_freq / vco_in; - target_freq - vco_in * plln - }) - .unwrap(); - - let vco_in = pllsrcclk / pllm; - assert!(vco_in >= 1_000_000 && vco_in <= 2_000_000); - - // Main scaler, must result in >= 100MHz (>= 192MHz for F401) - // and <= 432MHz, min 50, max 432 - let plln = if self.pll48clk { - // try the different valid pllq according to the valid - // main scaller values, and take the best - let pllq = (4..=9) - .min_by_key(|pllq| { - let plln = 48_000_000 * pllq / vco_in; - let pll48_diff = 48_000_000 - vco_in * plln / pllq; - let sysclk_diff = (sysclk as i32 - (vco_in * plln / sysclk_div) as i32).abs(); - (pll48_diff, sysclk_diff) - }) - .unwrap(); - 48_000_000 * pllq / vco_in - } else { - sysclk * sysclk_div / vco_in - }; - let pllp = (sysclk_div / 2) - 1; - - let pllq = (vco_in * plln + 47_999_999) / 48_000_000; - let pll48clk = vco_in * plln / pllq; - - unsafe { &*RCC::ptr() }.pllcfgr.write(|w| unsafe { - w.pllm().bits(pllm as u8); - w.plln().bits(plln as u16); - w.pllp().bits(pllp as u8); - w.pllq().bits(pllq as u8); - w.pllsrc().bit(self.hse.is_some()) - }); - - let real_sysclk = if sysclk_on_pll { - vco_in * plln / sysclk_div - } else { - sysclk - }; - (true, sysclk_on_pll, real_sysclk, Some(Hertz(pll48clk))) - } - - fn flash_setup(sysclk: u32) { - use crate::stm32::FLASH; - - #[cfg(any( - feature = "stm32f401", - feature = "stm32f405", - feature = "stm32f407", - feature = "stm32f410", - feature = "stm32f411", - feature = "stm32f412", - feature = "stm32f415", - feature = "stm32f417", - feature = "stm32f427", - feature = "stm32f429", - feature = "stm32f437", - feature = "stm32f439", - feature = "stm32f446", - feature = "stm32f469", - feature = "stm32f479" - ))] - let flash_latency_step = 30_000_000; - - #[cfg(any(feature = "stm32f413", feature = "stm32f423"))] - let flash_latency_step = 25_000_000; - - unsafe { - let flash = &(*FLASH::ptr()); - // Adjust flash wait states - flash.acr.modify(|_, w| { - w.latency().bits(((sysclk - 1) / flash_latency_step) as u8); - w.prften().set_bit(); - w.icen().set_bit(); - w.dcen().set_bit() - }) - } - } - - /// Initialises the hardware according to CFGR state returning a Clocks instance. - /// Panics if overclocking is attempted. - pub fn freeze(self) -> Clocks { - self.freeze_internal(false) - } - - /// Initialises the hardware according to CFGR state returning a Clocks instance. - /// Allows overclocking. - pub unsafe fn freeze_unchecked(self) -> Clocks { - self.freeze_internal(true) - } - - pub fn freeze_internal(self, unchecked: bool) -> Clocks { - let rcc = unsafe { &*RCC::ptr() }; - - let (use_pll, sysclk_on_pll, sysclk, pll48clk) = self.pll_setup(); - - assert!(unchecked || !sysclk_on_pll || sysclk <= SYSCLK_MAX && sysclk >= SYSCLK_MIN); - - let hclk = self.hclk.unwrap_or(sysclk); - let (hpre_bits, hpre_div) = match (sysclk + hclk - 1) / hclk { - 0 => unreachable!(), - 1 => (HPRE_A::DIV1, 1), - 2 => (HPRE_A::DIV2, 2), - 3..=5 => (HPRE_A::DIV4, 4), - 6..=11 => (HPRE_A::DIV8, 8), - 12..=39 => (HPRE_A::DIV16, 16), - 40..=95 => (HPRE_A::DIV64, 64), - 96..=191 => (HPRE_A::DIV128, 128), - 192..=383 => (HPRE_A::DIV256, 256), - _ => (HPRE_A::DIV512, 512), - }; - - // Calculate real AHB clock - let hclk = sysclk / hpre_div; - - let pclk1 = self - .pclk1 - .unwrap_or_else(|| core::cmp::min(PCLK1_MAX, hclk)); - let (ppre1_bits, ppre1) = match (hclk + pclk1 - 1) / pclk1 { - 0 => unreachable!(), - 1 => (0b000, 1), - 2 => (0b100, 2), - 3..=5 => (0b101, 4), - 6..=11 => (0b110, 8), - _ => (0b111, 16), - }; - - // Calculate real APB1 clock - let pclk1 = hclk / u32::from(ppre1); - - assert!(unchecked || pclk1 <= PCLK1_MAX); - - let pclk2 = self - .pclk2 - .unwrap_or_else(|| core::cmp::min(PCLK2_MAX, hclk)); - let (ppre2_bits, ppre2) = match (hclk + pclk2 - 1) / pclk2 { - 0 => unreachable!(), - 1 => (0b000, 1), - 2 => (0b100, 2), - 3..=5 => (0b101, 4), - 6..=11 => (0b110, 8), - _ => (0b111, 16), - }; - - // Calculate real APB2 clock - let pclk2 = hclk / u32::from(ppre2); - - assert!(unchecked || pclk2 <= PCLK2_MAX); - - Self::flash_setup(sysclk); - - if self.hse.is_some() { - // enable HSE and wait for it to be ready - rcc.cr.modify(|_, w| w.hseon().set_bit()); - while rcc.cr.read().hserdy().bit_is_clear() {} - } - - if use_pll { - // Enable PLL - rcc.cr.modify(|_, w| w.pllon().set_bit()); - - // Enable voltage regulator overdrive if HCLK is above the limit - #[cfg(any( - feature = "stm32f427", - feature = "stm32f429", - feature = "stm32f437", - feature = "stm32f439", - feature = "stm32f446", - feature = "stm32f469", - feature = "stm32f479" - ))] - if hclk > 168_000_000 { - // Enable clock for PWR peripheral - rcc.apb1enr.modify(|_, w| w.pwren().set_bit()); - - let pwr = unsafe { &*crate::stm32::PWR::ptr() }; - pwr.cr.modify(|_, w| w.oden().set_bit()); - while pwr.csr.read().odrdy().bit_is_clear() {} - pwr.cr.modify(|_, w| w.odswen().set_bit()); - while pwr.csr.read().odswrdy().bit_is_clear() {} - } - - // Wait for PLL to stabilise - while rcc.cr.read().pllrdy().bit_is_clear() {} - } - - // Set scaling factors - rcc.cfgr.modify(|_, w| unsafe { - w.ppre2() - .bits(ppre2_bits) - .ppre1() - .bits(ppre1_bits) - .hpre() - .variant(hpre_bits) - }); - - // Wait for the new prescalers to kick in - // "The clocks are divided with the new prescaler factor from 1 to 16 AHB cycles after write" - cortex_m::asm::delay(16); - - // Select system clock source - rcc.cfgr.modify(|_, w| { - w.sw().variant(if sysclk_on_pll { - SW_A::PLL - } else if self.hse.is_some() { - SW_A::HSE - } else { - SW_A::HSI - }) - }); - - let clocks = Clocks { - hclk: Hertz(hclk), - pclk1: Hertz(pclk1), - pclk2: Hertz(pclk2), - ppre1, - ppre2, - sysclk: Hertz(sysclk), - pll48clk, - }; - - if self.pll48clk { - assert!(clocks.is_pll48clk_valid()); - } - - clocks - } -} - -/// Frozen clock frequencies -/// -/// The existence of this value indicates that the clock configuration can no longer be changed -#[derive(Clone, Copy)] -pub struct Clocks { - hclk: Hertz, - pclk1: Hertz, - pclk2: Hertz, - ppre1: u8, - ppre2: u8, - sysclk: Hertz, - pll48clk: Option, -} - -impl Clocks { - /// Returns the frequency of the AHB1 - pub fn hclk(&self) -> Hertz { - self.hclk - } - - /// Returns the frequency of the APB1 - pub fn pclk1(&self) -> Hertz { - self.pclk1 - } - - /// Returns the frequency of the APB2 - pub fn pclk2(&self) -> Hertz { - self.pclk2 - } - - /// Returns the prescaler of the APB1 - pub fn ppre1(&self) -> u8 { - self.ppre1 - } - - /// Returns the prescaler of the APB2 - pub fn ppre2(&self) -> u8 { - self.ppre2 - } - - /// Returns the system (core) frequency - pub fn sysclk(&self) -> Hertz { - self.sysclk - } - - /// Returns the frequency of the PLL48 clock line - pub fn pll48clk(&self) -> Option { - self.pll48clk - } - - /// Returns true if the PLL48 clock is within USB - /// specifications. It is required to use the USB functionality. - pub fn is_pll48clk_valid(&self) -> bool { - // USB specification allows +-0.25% - self.pll48clk - .map(|freq| (48_000_000 - freq.0 as i32).abs() <= 120_000) - .unwrap_or(false) - } -} diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs new file mode 100644 index 00000000..ba0bc07e --- /dev/null +++ b/src/rcc/mod.rs @@ -0,0 +1,1458 @@ +//! Clock configuration. +//! +//! This module provides functionality to configure the RCC to generate the requested clocks. +//! +//! # Example +//! +//! ``` +//! let dp = stm32::Peripherals::take().unwrap(); +//! let clocks = rcc +//! .cfgr +//! .use_hse(8.mhz()) +//! .sysclk(168.mhz()) +//! .pclk1(24.mhz()) +//! .i2s_clk(86.mhz()) +//! .require_pll48clk() +//! .freeze(); +//! // Test that the I2S clock is suitable for 48000KHz audio. +//! assert!(clocks.i2s_clk().unwrap() == 48.mhz().into()); +//! ``` +//! +//! # Limitations +//! +//! Unlike the clock configuration tool provided by ST, the code does not extensively search all +//! possible configurations. Instead, it often relies on an iterative approach to reduce +//! compitational complexity. On most MCUs the code will first generate a configuration for the 48 +//! MHz clock and the system clock without taking other requested clocks into account, even if the +//! accuracy of these clocks is affected. **If you specific accuracy requirements, you should +//! always check the resulting frequencies!** +//! +//! Whereas the hardware often supports flexible clock source selection and many clocks can be +//! sourced from multiple PLLs, the code implements a fixed mapping between PLLs and clocks. The 48 +//! MHz clock is always generated by the main PLL, the I2S clocks are always generated by the I2S +//! PLL (unless a matching external clock input is provided), and similarly the SAI clocks are +//! always generated by the SAI PLL. It is therefore not possible to, for example, specify two +//! different I2S frequencies unless you also provide a matching I2S_CKIN signal for one of them. +//! +//! Some MCUs have limited clock generation hardware and do not provide either I2S or SAI PLLs even +//! though I2S or SAI are available. On the STM32F410, the I2S clock is generated by the main PLL, +//! and on the STM32F413/423 SAI clocks are generated by the I2S PLL. On these MCUs, the actual +//! frequencies may substantially deviate from the requested frequencies. + +use crate::stm32::rcc::cfgr::{HPRE_A, SW_A}; +use crate::stm32::RCC; + +use crate::time::Hertz; + +#[cfg(not(feature = "stm32f410"))] +use pll::I2sPll; +use pll::MainPll; +#[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +use pll::SaiPll; + +mod pll; + +/// Extension trait that constrains the `RCC` peripheral +pub trait RccExt { + /// Constrains the `RCC` peripheral so it plays nicely with the other abstractions + fn constrain(self) -> Rcc; +} + +impl RccExt for RCC { + fn constrain(self) -> Rcc { + Rcc { + cfgr: CFGR { + hse: None, + hclk: None, + pclk1: None, + pclk2: None, + sysclk: None, + pll48clk: false, + i2s_ckin: None, + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + i2s_clk: None, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb1_clk: None, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb2_clk: None, + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + sai1_clk: None, + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + sai2_clk: None, + }, + } + } +} + +/// Constrained RCC peripheral +pub struct Rcc { + pub cfgr: CFGR, +} + +/// Built-in high speed clock frequency +pub const HSI: u32 = 16_000_000; // Hz + +#[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" +))] +/// Minimum system clock frequency +pub const SYSCLK_MIN: u32 = 24_000_000; + +#[cfg(any(feature = "stm32f446"))] +/// Minimum system clock frequency +pub const SYSCLK_MIN: u32 = 12_500_000; + +#[cfg(feature = "stm32f401")] +/// Maximum system clock frequency +pub const SYSCLK_MAX: u32 = 84_000_000; + +#[cfg(any( + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f415", + feature = "stm32f417" +))] +/// Maximum system clock frequency +pub const SYSCLK_MAX: u32 = 168_000_000; + +#[cfg(any( + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" +))] +/// Maximum system clock frequency +pub const SYSCLK_MAX: u32 = 100_000_000; + +#[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" +))] +/// Maximum system clock frequency +pub const SYSCLK_MAX: u32 = 180_000_000; + +#[cfg(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" +))] +/// Maximum APB2 peripheral clock frequency +pub const PCLK2_MAX: u32 = SYSCLK_MAX; + +#[cfg(not(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" +)))] +/// Maximum APB2 peripheral clock frequency +pub const PCLK2_MAX: u32 = SYSCLK_MAX / 2; + +/// Maximum APB1 peripheral clock frequency +pub const PCLK1_MAX: u32 = PCLK2_MAX / 2; + +pub struct CFGR { + hse: Option, + hclk: Option, + pclk1: Option, + pclk2: Option, + sysclk: Option, + pll48clk: bool, + + i2s_ckin: Option, + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + i2s_clk: Option, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb1_clk: Option, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb2_clk: Option, + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + sai1_clk: Option, + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + sai2_clk: Option, +} + +impl CFGR { + /// Uses HSE (external oscillator) instead of HSI (internal RC oscillator) as the clock source. + /// Will result in a hang if an external oscillator is not connected or it fails to start. + pub fn use_hse(mut self, freq: F) -> Self + where + F: Into, + { + self.hse = Some(freq.into().0); + self + } + + pub fn hclk(mut self, freq: F) -> Self + where + F: Into, + { + self.hclk = Some(freq.into().0); + self + } + + pub fn pclk1(mut self, freq: F) -> Self + where + F: Into, + { + self.pclk1 = Some(freq.into().0); + self + } + + pub fn pclk2(mut self, freq: F) -> Self + where + F: Into, + { + self.pclk2 = Some(freq.into().0); + self + } + + pub fn sysclk(mut self, freq: F) -> Self + where + F: Into, + { + self.sysclk = Some(freq.into().0); + self + } + + pub fn require_pll48clk(mut self) -> Self { + self.pll48clk = true; + self + } + + /// Declares that the selected frequency is available at the I2S clock input pin (I2S_CKIN). + /// + /// If this frequency matches the requested SAI or I2S frequencies, the external I2S clock is + /// used to generate the clocks. + pub fn i2s_ckin(mut self, freq: F) -> Self + where + F: Into, + { + self.i2s_ckin = Some(freq.into().0); + self + } + + /// Selects an I2S clock frequency and enables the I2S clock. + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + pub fn i2s_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.i2s_clk = Some(freq.into().0); + self + } + + /// Selects an I2S clock frequency for the first set of I2S instancesand enables the I2S clock. + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + pub fn i2s_apb1_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.i2s_apb1_clk = Some(freq.into().0); + self + } + + /// Selects an I2S clock frequency for the second set of I2S instances and enables the I2S clock. + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + pub fn i2s_apb2_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.i2s_apb2_clk = Some(freq.into().0); + self + } + + /// Selects a SAIA clock frequency and enables the SAIA clock. + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + pub fn saia_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.sai1_clk = Some(freq.into().0); + self + } + + /// Selects a SAIB clock frequency and enables the SAIB clock. + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + pub fn saib_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.sai2_clk = Some(freq.into().0); + self + } + + /// Selects a SAI1 clock frequency and enables the SAI1 clock. + #[cfg(any(feature = "stm32f446",))] + pub fn sai1_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.sai1_clk = Some(freq.into().0); + self + } + + /// Selects a SAI2 clock frequency and enables the SAI2 clock. + #[cfg(any(feature = "stm32f446",))] + pub fn sai2_clk(mut self, freq: F) -> Self + where + F: Into, + { + self.sai2_clk = Some(freq.into().0); + self + } + #[cfg(feature = "stm32f410")] + #[inline(always)] + fn pll_setup(&self, pllsrcclk: u32, pllsysclk: Option) -> PllSetup { + let i2s_clocks = self.i2s_clocks(); + + let main_pll = if let Some(i2s_clk) = i2s_clocks.pll_i2s_clk { + // The I2S frequency is generated by the main PLL. The frequency needs to be accurate, + // so we need an expensive full PLL configuration search. + MainPll::setup_with_i2s( + pllsrcclk, + self.hse.is_some(), + pllsysclk, + self.pll48clk, + i2s_clk, + ) + } else { + MainPll::fast_setup(pllsrcclk, self.hse.is_some(), pllsysclk, self.pll48clk) + }; + + PllSetup { + use_pll: main_pll.use_pll, + pllsysclk: main_pll.pllsysclk, + pll48clk: main_pll.pll48clk, + i2s: i2s_clocks.real(main_pll.plli2sclk, self.i2s_ckin), + } + } + + #[cfg(any(feature = "stm32f413", feature = "stm32f423"))] + #[inline(always)] + fn pll_setup(&self, pllsrcclk: u32, pllsysclk: Option) -> PllSetup { + let rcc = unsafe { &*RCC::ptr() }; + + let i2s_clocks = self.i2s_clocks(); + let sai_clocks = self.sai_clocks(); + + let main_pll = MainPll::fast_setup(pllsrcclk, self.hse.is_some(), pllsysclk, self.pll48clk); + + let (i2s_pll, real_sai_clk) = if let Some(i2s_clk) = i2s_clocks.pll_i2s_clk { + // Currently, we only support generating SAI/PLL clocks with the I2S PLL. This is only + // really usable when the frequencies are identical or the I2S frequency is a multiple of + // the SAI frequency. Therefore, we just optimize the PLL for the I2S frequency and then + // derive the SAI frequency from the I2S frequency. + let i2s_pll = I2sPll::setup(pllsrcclk, Some(i2s_clk)); + + if let Some(sai_clk) = sai_clocks.pll_sai_clk { + let div = u32::min( + u32::max((i2s_pll.plli2sclk.unwrap() + (sai_clk >> 1)) / sai_clk, 1), + 31, + ); + rcc.dckcfgr.modify(|_, w| w.plli2sdivr().bits(div as u8)); + let real_sai_clk = sai_clk / div; + (i2s_pll, Some(real_sai_clk)) + } else { + (i2s_pll, None) + } + } else if let Some(pll_sai_clk) = sai_clocks.pll_sai_clk { + // We try all divider values to get the best approximation of the requested frequency. + // NOTE: STM32F413/423 have a different divider range than other models! + let (i2s_pll, real_sai_clk, div) = (1..31) + .map(|div| { + let i2s_pll = I2sPll::setup(pllsrcclk, Some(pll_sai_clk * div)); + let real_clk = i2s_pll.plli2sclk.unwrap() / div; + (i2s_pll, real_clk, div) + }) + .min_by_key(|(_, real_clk, _)| (*real_clk as i32 - pll_sai_clk as i32).abs()) + .unwrap(); + rcc.dckcfgr.modify(|_, w| w.plli2sdivr().bits(div as u8)); + (i2s_pll, Some(real_sai_clk)) + } else { + (I2sPll::unused(), None) + }; + + PllSetup { + use_pll: main_pll.use_pll, + use_i2spll: i2s_pll.use_pll, + pllsysclk: main_pll.pllsysclk, + pll48clk: main_pll.pll48clk, + i2s: i2s_clocks.real(i2s_pll.plli2sclk, self.i2s_ckin), + sai: sai_clocks.real(real_sai_clk, self.i2s_ckin), + } + } + + #[cfg(any(feature = "stm32f411", feature = "stm32f412", feature = "stm32f446"))] + #[inline(always)] + fn pll_setup(&self, pllsrcclk: u32, pllsysclk: Option) -> PllSetup { + let i2s_clocks = self.i2s_clocks(); + #[cfg(feature = "stm32f446")] + let sai_clocks = self.sai_clocks(); + + // All PLLs are completely independent. + let main_pll = MainPll::fast_setup(pllsrcclk, self.hse.is_some(), pllsysclk, self.pll48clk); + let i2s_pll = I2sPll::setup(pllsrcclk, i2s_clocks.pll_i2s_clk); + #[cfg(feature = "stm32f446")] + let sai_pll = SaiPll::setup(pllsrcclk, sai_clocks.pll_sai_clk); + + PllSetup { + use_pll: main_pll.use_pll, + use_i2spll: i2s_pll.use_pll, + #[cfg(feature = "stm32f446")] + use_saipll: sai_pll.use_pll, + pllsysclk: main_pll.pllsysclk, + pll48clk: main_pll.pll48clk, + i2s: i2s_clocks.real(i2s_pll.plli2sclk, self.i2s_ckin), + #[cfg(feature = "stm32f446")] + sai: sai_clocks.real(sai_pll.sai_clk, self.i2s_ckin), + } + } + + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + #[inline(always)] + fn pll_setup(&self, pllsrcclk: u32, pllsysclk: Option) -> PllSetup { + let i2s_clocks = self.i2s_clocks(); + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + let sai_clocks = self.sai_clocks(); + + // We have separate PLLs, but they share the "M" divider. + let main_pll = MainPll::fast_setup(pllsrcclk, self.hse.is_some(), pllsysclk, self.pll48clk); + let i2s_pll = I2sPll::setup_shared_m(pllsrcclk, main_pll.m, i2s_clocks.pll_i2s_clk); + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + let sai_pll = + SaiPll::setup_shared_m(pllsrcclk, main_pll.m.or(i2s_pll.m), sai_clocks.pll_sai_clk); + + PllSetup { + use_pll: main_pll.use_pll, + use_i2spll: i2s_pll.use_pll, + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + use_saipll: sai_pll.use_pll, + pllsysclk: main_pll.pllsysclk, + pll48clk: main_pll.pll48clk, + i2s: i2s_clocks.real(i2s_pll.plli2sclk, self.i2s_ckin), + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + sai: sai_clocks.real(sai_pll.sai_clk, self.i2s_ckin), + } + } + + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + fn sai_clocks(&self) -> SaiClocks { + let sai1_ext = self.sai1_clk.is_some() && self.sai1_clk == self.i2s_ckin; + #[cfg(not(feature = "stm32f446"))] + let sai2_ext = self.sai2_clk.is_some() && self.sai2_clk == self.i2s_ckin; + // Not the PLL output, but the target clock after the divider. + let pll_sai_clk = if sai1_ext { None } else { self.sai1_clk }; + // The STM32F446 only supports I2S_CKIN for SAI1. + #[cfg(feature = "stm32f446")] + let pll_sai_clk2 = self.sai2_clk; + #[cfg(not(feature = "stm32f446"))] + let pll_sai_clk2 = if sai2_ext { None } else { self.sai2_clk }; + if pll_sai_clk.is_some() && pll_sai_clk2.is_some() && pll_sai_clk != pll_sai_clk2 { + panic!("only one SAI PLL frequency implemented"); + } + SaiClocks { + sai1_ext, + #[cfg(not(feature = "stm32f446"))] + sai2_ext, + pll_sai_clk, + } + } + + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + fn i2s_clocks(&self) -> I2sClocks { + let i2s_apb1_ext = self.i2s_apb1_clk.is_some() && self.i2s_apb1_clk == self.i2s_ckin; + let i2s_apb2_ext = self.i2s_apb2_clk.is_some() && self.i2s_apb2_clk == self.i2s_ckin; + let pll_i2s_clk = if i2s_apb1_ext { + None + } else { + self.i2s_apb1_clk + }; + let pll_i2s_clk2 = if i2s_apb2_ext { + None + } else { + self.i2s_apb2_clk + }; + if pll_i2s_clk.is_some() && pll_i2s_clk2.is_some() && pll_i2s_clk != pll_i2s_clk2 { + panic!("only one I2S PLL frequency implemented"); + } + I2sClocks { + i2s_apb1_ext, + i2s_apb2_ext, + pll_i2s_clk, + } + } + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + fn i2s_clocks(&self) -> I2sClocks { + let i2s_ext = self.i2s_clk.is_some() && self.i2s_clk == self.i2s_ckin; + let pll_i2s_clk = if i2s_ext { None } else { self.i2s_clk }; + I2sClocks { + i2s_ext, + pll_i2s_clk, + } + } + + fn flash_setup(sysclk: u32) { + use crate::stm32::FLASH; + + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" + ))] + let flash_latency_step = 30_000_000; + + #[cfg(any(feature = "stm32f413", feature = "stm32f423"))] + let flash_latency_step = 25_000_000; + + unsafe { + let flash = &(*FLASH::ptr()); + // Adjust flash wait states + flash.acr.modify(|_, w| { + w.latency().bits(((sysclk - 1) / flash_latency_step) as u8); + w.prften().set_bit(); + w.icen().set_bit(); + w.dcen().set_bit() + }) + } + } + + /// Initialises the hardware according to CFGR state returning a Clocks instance. + /// Panics if overclocking is attempted. + pub fn freeze(self) -> Clocks { + self.freeze_internal(false) + } + + /// Initialises the hardware according to CFGR state returning a Clocks instance. + /// Allows overclocking. + pub unsafe fn freeze_unchecked(self) -> Clocks { + self.freeze_internal(true) + } + + pub fn freeze_internal(self, unchecked: bool) -> Clocks { + let rcc = unsafe { &*RCC::ptr() }; + + //let (use_pll, sysclk_on_pll, sysclk, pll48clk) = self.pll_setup(); + let pllsrcclk = self.hse.unwrap_or(HSI); + let sysclk = self.sysclk.unwrap_or(pllsrcclk); + let sysclk_on_pll = sysclk != pllsrcclk; + + let plls = self.pll_setup(pllsrcclk, if sysclk_on_pll { Some(sysclk) } else { None }); + let sysclk = if sysclk_on_pll { + plls.pllsysclk.unwrap() + } else { + sysclk + }; + + assert!(unchecked || !sysclk_on_pll || sysclk <= SYSCLK_MAX && sysclk >= SYSCLK_MIN); + + let hclk = self.hclk.unwrap_or(sysclk); + let (hpre_bits, hpre_div) = match (sysclk + hclk - 1) / hclk { + 0 => unreachable!(), + 1 => (HPRE_A::DIV1, 1), + 2 => (HPRE_A::DIV2, 2), + 3..=5 => (HPRE_A::DIV4, 4), + 6..=11 => (HPRE_A::DIV8, 8), + 12..=39 => (HPRE_A::DIV16, 16), + 40..=95 => (HPRE_A::DIV64, 64), + 96..=191 => (HPRE_A::DIV128, 128), + 192..=383 => (HPRE_A::DIV256, 256), + _ => (HPRE_A::DIV512, 512), + }; + + // Calculate real AHB clock + let hclk = sysclk / hpre_div; + + let pclk1 = self + .pclk1 + .unwrap_or_else(|| core::cmp::min(PCLK1_MAX, hclk)); + let (ppre1_bits, ppre1) = match (hclk + pclk1 - 1) / pclk1 { + 0 => unreachable!(), + 1 => (0b000, 1), + 2 => (0b100, 2), + 3..=5 => (0b101, 4), + 6..=11 => (0b110, 8), + _ => (0b111, 16), + }; + + // Calculate real APB1 clock + let pclk1 = hclk / u32::from(ppre1); + + assert!(unchecked || pclk1 <= PCLK1_MAX); + + let pclk2 = self + .pclk2 + .unwrap_or_else(|| core::cmp::min(PCLK2_MAX, hclk)); + let (ppre2_bits, ppre2) = match (hclk + pclk2 - 1) / pclk2 { + 0 => unreachable!(), + 1 => (0b000, 1), + 2 => (0b100, 2), + 3..=5 => (0b101, 4), + 6..=11 => (0b110, 8), + _ => (0b111, 16), + }; + + // Calculate real APB2 clock + let pclk2 = hclk / u32::from(ppre2); + + assert!(unchecked || pclk2 <= PCLK2_MAX); + + Self::flash_setup(sysclk); + + if self.hse.is_some() { + // enable HSE and wait for it to be ready + rcc.cr.modify(|_, w| w.hseon().set_bit()); + while rcc.cr.read().hserdy().bit_is_clear() {} + } + + if plls.use_pll { + // Enable PLL + rcc.cr.modify(|_, w| w.pllon().set_bit()); + + // Enable voltage regulator overdrive if HCLK is above the limit + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" + ))] + if hclk > 168_000_000 { + // Enable clock for PWR peripheral + rcc.apb1enr.modify(|_, w| w.pwren().set_bit()); + + let pwr = unsafe { &*crate::stm32::PWR::ptr() }; + pwr.cr.modify(|_, w| w.oden().set_bit()); + while pwr.csr.read().odrdy().bit_is_clear() {} + pwr.cr.modify(|_, w| w.odswen().set_bit()); + while pwr.csr.read().odswrdy().bit_is_clear() {} + } + + // Wait for PLL to stabilise + while rcc.cr.read().pllrdy().bit_is_clear() {} + } + + #[cfg(not(feature = "stm32f410"))] + if plls.use_i2spll { + // Enable PLL. + rcc.cr.modify(|_, w| w.plli2son().set_bit()); + + // Wait for PLL to stabilise + while rcc.cr.read().plli2srdy().bit_is_clear() {} + } + + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + if plls.use_saipll { + // Enable PLL. + rcc.cr.modify(|_, w| w.pllsaion().set_bit()); + + // Wait for PLL to stabilise + while rcc.cr.read().pllsairdy().bit_is_clear() {} + } + + // Select I2S and SAI clocks + plls.i2s.config_clocksel(); + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + plls.sai.config_clocksel(); + + // Set scaling factors + rcc.cfgr.modify(|_, w| unsafe { + w.ppre2() + .bits(ppre2_bits) + .ppre1() + .bits(ppre1_bits) + .hpre() + .variant(hpre_bits) + }); + + // Wait for the new prescalers to kick in + // "The clocks are divided with the new prescaler factor from 1 to 16 AHB cycles after write" + cortex_m::asm::delay(16); + + // Select system clock source + rcc.cfgr.modify(|_, w| { + w.sw().variant(if sysclk_on_pll { + SW_A::PLL + } else if self.hse.is_some() { + SW_A::HSE + } else { + SW_A::HSI + }) + }); + + let clocks = Clocks { + hclk: Hertz(hclk), + pclk1: Hertz(pclk1), + pclk2: Hertz(pclk2), + ppre1, + ppre2, + sysclk: Hertz(sysclk), + pll48clk: plls.pll48clk.map(Hertz), + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + i2s_clk: plls.i2s.i2s_clk.map(Hertz), + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb1_clk: plls.i2s.i2s_apb1_clk.map(Hertz), + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb2_clk: plls.i2s.i2s_apb2_clk.map(Hertz), + + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + saia_clk: plls.sai.sai1_clk.map(Hertz), + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + saib_clk: plls.sai.sai2_clk.map(Hertz), + #[cfg(feature = "stm32f446")] + sai1_clk: plls.sai.sai1_clk.map(Hertz), + #[cfg(feature = "stm32f446")] + sai2_clk: plls.sai.sai2_clk.map(Hertz), + }; + + if self.pll48clk { + assert!(clocks.is_pll48clk_valid()); + } + + clocks + } +} + +struct PllSetup { + use_pll: bool, + #[cfg(not(feature = "stm32f410"))] + use_i2spll: bool, + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + use_saipll: bool, + + pllsysclk: Option, + pll48clk: Option, + + i2s: RealI2sClocks, + + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", + ))] + sai: RealSaiClocks, +} + +#[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +))] +struct I2sClocks { + /// True if the clock for the APB1 I2S instances is identical to I2S_CKIN. + i2s_apb1_ext: bool, + /// True if the clock for the APB2 I2S instances is identical to I2S_CKIN. + i2s_apb2_ext: bool, + /// Target for the I2S PLL output. + pll_i2s_clk: Option, +} + +#[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +)))] +struct I2sClocks { + /// True if the clock for I2S is identical to I2S_CKIN. + i2s_ext: bool, + /// Target for the I2S PLL output. + pll_i2s_clk: Option, +} + +impl I2sClocks { + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + fn real(&self, pll_i2s_clk: Option, i2s_ckin: Option) -> RealI2sClocks { + RealI2sClocks { + i2s_apb1_ext: self.i2s_apb1_ext, + i2s_apb2_ext: self.i2s_apb2_ext, + i2s_apb1_clk: if self.i2s_apb1_ext { + i2s_ckin + } else { + pll_i2s_clk + }, + i2s_apb2_clk: if self.i2s_apb2_ext { + i2s_ckin + } else { + pll_i2s_clk + }, + } + } + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + fn real(&self, pll_i2s_clk: Option, i2s_ckin: Option) -> RealI2sClocks { + RealI2sClocks { + i2s_ext: self.i2s_ext, + i2s_clk: if self.i2s_ext { i2s_ckin } else { pll_i2s_clk }, + } + } +} + +#[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +))] +struct RealI2sClocks { + i2s_apb1_ext: bool, + i2s_apb2_ext: bool, + i2s_apb1_clk: Option, + i2s_apb2_clk: Option, +} + +#[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", +)))] +struct RealI2sClocks { + i2s_ext: bool, + i2s_clk: Option, +} + +impl RealI2sClocks { + fn config_clocksel(&self) { + let rcc = unsafe { &*RCC::ptr() }; + + #[cfg(not(any( + feature = "stm32f410", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + if self.i2s_ext { + rcc.cfgr.modify(|_, w| w.i2ssrc().ckin()); + } else { + rcc.cfgr.modify(|_, w| w.i2ssrc().plli2s()); + } + #[cfg(feature = "stm32f410")] + if self.i2s_ext { + rcc.dckcfgr.modify(|_, w| w.i2ssrc().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.i2ssrc().pllclkr()); + } + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + if self.i2s_apb1_ext { + rcc.dckcfgr.modify(|_, w| w.i2s1src().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.i2s1src().plli2sr()); + } + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + if self.i2s_apb2_ext { + rcc.dckcfgr.modify(|_, w| w.i2s2src().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.i2s2src().plli2sr()); + } + } +} + +#[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +struct SaiClocks { + /// True if the clock for SAI1 (STM32F446) or SAIA (all other models) is identical to I2S_CKIN. + sai1_ext: bool, + /// True if the clock for SAIB is identical to I2S_CKIN. + #[cfg(not(feature = "stm32f446"))] + sai2_ext: bool, + /// Target for the SAI clock as generated by PLL and SAI clock divider. + pll_sai_clk: Option, +} + +#[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +impl SaiClocks { + fn real(&self, pll_sai_clk: Option, i2s_ckin: Option) -> RealSaiClocks { + RealSaiClocks { + sai1_ext: self.sai1_ext, + #[cfg(not(feature = "stm32f446"))] + sai2_ext: self.sai2_ext, + sai1_clk: if self.sai1_ext { i2s_ckin } else { pll_sai_clk }, + #[cfg(not(feature = "stm32f446"))] + sai2_clk: if self.sai2_ext { i2s_ckin } else { pll_sai_clk }, + #[cfg(feature = "stm32f446")] + sai2_clk: pll_sai_clk, + } + } +} + +#[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +struct RealSaiClocks { + sai1_ext: bool, + #[cfg(not(feature = "stm32f446"))] + sai2_ext: bool, + sai1_clk: Option, + sai2_clk: Option, +} + +#[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +impl RealSaiClocks { + fn config_clocksel(&self) { + let rcc = unsafe { &*RCC::ptr() }; + + // Configure SAI clocks. + #[cfg(not(feature = "stm32f446",))] + if self.sai1_ext { + rcc.dckcfgr.modify(|_, w| w.sai1asrc().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.sai1asrc().pllsai()); + } + #[cfg(not(feature = "stm32f446",))] + if self.sai2_ext { + rcc.dckcfgr.modify(|_, w| w.sai1bsrc().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.sai1bsrc().pllsai()); + } + #[cfg(feature = "stm32f446")] + if self.sai1_ext { + rcc.dckcfgr.modify(|_, w| w.sai1src().i2s_ckin()); + } else { + rcc.dckcfgr.modify(|_, w| w.sai1src().pllsai()); + } + } +} + +/// Frozen clock frequencies +/// +/// The existence of this value indicates that the clock configuration can no longer be changed +#[derive(Clone, Copy)] +pub struct Clocks { + hclk: Hertz, + pclk1: Hertz, + pclk2: Hertz, + ppre1: u8, + ppre2: u8, + sysclk: Hertz, + pll48clk: Option, + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + i2s_clk: Option, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb1_clk: Option, + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb2_clk: Option, + + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + saia_clk: Option, + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + saib_clk: Option, + #[cfg(feature = "stm32f446")] + sai1_clk: Option, + #[cfg(feature = "stm32f446")] + sai2_clk: Option, +} + +impl Clocks { + /// Returns the frequency of the AHB1 + pub fn hclk(&self) -> Hertz { + self.hclk + } + + /// Returns the frequency of the APB1 + pub fn pclk1(&self) -> Hertz { + self.pclk1 + } + + /// Returns the frequency of the APB2 + pub fn pclk2(&self) -> Hertz { + self.pclk2 + } + + /// Returns the prescaler of the APB1 + pub fn ppre1(&self) -> u8 { + self.ppre1 + } + + /// Returns the prescaler of the APB2 + pub fn ppre2(&self) -> u8 { + self.ppre2 + } + + /// Returns the system (core) frequency + pub fn sysclk(&self) -> Hertz { + self.sysclk + } + + /// Returns the frequency of the PLL48 clock line + pub fn pll48clk(&self) -> Option { + self.pll48clk + } + + /// Returns true if the PLL48 clock is within USB + /// specifications. It is required to use the USB functionality. + pub fn is_pll48clk_valid(&self) -> bool { + // USB specification allows +-0.25% + self.pll48clk + .map(|freq| (48_000_000 - freq.0 as i32).abs() <= 120_000) + .unwrap_or(false) + } + + /// Returns the frequency of the I2S clock. + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + pub fn i2s_clk(&self) -> Option { + self.i2s_clk + } + /// Returns the frequency of the first I2S clock (for the I2S peripherals on APB1). + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + pub fn i2s_apb1_clk(&self) -> Option { + self.i2s_apb1_clk + } + /// Returns the frequency of the second I2S clock (for the I2S peripherals on APB2). + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + pub fn i2s_apb2_clk(&self) -> Option { + self.i2s_apb2_clk + } + + /// Returns the frequency of the SAI A clock. + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + pub fn saia_clk(&self) -> Option { + self.saia_clk + } + /// Returns the frequency of the SAI B clock. + #[cfg(any( + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479", + ))] + pub fn saib_clk(&self) -> Option { + self.saib_clk + } + /// Returns the frequency of the SAI1 clock. + #[cfg(feature = "stm32f446")] + pub fn sai1_clk(&self) -> Option { + self.sai1_clk + } + /// Returns the frequency of the SAI2 clock. + #[cfg(feature = "stm32f446")] + pub fn sai2_clk(&self) -> Option { + self.sai2_clk + } +} diff --git a/src/rcc/pll.rs b/src/rcc/pll.rs new file mode 100644 index 00000000..70bb750c --- /dev/null +++ b/src/rcc/pll.rs @@ -0,0 +1,506 @@ +use crate::stm32::RCC; + +pub struct MainPll { + pub use_pll: bool, + pub pllsysclk: Option, + pub pll48clk: Option, + /// "M" divisor, required for the other PLLs on some MCUs. + pub m: Option, + /// "R" output, required for I2S on STM32F410. + pub plli2sclk: Option, +} + +impl MainPll { + pub fn fast_setup( + pllsrcclk: u32, + use_hse: bool, + pllsysclk: Option, + pll48clk: bool, + ) -> MainPll { + let sysclk = pllsysclk.unwrap_or(pllsrcclk); + if pllsysclk.is_none() && !pll48clk { + // Even if we do not use the main PLL, we still need to set the PLL source as that setting + // applies to the I2S and SAI PLLs as well. + unsafe { &*RCC::ptr() } + .pllcfgr + .write(|w| w.pllsrc().bit(use_hse)); + + return MainPll { + use_pll: false, + pllsysclk: None, + pll48clk: None, + m: None, + plli2sclk: None, + }; + } + // Input divisor from PLL source clock, must result to frequency in + // the range from 1 to 2 MHz + let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000; + let pllm_max = pllsrcclk / 1_000_000; + + // Sysclk output divisor must be one of 2, 4, 6 or 8 + let sysclk_div = core::cmp::min(8, (432_000_000 / sysclk) & !1); + + let target_freq = if pll48clk { + 48_000_000 + } else { + sysclk * sysclk_div + }; + + // Find the lowest pllm value that minimize the difference between + // target frequency and the real vco_out frequency. + let pllm = (pllm_min..=pllm_max) + .min_by_key(|pllm| { + let vco_in = pllsrcclk / pllm; + let plln = target_freq / vco_in; + target_freq - vco_in * plln + }) + .unwrap(); + + let vco_in = pllsrcclk / pllm; + assert!(vco_in >= 1_000_000 && vco_in <= 2_000_000); + + // Main scaler, must result in >= 100MHz (>= 192MHz for F401) + // and <= 432MHz, min 50, max 432 + let plln = if pll48clk { + // try the different valid pllq according to the valid + // main scaller values, and take the best + let pllq = (4..=9) + .min_by_key(|pllq| { + let plln = 48_000_000 * pllq / vco_in; + let pll48_diff = 48_000_000 - vco_in * plln / pllq; + let sysclk_diff = (sysclk as i32 - (vco_in * plln / sysclk_div) as i32).abs(); + (pll48_diff, sysclk_diff) + }) + .unwrap(); + 48_000_000 * pllq / vco_in + } else { + sysclk * sysclk_div / vco_in + }; + let pllp = (sysclk_div / 2) - 1; + + let pllq = (vco_in * plln + 47_999_999) / 48_000_000; + let real_pll48clk = vco_in * plln / pllq; + + unsafe { &*RCC::ptr() }.pllcfgr.write(|w| unsafe { + w.pllm().bits(pllm as u8); + w.plln().bits(plln as u16); + w.pllp().bits(pllp as u8); + w.pllq().bits(pllq as u8); + w.pllsrc().bit(use_hse) + }); + + let real_pllsysclk = vco_in * plln / sysclk_div; + + MainPll { + use_pll: true, + pllsysclk: Some(real_pllsysclk), + pll48clk: if pll48clk { Some(real_pll48clk) } else { None }, + m: Some(pllm), + plli2sclk: None, + } + } + + #[cfg(feature = "stm32f410")] + pub fn setup_with_i2s( + pllsrcclk: u32, + use_hse: bool, + pllsysclk: Option, + pll48clk: bool, + plli2sclk: u32, + ) -> MainPll { + use super::{SYSCLK_MAX, SYSCLK_MIN}; + + // Input divisor from PLL source clock, must result to frequency in + // the range from 1 to 2 MHz + let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000; + let pllm_max = pllsrcclk / 1_000_000; + + let (pllm, plln, pllp, pllq, pllr, _) = (pllm_min..=pllm_max) + .filter_map(|m| { + let vco_in = pllsrcclk / m; + + // The VCO output must be within 100 and 432 MHz. + let plln_min = (100_000_000 + vco_in - 1) / vco_in; + let plln_max = 432_000_000 / vco_in; + + (plln_min..=plln_max) + .filter_map(|n| { + let vco_out = vco_in * n; + + // The "P" divider value must be even (2, 4, 6, 8). + let p = if let Some(pllsysclk) = pllsysclk { + let (p, p_output, p_error) = Self::best_divider( + vco_out, + SYSCLK_MIN * 2, + pllsysclk * 2, + SYSCLK_MAX * 2, + 1, + 4, + )?; + Some((p * 2, p_output / 2, p_error / 2)) + } else { + None + }; + + // The 48 MHz clock must be accurate within 0.25% for USB. + let q = if pll48clk { + Some(Self::best_divider( + vco_out, 47_880_000, 48_000_000, 48_120_000, 2, 15, + )?) + } else { + None + }; + + // We do not set any accuracy requirements for I2S, as on F410 this frequency is + // provided on a best-effort basis. + // TODO: What is the maximum valid input frequency for I2S? + let r = Self::best_divider(vco_out, 0, plli2sclk, u32::MAX, 2, 15)?; + + let error = p.map(|(_, _, error)| error).unwrap_or(0) + + p.map(|(_, _, error)| error).unwrap_or(0) + + r.2; + + Some((m, n, p.map(|p| p.0), q.map(|q| q.0), r.0, error)) + }) + .min_by_key(|(_, _, _, _, _, error)| *error) + }) + .min_by_key(|(_, _, _, _, _, error)| *error) + .expect("could not find a valid main PLL configuration"); + + unsafe { &*RCC::ptr() }.pllcfgr.write(|w| unsafe { + w.pllm().bits(pllm as u8); + w.plln().bits(plln as u16); + if let Some(pllp) = pllp { + w.pllp().bits(pllp as u8 / 2 - 1); + } + if let Some(pllq) = pllq { + w.pllq().bits(pllq as u8); + } + w.pllr().bits(pllr as u8); + w.pllsrc().bit(use_hse) + }); + + let real_pllsysclk = pllp.map(|pllp| pllsrcclk / pllm * plln / pllp); + let real_pll48clk = pllq.map(|pllq| pllsrcclk / pllm * plln / pllq); + + MainPll { + use_pll: true, + pllsysclk: real_pllsysclk, + pll48clk: real_pll48clk, + m: Some(pllm), + plli2sclk: None, + } + } + + #[cfg(feature = "stm32f410")] + fn best_divider( + vco_out: u32, + min: u32, + target: u32, + max: u32, + min_div: u32, + max_div: u32, + ) -> Option<(u32, u32, u32)> { + let div = (vco_out + target / 2) / target; + let min_div = u32::max( + min_div, + if max != 0 { + (vco_out + max - 1) / max + } else { + 0 + }, + ); + let max_div = u32::min(max_div, if min != 0 { vco_out / min } else { u32::MAX }); + if min_div > max_div { + return None; + } + let div = u32::min(u32::max(div, min_div), max_div); + let output = vco_out / div; + let error = (output as i32 - target as i32).abs() as u32; + Some((div, output, error)) + } +} + +#[cfg(not(feature = "stm32f410"))] +pub struct I2sPll { + pub use_pll: bool, + /// "M" divisor, required for the other PLLs on some MCUs. + pub m: Option, + /// PLL I2S clock output. + pub plli2sclk: Option, +} + +#[cfg(not(feature = "stm32f410"))] +impl I2sPll { + pub fn unused() -> I2sPll { + I2sPll { + use_pll: false, + m: None, + plli2sclk: None, + } + } + + pub fn setup(pllsrcclk: u32, plli2sclk: Option) -> I2sPll { + let target = if let Some(clk) = plli2sclk { + clk + } else { + return Self::unused(); + }; + // Input divisor from PLL source clock, must result to frequency in + // the range from 1 to 2 MHz + let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000; + let pllm_max = pllsrcclk / 1_000_000; + let (pll, config, _) = (pllm_min..=pllm_max) + .map(|m| Self::optimize_fixed_m(pllsrcclk, m, target)) + .min_by_key(|(_, _, error)| *error) + .expect("no suitable I2S PLL configuration found"); + Self::apply_config(config); + pll + } + + #[cfg(any( + feature = "stm32f401", + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + pub fn setup_shared_m(pllsrcclk: u32, m: Option, plli2sclk: Option) -> I2sPll { + // "m" is None if the main PLL is not in use. + let m = if let Some(m) = m { + m + } else { + return Self::setup(pllsrcclk, plli2sclk); + }; + let target = if let Some(clk) = plli2sclk { + clk + } else { + return Self::unused(); + }; + let (pll, config, _) = Self::optimize_fixed_m(pllsrcclk, m, target); + Self::apply_config(config); + pll + } + + fn optimize_fixed_m(pllsrcclk: u32, m: u32, plli2sclk: u32) -> (I2sPll, SingleOutputPll, u32) { + let (config, real_plli2sclk, error) = + SingleOutputPll::optimize(pllsrcclk, m, plli2sclk, 2, 7) + .expect("did not find any valid I2S PLL config"); + ( + I2sPll { + use_pll: true, + m: Some(config.m as u32), + plli2sclk: Some(real_plli2sclk), + }, + config, + error, + ) + } + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + fn apply_config(config: SingleOutputPll) { + let rcc = unsafe { &*RCC::ptr() }; + // "M" may have been written before, but the value is identical. + rcc.pllcfgr + .modify(|_, w| unsafe { w.pllm().bits(config.m) }); + rcc.plli2scfgr + .modify(|_, w| unsafe { w.plli2sn().bits(config.n).plli2sr().bits(config.outdiv) }); + } + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + fn apply_config(config: SingleOutputPll) { + let rcc = unsafe { &*RCC::ptr() }; + rcc.plli2scfgr + .modify(|_, w| unsafe { w.plli2sm().bits(config.m).plli2sr().bits(config.outdiv) }); + } +} + +#[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +pub struct SaiPll { + pub use_pll: bool, + /// SAI clock (PLL output divided by the SAI clock divider). + pub sai_clk: Option, +} + +#[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479", +))] +impl SaiPll { + pub fn unused() -> SaiPll { + SaiPll { + use_pll: false, + sai_clk: None, + } + } + + pub fn setup(pllsrcclk: u32, sai_clk: Option) -> SaiPll { + let target = if let Some(clk) = sai_clk { + clk + } else { + return Self::unused(); + }; + // Input divisor from PLL source clock, must result to frequency in + // the range from 1 to 2 MHz + let pllm_min = (pllsrcclk + 1_999_999) / 2_000_000; + let pllm_max = pllsrcclk / 1_000_000; + let (pll, config, saidiv, _) = (pllm_min..=pllm_max) + .map(|m| Self::optimize_fixed_m(pllsrcclk, m, target)) + .min_by_key(|(_, _, _, error)| *error) + .expect("no suitable SAI PLL configuration found"); + Self::apply_config(config, saidiv); + pll + } + + #[cfg(any( + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f469", + feature = "stm32f479" + ))] + pub fn setup_shared_m(pllsrcclk: u32, m: Option, sai_clk: Option) -> SaiPll { + // "m" is None if both other PLLs are not in use. + let m = if let Some(m) = m { + m + } else { + return Self::setup(pllsrcclk, sai_clk); + }; + let target = if let Some(clk) = sai_clk { + clk + } else { + return Self::unused(); + }; + let (pll, config, saidiv, _) = Self::optimize_fixed_m(pllsrcclk, m, target); + Self::apply_config(config, saidiv); + pll + } + + fn optimize_fixed_m( + pllsrcclk: u32, + m: u32, + sai_clk: u32, + ) -> (SaiPll, SingleOutputPll, u32, u32) { + // NOTE: This code tests lots of configurations due to the nested loops for the two + // dividers. A smarter approach can probably speed up the search. + let (config, saidiv, real_sai_clk, error) = (1..=32) + .filter_map(|saidiv| { + let target = sai_clk * saidiv; + let (config, real_sai_clk, error) = + SingleOutputPll::optimize(pllsrcclk, m, target, 2, 15)?; + Some((config, saidiv, real_sai_clk, error)) + }) + .min_by_key(|(_, _, _, error)| *error) + .expect("no suitable I2S PLL configuration found"); + ( + SaiPll { + use_pll: true, + sai_clk: Some(real_sai_clk), + }, + config, + saidiv, + error, + ) + } + + #[cfg(not(feature = "stm32f446"))] + fn apply_config(config: SingleOutputPll, saidiv: u32) { + let rcc = unsafe { &*RCC::ptr() }; + rcc.dckcfgr + .modify(|_, w| w.pllsaidivq().bits(saidiv as u8 - 1)); + // "M" may have been written before, but the value is identical. + rcc.pllcfgr + .modify(|_, w| unsafe { w.pllm().bits(config.m) }); + rcc.pllsaicfgr + .modify(|_, w| unsafe { w.pllsain().bits(config.n).pllsaiq().bits(config.outdiv) }); + } + #[cfg(feature = "stm32f446")] + fn apply_config(config: SingleOutputPll, saidiv: u32) { + let rcc = unsafe { &*RCC::ptr() }; + rcc.dckcfgr + .modify(|_, w| w.pllsaidivq().bits(saidiv as u8 - 1)); + rcc.pllsaicfgr.modify(|_, w| unsafe { + w.pllsaim() + .bits(config.m) + .pllsain() + .bits(config.n) + .pllsaiq() + .bits(config.outdiv) + }); + } +} + +#[cfg(not(feature = "stm32f410"))] +struct SingleOutputPll { + m: u8, + n: u16, + outdiv: u8, +} + +#[cfg(not(feature = "stm32f410"))] +impl SingleOutputPll { + fn optimize( + pllsrcclk: u32, + m: u32, + target: u32, + min_div: u32, + max_div: u32, + ) -> Option<(SingleOutputPll, u32, u32)> { + let vco_in = pllsrcclk / m; + + // We loop through the possible divider values to find the best configuration. Looping + // through all possible "N" values would result in more iterations. + let (n, outdiv, output, error) = (min_div..=max_div) + .filter_map(|outdiv| { + let target_vco_out = target * outdiv; + let n = (target_vco_out + (vco_in >> 1)) / vco_in; + let vco_out = vco_in * n; + if vco_out < 100_000_000 || vco_out > 432_000_000 { + return None; + } + let output = vco_out / outdiv; + let error = (output as i32 - target as i32).abs() as u32; + Some((n, outdiv, output, error)) + }) + .min_by_key(|(_, _, _, error)| *error)?; + Some(( + SingleOutputPll { + m: m as u8, + n: n as u16, + outdiv: outdiv as u8, + }, + output, + error, + )) + } +}