diff options
| author | Ian McIntyre <me@mciantyre.dev> | 2025-11-30 18:52:34 -0500 |
|---|---|---|
| committer | Ian McIntyre <me@mciantyre.dev> | 2025-11-30 19:10:51 -0500 |
| commit | 76199f21616ad86cf68f3b063c1ce23c6fc5a52f (patch) | |
| tree | 4c076d0afd649803a2bd9a5ed5cbb1f1c74fb459 /drivers/ccm-11xx/src/lib.rs | |
First commit
Diffstat (limited to 'drivers/ccm-11xx/src/lib.rs')
| -rw-r--r-- | drivers/ccm-11xx/src/lib.rs | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/drivers/ccm-11xx/src/lib.rs b/drivers/ccm-11xx/src/lib.rs new file mode 100644 index 0000000..09982d8 --- /dev/null +++ b/drivers/ccm-11xx/src/lib.rs @@ -0,0 +1,465 @@ +#![no_std] + +mod ral_common { + pub mod gpr_shared { + #[repr(C)] + #[allow(non_snake_case)] + pub struct RegisterBlock { + pub REG: u32, + pub SET: u32, + pub CLR: u32, + pub TOG: u32, + pub AUTHEN: u32, + pub AUTHEN_SET: u32, + pub AUTHEN_CLR: u32, + pub AUTHEN_TOG: u32, + } + } + pub mod gpr_private { + pub use super::gpr_shared::RegisterBlock; + } +} + +pub mod ral_11xx { + pub mod clock_group; + pub mod clock_root; + pub mod lpcg; + pub mod osc_pll; + pub mod pll; + use core::num::{NonZeroU8, NonZeroU32}; + + pub use crate::ral_common::{gpr_private, gpr_shared}; + + pub use self::{ + clock_group as CLOCK_GROUP, clock_root as CLOCK_ROOT, gpr_private as GPR_PRIVATE, + gpr_shared as GPR_SHARED, lpcg as LPCG, osc_pll as OSC_PLL, + }; + + #[repr(C)] + #[allow(non_snake_case)] + pub struct RegisterBlock { + pub CLOCK_ROOT: [clock_root::RegisterBlock; 79], + _reserved0: [u8; 6272], + pub CLOCK_GROUP: [clock_group::RegisterBlock; 2], + _reserved1: [u8; 1792], + pub GPR_SHARED: [gpr_shared::RegisterBlock; 8], + _reserved2: [u8; 800 - size_of::<gpr_private::RegisterBlock>()], + /// First register block is typically reserved. To avoid this + /// reserved block, make sure your index corresponds to the + /// register names. + pub GPR_PRIVATE: [gpr_private::RegisterBlock; 8], + _reserved3: [u8; 768], + pub OSC_PLL: [osc_pll::RegisterBlock; 29], + _reserved4: [u8; 3168], + pub LPCG: [lpcg::RegisterBlock; 138], + } + + pub type Instance = ral_registers::Instance<RegisterBlock>; + + /// A clock source. + /// + /// These typically map to an `OSCPLL`. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u32)] + #[non_exhaustive] + pub enum ClockSource { + /// The 16MHz RC oscillator. + Rc16M = 0, + /// The 48MHz RC oscillator. + Rc48M = 1, + /// The 48MHz RC oscillator with a fixed divide-by-2. + Rc48MDiv2 = 2, + /// The 400MHz RC oscillator. + Rc400M = 3, + /// The external 24MHz crystal oscillator VCO. + /// + /// This output isn't exposed by the clock tree. + /// However, it's exposed via [`XtalClk`]. + Xtal = 4, + /// The exposed 24MHz crystal oscillator. + XtalClk = 5, + /// ARM PLL VCO output. + /// + /// Not connected to the clock tree. + ArmPll = 6, + /// The ARM PLL output in the clock tree. + ArmPllClk = 7, + + /// Ths voltage controlled oscillator for PLL2. + /// + /// This output isn't exposed by the clock tree. + /// However, the [`Pll2Clk`] is exposed. + Pll2 = 8, + /// The PLL2 output into the clock tree. + Pll2Clk = 9, + /// PFD0 of PLL2. + Pll2Pfd0 = 10, + /// PFD1 of PLL2. + Pll2Pfd1 = 11, + /// PFD2 of PLL2. + Pll2Pfd2 = 12, + /// PFD3 of PLL2. + Pll2Pfd3 = 13, + + /// Ths voltage controlled oscillator for PLL3. + /// + /// This output isn't exposed by the clock tree. + /// However, the [`Pll3Clk`] is exposed. + Pll3 = 14, + /// The PLL3 output into the clock tree. + Pll3Clk = 15, + /// PLL3 with a fixed divide-by-2. + Pll3Div2 = 16, + /// PFD0 of PLL3. + Pll3Pfd0 = 17, + /// PFD1 of PLL3. + Pll3Pfd1 = 18, + /// PFD2 of PLL3. + Pll3Pfd2 = 19, + /// PFD3 of PLL3. + Pll3Pfd3 = 20, + + /// The voltage controlled oscillator for PLL1. + /// + /// This output isn't exposed by the clock tree. + /// However, the [`Pll1Clk`] is exposed. + Pll1 = 21, + /// The PLL1 output into the clock tree. + Pll1Clk = 22, + /// PLL1 with a fixed divide-by-2. + Pll1Div2 = 23, + /// PLL1 with a fixed divide-by-5. + Pll1Div5 = 24, + } + + /// A clock root. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(u32)] + #[non_exhaustive] + pub enum ClockRoot { + /// Cortex-M7. + M7 = 0, + /// `BUS_CLK`. + Bus = 2, + /// `BUS_LPSR_CLK`. + BusLpsr = 3, + /// SYSTICK for the Cortex-M7. + M7Systick = 8, + /// FlexSPI1. + Flexspi1 = 20, + /// FlexSPI2. + Flexspi2 = 21, + /// LPSPI1. + Lpspi1 = 43, + /// ENET. + Enet = 51, + /// ENET_1G. + Enet1G = 52, + /// ENET QOS on supported MCUs. + EnetQos = 53, + /// ENET_25M clock. + Enet25M = 54, + /// ENET PPT. + EnetTimer1 = 55, + /// ENET_1G PPT. + EnetTimer2 = 56, + /// ENET QOS PPT on supported MCUs. + EnetTimer3 = 57, + } + + /// A clock mux value. + /// + /// Use [`get`](Self::get) to acquire the raw value that + /// can be committed to the hardware. + #[derive(Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + pub struct ClockMux(NonZeroU32); + + impl ClockMux { + const fn new(raw: u32) -> Option<Self> { + match NonZeroU32::new(raw.saturating_add(1)) { + Some(mux) => Some(ClockMux(mux)), + None => None, + } + } + + /// Returns the raw mux value that's valid for + /// the hardware. + pub const fn get(self) -> u32 { + self.0.get() - 1 + } + } + + /// Produces the MUX selection for this clock root, as long + /// as the source can be multiplexed to this root. + /// + /// Returns `None` if the source isn't available for this + /// root. It may also return `None` if the mapping isn't implemented. + /// + /// The implementation may alias names for convenience. + /// For example, `Xtal` and `XtalClk` alias to the same mux + /// value, although `Xtal` isn't connected to the clock tree. + /// + /// The implementation may return roots that are marked "reserved" + /// on your chip. For example, some M7 clock sources are reserved on + /// the 1160 but implemented on the 1170. This call will return those + /// reserved muxes for the 1170, and it's the user's job to know that + /// the root is invalid on the 1160. + pub const fn try_mux(root: ClockRoot, source: ClockSource) -> Option<ClockMux> { + // These sources are always available no matter the + // user's selected root. + match source { + ClockSource::Rc48MDiv2 => return ClockMux::new(0b000), + ClockSource::Xtal | ClockSource::XtalClk => return ClockMux::new(0b001), + ClockSource::Rc400M => return ClockMux::new(0b010), + ClockSource::Rc16M => return ClockMux::new(0b011), + _ => {} + } + + // The remaining sources are specific to the root. + match root { + ClockRoot::Enet + | ClockRoot::Enet1G + | ClockRoot::EnetQos + | ClockRoot::Enet25M + | ClockRoot::EnetTimer1 + | ClockRoot::EnetTimer2 + | ClockRoot::EnetTimer3 => match source { + ClockSource::Pll1Div2 => ClockMux::new(0b100), + ClockSource::Pll1Div5 => ClockMux::new(0b110), + _ => None, + }, + ClockRoot::M7 => match source { + ClockSource::ArmPll | ClockSource::ArmPllClk => ClockMux::new(0b100), + _ => None, + }, + ClockRoot::Bus => match source { + ClockSource::Pll1Div5 => ClockMux::new(0b101), + _ => None, + }, + ClockRoot::BusLpsr => match source { + ClockSource::Pll3Pfd3 => ClockMux::new(0b100), + ClockSource::Pll3 | ClockSource::Pll3Clk => ClockMux::new(0b101), + ClockSource::Pll2 | ClockSource::Pll2Clk => ClockMux::new(0b110), + ClockSource::Pll1Div5 => ClockMux::new(0b111), + _ => None, + }, + ClockRoot::M7Systick => match source { + ClockSource::Pll1Div5 => ClockMux::new(0b110), + _ => None, + }, + ClockRoot::Lpspi1 => match source { + _ => None, + }, + ClockRoot::Flexspi1 | ClockRoot::Flexspi2 => match source { + ClockSource::Pll3Pfd0 => ClockMux::new(0b100), + ClockSource::Pll2 | ClockSource::Pll2Clk => ClockMux::new(0b101), + ClockSource::Pll2Pfd2 => ClockMux::new(0b110), + ClockSource::Pll3 | ClockSource::Pll3Clk => ClockMux::new(0b111), + _ => None, + }, + } + } + + /// Returns the MUX selection for the given clock root for the + /// source. + /// + /// See [`try_mux`] for more information. + /// + /// # Panics + /// + /// Panics if the clock root cannot be muxed to the given source. + pub const fn mux(root: ClockRoot, source: ClockSource) -> ClockMux { + let Some(mux) = try_mux(root, source) else { + panic!("Root clock cannot be driven by the source"); + }; + mux + } + + /// Returns `true` if this clock source supports setpoints. + /// + /// If the source supports setpoints, it can be controlled by + /// the GPC. + pub fn source_has_setpoint(ccm: Instance, clock_source: ClockSource) -> bool { + ral_registers::read_reg!( + self, + ccm, + OSC_PLL[clock_source as usize].CONFIG, + SETPOINT_PRESENT == 1 + ) + } + + /// Returns `true` if this clock root supports setpoints. + /// + /// If the root supports setpoints, it can be controlled by + /// the GPC. + pub fn root_has_setpoint(ccm: Instance, clock_root: ClockRoot) -> bool { + ral_registers::read_reg!( + self, + ccm, + CLOCK_ROOT[clock_root as usize].CONFIG, + SETPOINT_PRESENT == 1 + ) + } + + /// The clock source is invalid for the given operation. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + pub struct InvalidSourceError(pub ClockSource); + + /// The clock root is invalid for the given operation. + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(transparent)] + pub struct InvalidRootError(pub ClockRoot); + + /// Enable setpoint mode for this clock source. + /// + /// This implicitly disables CPULP and domain modes; the source + /// cannot operate in different modes. It also disables the source + /// clock in the `DIRECT` register. + pub fn enable_source_setpoints( + ccm: Instance, + clock_source: ClockSource, + ) -> Result<(), InvalidSourceError> { + if !source_has_setpoint(ccm, clock_source) { + return Err(InvalidSourceError(clock_source)); + } + + set_source_direct(ccm, clock_source, false); + ral_registers::modify_reg!(self, ccm, OSC_PLL[clock_source as usize].AUTHEN, + CPULPM: 0, + SETPOINT_MODE: 1, + DOMAIN_MODE: 0, + ); + + Ok(()) + } + + /// Enables setpoint mode for this clock root. + /// + /// This implicitly disables domain mode; the root + /// cannot operate in different modes. + pub fn enable_root_setpoints( + ccm: Instance, + clock_root: ClockRoot, + ) -> Result<(), InvalidRootError> { + if !root_has_setpoint(ccm, clock_root) { + return Err(InvalidRootError(clock_root)); + } + + ral_registers::modify_reg!(self, ccm, CLOCK_ROOT[clock_root as usize].AUTHEN, + SETPOINT_MODE: 1, + DOMAIN_MODE: 0, + ); + + Ok(()) + } + + /// Specify which setpoints enable this clock source. + /// + /// `setpoints` describes the bitmask of enabled setpoints. + /// `standby` describes if the source remains enabled during a standby + /// transition in that setpoint. + pub fn set_source_setpoints( + ccm: Instance, + clock_source: ClockSource, + setpoints: u16, + standby: u16, + ) { + ral_registers::write_reg!(self, ccm, OSC_PLL[clock_source as usize].SETPOINT, SETPOINT: setpoints as u32, STANDBY: standby as u32); + } + + /// Set the clock root's selection and divider. + /// + /// Spins while the change is in progress. + pub fn set_clock_root(ccm: Instance, clock_root: ClockRoot, mux: ClockMux, div: NonZeroU8) { + ral_registers::modify_reg!(self, ccm, CLOCK_ROOT[clock_root as usize].CONTROL, MUX: mux.get(), DIV: (div.get() as u32) - 1); + while ral_registers::read_reg!( + self, + ccm, + CLOCK_ROOT[clock_root as usize].STATUS0, + CHANGING == 1 + ) {} + } + + /// Directly set the clock source on or off. + pub fn set_source_direct(ccm: Instance, clock_source: ClockSource, on: bool) { + ral_registers::write_reg!(self, ccm, OSC_PLL[clock_source as usize].DIRECT, on as u32); + } +} + +pub mod ral_1180 { + pub use crate::ral_common::{gpr_private, gpr_shared}; + pub mod clock_root; + pub mod lpcg; + pub mod observe; + pub mod osc_pll; + + pub use self::{ + clock_root as CLOCK_ROOT, gpr_private as GPR_PRIVATE, gpr_shared as GPR_SHARED, + lpcg as LPCG, observe as OBSERVE, osc_pll as OSC_PLL, + }; + + #[repr(C)] + #[allow(non_snake_case)] + pub struct RegisterBlock { + pub CLOCK_ROOT: [clock_root::RegisterBlock; 74], + _reserved0: [u8; 7936], + pub OBSERVE: [observe::RegisterBlock; 2], + _reserved1: [u8; 768], + pub GPR_SHARED: [gpr_shared::RegisterBlock; 16], + pub GPR_SHARED_STATUS: [u32; 8], + _reserved2: [u8; 480], + pub GPR_PRIVATE: [gpr_private::RegisterBlock; 4], + _reserved3: [u8; 896], + pub OSC_PLL: [osc_pll::RegisterBlock; 25], + _reserved4: [u8; 10688], + pub LPCG: [lpcg::RegisterBlock; 149], + } +} + +ral_registers::register! { + #[doc = "Clock root working status"] + STATUS0_RO<u32> RO [ + #[doc = "Current clock root POWERDOWN setting"] + POWERDOWN start(27) width(1) RO {} + #[doc = "Internal updating in generation logic"] + SLICE_BUSY start(28) width(1) RO {} + #[doc = "Internal status synchronization to clock generation logic"] + UPDATE_FORWARD start(29) width(1) RO {} + #[doc = "Internal status synchronization from clock generation logic"] + UPDATE_REVERSE start(30) width(1) RO {} + #[doc = "Internal updating in clock root"] + CHANGING start(31) width(1) RO {} + ] +} + +#[cfg(test)] +mod tests { + use core::mem::offset_of; + + #[test] + fn layout_1180() { + use super::ral_1180::RegisterBlock; + + assert_eq!(offset_of!(RegisterBlock, CLOCK_ROOT), 0); + assert_eq!(offset_of!(RegisterBlock, OBSERVE), 0x4400); + assert_eq!(offset_of!(RegisterBlock, GPR_SHARED), 0x4800); + assert_eq!(offset_of!(RegisterBlock, GPR_SHARED_STATUS), 0x4a00); + assert_eq!(offset_of!(RegisterBlock, GPR_PRIVATE), 0x4C00); + assert_eq!(offset_of!(RegisterBlock, OSC_PLL), 0x5000,); + assert_eq!(offset_of!(RegisterBlock, LPCG), 0x8000); + } + + #[test] + fn layout_11xx() { + use super::ral_11xx::RegisterBlock; + + assert_eq!(offset_of!(RegisterBlock, CLOCK_ROOT), 0); + assert_eq!(offset_of!(RegisterBlock, CLOCK_GROUP), 0x4000); + assert_eq!(offset_of!(RegisterBlock, GPR_SHARED), 0x4800); + assert_eq!(offset_of!(RegisterBlock, GPR_PRIVATE), 0x4c00); + assert_eq!(offset_of!(RegisterBlock, OSC_PLL), 0x5000); + assert_eq!(offset_of!(RegisterBlock, LPCG), 0x6000); + } +} |
