diff options
Diffstat (limited to 'drivers/ccm-11xx/src/ral_11xx/pll.rs')
| -rw-r--r-- | drivers/ccm-11xx/src/ral_11xx/pll.rs | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/drivers/ccm-11xx/src/ral_11xx/pll.rs b/drivers/ccm-11xx/src/ral_11xx/pll.rs new file mode 100644 index 0000000..d5e04d4 --- /dev/null +++ b/drivers/ccm-11xx/src/ral_11xx/pll.rs @@ -0,0 +1,598 @@ +use core::num::NonZero; + +pub type Instance = ral_registers::Instance<RegisterBlock>; + +#[repr(C)] +#[allow(non_snake_case)] +pub struct RegisterBlock { + reserved_0: [u8; 512], + pub ARM_PLL_CTRL: u32, + reserved_1: [u8; 12], + pub SYS_PLL3_CTRL: u32, + reserved_2: [u8; 12], + pub SYS_PLL3_UPDATE: u32, + reserved_3: [u8; 12], + pub SYS_PLL3_PFD: u32, + reserved_4: [u8; 12], + pub SYS_PLL2_CTRL: u32, + reserved_5: [u8; 12], + pub SYS_PLL2_UPDATE: u32, + reserved_6: [u8; 12], + pub SYS_PLL2_SS: u32, + reserved_7: [u8; 12], + pub SYS_PLL2_PFD: u32, + reserved_8: [u8; 44], + pub SYS_PLL2_MFD: u32, + reserved_9: [u8; 12], + pub SYS_PLL1_SS: u32, + reserved_10: [u8; 12], + pub SYS_PLL1_CTRL: u32, + reserved_11: [u8; 12], + pub SYS_PLL1_DENOMINATOR: u32, + reserved_12: [u8; 12], + pub SYS_PLL1_NUMERATOR: u32, + reserved_13: [u8; 12], + pub SYS_PLL1_DIV_SELECT: u32, + reserved_14: [u8; 12], + pub PLL_AUDIO_CTRL: u32, + reserved_15: [u8; 12], + pub PLL_AUDIO_SS: u32, + reserved_16: [u8; 12], + pub PLL_AUDIO_DENOMINATOR: u32, + reserved_17: [u8; 12], + pub PLL_AUDIO_NUMERATOR: u32, + reserved_18: [u8; 12], + pub PLL_AUDIO_DIV_SELECT: u32, + reserved_19: [u8; 12], + pub PLL_VIDEO_CTRL: u32, + reserved_20: [u8; 12], + pub PLL_VIDEO_SS: u32, + reserved_21: [u8; 12], + pub PLL_VIDEO_DENOMINATOR: u32, + reserved_22: [u8; 12], + pub PLL_VIDEO_NUMERATOR: u32, + reserved_23: [u8; 12], + pub PLL_VIDEO_DIV_SELECT: u32, +} + +ral_registers::register! { + #[doc = "SYS_PLL1_CTRL_REGISTER"] + pub SYS_PLL1_CTRL<u32> RW [ + #[doc = "ENABLE_CLK"] + ENABLE_CLK start(13) width(1) RW {} + #[doc = "SYS_PLL1_GATE"] + SYS_PLL1_GATE start(14) width(1) RW {} + #[doc = "SYS_PLL1_DIV2"] + SYS_PLL1_DIV2 start(25) width(1) RW {} + #[doc = "SYS_PLL1_DIV5"] + SYS_PLL1_DIV5 start(26) width(1) RW {} + #[doc = "SYS_PLL1_DIV5_CONTROL_MODE"] + SYS_PLL1_DIV5_CONTROL_MODE start(27) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "SYS_PLL1_DIV2_CONTROL_MODE"] + SYS_PLL1_DIV2_CONTROL_MODE start(28) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "SYS_PLL1_STABLE"] + SYS_PLL1_STABLE start(29) width(1) RO {} + #[doc = "SYS_PLL1_AI_BUSY"] + SYS_PLL1_AI_BUSY start(30) width(1) RO {} + #[doc = "SYS_PLL1_CONTROL_MODE"] + SYS_PLL1_CONTROL_MODE start(31) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +/// Manage all SYS_PLL1 clocks using setpoints. +pub fn enable_sys_pll1_setpoints(pll: ral_registers::Instance<RegisterBlock>) { + ral_registers::modify_reg!(self, pll, SYS_PLL1_CTRL, + SYS_PLL1_CONTROL_MODE: GPC, + SYS_PLL1_DIV2_CONTROL_MODE: GPC, + SYS_PLL1_DIV5_CONTROL_MODE: GPC, + ); +} + +ral_registers::register! { + #[doc = "ARM_PLL_CTRL_REGISTER"] + pub ARM_PLL_CTRL<u32> RW [ + #[doc = "DIV_SELECT"] + DIV_SELECT start(0) width(8) RW {} + #[doc = "PLL Start up initialization"] + HOLD_RING_OFF start(12) width(1) RW {} + #[doc = "Powers up the PLL."] + POWERUP start(13) width(1) RW {} + #[doc = "Enable the clock output."] + ENABLE_CLK start(14) width(1) RW {} + #[doc = "POST_DIV_SEL"] + POST_DIV_SEL start(15) width(2) RW { + #[doc = "Divide by 2"] + DIV2 = 0, + #[doc = "Divide by 4"] + DIV4 = 0x1, + #[doc = "Divide by 8"] + DIV8 = 0x2, + #[doc = "Divide by 1"] + DIV1 = 0x3, + } + #[doc = "Bypass the pll."] + BYPASS start(17) width(1) RW {} + #[doc = "ARM_PLL_STABLE"] + ARM_PLL_STABLE start(29) width(1) RO {} + #[doc = "ARM_PLL_GATE"] + ARM_PLL_GATE start(30) width(1) RW {} + #[doc = "pll_arm_control_mode"] + ARM_PLL_CONTROL_MODE start(31) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +/// Post divider for the ARM PLL. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum ArmPllPostDiv { + /// Divide by 2. + Div2 = 0, + /// Divide by 4. + Div4 = 1, + /// Divide by 8. + Div8 = 2, + /// Divide by 1. + Div1 = 3, +} + +impl ArmPllPostDiv { + /// Return the divisor for this post divider. + #[must_use] + pub const fn divisor(self) -> u32 { + match self { + Self::Div2 => 2, + Self::Div4 => 4, + Self::Div8 => 8, + Self::Div1 => 1, + } + } +} + +/// The ARM PLL loop divider. +/// +/// This value is always clamped between 104 and 208. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ArmPllDivSelect(NonZero<u32>); + +impl ArmPllDivSelect { + /// Construct a loop divider. + /// + /// The implementation clamps the value between 104 and 208. + #[must_use] + pub const fn new(div_select: u32) -> Option<Self> { + if 104 <= div_select && div_select <= 208 { + Some(Self(NonZero::new(div_select).unwrap())) + } else { + None + } + } + /// Returns the divider as a raw value. + #[must_use] + #[inline] + pub const fn get(self) -> u32 { + self.0.get() + } +} + +/// Manage the ARM PLL with setpoints. +/// +/// `post_div` is the post divider selection applied when the +/// PLL is enabled. Similarly `div_select` is the loop divider. +/// Use [`arm_pll_frequency`] to understand the expected frequency +/// affected by this call. +pub fn enable_arm_pll_setpoints( + pll: ral_registers::Instance<RegisterBlock>, + post_div: ArmPllPostDiv, + div_select: ArmPllDivSelect, +) { + ral_registers::modify_reg!(self, pll, ARM_PLL_CTRL, + DIV_SELECT: div_select.get(), + POST_DIV_SEL: post_div as u32, + ARM_PLL_GATE: 1, + ARM_PLL_CONTROL_MODE: GPC, + ENABLE_CLK: 0, + ); +} + +/// Computes the expected ARM PLL frequency. +pub const fn arm_pll_frequency(post_div: ArmPllPostDiv, div_select: ArmPllDivSelect) -> u32 { + 24_000_000 / 2 / post_div.divisor() * div_select.get() +} + +ral_registers::register! { + #[doc = "SYS_PLL2_CTRL_REGISTER"] + pub SYS_PLL2_CTRL<u32> RW [ + #[doc = "Enable Internal PLL Regulator"] + PLL_REG_EN start(3) width(1) RW {} + #[doc = "PLL Start up initialization"] + HOLD_RING_OFF start(11) width(1) RW {} + #[doc = "Enable the clock output."] + ENABLE_CLK start(13) width(1) RW {} + #[doc = "Bypass the pll."] + BYPASS start(16) width(1) RW {} + #[doc = "DITHER_ENABLE"] + DITHER_ENABLE start(17) width(1) RW {} + #[doc = "PFD_OFFSET_EN"] + PFD_OFFSET_EN start(18) width(1) RW {} + #[doc = "PLL_DDR_OVERRIDE"] + PLL_DDR_OVERRIDE start(19) width(1) RW {} + #[doc = "Powers up the PLL."] + POWERUP start(23) width(1) RW {} + #[doc = "SYS_PLL2_STABLE"] + SYS_PLL2_STABLE start(29) width(1) RO {} + #[doc = "SYS_PLL2_GATE"] + SYS_PLL2_GATE start(30) width(1) RW {} + #[doc = "SYS_PLL2_control_mode"] + SYS_PLL2_CONTROL_MODE start(31) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +ral_registers::register! { + #[doc = "SYS_PLL2_UPDATE_REGISTER"] + pub SYS_PLL2_UPDATE<u32> RW [ + #[doc = "PFD0_UPDATE"] + PFD0_UPDATE start(1) width(1) RW {} + #[doc = "PFD1_UPDATE"] + PFD1_UPDATE start(2) width(1) RW {} + #[doc = "PFD2_UPDATE"] + PFD2_UPDATE start(3) width(1) RW {} + #[doc = "PFD3_UPDATE"] + PFD3_UPDATE start(4) width(1) RW {} + #[doc = "pfd0_control_mode"] + PFD0_CONTROL_MODE start(5) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pfd1_control_mode"] + PFD1_CONTROL_MODE start(6) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pfd2_control_mode"] + PFD2_CONTROL_MODE start(7) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pfd3_control_mode"] + PFD3_CONTROL_MODE start(8) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +/// Manage SYS_PLL2 clocks and PFDs using setpoints. +pub fn enable_sys_pll2_setpoints(pll: ral_registers::Instance<RegisterBlock>) { + ral_registers::modify_reg!(self, pll, SYS_PLL2_UPDATE, + PFD3_CONTROL_MODE: GPC, + PFD2_CONTROL_MODE: GPC, + PFD1_CONTROL_MODE: GPC, + PFD0_CONTROL_MODE: GPC, + ); + ral_registers::modify_reg!(self, pll, SYS_PLL2_CTRL, + SYS_PLL2_CONTROL_MODE: GPC, + SYS_PLL2_GATE: 1, + ENABLE_CLK: 0, + ); +} + +ral_registers::register! { + #[doc = "SYS_PLL3_CTRL_REGISTER"] + pub SYS_PLL3_CTRL<u32> RW [ + #[doc = "SYS PLL3 DIV2 gate"] + SYS_PLL3_DIV2 start(3) width(1) RW {} + #[doc = "Enable Internal PLL Regulator"] + PLL_REG_EN start(4) width(1) RW {} + #[doc = "PLL Start up initialization"] + HOLD_RING_OFF start(11) width(1) RW {} + #[doc = "Enable the clock output."] + ENABLE_CLK start(13) width(1) RW {} + #[doc = "BYPASS"] + BYPASS start(16) width(1) RW {} + #[doc = "Powers up the PLL."] + POWERUP start(21) width(1) RW {} + #[doc = "SYS_PLL3_DIV2_CONTROL_MODE"] + SYS_PLL3_DIV2_CONTROL_MODE start(28) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "SYS_PLL3_STABLE"] + SYS_PLL3_STABLE start(29) width(1) RO {} + #[doc = "SYS_PLL3_GATE"] + SYS_PLL3_GATE start(30) width(1) RW {} + #[doc = "SYS_PLL3_control_mode"] + SYS_PLL3_CONTROL_MODE start(31) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +ral_registers::register! { + #[doc = "SYS_PLL3_UPDATE_REGISTER"] + pub SYS_PLL3_UPDATE<u32> RW [ + #[doc = "PFD0_OVERRIDE"] + PFD0_UPDATE start(1) width(1) RW {} + #[doc = "PFD1_OVERRIDE"] + PFD1_UPDATE start(2) width(1) RW {} + #[doc = "PFD2_OVERRIDE"] + PFD2_UPDATE start(3) width(1) RW {} + #[doc = "PFD3_UPDATE"] + PFD3_UPDATE start(4) width(1) RW {} + #[doc = "pfd0_control_mode"] + PFD0_CONTROL_MODE start(5) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pfd1_control_mode"] + PFD1_CONTROL_MODE start(6) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pdf2_control_mode"] + PFD2_CONTROL_MODE start(7) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + #[doc = "pfd3_control_mode"] + PFD3_CONTROL_MODE start(8) width(1) RW { + #[doc = "Software Mode (Default)"] + SW = 0, + #[doc = "GPC Mode"] + GPC = 0x1, + } + ] +} + +/// Manage SYS_PLL3 clocks and PFDs using setpoints. +pub fn enable_sys_pll3_setpoints(pll: ral_registers::Instance<RegisterBlock>) { + ral_registers::modify_reg!(self, pll, SYS_PLL3_UPDATE, + PFD3_CONTROL_MODE: GPC, + PFD2_CONTROL_MODE: GPC, + PFD1_CONTROL_MODE: GPC, + PFD0_CONTROL_MODE: GPC, + ); + ral_registers::modify_reg!(self, pll, SYS_PLL3_CTRL, + SYS_PLL3_CONTROL_MODE: GPC, + SYS_PLL3_DIV2_CONTROL_MODE: GPC, + SYS_PLL3_GATE: 1, + ENABLE_CLK: 0, + ); +} + +ral_registers::register! { + #[doc = "SYS_PLL3_PFD_REGISTER"] + pub SYS_PLL3_PFD<u32> RW [ + #[doc = "PFD0_FRAC"] + PFD0_FRAC start(0) width(6) RW {} + #[doc = "PFD0_STABLE"] + PFD0_STABLE start(6) width(1) RO {} + #[doc = "PFD0_DIV1_CLKGATE"] + PFD0_DIV1_CLKGATE start(7) width(1) RW {} + #[doc = "PFD1_FRAC"] + PFD1_FRAC start(8) width(6) RW {} + #[doc = "PFD1_STABLE"] + PFD1_STABLE start(14) width(1) RO {} + #[doc = "PFD1_DIV1_CLKGATE"] + PFD1_DIV1_CLKGATE start(15) width(1) RW {} + #[doc = "PFD2_FRAC"] + PFD2_FRAC start(16) width(6) RW {} + #[doc = "PFD2_STABLE"] + PFD2_STABLE start(22) width(1) RO {} + #[doc = "PFD2_DIV1_CLKGATE"] + PFD2_DIV1_CLKGATE start(23) width(1) RW {} + #[doc = "PFD3_FRAC"] + PFD3_FRAC start(24) width(6) RW {} + #[doc = "PFD3_STABLE"] + PFD3_STABLE start(30) width(1) RO {} + #[doc = "PFD3_DIV1_CLKGATE"] + PFD3_DIV1_CLKGATE start(31) width(1) RW {} + ] +} + +ral_registers::register! { + #[doc = "SYS_PLL2_PFD_REGISTER"] + pub SYS_PLL2_PFD<u32> RW [ + #[doc = "PFD0_FRAC"] + PFD0_FRAC start(0) width(6) RW {} + #[doc = "PFD0_STABLE"] + PFD0_STABLE start(6) width(1) RO {} + #[doc = "PFD0_DIV1_CLKGATE"] + PFD0_DIV1_CLKGATE start(7) width(1) RW {} + #[doc = "PFD1_FRAC"] + PFD1_FRAC start(8) width(6) RW {} + #[doc = "PFD1_STABLE"] + PFD1_STABLE start(14) width(1) RO {} + #[doc = "PFD1_DIV1_CLKGATE"] + PFD1_DIV1_CLKGATE start(15) width(1) RW {} + #[doc = "PFD2_FRAC"] + PFD2_FRAC start(16) width(6) RW {} + #[doc = "PFD2_STABLE"] + PFD2_STABLE start(22) width(1) RO {} + #[doc = "PFD2_DIV1_CLKGATE"] + PFD2_DIV1_CLKGATE start(23) width(1) RW {} + #[doc = "PFD3_FRAC"] + PFD3_FRAC start(24) width(6) RW {} + #[doc = "PFD3_STABLE"] + PFD3_STABLE start(30) width(1) RO {} + #[doc = "PFD3_DIV1_CLKGATE"] + PFD3_DIV1_CLKGATE start(31) width(1) RW {} + ] +} + +/// A fractional divider for a PFD. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct PfdFrac(NonZero<u8>); + +impl PfdFrac { + /// Create a new PDF fractional divider. + /// + /// Returns `None` if `div` is not between 12 and 35. + pub const fn new(div: u8) -> Option<Self> { + // Safety: div is between 12 and 35. + unsafe { + if 12 <= div && div <= 35 { + Some(Self(NonZero::new_unchecked(div))) + } else { + None + } + } + } + + /// Similar to `new`, but this will automatically clamp + /// the value between the 12 and 35. + pub const fn clamp(div: u8) -> Self { + // Safety: div is clamped between 12 and 35. + unsafe { + Self(NonZero::new_unchecked(if div < 12 { + 12 + } else if div > 35 { + 35 + } else { + div + })) + } + } + + /// Return the inner value. + #[inline] + pub const fn get(self) -> u8 { + self.0.get() + } +} + +/// Returns the PLL3 PFD fractional dividers. +/// +/// If a divider is invalid, the implementation returns `None` +/// for that divider. +pub fn pll3_pfd_fracs(pll: ral_registers::Instance<RegisterBlock>) -> [Option<PfdFrac>; 4] { + let (pfd0, pfd1, pfd2, pfd3) = ral_registers::read_reg!( + self, + pll, + SYS_PLL3_PFD, + PFD0_FRAC, + PFD1_FRAC, + PFD2_FRAC, + PFD3_FRAC + ); + + [ + PfdFrac::new(pfd0 as _), + PfdFrac::new(pfd1 as _), + PfdFrac::new(pfd2 as _), + PfdFrac::new(pfd3 as _), + ] +} + +/// Set the PLL3 PFD fractional dividers. +/// +/// The call updates all four dividers without affecting the gating. +/// However, it does not update the values. +pub fn set_pll3_pfd_fracs(pll: ral_registers::Instance<RegisterBlock>, pfds: [PfdFrac; 4]) { + ral_registers::modify_reg!(self, pll, SYS_PLL3_PFD, + PFD3_FRAC: pfds[3].get() as u32, + PFD2_FRAC: pfds[2].get() as u32, + PFD1_FRAC: pfds[1].get() as u32, + PFD0_FRAC: pfds[0].get() as u32, + ); +} + +/// Update the PFD fractional divider values that were previously +/// set. +/// +/// Only those that have a `true` update are updated. +pub fn update_pll3_pfd_fracs(pll: ral_registers::Instance<RegisterBlock>, update: [bool; 4]) { + ral_registers::modify_reg!(self, pll, SYS_PLL3_UPDATE, + PFD3_UPDATE: update[3] as u32, + PFD2_UPDATE: update[2] as u32, + PFD1_UPDATE: update[1] as u32, + PFD0_UPDATE: update[0] as u32, + ); + ral_registers::modify_reg!(self, pll, SYS_PLL3_UPDATE, + PFD3_UPDATE: false as u32, + PFD2_UPDATE: false as u32, + PFD1_UPDATE: false as u32, + PFD0_UPDATE: false as u32, + ); +} + +/// Returns the PLL2 PFD fractional dividers. +/// +/// If a divider is invalid, the implementation returns `None` +/// for that divider. +pub fn pll2_pfd_fracs(pll: ral_registers::Instance<RegisterBlock>) -> [Option<PfdFrac>; 4] { + let (pfd0, pfd1, pfd2, pfd3) = ral_registers::read_reg!( + self, + pll, + SYS_PLL2_PFD, + PFD0_FRAC, + PFD1_FRAC, + PFD2_FRAC, + PFD3_FRAC + ); + + [ + PfdFrac::new(pfd0 as _), + PfdFrac::new(pfd1 as _), + PfdFrac::new(pfd2 as _), + PfdFrac::new(pfd3 as _), + ] +} + +/// Set the PLL2 PFD fractional dividers. +/// +/// The call updates all four dividers without affecting the gating. +/// However, it does not update the values. +pub fn set_pll2_pfd_fracs(pll: ral_registers::Instance<RegisterBlock>, pfds: [PfdFrac; 4]) { + ral_registers::modify_reg!(self, pll, SYS_PLL2_PFD, + PFD3_FRAC: pfds[3].get() as u32, + PFD2_FRAC: pfds[2].get() as u32, + PFD1_FRAC: pfds[1].get() as u32, + PFD0_FRAC: pfds[0].get() as u32, + ); +} |
