aboutsummaryrefslogtreecommitdiff
path: root/drivers/ccm-10xx
diff options
context:
space:
mode:
authorIan McIntyre <me@mciantyre.dev>2025-11-30 18:52:34 -0500
committerIan McIntyre <me@mciantyre.dev>2025-11-30 19:10:51 -0500
commit76199f21616ad86cf68f3b063c1ce23c6fc5a52f (patch)
tree4c076d0afd649803a2bd9a5ed5cbb1f1c74fb459 /drivers/ccm-10xx
First commit
Diffstat (limited to 'drivers/ccm-10xx')
-rw-r--r--drivers/ccm-10xx/Cargo.toml8
-rw-r--r--drivers/ccm-10xx/src/ahb.rs103
-rw-r--r--drivers/ccm-10xx/src/ccm.rs805
-rw-r--r--drivers/ccm-10xx/src/ccm_analog.rs244
-rw-r--r--drivers/ccm-10xx/src/lib.rs18
-rw-r--r--drivers/ccm-10xx/src/ral/ccm.rs85
-rw-r--r--drivers/ccm-10xx/src/ral/ccm_analog.rs129
7 files changed, 1392 insertions, 0 deletions
diff --git a/drivers/ccm-10xx/Cargo.toml b/drivers/ccm-10xx/Cargo.toml
new file mode 100644
index 0000000..962afa6
--- /dev/null
+++ b/drivers/ccm-10xx/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "imxrt-drivers-ccm-10xx"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+defmt = { workspace = true, optional = true }
+ral-registers = { workspace = true }
diff --git a/drivers/ccm-10xx/src/ahb.rs b/drivers/ccm-10xx/src/ahb.rs
new file mode 100644
index 0000000..392be61
--- /dev/null
+++ b/drivers/ccm-10xx/src/ahb.rs
@@ -0,0 +1,103 @@
+//! Routines for configurating AHB clocks.
+
+use crate::{ccm, ccm_analog, ral};
+
+/// The ARM PLL.
+pub mod pll1 {
+ use super::{ccm, ccm_analog, ral};
+
+ /// A configuration for PLL1.
+ pub struct AbhConfiguration {
+ /// The PLL1 divider.
+ pub div_sel: u32,
+ /// The ARM divider, immediately after the PLL.
+ pub arm_divider: u32,
+ /// The AHB divider, downstream of the PLL and ARM divider.
+ pub ahb_divider: u32,
+ }
+
+ impl AbhConfiguration {
+ /// Produces the target AHB frequency expressed by the configuration.
+ pub const fn ahb_frequency(&self) -> u32 {
+ ccm_analog::pll1::frequency(self.div_sel / self.arm_divider / self.ahb_divider)
+ }
+ }
+
+ /// Configure the AHB root using PLL1.
+ ///
+ /// This does not touch any of the clock gates downstream of the root clock.
+ /// You're responsible for managing those. Note that is affects the IPG
+ /// clock; however, it does not affect the IPG divider.
+ pub fn configure_ahb(
+ ccm: ral::ccm::CCM,
+ ccm_analog: ral::ccm_analog::CCM_ANALOG,
+ config: &AbhConfiguration,
+ ) {
+ if ccm::ahb_clk::Selection::PeriphClk2Sel == ccm::ahb_clk::selection(ccm) {
+ // Switch to the pre-peripheral clock before changing
+ // peripheral clock 2...
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
+ }
+
+ // Temporarily switch to the crystal oscillator.
+ ccm::periph_clk2::set_divider(ccm, 1);
+ ccm::periph_clk2::set_selection(ccm, ccm::periph_clk2::Selection::Osc);
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PeriphClk2Sel);
+
+ // Prepare PLL1.
+ ccm_analog::pll1::restart(ccm_analog, config.div_sel);
+ ccm::arm_divider::set_divider(ccm, config.arm_divider);
+ ccm::ahb_clk::set_divider(ccm, config.ahb_divider);
+
+ // Switch back to PLL1.
+ ccm::pre_periph_clk_pll1::set_selection(ccm, ccm::pre_periph_clk_pll1::Selection::Pll1);
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
+ }
+}
+
+/// PLL6 that's fixed at 500MHz.
+pub mod pll6_500mhz {
+ use super::{ccm, ccm_analog, ral};
+
+ /// Configuration for PLL6 using a 500MHz.
+ pub struct AbhConfiguration {
+ /// The AHB divider, just before the root clock.
+ pub ahb_divider: u32,
+ }
+
+ impl AbhConfiguration {
+ /// Returns the frequency of the root clock.
+ pub const fn ahb_frequency(&self) -> u32 {
+ ccm_analog::pll6_500mhz::FREQUENCY / self.ahb_divider
+ }
+ }
+
+ /// Configure the AHB root using PLL6 fixed at 500MHz.
+ ///
+ /// This does not touch any of the clock gates downstream of the root clock.
+ /// You're responsible for managing those. Note that this affects the IPG
+ /// clock; however, it does not touch the divider.
+ pub fn configure_ahb(
+ ccm: ral::ccm::CCM,
+ ccm_analog: ral::ccm_analog::CCM_ANALOG,
+ config: &AbhConfiguration,
+ ) {
+ if ccm::ahb_clk::Selection::PeriphClk2Sel == ccm::ahb_clk::selection(ccm) {
+ // Switch to the pre-peripheral clock before changing
+ // peripheral clock 2...
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
+ }
+
+ // Temporarily switch to the crystal oscillator.
+ ccm::periph_clk2::set_selection(ccm, ccm::periph_clk2::Selection::Osc);
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PeriphClk2Sel);
+
+ // Prepare PLL6.
+ ccm_analog::pll6_500mhz::restart(ccm_analog);
+ ccm::ahb_clk::set_divider(ccm, config.ahb_divider);
+
+ // Switch to PLL6.
+ ccm::pre_periph_clk_pll6::set_selection(ccm, ccm::pre_periph_clk_pll6::Selection::Pll6);
+ ccm::ahb_clk::set_selection(ccm, ccm::ahb_clk::Selection::PrePeriphClkSel);
+ }
+}
diff --git a/drivers/ccm-10xx/src/ccm.rs b/drivers/ccm-10xx/src/ccm.rs
new file mode 100644
index 0000000..6e2e051
--- /dev/null
+++ b/drivers/ccm-10xx/src/ccm.rs
@@ -0,0 +1,805 @@
+//! Clock controller module.
+
+use crate::ral;
+
+pub use ral::ccm::CCM;
+pub type Instance = ral_registers::Instance<ral::ccm::RegisterBlock>;
+
+/// Wait for all handshake bits to deassert.
+fn wait_handshake(ccm: ral::ccm::CCM) {
+ while ral::read_reg!(ral::ccm, ccm, CDHIPR) != 0 {}
+}
+
+/// PERCLK clock.
+///
+/// The PERCLK clock controls GPT and PIT timers.
+pub mod perclk_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// PERCLK clock selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// Derive from the IPG clock root.
+ Ipg = 0,
+ /// Derive from the oscillator clock.
+ Oscillator = 1,
+ }
+
+ /// Set the PERCLK clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CSCMR1, PERCLK_CLK_SEL: selection as u32);
+ }
+
+ /// Returns the PERCLK clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ if ral::read_reg!(ral::ccm, ccm, CSCMR1, PERCLK_CLK_SEL == 1) {
+ Selection::Oscillator
+ } else {
+ Selection::Ipg
+ }
+ }
+
+ /// The smallest PERCLK divider.
+ pub const MIN_DIVIDER: u32 = 1;
+ /// The largest PERCLK divider.
+ pub const MAX_DIVIDER: u32 = 64;
+
+ /// Set the PERCLK clock divider.
+ ///
+ /// The implementation clamps `divider` between [`MIN_DIVIDER`] and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CSCMR1, PERCLK_PODF: podf);
+ }
+
+ /// Returns the PERCLK clock divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CSCMR1, PERCLK_PODF) + 1
+ }
+}
+
+/// IPG clock.
+///
+/// The IPG clock is divided from the core clock.
+pub mod ipg_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Returns the IPG clock divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CBCDR, IPG_PODF) + 1
+ }
+
+ /// The smallest IPG divider.
+ pub const MIN_DIVIDER: u32 = 1;
+ /// The largest IPG divider.
+ pub const MAX_DIVIDER: u32 = 4;
+
+ /// Sets the IPG clock divider.
+ ///
+ /// The implementation clamps `divider` between [`MIN_DIVIDER`] and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CBCDR, IPG_PODF: podf);
+ }
+}
+
+/// Low power mode.
+///
+/// From the reference manual,
+///
+/// > Setting the low power mode that system will enter on next assertion of dsm_request signal.
+///
+/// Practically, this affects the processor behavior when you use WFI, WFE, or enter another
+/// low-power state. Low-power settings that aren't "run" halt the ARM SYSTICK peripheral.
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum LowPowerMode {
+ /// Remain in run mode when entering low power.
+ RemainInRun = ral::ccm::CLPCR::LPM::RUN,
+ /// Move to wait mode when entering low power.
+ TransferToWait = ral::ccm::CLPCR::LPM::WAIT,
+ /// Stop when entering low power.
+ TransferToStop = ral::ccm::CLPCR::LPM::STOP,
+}
+
+/// Set the CCM low power mode.
+pub fn set_low_power_mode(ccm: ral::ccm::CCM, mode: LowPowerMode) {
+ ral::modify_reg!(ral::ccm, ccm, CLPCR, LPM: mode as u32);
+}
+
+/// Returns the CCM low power mode.
+///
+/// Returns `None` if the low power mode is the reserved field value.
+pub fn low_power_mode(ccm: ral::ccm::CCM) -> Option<LowPowerMode> {
+ use ral::ccm::CLPCR::LPM::{RUN, STOP, WAIT};
+ Some(match ral::read_reg!(ral::ccm, ccm, CLPCR, LPM) {
+ RUN => LowPowerMode::RemainInRun,
+ WAIT => LowPowerMode::TransferToWait,
+ STOP => LowPowerMode::TransferToStop,
+ _ => return None,
+ })
+}
+
+/// UART clock root.
+///
+/// `uart_clk` provides the clock source for all LPUART peripherals.
+/// You must disable LPUART clock gates before selecting the clock
+/// and divider.
+pub mod uart_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Returns the UART clock divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CSCDR1, UART_CLK_PODF) + 1
+ }
+
+ /// The smallest UART clock divider.
+ pub const MIN_DIVIDER: u32 = 1;
+ /// The largest UART clock divider.
+ pub const MAX_DIVIDER: u32 = 1 << 6;
+
+ /// Set the UART clock divider.
+ ///
+ /// The implementation clamps `divider` between [`MIN_DIVIDER`] and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CSCDR1, UART_CLK_PODF: podf);
+ }
+
+ /// UART clock selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// PLL 3 divided by 6.
+ ///
+ /// This is typically 480MHz / 6 == 80MHz.
+ Pll3Div6 = 0,
+ /// 24MHz oscillator.
+ Oscillator = 1,
+ }
+
+ /// Return the UART clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CSCDR1, UART_CLK_SEL) {
+ 0 => Selection::Pll3Div6,
+ 1 => Selection::Oscillator,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Set the UART clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CSCDR1, UART_CLK_SEL: selection as u32);
+ }
+}
+
+/// LPI2C clock root.
+///
+/// `lpi2c_clk` provides the clock source for all LPI2C peripherals.
+/// You must disable LPI2C clock gates before selecting the clock
+/// and divider.
+pub mod lpi2c_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Returns the LPI2C clock divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CSCDR2, LPI2C_CLK_PODF) + 1
+ }
+
+ /// The smallest LPI2C clock divider.
+ pub const MIN_DIVIDER: u32 = 1;
+ /// The largest LPI2C clock divider.
+ pub const MAX_DIVIDER: u32 = 64;
+
+ /// Set the LPI2C clock divider.
+ ///
+ /// The implementation clamps `divider` between [`MIN_DIVIDER`] and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CSCDR2, LPI2C_CLK_PODF: podf);
+ }
+
+ /// LPI2C clock selections.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// Derive from PLL3 divided by 8.
+ Pll3Div8 = 0,
+ /// Derive from the crystal oscillator.
+ Oscillator = 1,
+ }
+
+ /// Returns the LPI2C clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CSCDR2, LPI2C_CLK_SEL) {
+ 0 => Selection::Pll3Div8,
+ 1 => Selection::Oscillator,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Set the LPI2C clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CSCDR2, LPI2C_CLK_SEL: selection as u32);
+ }
+}
+
+/// LPSPI clock root.
+///
+/// `lpspi_clk` provides the clock source for all LPSPI peripherals.
+/// You must disable LPSPI clock gates before selecting the clock
+/// and divider.
+pub mod lpspi_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Returns the LPSPI clock divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CBCMR, LPSPI_PODF) + 1
+ }
+
+ /// The smallest LPSPI clock divider.
+ pub const MIN_DIVIDER: u32 = 1;
+ /// The largest LPSPI clock divider.
+ pub const MAX_DIVIDER: u32 = 8;
+
+ /// Set the LPSPI clock divider.
+ ///
+ /// The implementation clamps `divider` between [`MIN_DIVIDER`] and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ // 1010 MCUs support an extra bit in this field, so this
+ // could be a max of 16 for those chips.
+ let podf = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CBCMR, LPSPI_PODF: podf);
+ }
+
+ /// LPSPI clock selections.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// Derive from PLL3_PFD1.
+ Pll3Pfd1 = 0,
+ /// Derive from the PLL3_PFD0.
+ Pll3Pfd0 = 1,
+ /// Derive from PLL2.
+ Pll2 = 2,
+ /// Derive from PLL2_PFD2.
+ Pll2Pfd2 = 3,
+ }
+
+ /// Returns the LPSPI clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CBCMR, LPSPI_CLK_SEL) {
+ 0 => Selection::Pll3Pfd1,
+ 1 => Selection::Pll3Pfd0,
+ 2 => Selection::Pll2,
+ 3 => Selection::Pll2Pfd2,
+ _ => unreachable!(),
+ }
+ }
+
+ /// Set the LPSPI clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CBCMR, LPSPI_CLK_SEL: selection as u32);
+ }
+}
+
+/// AHB (ARM) clock root.
+///
+/// The AHB module exposes all selections and dividers that affect
+/// the AHB / ARM clock. This includes all clock trees behind the main
+/// selector, and the driving PLL.
+///
+/// Chip-specific features affect what's exposed from this module.
+///
+/// Note: the i.MX RT 1010 processors refer to the AHB clock root as the
+/// "core clock root." Nevertheless, the core clock root has an "AHB divider."
+/// For consistency, we refer to the 1010's core clock root as the AHB clock
+/// root.
+pub mod ahb_clk {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Set the AHB divider.
+ ///
+ /// The implementation clamps `divider` between 1 and 8.
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(1, 8) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CBCDR, AHB_PODF: podf);
+ super::wait_handshake(ccm);
+ }
+
+ /// Returns the AHB divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CBCDR, AHB_PODF) + 1
+ }
+
+ /// Peripheral clock selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// Derive from PRE_PERIPH_CLK.
+ PrePeriphClkSel = 0,
+ /// Derive from PERIPH_CLK2.
+ PeriphClk2Sel = 1,
+ }
+
+ /// Set the peripheral clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CBCDR, PERIPH_CLK_SEL: selection as u32);
+ super::wait_handshake(ccm);
+ }
+
+ /// Returns the peripheral clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CBCDR, PERIPH_CLK_SEL) {
+ 0 => Selection::PrePeriphClkSel,
+ 1 => Selection::PeriphClk2Sel,
+ _ => unreachable!(),
+ }
+ }
+}
+
+/// ARM divider.
+///
+/// This divides the output from the AHB PLL (either PLL1 or PLL6, depending
+/// on the system).
+pub mod arm_divider {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Set the ARM divider.
+ ///
+ /// The implementation clamps `divider` between 1 and 8.
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(1, 8) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CACRR, ARM_PODF: podf);
+ crate::ccm::wait_handshake(ccm);
+ }
+
+ /// Returns the ARM divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CACRR, ARM_PODF) + 1
+ }
+}
+
+/// One of the accessory muxes upstream of the AHB / ARM clock.
+pub mod periph_clk2 {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Set the peripheral clock 2 divider.
+ ///
+ /// The implementation clamps `divider` between 1 and 8. You should first switch
+ /// away the core clock selection before changing this divider.
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let podf = divider.clamp(1, 8) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CBCDR, PERIPH_CLK2_PODF: podf);
+ }
+
+ /// Returns the peripheral clock 2 divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ ral::read_reg!(ral::ccm, ccm, CBCDR, PERIPH_CLK2_PODF) + 1
+ }
+
+ /// Peripheral CLK2 selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// Use PLL3 (possibly bypassed by an upstream mux).
+ Pll3Sw = 0,
+ /// Crystall oscillator.
+ Osc = 1,
+ /// The PLL2 bypass.
+ Pll2Bypass = 2,
+ }
+
+ /// Set the peripheral clock2 selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CBCMR, PERIPH_CLK2_SEL: selection as u32);
+ crate::ccm::wait_handshake(ccm);
+ }
+
+ /// Returns the peripheral clock2 selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ let raw = ral::read_reg!(ral::ccm, ccm, CBCMR, PERIPH_CLK2_SEL);
+ match raw {
+ 0 => Selection::Pll3Sw,
+ 1 => Selection::Osc,
+ 2 => Selection::Pll2Bypass,
+ _ => unreachable!(),
+ }
+ }
+}
+
+/// FlexSPI1 with AXI/SEMC selectors.
+pub mod flexspi1_clk_axi_semc {
+ use crate::{ccm::CCM, ral};
+
+ /// FlexSPI1 selections
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// The SEMC clock root.
+ AxiSemc = 0,
+ /// PLL3.
+ Pll3 = 1,
+ /// PFD2 of PLL2.
+ Pll2Pfd2 = 2,
+ /// PFD0 of PLL3.
+ Pll3Pfd0 = 3,
+ }
+
+ /// Set the FlexSPI1 clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_CLK_SEL: selection as u32);
+ }
+
+ /// Return the FlexSPI1 clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_CLK_SEL) {
+ 0 => Selection::AxiSemc,
+ 1 => Selection::Pll3,
+ 2 => Selection::Pll2Pfd2,
+ 3 => Selection::Pll3Pfd0,
+ _ => unreachable!(),
+ }
+ }
+
+ /// The smallest divider clamped by the implementation.
+ pub const MIN_DIVIDER: u32 = 1;
+
+ /// The largest divider clamped by the implementation.
+ pub const MAX_DIVIDER: u32 = 8;
+
+ /// Return the divider.
+ pub fn divider(ccm: CCM) -> u32 {
+ 1 + ral::read_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_PODF)
+ }
+
+ /// Set the FlexSPI1 root clock divider.
+ ///
+ /// The implementation clamps the input between [`MIN_DIVIDER`]
+ /// and [`MAX_DIVIDER`].
+ pub fn set_divider(ccm: CCM, divider: u32) {
+ let divider = divider.clamp(MIN_DIVIDER, MAX_DIVIDER) - 1;
+ ral::modify_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_PODF: divider);
+ }
+}
+
+/// FlexSPI1 with a PLL2 root clock.
+///
+/// This root clock can also switch to the `periph_clk2` source.
+/// However, this isn't yet implemented.
+pub mod flexspi1_clk_root_pll2 {
+ #[doc(inline)]
+ pub use super::flexspi1_clk_axi_semc::{MAX_DIVIDER, MIN_DIVIDER, divider, set_divider};
+ use crate::{ccm::CCM, ral};
+
+ /// FlexSPI1 selections
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// PLL2.
+ Pll2 = 0,
+ /// PLL3.
+ Pll3 = 1,
+ /// PFD2 of PLL2.
+ Pll2Pfd2 = 2,
+ /// PFD0 of PLL3.
+ Pll3Pfd0 = 3,
+ }
+
+ /// Set the FlexSPI1 clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_CLK_SEL: selection as u32);
+ }
+
+ /// Return the FlexSPI1 clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ match ral::read_reg!(ral::ccm, ccm, CSCMR1, FLEXSPI_CLK_SEL) {
+ 0 => Selection::Pll2,
+ 1 => Selection::Pll3,
+ 2 => Selection::Pll2Pfd2,
+ 3 => Selection::Pll3Pfd0,
+ _ => unreachable!(),
+ }
+ }
+}
+
+/// Pre-peripheral clock.
+pub mod pre_periph_clk_pll1 {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Pre-peripheral clock selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// PLL2.
+ Pll2 = 0,
+ /// PFD2 of PLL2
+ Pll2Pfd2 = 1,
+ /// PFD0 of PLL2.
+ Pll2Pfd0 = 2,
+ /// PLL1.
+ Pll1 = 3,
+ }
+
+ /// Set the pre-peripheral clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CBCMR, PRE_PERIPH_CLK_SEL: selection as u32);
+ }
+
+ /// Returns the pre-peripheral clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ use Selection::*;
+ match ral::read_reg!(ral::ccm, ccm, CBCMR, PRE_PERIPH_CLK_SEL) {
+ 0 => Pll2,
+ 1 => Pll2Pfd2,
+ 2 => Pll2Pfd0,
+ 3 => Pll1,
+ _ => unreachable!(),
+ }
+ }
+}
+
+/// Pre-peripheral clock.
+pub mod pre_periph_clk_pll6 {
+ use crate::ral::{self, ccm::CCM};
+
+ /// Pre-peripheral clock selection.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Selection {
+ /// PLL2.
+ Pll2 = 0,
+ /// PFD3 of PLL3.
+ Pll3Pfd3 = 1,
+ /// PFD3 of PLL2.
+ Pll2Pfd3 = 2,
+ /// PLL6.
+ Pll6 = 3,
+ }
+
+ /// Set the pre-peripheral clock selection.
+ pub fn set_selection(ccm: CCM, selection: Selection) {
+ ral::modify_reg!(ral::ccm, ccm, CBCMR, PRE_PERIPH_CLK_SEL: selection as u32);
+ }
+
+ /// Returns the pre-peripheral clock selection.
+ pub fn selection(ccm: CCM) -> Selection {
+ use Selection::*;
+ match ral::read_reg!(ral::ccm, ccm, CBCMR, PRE_PERIPH_CLK_SEL) {
+ 0 => Pll2,
+ 1 => Pll3Pfd3,
+ 2 => Pll2Pfd3,
+ 3 => Pll6,
+ _ => unreachable!(),
+ }
+ }
+}
+
+/// Clock gate control.
+pub mod clock_gate {
+ use crate::ral::{self, ccm::CCM};
+
+ /// A clock gate locator.
+ ///
+ /// This enum encodes the register and field for a clock gate.
+ /// Note that not all clock gates are valid for all MCUs.
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u16)]
+ pub enum Locator {
+ Ccgr0Cg00 = clock_gate(0, 00),
+ Ccgr0Cg01 = clock_gate(0, 01),
+ Ccgr0Cg02 = clock_gate(0, 02),
+ Ccgr0Cg03 = clock_gate(0, 03),
+ Ccgr0Cg04 = clock_gate(0, 04),
+ Ccgr0Cg05 = clock_gate(0, 05),
+ Ccgr0Cg06 = clock_gate(0, 06),
+ Ccgr0Cg07 = clock_gate(0, 07),
+ Ccgr0Cg08 = clock_gate(0, 08),
+ Ccgr0Cg09 = clock_gate(0, 09),
+ Ccgr0Cg10 = clock_gate(0, 10),
+ Ccgr0Cg11 = clock_gate(0, 11),
+ Ccgr0Cg12 = clock_gate(0, 12),
+ Ccgr0Cg13 = clock_gate(0, 13),
+ Ccgr0Cg14 = clock_gate(0, 14),
+ Ccgr0Cg15 = clock_gate(0, 15),
+
+ Ccgr1Cg00 = clock_gate(1, 00),
+ Ccgr1Cg01 = clock_gate(1, 01),
+ Ccgr1Cg02 = clock_gate(1, 02),
+ Ccgr1Cg03 = clock_gate(1, 03),
+ Ccgr1Cg04 = clock_gate(1, 04),
+ Ccgr1Cg05 = clock_gate(1, 05),
+ Ccgr1Cg06 = clock_gate(1, 06),
+ Ccgr1Cg07 = clock_gate(1, 07),
+ Ccgr1Cg08 = clock_gate(1, 08),
+ Ccgr1Cg09 = clock_gate(1, 09),
+ Ccgr1Cg10 = clock_gate(1, 10),
+ Ccgr1Cg11 = clock_gate(1, 11),
+ Ccgr1Cg12 = clock_gate(1, 12),
+ Ccgr1Cg13 = clock_gate(1, 13),
+ Ccgr1Cg14 = clock_gate(1, 14),
+ Ccgr1Cg15 = clock_gate(1, 15),
+
+ Ccgr2Cg00 = clock_gate(2, 00),
+ Ccgr2Cg01 = clock_gate(2, 01),
+ Ccgr2Cg02 = clock_gate(2, 02),
+ Ccgr2Cg03 = clock_gate(2, 03),
+ Ccgr2Cg04 = clock_gate(2, 04),
+ Ccgr2Cg05 = clock_gate(2, 05),
+ Ccgr2Cg06 = clock_gate(2, 06),
+ Ccgr2Cg07 = clock_gate(2, 07),
+ Ccgr2Cg08 = clock_gate(2, 08),
+ Ccgr2Cg09 = clock_gate(2, 09),
+ Ccgr2Cg10 = clock_gate(2, 10),
+ Ccgr2Cg11 = clock_gate(2, 11),
+ Ccgr2Cg12 = clock_gate(2, 12),
+ Ccgr2Cg13 = clock_gate(2, 13),
+ Ccgr2Cg14 = clock_gate(2, 14),
+ Ccgr2Cg15 = clock_gate(2, 15),
+
+ Ccgr3Cg00 = clock_gate(3, 00),
+ Ccgr3Cg01 = clock_gate(3, 01),
+ Ccgr3Cg02 = clock_gate(3, 02),
+ Ccgr3Cg03 = clock_gate(3, 03),
+ Ccgr3Cg04 = clock_gate(3, 04),
+ Ccgr3Cg05 = clock_gate(3, 05),
+ Ccgr3Cg06 = clock_gate(3, 06),
+ Ccgr3Cg07 = clock_gate(3, 07),
+ Ccgr3Cg08 = clock_gate(3, 08),
+ Ccgr3Cg09 = clock_gate(3, 09),
+ Ccgr3Cg10 = clock_gate(3, 10),
+ Ccgr3Cg11 = clock_gate(3, 11),
+ Ccgr3Cg12 = clock_gate(3, 12),
+ Ccgr3Cg13 = clock_gate(3, 13),
+ Ccgr3Cg14 = clock_gate(3, 14),
+ Ccgr3Cg15 = clock_gate(3, 15),
+
+ Ccgr4Cg00 = clock_gate(4, 00),
+ Ccgr4Cg01 = clock_gate(4, 01),
+ Ccgr4Cg02 = clock_gate(4, 02),
+ Ccgr4Cg03 = clock_gate(4, 03),
+ Ccgr4Cg04 = clock_gate(4, 04),
+ Ccgr4Cg05 = clock_gate(4, 05),
+ Ccgr4Cg06 = clock_gate(4, 06),
+ Ccgr4Cg07 = clock_gate(4, 07),
+ Ccgr4Cg08 = clock_gate(4, 08),
+ Ccgr4Cg09 = clock_gate(4, 09),
+ Ccgr4Cg10 = clock_gate(4, 10),
+ Ccgr4Cg11 = clock_gate(4, 11),
+ Ccgr4Cg12 = clock_gate(4, 12),
+ Ccgr4Cg13 = clock_gate(4, 13),
+ Ccgr4Cg14 = clock_gate(4, 14),
+ Ccgr4Cg15 = clock_gate(4, 15),
+
+ Ccgr5Cg00 = clock_gate(5, 00),
+ Ccgr5Cg01 = clock_gate(5, 01),
+ Ccgr5Cg02 = clock_gate(5, 02),
+ Ccgr5Cg03 = clock_gate(5, 03),
+ Ccgr5Cg04 = clock_gate(5, 04),
+ Ccgr5Cg05 = clock_gate(5, 05),
+ Ccgr5Cg06 = clock_gate(5, 06),
+ Ccgr5Cg07 = clock_gate(5, 07),
+ Ccgr5Cg08 = clock_gate(5, 08),
+ Ccgr5Cg09 = clock_gate(5, 09),
+ Ccgr5Cg10 = clock_gate(5, 10),
+ Ccgr5Cg11 = clock_gate(5, 11),
+ Ccgr5Cg12 = clock_gate(5, 12),
+ Ccgr5Cg13 = clock_gate(5, 13),
+ Ccgr5Cg14 = clock_gate(5, 14),
+ Ccgr5Cg15 = clock_gate(5, 15),
+
+ Ccgr6Cg00 = clock_gate(6, 00),
+ Ccgr6Cg01 = clock_gate(6, 01),
+ Ccgr6Cg02 = clock_gate(6, 02),
+ Ccgr6Cg03 = clock_gate(6, 03),
+ Ccgr6Cg04 = clock_gate(6, 04),
+ Ccgr6Cg05 = clock_gate(6, 05),
+ Ccgr6Cg06 = clock_gate(6, 06),
+ Ccgr6Cg07 = clock_gate(6, 07),
+ Ccgr6Cg08 = clock_gate(6, 08),
+ Ccgr6Cg09 = clock_gate(6, 09),
+ Ccgr6Cg10 = clock_gate(6, 10),
+ Ccgr6Cg11 = clock_gate(6, 11),
+ Ccgr6Cg12 = clock_gate(6, 12),
+ Ccgr6Cg13 = clock_gate(6, 13),
+ Ccgr6Cg14 = clock_gate(6, 14),
+ Ccgr6Cg15 = clock_gate(6, 15),
+
+ Ccgr7Cg00 = clock_gate(7, 00),
+ Ccgr7Cg01 = clock_gate(7, 01),
+ Ccgr7Cg02 = clock_gate(7, 02),
+ Ccgr7Cg03 = clock_gate(7, 03),
+ Ccgr7Cg04 = clock_gate(7, 04),
+ Ccgr7Cg05 = clock_gate(7, 05),
+ Ccgr7Cg06 = clock_gate(7, 06),
+ Ccgr7Cg07 = clock_gate(7, 07),
+ Ccgr7Cg08 = clock_gate(7, 08),
+ Ccgr7Cg09 = clock_gate(7, 09),
+ Ccgr7Cg10 = clock_gate(7, 10),
+ Ccgr7Cg11 = clock_gate(7, 11),
+ Ccgr7Cg12 = clock_gate(7, 12),
+ Ccgr7Cg13 = clock_gate(7, 13),
+ Ccgr7Cg14 = clock_gate(7, 14),
+ Ccgr7Cg15 = clock_gate(7, 15),
+ }
+
+ /// Encode a clock gate.
+ const fn clock_gate(register: u8, field: u8) -> u16 {
+ ((register as u16) << 8) | ((field * 2) as u16)
+ }
+
+ impl Locator {
+ /// Access the register index.
+ const fn register(self) -> usize {
+ (((self as u16) >> 8) & 0xFF) as usize
+ }
+ /// Produce the bit shift for the field.
+ const fn shift(self) -> u32 {
+ ((self as u16) & 0xFF) as u32
+ }
+ /// Produce the mask for the field.
+ const fn mask(self) -> u32 {
+ 0b11 << self.shift()
+ }
+ }
+
+ /// The activity for a [`Locator`].
+ #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+ #[repr(u32)]
+ pub enum Activity {
+ /// Clock is off in all modes.
+ Off = 0,
+ /// Clock is on in run mode, but off in WAIT and STOP modes.
+ OnlyRun = 1,
+ /// Clock is on in all modes, except stop mode.
+ On = 3,
+ }
+
+ impl From<bool> for Activity {
+ fn from(value: bool) -> Self {
+ if value { Self::On } else { Self::Off }
+ }
+ }
+
+ /// Helper constant to turn off a clock gate.
+ pub const OFF: Activity = Activity::Off;
+ /// Helper constant to turn on a clock gate.
+ pub const ON: Activity = Activity::On;
+
+ /// Returns a clock gate's activity.
+ ///
+ /// Returns `None` if the field represents the reserved value.
+ pub fn get(ccm: CCM, locator: Locator) -> Option<Activity> {
+ let ccgr = ral::read_reg!(ral::ccm, ccm, CCGR[locator.register()]);
+ let field = (ccgr & locator.mask()) >> locator.shift();
+ Some(match field {
+ 0 => Activity::Off,
+ 1 => Activity::OnlyRun,
+ 3 => Activity::On,
+ _ => return None,
+ })
+ }
+
+ /// Set the clock gate's activity.
+ pub fn set(ccm: CCM, locator: Locator, activity: Activity) {
+ ral::modify_reg!(ral::ccm, ccm, CCGR[locator.register()], |mut ccgr| {
+ ccgr &= !locator.mask();
+ ccgr |= (activity as u32) << locator.shift();
+ ccgr
+ });
+ }
+}
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);
+ }
+}
diff --git a/drivers/ccm-10xx/src/lib.rs b/drivers/ccm-10xx/src/lib.rs
new file mode 100644
index 0000000..ad286cf
--- /dev/null
+++ b/drivers/ccm-10xx/src/lib.rs
@@ -0,0 +1,18 @@
+//! A clock configuration module (CCM) driver for i.MX RT 1000 MCUs.
+//!
+//! The APIs and modules exposed by this package may not always work on your
+//! MCU! You're expected to pick and choose the components that are available.
+//! If you're not sure what those are, use a chip-specific package that does
+//! the filtering for you.
+
+#![no_std]
+
+pub mod ral {
+ pub(crate) use ral_registers::{modify_reg, read_reg, write_reg};
+ pub mod ccm;
+ pub mod ccm_analog;
+}
+
+pub mod ahb;
+pub mod ccm;
+pub mod ccm_analog;
diff --git a/drivers/ccm-10xx/src/ral/ccm.rs b/drivers/ccm-10xx/src/ral/ccm.rs
new file mode 100644
index 0000000..e8033ac
--- /dev/null
+++ b/drivers/ccm-10xx/src/ral/ccm.rs
@@ -0,0 +1,85 @@
+use ral_registers::{Instance, register};
+
+#[repr(C)]
+#[allow(non_snake_case)]
+pub struct RegisterBlock {
+ pub CCR: u32,
+ _reserved0: [u8; 0x04],
+ pub CSR: u32,
+ pub CCSR: u32,
+ pub CACRR: u32,
+ pub CBCDR: u32,
+ pub CBCMR: u32,
+ pub CSCMR1: u32,
+ pub CSCMR2: u32,
+ pub CSCDR1: u32,
+ pub CS1CDR: u32,
+ pub CS2CDR: u32,
+ pub CDCDR: u32,
+ _reserved1: [u8; 0x04],
+ pub CSCDR2: u32,
+ pub CSCDR3: u32,
+ _reserved2: [u8; 0x08],
+ pub CDHIPR: u32,
+ _reserved3: [u8; 0x08],
+ pub CLPCR: u32,
+ pub CISR: u32,
+ pub CIMR: u32,
+ pub CCOSR: u32,
+ pub CGPR: u32,
+ pub CCGR: [u32; 8],
+ pub CMEOR: u32,
+}
+
+/// A CCM instance.
+pub type CCM = Instance<RegisterBlock>;
+
+register!(pub(crate) CACRR<u32> RW [
+ ARM_PODF start(0) width(3) RW {}
+]);
+
+register!(pub(crate) CSCMR1<u32> RW [
+ FLEXSPI_CLK_SRC start(31) width(1) RW {}
+ FLEXSPI_CLK_SEL start(29) width(2) RW {}
+ FLEXSPI_PODF start(23) width(3) RW {}
+ PERCLK_PODF start(0) width(6) RW {}
+ PERCLK_CLK_SEL start(6) width(1) RW {}
+]);
+
+register!(pub(crate) CBCDR<u32> RW [
+ PERIPH_CLK2_PODF start(27) width(3) RW {}
+ PERIPH_CLK_SEL start(25) width(1) RW {}
+ AHB_PODF start(10) width(3) RW {}
+ IPG_PODF start(8) width(2) RW {}
+]);
+
+register!(pub(crate) CLPCR<u32> RW [
+ LPM start(0) width(2) RW {
+ RUN = 0,
+ WAIT = 1,
+ STOP = 2,
+ }
+]);
+
+register!(pub(crate) CSCDR1<u32> RW [
+ UART_CLK_PODF start(0) width(6) RW {}
+ /// Reduced to 1 bit, which is supported across
+ /// all chip variants.
+ UART_CLK_SEL start(6) width(1) RW {}
+]);
+
+register!(pub(crate) CSCDR2<u32> RW [
+ LPI2C_CLK_PODF start(19) width(6) RW {}
+ LPI2C_CLK_SEL start(18) width(1) RW {}
+]);
+
+register!(pub(crate) CBCMR<u32> RW [
+ /// Four bits wide on the 1010.
+ LPSPI_PODF start(26) width(3) RW {}
+ PRE_PERIPH_CLK_SEL start(18) width(2) RW {}
+ PERIPH_CLK2_SEL start(12) width(2) RW {}
+ LPSPI_CLK_SEL start(4) width(2) RW {}
+]);
+
+register!(pub(crate) CCGR<u32> RW []);
+register!(pub(crate) CDHIPR<u32> RO []);
diff --git a/drivers/ccm-10xx/src/ral/ccm_analog.rs b/drivers/ccm-10xx/src/ral/ccm_analog.rs
new file mode 100644
index 0000000..958c7c1
--- /dev/null
+++ b/drivers/ccm-10xx/src/ral/ccm_analog.rs
@@ -0,0 +1,129 @@
+use ral_registers::{Instance, register};
+
+#[repr(C)]
+#[allow(non_snake_case)]
+pub struct RegisterBlock {
+ pub PLL_ARM: u32,
+ pub PLL_ARM_SET: u32,
+ pub PLL_ARM_CLR: u32,
+ pub PLL_ARM_TOG: u32,
+ pub PLL_USB1: u32,
+ pub PLL_USB1_SET: u32,
+ pub PLL_USB1_CLR: u32,
+ pub PLL_USB1_TOG: u32,
+ pub PLL_USB2: u32,
+ pub PLL_USB2_SET: u32,
+ pub PLL_USB2_CLR: u32,
+ pub PLL_USB2_TOG: u32,
+ pub PLL_SYS: u32,
+ pub PLL_SYS_SET: u32,
+ pub PLL_SYS_CLR: u32,
+ pub PLL_SYS_TOG: u32,
+ pub PLL_SYS_SS: u32,
+ _reserved0: [u8; 12],
+ pub PLL_SYS_NUM: u32,
+ _reserved1: [u8; 12],
+ pub PLL_SYS_DENOM: u32,
+ _reserved2: [u8; 12],
+ pub PLL_AUDIO: u32,
+ pub PLL_AUDIO_SET: u32,
+ pub PLL_AUDIO_CLR: u32,
+ pub PLL_AUDIO_TOG: u32,
+ pub PLL_AUDIO_NUM: u32,
+ _reserved3: [u8; 12],
+ pub PLL_AUDIO_DENOM: u32,
+ _reserved4: [u8; 12],
+ pub PLL_VIDEO: u32,
+ pub PLL_VIDEO_SET: u32,
+ pub PLL_VIDEO_CLR: u32,
+ pub PLL_VIDEO_TOG: u32,
+ pub PLL_VIDEO_NUM: u32,
+ _reserved5: [u8; 12],
+ pub PLL_VIDEO_DENOM: u32,
+ _reserved6: [u8; 28],
+ pub PLL_ENET: u32,
+ pub PLL_ENET_SET: u32,
+ pub PLL_ENET_CLR: u32,
+ pub PLL_ENET_TOG: u32,
+ pub PFD_480: u32,
+ pub PFD_480_SET: u32,
+ pub PFD_480_CLR: u32,
+ pub PFD_480_TOG: u32,
+ pub PFD_528: u32,
+ pub PFD_528_SET: u32,
+ pub PFD_528_CLR: u32,
+ pub PFD_528_TOG: u32,
+ _reserved7: [u8; 64],
+ pub MISC0: u32,
+ pub MISC0_SET: u32,
+ pub MISC0_CLR: u32,
+ pub MISC0_TOG: u32,
+ pub MISC1: u32,
+ pub MISC1_SET: u32,
+ pub MISC1_CLR: u32,
+ pub MISC1_TOG: u32,
+ pub MISC2: u32,
+ pub MISC2_SET: u32,
+ pub MISC2_CLR: u32,
+ pub MISC2_TOG: u32,
+}
+
+/// A CCM\_ANALOG instance.
+#[allow(non_camel_case_types)]
+pub type CCM_ANALOG = Instance<RegisterBlock>;
+
+register!(pub PLL_USB1<u32> RW [
+ LOCK start(31) width(1) RW {}
+ BYPASS start(16) width(1) RW {}
+ ENABLE start(13) width(1) RW {}
+ POWER start(12) width(1) RW {}
+ EN_USB_CLKS start(6) width(1) RW {}
+]);
+pub use PLL_USB1 as PLL_USB1_SET;
+pub use PLL_USB1 as PLL_USB1_CLR;
+
+pub use PLL_USB1 as PLL_USB2;
+pub use PLL_USB2 as PLL_USB2_SET;
+pub use PLL_USB2 as PLL_USB2_CLR;
+
+register!(pub PLL_ARM<u32> RW [
+ LOCK start(31) width(1) RW {}
+ ENABLE start(13) width(1) RW {}
+ POWERDOWN start(12) width(1) RW {}
+ DIV_SELECT start(0) width(7) RW {}
+]);
+pub use PLL_ARM as PLL_ARM_SET;
+
+register!(pub PLL_ENET<u32> RW [
+ LOCK start(31) width(1) RW {}
+ ENET_500M_REF_EN start(22) width(1) RW {}
+ POWERDOWN start(12) width(1) RW {}
+ BYPASS start(16) width(1) RW {}
+ BYPASS_CLK_SRC start(14) width(2) RW {}
+ ENABLE start(13) width(1) RW {}
+ DIV_SELECT start(0) width(2) RW {
+ DIV_25MHZ = 0,
+ DIV_50MHZ = 1,
+ DIV_100MHZ = 2,
+ DIV_125MHZ = 3,
+ }
+]);
+
+register!(pub PLL_SYS<u32> RW [
+ LOCK start(31) width(1) RW {}
+ BYPASS start(16) width(1) RW {}
+ ENABLE start(13) width(1) RW {}
+ POWERDOWN start(12) width(1) RW {}
+]);
+
+pub use PLL_SYS as PLL_SYS_SET;
+pub use PLL_SYS as PLL_SYS_CLR;
+
+register!(pub PFD_480<u32> RW [
+ PFD3_FRAC start(24) width(6) RW {}
+ PFD2_FRAC start(16) width(6) RW {}
+ PFD1_FRAC start(8) width(6) RW {}
+ PFD0_FRAC start(0) width(6) RW {}
+]);
+
+pub use PFD_480 as PFD_528;