From 96672aeae0358e49834b5bfa2b368ef9cadfb736 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Mon, 28 Dec 2020 21:49:09 +0100 Subject: [PATCH 1/7] Move rcc.rs to rcc/mod.rs. The clock configuration code will become much more complex. --- src/{rcc.rs => rcc/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{rcc.rs => rcc/mod.rs} (100%) diff --git a/src/rcc.rs b/src/rcc/mod.rs similarity index 100% rename from src/rcc.rs rename to src/rcc/mod.rs From c5e75daac08adb11e886c18cea641e2c1bb5a7ad Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Wed, 30 Dec 2020 10:46:04 +0100 Subject: [PATCH 2/7] rcc: Add functions to set I2S/SAI clocks. --- src/rcc/mod.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index e37f689a..3942bb62 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -19,6 +19,61 @@ impl RccExt for RCC { 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, }, } } @@ -124,6 +179,62 @@ pub struct CFGR { 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 { @@ -174,6 +285,130 @@ impl CFGR { 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 + } + #[inline(always)] fn pll_setup(&self) -> (bool, bool, u32, Option) { let pllsrcclk = self.hse.unwrap_or(HSI); From fdbf186d7415a1d9b1ef705c96be77a544546837 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Wed, 30 Dec 2020 10:51:43 +0100 Subject: [PATCH 3/7] rcc: Refactor the PLL config code and add support for I2S/SAI clocks. There are many different clock trees with different types of PLL clock connections, so we need different PLL optimization algorithms with different complexity. --- src/rcc/mod.rs | 812 +++++++++++++++++++++++++++++++++++++++++++++---- src/rcc/pll.rs | 496 ++++++++++++++++++++++++++++++ 2 files changed, 1245 insertions(+), 63 deletions(-) create mode 100644 src/rcc/pll.rs diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index 3942bb62..262e9234 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -3,6 +3,22 @@ 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 @@ -408,79 +424,256 @@ impl CFGR { self.sai2_clk = Some(freq.into().0); self } - + #[cfg(feature = "stm32f410")] #[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 + 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 { - sysclk * sysclk_div + MainPll::fast_setup(pllsrcclk, self.hse.is_some(), pllsysclk, self.pll48clk) }; - // 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) + 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(); - 48_000_000 * pllq / vco_in + rcc.dckcfgr.modify(|_, w| w.plli2sdivr().bits(div as u8)); + (i2s_pll, Some(real_sai_clk)) } else { - sysclk * sysclk_div / vco_in + (I2sPll::unused(), None) }; - let pllp = (sysclk_div / 2) - 1; - let pllq = (vco_in * plln + 47_999_999) / 48_000_000; - let pll48clk = vco_in * plln / pllq; + 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), + } + } - 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()) - }); + #[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); - let real_sysclk = if sysclk_on_pll { - vco_in * plln / sysclk_div + 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 { - sysclk + self.i2s_apb1_clk }; - (true, sysclk_on_pll, real_sysclk, Some(Hertz(pll48clk))) + 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) { @@ -535,7 +728,17 @@ impl CFGR { 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 (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); @@ -598,7 +801,7 @@ impl CFGR { while rcc.cr.read().hserdy().bit_is_clear() {} } - if use_pll { + if plls.use_pll { // Enable PLL rcc.cr.modify(|_, w| w.pllon().set_bit()); @@ -627,6 +830,47 @@ impl CFGR { 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() @@ -659,7 +903,56 @@ impl CFGR { ppre1, ppre2, sysclk: Hertz(sysclk), - pll48clk, + pll48clk: plls.pll48clk.map(|clk| Hertz(clk)), + + #[cfg(not(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + )))] + i2s_clk: plls.i2s.i2s_clk.map(|clk| Hertz(clk)), + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb1_clk: plls.i2s.i2s_apb1_clk.map(|clk| Hertz(clk)), + #[cfg(any( + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423", + feature = "stm32f446", + ))] + i2s_apb2_clk: plls.i2s.i2s_apb2_clk.map(|clk| Hertz(clk)), + + #[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(|clk| Hertz(clk)), + #[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(|clk| Hertz(clk)), + #[cfg(feature = "stm32f446")] + sai1_clk: plls.sai.sai1_clk.map(|clk| Hertz(clk)), + #[cfg(feature = "stm32f446")] + sai2_clk: plls.sai.sai2_clk.map(|clk| Hertz(clk)), }; if self.pll48clk { @@ -670,6 +963,280 @@ impl CFGR { } } +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 @@ -682,6 +1249,55 @@ pub struct Clocks { 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 { @@ -728,4 +1344,74 @@ impl Clocks { .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..e8cdfc31 --- /dev/null +++ b/src/rcc/pll.rs @@ -0,0 +1,496 @@ +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 { + 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.write(|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)); + // "M" may have been written before, but the value is identical. + rcc.pllcfgr.write(|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)); + 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, + )) + } +} From 79fc1cab359a2599cd571fe0a03527bd6438e36d Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Wed, 30 Dec 2020 10:51:57 +0100 Subject: [PATCH 4/7] rcc: Add some additional documentation and a usage example. --- src/rcc/mod.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index 262e9234..d8ce017c 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -1,3 +1,44 @@ +//! 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; From 3f57a14cb21e58819936312cc0ac40d068617593 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sun, 3 Jan 2021 02:24:27 +0100 Subject: [PATCH 5/7] rcc: Fix I2S/SAI PLL configuration. The PLLSRC needs to be set even if the main PLL is not enabled, because the source is also used by the other PLLs. The old code also did not correctly set M, destroying all other values in the process. --- src/rcc/pll.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/rcc/pll.rs b/src/rcc/pll.rs index e8cdfc31..70bb750c 100644 --- a/src/rcc/pll.rs +++ b/src/rcc/pll.rs @@ -19,6 +19,12 @@ impl MainPll { ) -> 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, @@ -307,7 +313,8 @@ impl I2sPll { fn apply_config(config: SingleOutputPll) { let rcc = unsafe { &*RCC::ptr() }; // "M" may have been written before, but the value is identical. - rcc.pllcfgr.write(|w| unsafe { w.pllm().bits(config.m) }); + rcc.pllcfgr + .modify(|_, w| unsafe { w.pllm().bits(config.m) }); rcc.plli2scfgr .modify(|_, w| unsafe { w.plli2sn().bits(config.n).plli2sr().bits(config.outdiv) }); } @@ -429,16 +436,19 @@ impl SaiPll { #[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)); + rcc.dckcfgr + .modify(|_, w| w.pllsaidivq().bits(saidiv as u8 - 1)); // "M" may have been written before, but the value is identical. - rcc.pllcfgr.write(|w| unsafe { w.pllm().bits(config.m) }); + 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)); + rcc.dckcfgr + .modify(|_, w| w.pllsaidivq().bits(saidiv as u8 - 1)); rcc.pllsaicfgr.modify(|_, w| unsafe { w.pllsaim() .bits(config.m) From c0ded877b85e62b2c5d9caea39bcdbe6431c06ff Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sun, 10 Jan 2021 17:00:10 +0100 Subject: [PATCH 6/7] rcc: Fix clippy errors. --- src/rcc/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index d8ce017c..ba0bc07e 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -944,7 +944,7 @@ impl CFGR { ppre1, ppre2, sysclk: Hertz(sysclk), - pll48clk: plls.pll48clk.map(|clk| Hertz(clk)), + pll48clk: plls.pll48clk.map(Hertz), #[cfg(not(any( feature = "stm32f412", @@ -952,21 +952,21 @@ impl CFGR { feature = "stm32f423", feature = "stm32f446", )))] - i2s_clk: plls.i2s.i2s_clk.map(|clk| Hertz(clk)), + 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(|clk| Hertz(clk)), + 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(|clk| Hertz(clk)), + i2s_apb2_clk: plls.i2s.i2s_apb2_clk.map(Hertz), #[cfg(any( feature = "stm32f413", @@ -978,7 +978,7 @@ impl CFGR { feature = "stm32f469", feature = "stm32f479", ))] - saia_clk: plls.sai.sai1_clk.map(|clk| Hertz(clk)), + saia_clk: plls.sai.sai1_clk.map(Hertz), #[cfg(any( feature = "stm32f413", feature = "stm32f423", @@ -989,11 +989,11 @@ impl CFGR { feature = "stm32f469", feature = "stm32f479", ))] - saib_clk: plls.sai.sai2_clk.map(|clk| Hertz(clk)), + saib_clk: plls.sai.sai2_clk.map(Hertz), #[cfg(feature = "stm32f446")] - sai1_clk: plls.sai.sai1_clk.map(|clk| Hertz(clk)), + sai1_clk: plls.sai.sai1_clk.map(Hertz), #[cfg(feature = "stm32f446")] - sai2_clk: plls.sai.sai2_clk.map(|clk| Hertz(clk)), + sai2_clk: plls.sai.sai2_clk.map(Hertz), }; if self.pll48clk { From 9ac793faa67778b0132203daf3ef1efa5e9fea53 Mon Sep 17 00:00:00 2001 From: Mathias Gottschlag Date: Sun, 10 Jan 2021 17:01:47 +0100 Subject: [PATCH 7/7] Added changelog entry for I2S/SAI clocks. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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