//! Deterministic STM32F722 PLL clock-source selection. use kaptan_err_reportable::KaptanError; use stm32f7::stm32f722; const CLOCK_SWITCH_POLL_LIMIT: u32 = 1_024; // Crystal startup is slower than a clock-tree state transition. This remains // bounded so an absent oscillator reaches safe-idle instead of hanging boot. const HSE_STARTUP_POLL_LIMIT: u32 = 1_000_000; /// Failure to establish the 216 MHz PLL clock profile. #[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)] pub enum ClockProfileError { #[kaptan_error(id = 11001)] HsiNotReady, #[kaptan_error(id = 11002)] SystemClockNotHsi, #[kaptan_error(id = 11003)] PllNotStopped, #[kaptan_error(id = 11004)] PllNotReady, #[kaptan_error(id = 11005)] OverDriveNotReady, #[kaptan_error(id = 11006)] OverDriveSwitchNotReady, #[kaptan_error(id = 11007)] SystemClockNotPll, #[kaptan_error(id = 11008)] HseNotReady, } /// Selects the F722 platform's 8 MHz HSE PLL profile. /// /// SYSCLK/HCLK run at 216 MHz; APB1 is 54 MHz (timers run at 108 MHz), and /// APB2 is 108 MHz. Both supported flight-controller boards have an 8 MHz /// external oscillator; using it as the PLL source gives USB an accurate /// 48 MHz clock through PLLQ. pub fn select_pll216_profile( rcc: &stm32f722::RCC, pwr: &stm32f722::PWR, flash: &stm32f722::FLASH, ) -> Result<(), ClockProfileError> { rcc.cr().modify(|_, write| write.hsion().on()); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || { rcc.cr().read().hsirdy().is_ready() }) { return Err(ClockProfileError::HsiNotReady); } // Return to HSI before changing a PLL left configured by an earlier boot stage. rcc.cfgr().modify(|_, write| write.sw().hsi()); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || rcc.cfgr().read().sws().is_hsi()) { return Err(ClockProfileError::SystemClockNotHsi); } rcc.cr().modify(|_, write| write.hseon().on()); if !wait_until(HSE_STARTUP_POLL_LIMIT, || { rcc.cr().read().hserdy().is_ready() }) { return Err(ClockProfileError::HseNotReady); } rcc.cr().modify(|_, write| write.pllon().off()); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || { !rcc.cr().read().pllrdy().is_ready() }) { return Err(ClockProfileError::PllNotStopped); } // Scale 1, over-drive and seven flash wait states are required at 216 MHz. rcc.apb1enr().modify(|_, write| write.pwren().set_bit()); pwr.cr1().modify(|read, write| { // SAFETY: this read-modify-write changes only the documented voltage-scale and // over-drive-enable fields; it preserves every other PWR_CR1 bit owned by boot. unsafe { write.bits((read.bits() & !(0b11 << 14)) | (0b11 << 14) | (1 << 16)) } }); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || { pwr.csr1().read().odrdy().bit_is_set() }) { return Err(ClockProfileError::OverDriveNotReady); } pwr.cr1().modify(|_, write| write.odswen().set_bit()); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || { pwr.csr1().read().odswrdy().bit_is_set() }) { return Err(ClockProfileError::OverDriveSwitchNotReady); } flash.acr().modify(|read, write| { // SAFETY: these are the STM32F7-documented latency, instruction-cache and // data-cache fields for 216 MHz; the read-modify-write retains other ACR bits. unsafe { write.bits((read.bits() & !0x0f) | 7 | (1 << 8) | (1 << 9)) } }); // HSE / 8 * 432 / 2 = 216 MHz; Q=9 supplies the exact 48 MHz USB domain. rcc.pllcfgr().write(|write| { // SAFETY: M=8, N=432, P=/2 (the reset encoding), Q=9, and the HSE source // are valid for the 8 MHz board oscillator; the PLL is disabled first. unsafe { write.bits(8 | (432 << 6) | (1 << 22) | (9 << 24)) } }); rcc.cr().modify(|_, write| write.pllon().on()); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || { rcc.cr().read().pllrdy().is_ready() }) { return Err(ClockProfileError::PllNotReady); } rcc.cfgr().modify(|read, write| { // AHB /1, APB1 /4, APB2 /2, SYSCLK = PLL. // SAFETY: this preserves every CFGR field except the documented prescaler and // system-clock-source fields, which are written with encodings valid for F722. unsafe { write.bits( (read.bits() & !((0x0f << 4) | (0x07 << 10) | (0x07 << 13) | 0x03)) | (0b101 << 10) | (0b100 << 13) | 0b10, ) } }); if !wait_until(CLOCK_SWITCH_POLL_LIMIT, || rcc.cfgr().read().sws().is_pll()) { return Err(ClockProfileError::SystemClockNotPll); } Ok(()) } fn wait_until(limit: u32, mut ready: impl FnMut() -> bool) -> bool { for _ in 0..limit { if ready() { return true; } } false }