diff options
Diffstat (limited to 'drivers/ccm-10xx/src/ccm_analog.rs')
| -rw-r--r-- | drivers/ccm-10xx/src/ccm_analog.rs | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/drivers/ccm-10xx/src/ccm_analog.rs b/drivers/ccm-10xx/src/ccm_analog.rs new file mode 100644 index 0000000..a427827 --- /dev/null +++ b/drivers/ccm-10xx/src/ccm_analog.rs @@ -0,0 +1,244 @@ +use core::num::NonZero; + +pub use crate::ral::ccm_analog::CCM_ANALOG; +pub type Instance = ral_registers::Instance<crate::ral::ccm_analog::RegisterBlock>; + +/// Frequency (Hz) of the external crystal oscillator. +pub const XTAL_OSCILLATOR_HZ: u32 = 24_000_000; + +const fn pll_pfd_divider(pll_hz: u32, target_hz: u32) -> Option<NonZero<u32>> { + let div = pll_hz / target_hz * 18; + // Safety: divider is between the non-zero min and max values. + unsafe { + if pll3::MIN_FRAC <= div && div <= pll3::MAX_FRAC { + Some(NonZero::new_unchecked(div)) + } else { + None + } + } +} + +/// The system PLL. +pub mod pll2 { + use core::num::NonZero; + + use crate::ral; + + /// PLL2 frequency (Hz). + /// + /// The reference manual notes that PLL2 should always run at 528MHz, + /// so this constant assumes that PLL2's DIV_SELECT field isn't + /// changed at runtime. + pub const FREQUENCY: u32 = 528_000_000; + + /// The smallest PLL2_PFD divider. + pub const MIN_FRAC: u32 = super::pll3::MIN_FRAC; + /// The largest PLL2_PFD divider. + pub const MAX_FRAC: u32 = super::pll3::MAX_FRAC; + + /// Produces a PFD divider to reach the target PFD frequency, in Hz. + pub const fn pll_pfd_divider(target_hz: u32) -> Option<NonZero<u32>> { + super::pll_pfd_divider(FREQUENCY, target_hz) + } + + /// Restart the system PLL. + pub fn restart(ccm_analog: super::CCM_ANALOG) { + loop { + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, ENABLE == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_SET, ENABLE: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, POWERDOWN == 1) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_CLR, POWERDOWN: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, LOCK == 0) { + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_SYS, BYPASS == 1) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_SYS_CLR, BYPASS: 1); + continue; + } + break; + } + } +} + +/// The USB PLL. +/// +/// When an implementation has multiple USB peripherals, this +/// PLL is associated with USB1. +pub mod pll3 { + /// PLL3 frequency (Hz). + /// + /// The reference manual notes that PLL3 should always run at 480MHz, + /// so this constant assumes that PLL3's DIV_SELECT field isn't + /// changed at runtime. + pub const FREQUENCY: u32 = 480_000_000; + + /// The smallest PLL3_PFD divider. + pub const MIN_FRAC: u32 = 12; + /// The largest PLL3_PFD divider. + pub const MAX_FRAC: u32 = 35; + + use core::num::NonZero; + + use crate::ral; + + /// Produces a PFD divider to reach the target PFD frequency, in Hz. + pub const fn pll_pfd_divider(target_hz: u32) -> Option<NonZero<u32>> { + super::pll_pfd_divider(FREQUENCY, target_hz) + } + + /// Restart the USB(1) PLL. + pub fn restart(ccm_analog: super::CCM_ANALOG) { + loop { + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, ENABLE == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, ENABLE: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, POWER == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, POWER: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, LOCK == 0) { + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, BYPASS == 1) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_CLR, BYPASS: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB1, EN_USB_CLKS == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB1_SET, EN_USB_CLKS: 1); + continue; + } + break; + } + } +} + +/// The USB2 PLL, when available. +pub mod pll7 { + /// PLL7 frequency (Hz). + /// + /// The reference manual notes that PLL7 should always run at 480MHz, + /// so this constant assumes that PLL7's DIV_SELECT field isn't + /// changed at runtime. + pub const FREQUENCY: u32 = 480_000_000; + + /// The smallest PLL7_PFD divider. + pub const MIN_FRAC: u8 = 12; + /// The largest PLL7_PFD divider. + pub const MAX_FRAC: u8 = 35; + + use crate::ral; + + /// Restart the USB2 PLL. + pub fn restart(ccm_analog: super::CCM_ANALOG) { + loop { + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, ENABLE == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, ENABLE: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, POWER == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, POWER: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, LOCK == 0) { + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, BYPASS == 1) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_CLR, BYPASS: 1); + continue; + } + if ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_USB2, EN_USB_CLKS == 0) { + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_USB2_SET, EN_USB_CLKS: 1); + continue; + } + break; + } + } +} + +/// The ARM PLL. +/// +/// When available, this is always divided by the CCM `ARM_DIVIDER`. +pub mod pll1 { + use crate::ral; + + /// Restart PLL1 with a new divider selection. + /// + /// PLL1 should not be driving any components when + /// this restart happens. You're responsible for + /// switching over clocks. + /// + /// The implementation clamps `div_sel` between 54 and 108. + /// + /// When this function returns, PLL1 is running and stable. + pub fn restart(ccm_analog: super::CCM_ANALOG, div_sel: u32) { + // Restart PLL1. + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ARM, POWERDOWN: 1); + // Clears POWERDOWN bit from above. + ral::write_reg!( + ral::ccm_analog, + ccm_analog, + PLL_ARM, + DIV_SELECT: div_sel.clamp(54, 108) + ); + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ARM_SET, ENABLE: 1); + // Wait for lock... + while ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_ARM, LOCK == 0) {} + } + + /// Compute the PLL1 frequency (Hz) for a `DIV_SEL` value. + pub const fn frequency(div_sel: u32) -> u32 { + super::XTAL_OSCILLATOR_HZ * div_sel / 2 + } +} + +/// A fixed 500MHz ENET PLL. +pub mod pll6_500mhz { + use crate::ral; + + /// PLL6 frequency (Hz). + pub const FREQUENCY: u32 = 500_000_000; + + /// Restart PLL6. + /// + /// PLL6 should not be driving any components + /// when this restart happens. You're responsible + /// for switching over clocks. + /// + /// When this function returns, PLL6 is running and + /// stable. + pub fn restart(ccm_analog: super::CCM_ANALOG) { + // Clears BYPASS, if enabled. + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, POWERDOWN: 1); + // Clears POWERDOWN from above. + ral::write_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, ENET_500M_REF_EN: 1); + while ral::read_reg!(ral::ccm_analog, ccm_analog, PLL_ENET, LOCK == 0) {} + } +} + +/// The configurable ENET PLL. +pub mod pll6 { + use crate::ral::{self, ccm_analog}; + + /// Frequency selection. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u32)] + pub enum Frequency { + Frequency25MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_25MHZ, + Frequency50MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_50MHZ, + Frequency100MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_100MHZ, + Frequency125MHz = ccm_analog::PLL_ENET::DIV_SELECT::DIV_125MHZ, + } + + /// Restart the PLL with a new frequency. + pub fn restart(ccm_analog: ccm_analog::CCM_ANALOG, frequency: Frequency) { + ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, BYPASS: 1, BYPASS_CLK_SRC: 0); + ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, ENABLE: 1, POWERDOWN: 0, DIV_SELECT: frequency as u32); + while ral::read_reg!(ccm_analog, ccm_analog, PLL_ENET, LOCK == 0) {} + ral::modify_reg!(ccm_analog, ccm_analog, PLL_ENET, BYPASS: 0); + } +} |
