Files
kaptan/firmware/platforms/stm32f7/src/f722/peripherals/clock.rs
T

132 lines
4.8 KiB
Rust

//! 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
}