aboutsummaryrefslogtreecommitdiff
path: root/drivers/ccm-10xx/src/ccm_analog.rs
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ccm-10xx/src/ccm_analog.rs')
-rw-r--r--drivers/ccm-10xx/src/ccm_analog.rs244
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);
+ }
+}