diff options
| author | Dario Nieuwenhuis <[email protected]> | 2025-07-24 21:23:02 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-24 21:23:02 +0000 |
| commit | ff29d61b318efb54912096ec7771b0a906380440 (patch) | |
| tree | 2e763aae46a2969ef7a6e69e5fe868bccf4742aa | |
| parent | 3bb94469f4efebee04f2d8e5f380c1cfbc967ad6 (diff) | |
| parent | 0d1e34d0fcc1fb995f0da46eede063cfbd9c962e (diff) | |
Merge pull request #4313 from snakehand/main
U5: Enable MSI auto calibration and compute frequencies
| -rw-r--r-- | embassy-stm32/src/rcc/u5.rs | 165 |
1 files changed, 160 insertions, 5 deletions
diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 97eb2eb6d..06895a99a 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs | |||
| @@ -5,7 +5,7 @@ pub use crate::pac::rcc::vals::{ | |||
| 5 | Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, | 5 | Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, |
| 6 | Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, | 6 | Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, |
| 7 | }; | 7 | }; |
| 8 | use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge}; | 8 | use crate::pac::rcc::vals::{Hseext, Msipllfast, Msipllsel, Msirgsel, Pllmboost, Pllrge}; |
| 9 | #[cfg(all(peri_usb_otg_hs))] | 9 | #[cfg(all(peri_usb_otg_hs))] |
| 10 | pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; | 10 | pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; |
| 11 | use crate::pac::{FLASH, PWR, RCC}; | 11 | use crate::pac::{FLASH, PWR, RCC}; |
| @@ -64,6 +64,46 @@ pub struct Pll { | |||
| 64 | pub divr: Option<PllDiv>, | 64 | pub divr: Option<PllDiv>, |
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | #[derive(Clone, Copy, PartialEq)] | ||
| 68 | pub enum MsiAutoCalibration { | ||
| 69 | /// MSI auto-calibration is disabled | ||
| 70 | Disabled, | ||
| 71 | /// MSIS is given priority for auto-calibration | ||
| 72 | MSIS, | ||
| 73 | /// MSIK is given priority for auto-calibration | ||
| 74 | MSIK, | ||
| 75 | /// MSIS with fast mode (always on) | ||
| 76 | MsisFast, | ||
| 77 | /// MSIK with fast mode (always on) | ||
| 78 | MsikFast, | ||
| 79 | } | ||
| 80 | |||
| 81 | impl MsiAutoCalibration { | ||
| 82 | const fn default() -> Self { | ||
| 83 | MsiAutoCalibration::Disabled | ||
| 84 | } | ||
| 85 | |||
| 86 | fn base_mode(&self) -> Self { | ||
| 87 | match self { | ||
| 88 | MsiAutoCalibration::Disabled => MsiAutoCalibration::Disabled, | ||
| 89 | MsiAutoCalibration::MSIS => MsiAutoCalibration::MSIS, | ||
| 90 | MsiAutoCalibration::MSIK => MsiAutoCalibration::MSIK, | ||
| 91 | MsiAutoCalibration::MsisFast => MsiAutoCalibration::MSIS, | ||
| 92 | MsiAutoCalibration::MsikFast => MsiAutoCalibration::MSIK, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | fn is_fast(&self) -> bool { | ||
| 97 | matches!(self, MsiAutoCalibration::MsisFast | MsiAutoCalibration::MsikFast) | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | impl Default for MsiAutoCalibration { | ||
| 102 | fn default() -> Self { | ||
| 103 | Self::default() | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 67 | #[derive(Clone, Copy)] | 107 | #[derive(Clone, Copy)] |
| 68 | pub struct Config { | 108 | pub struct Config { |
| 69 | // base clock sources | 109 | // base clock sources |
| @@ -95,6 +135,7 @@ pub struct Config { | |||
| 95 | 135 | ||
| 96 | /// Per-peripheral kernel clock selection muxes | 136 | /// Per-peripheral kernel clock selection muxes |
| 97 | pub mux: super::mux::ClockMux, | 137 | pub mux: super::mux::ClockMux, |
| 138 | pub auto_calibration: MsiAutoCalibration, | ||
| 98 | } | 139 | } |
| 99 | 140 | ||
| 100 | impl Config { | 141 | impl Config { |
| @@ -116,6 +157,7 @@ impl Config { | |||
| 116 | voltage_range: VoltageScale::RANGE1, | 157 | voltage_range: VoltageScale::RANGE1, |
| 117 | ls: crate::rcc::LsConfig::new(), | 158 | ls: crate::rcc::LsConfig::new(), |
| 118 | mux: super::mux::ClockMux::default(), | 159 | mux: super::mux::ClockMux::default(), |
| 160 | auto_calibration: MsiAutoCalibration::default(), | ||
| 119 | } | 161 | } |
| 120 | } | 162 | } |
| 121 | } | 163 | } |
| @@ -131,7 +173,42 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 131 | PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); | 173 | PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); |
| 132 | while !PWR.vosr().read().vosrdy() {} | 174 | while !PWR.vosr().read().vosrdy() {} |
| 133 | 175 | ||
| 134 | let msis = config.msis.map(|range| { | 176 | let lse_calibration_freq = if config.auto_calibration != MsiAutoCalibration::Disabled { |
| 177 | // LSE must be configured and peripherals clocked for MSI auto-calibration | ||
| 178 | let lse_config = config | ||
| 179 | .ls | ||
| 180 | .lse | ||
| 181 | .clone() | ||
| 182 | .expect("LSE must be configured for MSI auto-calibration"); | ||
| 183 | assert!(lse_config.peripherals_clocked); | ||
| 184 | |||
| 185 | // Expect less than +/- 5% deviation for LSE frequency | ||
| 186 | if (31_100..=34_400).contains(&lse_config.frequency.0) { | ||
| 187 | // Check that the calibration is applied to an active clock | ||
| 188 | match ( | ||
| 189 | config.auto_calibration.base_mode(), | ||
| 190 | config.msis.is_some(), | ||
| 191 | config.msik.is_some(), | ||
| 192 | ) { | ||
| 193 | (MsiAutoCalibration::MSIS, true, _) => { | ||
| 194 | // MSIS is active and using LSE for auto-calibration | ||
| 195 | Some(lse_config.frequency) | ||
| 196 | } | ||
| 197 | (MsiAutoCalibration::MSIK, _, true) => { | ||
| 198 | // MSIK is active and using LSE for auto-calibration | ||
| 199 | Some(lse_config.frequency) | ||
| 200 | } | ||
| 201 | // improper configuration | ||
| 202 | _ => panic!("MSIx auto-calibration is enabled for a source that has not been configured."), | ||
| 203 | } | ||
| 204 | } else { | ||
| 205 | panic!("LSE frequency more than 5% off from 32.768 kHz, cannot use for MSI auto-calibration"); | ||
| 206 | } | ||
| 207 | } else { | ||
| 208 | None | ||
| 209 | }; | ||
| 210 | |||
| 211 | let mut msis = config.msis.map(|range| { | ||
| 135 | // Check MSI output per RM0456 § 11.4.10 | 212 | // Check MSI output per RM0456 § 11.4.10 |
| 136 | match config.voltage_range { | 213 | match config.voltage_range { |
| 137 | VoltageScale::RANGE4 => { | 214 | VoltageScale::RANGE4 => { |
| @@ -156,11 +233,21 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 156 | w.set_msipllen(false); | 233 | w.set_msipllen(false); |
| 157 | w.set_msison(true); | 234 | w.set_msison(true); |
| 158 | }); | 235 | }); |
| 236 | let msis = if let (Some(freq), MsiAutoCalibration::MSIS) = | ||
| 237 | (lse_calibration_freq, config.auto_calibration.base_mode()) | ||
| 238 | { | ||
| 239 | // Enable the MSIS auto-calibration feature | ||
| 240 | RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIS)); | ||
| 241 | RCC.cr().modify(|w| w.set_msipllen(true)); | ||
| 242 | calculate_calibrated_msi_frequency(range, freq) | ||
| 243 | } else { | ||
| 244 | msirange_to_hertz(range) | ||
| 245 | }; | ||
| 159 | while !RCC.cr().read().msisrdy() {} | 246 | while !RCC.cr().read().msisrdy() {} |
| 160 | msirange_to_hertz(range) | 247 | msis |
| 161 | }); | 248 | }); |
| 162 | 249 | ||
| 163 | let msik = config.msik.map(|range| { | 250 | let mut msik = config.msik.map(|range| { |
| 164 | // Check MSI output per RM0456 § 11.4.10 | 251 | // Check MSI output per RM0456 § 11.4.10 |
| 165 | match config.voltage_range { | 252 | match config.voltage_range { |
| 166 | VoltageScale::RANGE4 => { | 253 | VoltageScale::RANGE4 => { |
| @@ -184,10 +271,44 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 184 | RCC.cr().modify(|w| { | 271 | RCC.cr().modify(|w| { |
| 185 | w.set_msikon(true); | 272 | w.set_msikon(true); |
| 186 | }); | 273 | }); |
| 274 | let msik = if let (Some(freq), MsiAutoCalibration::MSIK) = | ||
| 275 | (lse_calibration_freq, config.auto_calibration.base_mode()) | ||
| 276 | { | ||
| 277 | // Enable the MSIK auto-calibration feature | ||
| 278 | RCC.cr().modify(|w| w.set_msipllsel(Msipllsel::MSIK)); | ||
| 279 | RCC.cr().modify(|w| w.set_msipllen(true)); | ||
| 280 | calculate_calibrated_msi_frequency(range, freq) | ||
| 281 | } else { | ||
| 282 | msirange_to_hertz(range) | ||
| 283 | }; | ||
| 187 | while !RCC.cr().read().msikrdy() {} | 284 | while !RCC.cr().read().msikrdy() {} |
| 188 | msirange_to_hertz(range) | 285 | msik |
| 189 | }); | 286 | }); |
| 190 | 287 | ||
| 288 | if let Some(lse_freq) = lse_calibration_freq { | ||
| 289 | // If both MSIS and MSIK are enabled, we need to check if they are using the same internal source. | ||
| 290 | if let (Some(msis_range), Some(msik_range)) = (config.msis, config.msik) { | ||
| 291 | if (msis_range as u8 >> 2) == (msik_range as u8 >> 2) { | ||
| 292 | // Clock source is shared, both will be auto calibrated, recalculate other frequency | ||
| 293 | match config.auto_calibration.base_mode() { | ||
| 294 | MsiAutoCalibration::MSIS => { | ||
| 295 | msik = Some(calculate_calibrated_msi_frequency(msik_range, lse_freq)); | ||
| 296 | } | ||
| 297 | MsiAutoCalibration::MSIK => { | ||
| 298 | msis = Some(calculate_calibrated_msi_frequency(msis_range, lse_freq)); | ||
| 299 | } | ||
| 300 | _ => {} | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | // Check if Fast mode should be used | ||
| 305 | if config.auto_calibration.is_fast() { | ||
| 306 | RCC.cr().modify(|w| { | ||
| 307 | w.set_msipllfast(Msipllfast::FAST); | ||
| 308 | }); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 191 | let hsi = config.hsi.then(|| { | 312 | let hsi = config.hsi.then(|| { |
| 192 | RCC.cr().modify(|w| w.set_hsion(true)); | 313 | RCC.cr().modify(|w| w.set_hsion(true)); |
| 193 | while !RCC.cr().read().hsirdy() {} | 314 | while !RCC.cr().read().hsirdy() {} |
| @@ -514,3 +635,37 @@ fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput, voltag | |||
| 514 | 635 | ||
| 515 | PllOutput { p, q, r } | 636 | PllOutput { p, q, r } |
| 516 | } | 637 | } |
| 638 | |||
| 639 | /// Fraction structure for MSI auto-calibration | ||
| 640 | /// Represents the multiplier as numerator/denominator that LSE frequency is multiplied by | ||
| 641 | #[derive(Debug, Clone, Copy)] | ||
| 642 | struct MsiFraction { | ||
| 643 | numerator: u32, | ||
| 644 | denominator: u32, | ||
| 645 | } | ||
| 646 | |||
| 647 | impl MsiFraction { | ||
| 648 | const fn new(numerator: u32, denominator: u32) -> Self { | ||
| 649 | Self { numerator, denominator } | ||
| 650 | } | ||
| 651 | |||
| 652 | /// Calculate the calibrated frequency given an LSE frequency | ||
| 653 | fn calculate_frequency(&self, lse_freq: Hertz) -> Hertz { | ||
| 654 | Hertz(lse_freq.0 * self.numerator / self.denominator) | ||
| 655 | } | ||
| 656 | } | ||
| 657 | |||
| 658 | fn get_msi_calibration_fraction(range: Msirange) -> MsiFraction { | ||
| 659 | // Exploiting the MSIx internals to make calculations compact | ||
| 660 | let denominator = (range as u32 & 0x03) + 1; | ||
| 661 | // Base multipliers are deduced from Table 82: MSI oscillator characteristics in data sheet | ||
| 662 | let numerator = [1465, 122, 94, 12][range as usize >> 2]; | ||
| 663 | |||
| 664 | MsiFraction::new(numerator, denominator) | ||
| 665 | } | ||
| 666 | |||
| 667 | /// Calculate the calibrated MSI frequency for a given range and LSE frequency | ||
| 668 | fn calculate_calibrated_msi_frequency(range: Msirange, lse_freq: Hertz) -> Hertz { | ||
| 669 | let fraction = get_msi_calibration_fraction(range); | ||
| 670 | fraction.calculate_frequency(lse_freq) | ||
| 671 | } | ||
