From c94c09c72d16fd5cc9cb7c925386e7a2d6de1dcd Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 18 Dec 2025 14:11:10 +0100 Subject: Pre-test --- embassy-mcxa/src/clocks/config.rs | 66 ++++++++ embassy-mcxa/src/clocks/mod.rs | 318 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 368 insertions(+), 16 deletions(-) diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs index 9f97160ff..0f362ad0a 100644 --- a/embassy-mcxa/src/clocks/config.rs +++ b/embassy-mcxa/src/clocks/config.rs @@ -121,8 +121,12 @@ pub struct ClocksConfig { pub fro16k: Option, /// SOSC, clk_in clock source pub sosc: Option, + /// SPLL + pub spll: Option, } +// SOSC + /// The mode of the external reference clock #[derive(Copy, Clone)] pub enum SoscMode { @@ -143,6 +147,67 @@ pub struct SoscConfig { pub power: PoweredClock, } +// SPLL + +// Fin: 32kHz to 150MHz +// Fcco: 275MHz to 550MHz +// Fout: 4.3MHz to 2x Max CPU Frequency + +pub struct SpllConfig { + pub source: SpllSource, + pub mode: SpllMode, + pub power: PoweredClock, +} + +pub enum SpllSource { + /// External Oscillator (8-50M) + Sosc, + /// Fast Internal Oscillator (45M) + // NOTE: Figure 69 says "firc_45mhz"/"clk_45m", not "fro_hf_gated", + // so this is is always 45MHz. + Firc, + /// S Internal Oscillator (12M) + Sirc, + + // TODO: the reference manual hints that ROSC is possible, + // however the minimum input frequency is 32K, but ROSC is 16K. + // Some diagrams show this option, and some diagrams omit it. + // + // /// Realtime Internal Oscillator (16K Osc) + // Rosc, +} + +/// N: 1..=255 +/// M: 1..=65535 +/// P: 1..=31 +pub enum SpllMode { + /// Fout = M x Fin + Mode1a { + m_mult: u16, + }, + /// if !bypass_p2_div: Fout = (M / (2 x P)) x Fin + /// if bypass_p2_div: Fout = (M / P ) x Fin + Mode1b { + m_mult: u16, + p_div: u8, + bypass_p2_div: bool, + }, + /// Fout = (M / N) x Fin + Mode1c { + m_mult: u16, + n_div: u8, + }, + /// if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin + /// if bypass_p2_div: Fout = (M / ( N x P )) x Fin + Mode1d { + m_mult: u16, + n_div: u8, + p_div: u8, + bypass_p2_div: bool, + } +} + + // FIRC/FRO180M /// ```text @@ -222,6 +287,7 @@ impl Default for ClocksConfig { vdd_core_domain_active: true, }), sosc: None, + spll: None, } } } diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index b41a1ba46..98f7075eb 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs @@ -88,6 +88,7 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { operator.configure_sirc_clocks()?; operator.configure_fro16k_clocks()?; operator.configure_sosc()?; + operator.configure_spll()?; // For now, just use FIRC as the main/cpu clock, which should already be // the case on reset @@ -825,7 +826,7 @@ impl ClockOperator<'_> { Ok(()) } - /// Configure the FRO16K/clk_16k clock family + /// Configure the ROSC/FRO16K/clk_16k clock family fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { let Some(fro16k) = self.config.fro16k.as_ref() else { return Ok(()); @@ -866,21 +867,30 @@ impl ClockOperator<'_> { Ok(()) } + fn ensure_ldo_active(&mut self) { + // TODO: Config for the LDO? For now, just enable + // using the default settings: + // LDOBYPASS: 0/not bypassed + // VOUT_SEL: 0b100: 1.1v + // LDOEN: 0/Disabled + let already_enabled = { + let ldocsr = self.scg0.ldocsr().read(); + ldocsr.ldoen().is_enabled() && ldocsr.vout_ok().is_enabled() + }; + if !already_enabled { + self.scg0.ldocsr().modify(|_r, w| w.ldoen().enabled()); + while self.scg0.ldocsr().read().vout_ok().is_disabled() {} + } + } + /// Configure the SOSC/clk_in oscillator fn configure_sosc(&mut self) -> Result<(), ClockError> { let Some(parts) = self.config.sosc.as_ref() else { return Ok(()); }; - let scg0 = unsafe { pac::Scg0::steal() }; - - // TODO: Config for the LDO? For now, if we have Sosc, just enable - // using the default settings: - // LDOBYPASS: 0/not bypassed - // VOUT_SEL: 0b100: 1.1v - // LDOEN: 0/Disabled - scg0.ldocsr().modify(|_r, w| w.ldoen().enabled()); - while scg0.ldocsr().read().vout_ok().is_disabled() {} + // Enable (and wait for) LDO to be active + self.ensure_ldo_active(); // TODO: something something pins? This seems to work when the pins are // not enabled, even if GPIO hasn't been initialized at all yet. @@ -920,14 +930,14 @@ impl ClockOperator<'_> { }; // Set source/erefs and range - scg0.sosccfg().modify(|_r, w| { + self.scg0.sosccfg().modify(|_r, w| { w.erefs().variant(eref); w.range().variant(range); w }); // Disable lock - scg0.sosccsr().modify(|_r, w| w.lk().clear_bit()); + self.scg0.sosccsr().modify(|_r, w| w.lk().clear_bit()); // TODO: We could enable the SOSC clock monitor. There are some things to // figure out first: @@ -938,7 +948,7 @@ impl ClockOperator<'_> { // * We need to decide if we need an interrupt or a reset if the monitor trips // Apply remaining config - scg0.sosccsr().modify(|_r, w| { + self.scg0.sosccsr().modify(|_r, w| { // For now, just disable the monitor. See above. w.sosccm().disabled(); @@ -957,8 +967,8 @@ impl ClockOperator<'_> { }); // Wait for SOSC to be valid, check for errors - while !scg0.sosccsr().read().soscvld().bit_is_set() {} - if scg0.sosccsr().read().soscerr().is_enabled_and_error() { + while !self.scg0.sosccsr().read().soscvld().bit_is_set() {} + if self.scg0.sosccsr().read().soscerr().is_enabled_and_error() { return Err(ClockError::BadConfig { clock: "clk_in", reason: "soscerr is set", @@ -966,7 +976,7 @@ impl ClockOperator<'_> { } // Re-lock the sosc - scg0.sosccsr().modify(|_r, w| w.lk().set_bit()); + self.scg0.sosccsr().modify(|_r, w| w.lk().set_bit()); self.clocks.clk_in = Some(Clock { frequency: freq, @@ -975,6 +985,282 @@ impl ClockOperator<'_> { Ok(()) } + + fn configure_spll(&mut self) -> Result<(), ClockError> { + // # Vocab + // + // | Name | Meaning | + // | :--- | :--- | + // | Fin | Frequency of clkin | + // | clkout | Output clock of the PLL | + // | Fout | Frequency of clkout (depends on mode) | + // | clkref | PLL Reference clock, the input clock to the PFD | + // | Fref | Frequency of clkref, Fref = Fin / N | + // | Fcco | Frequency of the output clock of the CCO, Fcco = M * Fref | + // | N | Predivider value | + // | M | Feedback divider value | + // | P | Postdivider value | + // | Tpon | PLL start-up time | + + let Some(cfg) = self.config.spll.as_ref() else { + return Ok(()); + }; + + let res = match cfg.source { + config::SpllSource::Sosc => self + .clocks + .clk_in + .as_ref() + .map(|c| (c, pac::scg0::spllctrl::Source::Sosc)) + .ok_or("sosc not active"), + config::SpllSource::Firc => self + .clocks + .clk_45m + .as_ref() + .map(|c| (c, pac::scg0::spllctrl::Source::Firc)) + .ok_or("firc not active"), + config::SpllSource::Sirc => self + .clocks + .fro_12m + .as_ref() + .map(|c| (c, pac::scg0::spllctrl::Source::Sirc)) + .ok_or("sirc not active"), + }; + let (clk, variant) = res.map_err(|s| ClockError::BadConfig { + clock: "spll", + reason: s, + })?; + if !clk.power.meets_requirement_of(&cfg.power) { + return Err(ClockError::BadConfig { clock: "spll", reason: "needs low power source" }); + } + + // Bandwidth calc + // + // > In normal applications, you must calculate the bandwidth manually by using the feedback divider M (ranging from 1 to 216-1), + // > Equation 1, and Equation 2. The PLL is automatically stable in such case. In normal applications, SPLLCTRL[BANDDIRECT] must + // > be 0; in this case, the bandwidth changes as a function of M. + let f_in = clk.frequency; + let bp_pre: bool; + let bp_post: bool; + let bp_post2: bool; + let m: u16; + let p: Option; + let n: Option; + + // Calculate both Fout and Fcco so we can ensure they don't overflow + // and are in range + let fout: Option; + let fcco: Option; + + match cfg.mode { + // Fout = M x Fin + config::SpllMode::Mode1a { m_mult } => { + bp_pre = true; + bp_post = true; + bp_post2 = false; + m = m_mult; + p = None; + n = None; + fcco = f_in.checked_mul(m_mult as u32); + fout = fcco; + } + // if !bypass_p2_div: Fout = (M / (2 x P)) x Fin + // if bypass_p2_div: Fout = (M / P ) x Fin + config::SpllMode::Mode1b { + m_mult, + p_div, + bypass_p2_div, + } => { + bp_pre = true; + bp_post = false; + bp_post2 = bypass_p2_div; + m = m_mult; + p = Some(p_div); + n = None; + let mut div = p_div as u32; + if !bypass_p2_div { + div *= 2; + } + fcco = f_in.checked_mul(m_mult as u32); + fout = (f_in / div).checked_mul(m_mult as u32); + } + // Fout = (M / N) x Fin + config::SpllMode::Mode1c { m_mult, n_div } => { + bp_pre = false; + bp_post = true; + bp_post2 = false; + m = m_mult; + p = None; + n = Some(n_div); + fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); + fout = fcco; + } + // if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin + // if bypass_p2_div: Fout = (M / ( N x P )) x Fin + config::SpllMode::Mode1d { + m_mult, + n_div, + p_div, + bypass_p2_div, + } => { + bp_pre = false; + bp_post = false; + bp_post2 = bypass_p2_div; + m = m_mult; + p = Some(p_div); + n = Some(n_div); + // This can't overflow: u8 x u8 (x 2) always fits in u32 + let mut div = (p_div as u32) * (n_div as u32); + if !bypass_p2_div { + div *= 2; + } + fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); + fout = (f_in / div).checked_mul(m_mult as u32); + } + }; + let fcco = fcco.ok_or(ClockError::BadConfig { + clock: "spll", + reason: "fcco invalid", + })?; + let fout = fout.ok_or(ClockError::BadConfig { + clock: "spll", + reason: "fout invalid", + })?; + // Fcco: 275MHz to 550MHz + if !(275_000_000..=550_000_000).contains(&fcco) { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "fcco invalid", + }); + } + // Fout: 4.3MHz to 2x Max CPU Frequency + + // TODO: Different for different CPUs? + const CPU_MAX_FREQ: u32 = 180_000_000; + if !(4_300_000..=(2 * CPU_MAX_FREQ)).contains(&fout) { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "fout invalid", + }); + } + + // TODO: + assert!(clk.frequency != 0); + assert!(m >= 1); + if let Some(p) = p.as_ref() { + assert!(*p >= 1); + } + if let Some(n) = n.as_ref() { + assert!(*n >= 1); + } + + // A = floor(m / 4) + 1 + let selp_a = (m / 4) + 1; + // SELP = A if A < 31 + // = 31 if A >= 31 + let selp = selp_a.min(31); + + // A = 1 if M >= 8000 + // = floor(8000 / M) if 8000 > M >= 122 + // = 2 x floor(M / 4) / 3 if 122 > M >= 1 + let seli_a = if m >= 8000 { + 1 + } else if m >= 122 { + 8000 / m + } else { + (2 * (m / 4)) / 3 + }; + // SELI = A if A < 63 + // = 63 if A >= 63 + let seli = seli_a.min(63); + // SELR must be 0. + let selr = 0; + + self.scg0.spllctrl().modify(|_r, w| { + w.source().variant(variant); + unsafe { + w.selp().bits(selp as u8); + w.seli().bits(seli as u8); + w.selr().bits(selr); + } + w + }); + + if let Some(n) = n { + self.scg0.spllndiv().modify(|_r, w| unsafe { w.ndiv().bits(n) }); + } + if let Some(p) = p { + self.scg0.spllpdiv().modify(|_r, w| unsafe { w.pdiv().bits(p) }); + } + self.scg0.spllmdiv().modify(|_r, w| unsafe { w.mdiv().bits(m) }); + + self.scg0.spllctrl().modify(|_r, w| { + w.bypassprediv().bit(bp_pre); + w.bypasspostdiv().bit(bp_post); + w.bypasspostdiv2().bit(bp_post2); + + // TODO: support FRM? + w.frm().disabled(); + + w + }); + + // Unlock + self.scg0.spllcsr().modify(|_r, w| w.lk().write_enabled()); + + // TODO: Support clock monitors? + // self.scg0.spllcsr().modify(|_r, w| w.spllcm().?); + + self.scg0.trim_lock().write(|w| unsafe { + w.trim_lock_key().bits(0x5a5a); + w.trim_unlock().not_locked() + }); + + // SPLLLOCK_CNFG: The lock time programmed in this register must be + // equal to meet the PLL 500μs lock time plus the 300 refclk count startup. + // + // LOCK_TIME = 500μs/T ref + 300, F ref = F in /N (input frequency divided by pre-divider ratio). + // + // 500us is 1/2000th of a second, therefore Fref / 2000 is the number of cycles in 500us. + let f_ref = if let Some(n) = n { f_in / (n as u32) } else { f_in }; + let lock_time = f_ref.div_ceil(2000) + 300; + self.scg0 + .splllock_cnfg() + .write(|w| unsafe { w.lock_time().bits(lock_time) }); + + // TODO: Support Spread spectrum? + + self.scg0.spllcsr().modify(|_r, w| { + w.spllclken().enabled(); + w.spllpwren().enabled(); + w.spllsten().bit(matches!(cfg.power, PoweredClock::AlwaysEnabled)); + w + }); + + // Wait for SPLL to set up + loop { + let csr = self.scg0.spllcsr().read(); + if csr.spll_lock().is_enabled_and_valid() { + if csr.spllerr().is_enabled_and_error() { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "spllerr is set", + }); + } + break; + } + } + + // Re-lock SPLL CSR + self.scg0.spllcsr().modify(|_r, w| w.lk().write_disabled()); + + self.clocks.pll1_clk = Some(Clock { + frequency: fout, + power: cfg.power, + }); + + Ok(()) + } } // -- cgit From b0cf9b4626e7fa99d1da82a5eb745e9ed14508de Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 18 Dec 2025 14:53:33 +0100 Subject: First basic test works --- embassy-mcxa/src/clocks/config.rs | 15 +++---- embassy-mcxa/src/clocks/mod.rs | 83 +++++++++++++++++++++++++++++++++++---- examples/mcxa/src/bin/clkout.rs | 25 ++++++++++-- 3 files changed, 103 insertions(+), 20 deletions(-) diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs index 0f362ad0a..3f1729d00 100644 --- a/embassy-mcxa/src/clocks/config.rs +++ b/embassy-mcxa/src/clocks/config.rs @@ -157,6 +157,8 @@ pub struct SpllConfig { pub source: SpllSource, pub mode: SpllMode, pub power: PoweredClock, + /// Is the "pll1_clk_div" clock enabled? + pub pll1_clk_div: Option, } pub enum SpllSource { @@ -168,7 +170,6 @@ pub enum SpllSource { Firc, /// S Internal Oscillator (12M) Sirc, - // TODO: the reference manual hints that ROSC is possible, // however the minimum input frequency is 32K, but ROSC is 16K. // Some diagrams show this option, and some diagrams omit it. @@ -182,9 +183,7 @@ pub enum SpllSource { /// P: 1..=31 pub enum SpllMode { /// Fout = M x Fin - Mode1a { - m_mult: u16, - }, + Mode1a { m_mult: u16 }, /// if !bypass_p2_div: Fout = (M / (2 x P)) x Fin /// if bypass_p2_div: Fout = (M / P ) x Fin Mode1b { @@ -193,10 +192,7 @@ pub enum SpllMode { bypass_p2_div: bool, }, /// Fout = (M / N) x Fin - Mode1c { - m_mult: u16, - n_div: u8, - }, + Mode1c { m_mult: u16, n_div: u8 }, /// if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin /// if bypass_p2_div: Fout = (M / ( N x P )) x Fin Mode1d { @@ -204,10 +200,9 @@ pub enum SpllMode { n_div: u8, p_div: u8, bypass_p2_div: bool, - } + }, } - // FIRC/FRO180M /// ```text diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index 98f7075eb..866e78bf2 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs @@ -200,6 +200,9 @@ pub struct Clocks { /// `pll1_clk` is the output of the main system PLL, `pll1`. pub pll1_clk: Option, + + /// `pll1_clk_div` is a configurable frequency clock, sourced from `pll1_clk` + pub pll1_clk_div: Option, } /// `ClockError` is the main error returned when configuring or checking clock state @@ -548,13 +551,37 @@ impl Clocks { } /// Ensure the `pll1_clk` clock is active and valid at the given power state. - pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "pll1_clk" }) + pub fn ensure_pll1_clk_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.pll1_clk.as_ref() else { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "not low power active", + }); + } + Ok(clk.frequency) } /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. - pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) + pub fn ensure_pll1_clk_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.pll1_clk_div.as_ref() else { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "not low power active", + }); + } + Ok(clk.frequency) } /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active @@ -1031,7 +1058,10 @@ impl ClockOperator<'_> { reason: s, })?; if !clk.power.meets_requirement_of(&cfg.power) { - return Err(ClockError::BadConfig { clock: "spll", reason: "needs low power source" }); + return Err(ClockError::BadConfig { + clock: "spll", + reason: "needs low power source", + }); } // Bandwidth calc @@ -1118,9 +1148,20 @@ impl ClockOperator<'_> { fout = (f_in / div).checked_mul(m_mult as u32); } }; + + defmt::debug!("f_in: {:?}", f_in); + defmt::debug!("bp_pre: {:?}", bp_pre); + defmt::debug!("bp_post: {:?}", bp_post); + defmt::debug!("bp_post2: {:?}", bp_post2); + defmt::debug!("m: {:?}", m); + defmt::debug!("p: {:?}", p); + defmt::debug!("n: {:?}", n); + defmt::debug!("fout: {:?}", fout); + defmt::debug!("fcco: {:?}", fcco); + let fcco = fcco.ok_or(ClockError::BadConfig { clock: "spll", - reason: "fcco invalid", + reason: "fcco invalid1", })?; let fout = fout.ok_or(ClockError::BadConfig { clock: "spll", @@ -1130,7 +1171,7 @@ impl ClockOperator<'_> { if !(275_000_000..=550_000_000).contains(&fcco) { return Err(ClockError::BadConfig { clock: "spll", - reason: "fcco invalid", + reason: "fcco invalid2", }); } // Fout: 4.3MHz to 2x Max CPU Frequency @@ -1149,6 +1190,7 @@ impl ClockOperator<'_> { assert!(m >= 1); if let Some(p) = p.as_ref() { assert!(*p >= 1); + assert!(*p <= 31); } if let Some(n) = n.as_ref() { assert!(*n >= 1); @@ -1254,11 +1296,38 @@ impl ClockOperator<'_> { // Re-lock SPLL CSR self.scg0.spllcsr().modify(|_r, w| w.lk().write_disabled()); + // Store clock state self.clocks.pll1_clk = Some(Clock { frequency: fout, power: cfg.power, }); + // Do we enable the `pll1_clk_div` output? + if let Some(d) = cfg.pll1_clk_div.as_ref() { + // Halt and reset the div; then set our desired div. + self.syscon.pll1clkdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + unsafe { w.div().bits(d.into_bits()) }; + w + }); + // Then unhalt it, and reset it + self.syscon.pll1clkdiv().write(|w| { + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.pll1clkdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.pll1_clk_div = Some(Clock { + frequency: fout / d.into_divisor(), + power: cfg.power, + }); + } + Ok(()) } } diff --git a/examples/mcxa/src/bin/clkout.rs b/examples/mcxa/src/bin/clkout.rs index e6e6a2d3d..94f7d7bbf 100644 --- a/examples/mcxa/src/bin/clkout.rs +++ b/examples/mcxa/src/bin/clkout.rs @@ -4,7 +4,7 @@ use embassy_executor::Spawner; use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; use embassy_mcxa::clocks::PoweredClock; -use embassy_mcxa::clocks::config::{SoscConfig, SoscMode}; +use embassy_mcxa::clocks::config::{SoscConfig, SoscMode, SpllConfig, SpllMode, SpllSource}; use embassy_mcxa::gpio::{DriveStrength, Level, Output, SlewRate}; use embassy_time::Timer; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; @@ -18,6 +18,18 @@ async fn main(_spawner: Spawner) { frequency: 8_000_000, power: PoweredClock::NormalEnabledDeepSleepDisabled, }); + cfg.clock_cfg.spll = Some(SpllConfig { + source: SpllSource::Sirc, // 12MHz + // 12 x 32 => 384MHz + // 384 / (16 x 2) => 12.0MHz + mode: SpllMode::Mode1b { + m_mult: 32, + p_div: 16, + bypass_p2_div: false, + }, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + pll1_clk_div: None, + }); let p = hal::init(cfg); @@ -39,11 +51,18 @@ async fn main(_spawner: Spawner) { div: const { Div4::from_divisor(16).unwrap() }, level: PoweredClock::NormalEnabledDeepSleepDisabled, }; + const M1_CONFIG: Config = Config { + sel: ClockOutSel::Pll1Clk, + div: const { Div4::from_divisor(12).unwrap() }, + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }; + #[rustfmt::skip] let configs = [ - ("16K -> /1 = 16K", K16_CONFIG), - ("12M -> /3 = 4M", M4_CONFIG), + ("16K -> /1 = 16K", K16_CONFIG), + ("12M -> /3 = 4M", M4_CONFIG), ("8M -> /16 = 512K", K512_CONFIG), + ("12M-> /12 = 1M", M1_CONFIG), ]; loop { -- cgit From 1a3eb7ef28c071e17f93663f7e07d7d6e9f7789e Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 18 Dec 2025 19:31:25 +0100 Subject: Cleanups, docs --- embassy-mcxa/src/clocks/config.rs | 88 ++++++++++++---- embassy-mcxa/src/clocks/mod.rs | 207 +++++++++++++++++--------------------- examples/mcxa/src/bin/clkout.rs | 3 +- 3 files changed, 167 insertions(+), 131 deletions(-) diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs index 3f1729d00..4beca5f27 100644 --- a/embassy-mcxa/src/clocks/config.rs +++ b/embassy-mcxa/src/clocks/config.rs @@ -136,7 +136,7 @@ pub enum SoscMode { ActiveClock, } -// SOSC/clk_in configuration +/// SOSC/clk_in configuration #[derive(Copy, Clone)] pub struct SoscConfig { /// Mode of the external reference clock @@ -149,22 +149,23 @@ pub struct SoscConfig { // SPLL -// Fin: 32kHz to 150MHz -// Fcco: 275MHz to 550MHz -// Fout: 4.3MHz to 2x Max CPU Frequency - +/// PLL1/SPLL configuration pub struct SpllConfig { + /// Input clock source for the PLL1/SPLL pub source: SpllSource, + /// Mode of operation for the PLL1/SPLL pub mode: SpllMode, + /// Power state of the SPLL pub power: PoweredClock, /// Is the "pll1_clk_div" clock enabled? pub pll1_clk_div: Option, } +/// Input clock source for the PLL1/SPLL pub enum SpllSource { - /// External Oscillator (8-50M) + /// External Oscillator (8-50MHz) Sosc, - /// Fast Internal Oscillator (45M) + /// Fast Internal Oscillator (45MHz) // NOTE: Figure 69 says "firc_45mhz"/"clk_45m", not "fro_hf_gated", // so this is is always 45MHz. Firc, @@ -173,32 +174,83 @@ pub enum SpllSource { // TODO: the reference manual hints that ROSC is possible, // however the minimum input frequency is 32K, but ROSC is 16K. // Some diagrams show this option, and some diagrams omit it. + // SVD shows it as "reserved". // // /// Realtime Internal Oscillator (16K Osc) // Rosc, } -/// N: 1..=255 -/// M: 1..=65535 -/// P: 1..=31 +/// Mode of operation for the SPLL/PLL1 +/// +/// NOTE: Currently, only "Mode 1" normal operational modes are implemented, +/// as described in the Reference Manual. +#[non_exhaustive] pub enum SpllMode { - /// Fout = M x Fin - Mode1a { m_mult: u16 }, - /// if !bypass_p2_div: Fout = (M / (2 x P)) x Fin - /// if bypass_p2_div: Fout = (M / P ) x Fin + /// Mode 1a does not use the Pre/Post dividers. + /// + /// `Fout = m_mult x SpllSource` + /// + /// Both of the following constraints must be met: + /// + /// * Fout: 275MHz to 550MHz + /// * Fout: 4.3MHz to 2x Max CPU Frequency + Mode1a { + /// PLL Multiplier. Must be in the range 1..=65535. + m_mult: u16, + }, + + /// Mode 1b does not use the Pre-divider. + /// + /// * `if !bypass_p2_div: Fout = (M / (2 x P)) x Fin` + /// * `if bypass_p2_div: Fout = (M / P ) x Fin` + /// + /// Both of the following constraints must be met: + /// + /// * Fcco: 275MHz to 550MHz + /// * `Fcco = m_mult x SpllSource` + /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1b { + /// PLL Multiplier. `m_mult` must be in the range 1..=65535. m_mult: u16, + /// Post Divider. `p_div` must be in the range 1..=31. p_div: u8, + /// Bonus post divider bypass_p2_div: bool, }, - /// Fout = (M / N) x Fin - Mode1c { m_mult: u16, n_div: u8 }, - /// if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin - /// if bypass_p2_div: Fout = (M / ( N x P )) x Fin + + /// Mode 1c does use the Pre-divider, but does not use the Post-divider + /// + /// `Fout = (M / N) x Fin` + /// + /// Both of the following constraints must be met: + /// + /// * Fout: 275MHz to 550MHz + /// * Fout: 4.3MHz to 2x Max CPU Frequency + Mode1c { + /// PLL Multiplier. `m_mult` must be in the range 1..=65535. + m_mult: u16, + /// Pre Divider. `n_div` must be in the range 1..=255. + n_div: u8, + }, + + /// Mode 1b uses both the Pre and Post dividers. + /// + /// * `if !bypass_p2_div: Fout = (M / (N x 2 x P)) x Fin` + /// * `if bypass_p2_div: Fout = (M / ( N x P )) x Fin` + /// + /// Both of the following constraints must be met: + /// + /// * Fcco: 275MHz to 550MHz + /// * `Fcco = (m_mult x SpllSource) / (n_div x p_div (x 2))` + /// * Fout: 4.3MHz to 2x Max CPU Frequency Mode1d { + /// PLL Multiplier. `m_mult` must be in the range 1..=65535. m_mult: u16, + /// Pre Divider. `n_div` must be in the range 1..=255. n_div: u8, + /// Post Divider. `p_div` must be in the range 1..=31. p_div: u8, + /// Bonus post divider bypass_p2_div: bool, }, } diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index 866e78bf2..5a2d5ed5c 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs @@ -439,72 +439,49 @@ pub unsafe fn pulse_reset() { /// selected clocks are active at a suitable level at time of construction. These methods /// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. impl Clocks { - /// Ensure the `fro_lf_div` clock is active and valid at the given power state. - pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_lf_div.as_ref() else { + fn ensure_clock_active( + &self, + clock: &Option, + name: &'static str, + at_level: &PoweredClock, + ) -> Result { + let Some(clk) = clock.as_ref() else { return Err(ClockError::BadConfig { - clock: "fro_lf_div", + clock: name, reason: "required but not active", }); }; if !clk.power.meets_requirement_of(at_level) { return Err(ClockError::BadConfig { - clock: "fro_lf_div", + clock: name, reason: "not low power active", }); } Ok(clk.frequency) } + /// Ensure the `fro_lf_div` clock is active and valid at the given power state. + #[inline] + pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { + self.ensure_clock_active(&self.fro_lf_div, "fro_lf_div", at_level) + } + /// Ensure the `fro_hf` clock is active and valid at the given power state. + #[inline] pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_hf.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_hf", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_hf", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.fro_hf, "fro_hf", at_level) } /// Ensure the `fro_hf_div` clock is active and valid at the given power state. + #[inline] pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_hf_div.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.fro_hf_div, "fro_hf_div", at_level) } /// Ensure the `clk_in` clock is active and valid at the given power state. + #[inline] pub fn ensure_clk_in_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.clk_in.as_ref() else { - return Err(ClockError::BadConfig { - clock: "clk_in", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "clk_in", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.clk_in, "clk_in", at_level) } /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. @@ -534,54 +511,21 @@ impl Clocks { } /// Ensure the `clk_1m` clock is active and valid at the given power state. + #[inline] pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.clk_1m.as_ref() else { - return Err(ClockError::BadConfig { - clock: "clk_1m", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "clk_1m", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.clk_1m, "clk_1m", at_level) } /// Ensure the `pll1_clk` clock is active and valid at the given power state. + #[inline] pub fn ensure_pll1_clk_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.pll1_clk.as_ref() else { - return Err(ClockError::BadConfig { - clock: "spll", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "spll", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.pll1_clk, "pll1_clk", at_level) } /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. + #[inline] pub fn ensure_pll1_clk_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.pll1_clk_div.as_ref() else { - return Err(ClockError::BadConfig { - clock: "spll", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "spll", - reason: "not low power active", - }); - } - Ok(clk.frequency) + self.ensure_clock_active(&self.pll1_clk_div, "pll1_clk_div", at_level) } /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active @@ -1029,10 +973,12 @@ impl ClockOperator<'_> { // | P | Postdivider value | // | Tpon | PLL start-up time | + // No PLL? Nothing to do! let Some(cfg) = self.config.spll.as_ref() else { return Ok(()); }; + // match on the source, ensure it is active already let res = match cfg.source { config::SpllSource::Sosc => self .clocks @@ -1053,10 +999,12 @@ impl ClockOperator<'_> { .map(|c| (c, pac::scg0::spllctrl::Source::Sirc)) .ok_or("sirc not active"), }; + // This checks if active let (clk, variant) = res.map_err(|s| ClockError::BadConfig { clock: "spll", reason: s, })?; + // This checks the correct power reqs if !clk.power.meets_requirement_of(&cfg.power) { return Err(ClockError::BadConfig { clock: "spll", @@ -1069,6 +1017,14 @@ impl ClockOperator<'_> { // > In normal applications, you must calculate the bandwidth manually by using the feedback divider M (ranging from 1 to 216-1), // > Equation 1, and Equation 2. The PLL is automatically stable in such case. In normal applications, SPLLCTRL[BANDDIRECT] must // > be 0; in this case, the bandwidth changes as a function of M. + if clk.frequency == 0 { + return Err(ClockError::BadConfig { + clock: "spll", + reason: "internal error", + }); + } + + // These are calculated differently depending on the mode. let f_in = clk.frequency; let bp_pre: bool; let bp_post: bool; @@ -1082,13 +1038,44 @@ impl ClockOperator<'_> { let fout: Option; let fcco: Option; + let m_check = |m: u16| { + if !(1..=u16::MAX).contains(&m) { + Err(ClockError::BadConfig { + clock: "spll", + reason: "m_mult out of range", + }) + } else { + Ok(m) + } + }; + let p_check = |p: u8| { + if !(1..=31).contains(&p) { + Err(ClockError::BadConfig { + clock: "spll", + reason: "p_div out of range", + }) + } else { + Ok(p) + } + }; + let n_check = |n: u8| { + if !(1..=u8::MAX).contains(&n) { + Err(ClockError::BadConfig { + clock: "spll", + reason: "n_div out of range", + }) + } else { + Ok(n) + } + }; + match cfg.mode { // Fout = M x Fin config::SpllMode::Mode1a { m_mult } => { bp_pre = true; bp_post = true; bp_post2 = false; - m = m_mult; + m = m_check(m_mult)?; p = None; n = None; fcco = f_in.checked_mul(m_mult as u32); @@ -1104,8 +1091,8 @@ impl ClockOperator<'_> { bp_pre = true; bp_post = false; bp_post2 = bypass_p2_div; - m = m_mult; - p = Some(p_div); + m = m_check(m_mult)?; + p = Some(p_check(p_div)?); n = None; let mut div = p_div as u32; if !bypass_p2_div { @@ -1119,9 +1106,9 @@ impl ClockOperator<'_> { bp_pre = false; bp_post = true; bp_post2 = false; - m = m_mult; + m = m_check(m_mult)?; p = None; - n = Some(n_div); + n = Some(n_check(n_div)?); fcco = (f_in / (n_div as u32)).checked_mul(m_mult as u32); fout = fcco; } @@ -1136,9 +1123,9 @@ impl ClockOperator<'_> { bp_pre = false; bp_post = false; bp_post2 = bypass_p2_div; - m = m_mult; - p = Some(p_div); - n = Some(n_div); + m = m_check(m_mult)?; + p = Some(p_check(p_div)?); + n = Some(n_check(n_div)?); // This can't overflow: u8 x u8 (x 2) always fits in u32 let mut div = (p_div as u32) * (n_div as u32); if !bypass_p2_div { @@ -1149,16 +1136,21 @@ impl ClockOperator<'_> { } }; - defmt::debug!("f_in: {:?}", f_in); - defmt::debug!("bp_pre: {:?}", bp_pre); - defmt::debug!("bp_post: {:?}", bp_post); - defmt::debug!("bp_post2: {:?}", bp_post2); - defmt::debug!("m: {:?}", m); - defmt::debug!("p: {:?}", p); - defmt::debug!("n: {:?}", n); - defmt::debug!("fout: {:?}", fout); - defmt::debug!("fcco: {:?}", fcco); + // Dump all the PLL calcs if needed for debugging + #[cfg(feature = "defmt")] + { + defmt::debug!("f_in: {:?}", f_in); + defmt::debug!("bp_pre: {:?}", bp_pre); + defmt::debug!("bp_post: {:?}", bp_post); + defmt::debug!("bp_post2: {:?}", bp_post2); + defmt::debug!("m: {:?}", m); + defmt::debug!("p: {:?}", p); + defmt::debug!("n: {:?}", n); + defmt::debug!("fout: {:?}", fout); + defmt::debug!("fcco: {:?}", fcco); + } + // Ensure the Fcco and Fout calcs didn't overflow let fcco = fcco.ok_or(ClockError::BadConfig { clock: "spll", reason: "fcco invalid1", @@ -1167,6 +1159,7 @@ impl ClockOperator<'_> { clock: "spll", reason: "fout invalid", })?; + // Fcco: 275MHz to 550MHz if !(275_000_000..=550_000_000).contains(&fcco) { return Err(ClockError::BadConfig { @@ -1174,10 +1167,11 @@ impl ClockOperator<'_> { reason: "fcco invalid2", }); } - // Fout: 4.3MHz to 2x Max CPU Frequency // TODO: Different for different CPUs? const CPU_MAX_FREQ: u32 = 180_000_000; + + // Fout: 4.3MHz to 2x Max CPU Frequency if !(4_300_000..=(2 * CPU_MAX_FREQ)).contains(&fout) { return Err(ClockError::BadConfig { clock: "spll", @@ -1185,17 +1179,6 @@ impl ClockOperator<'_> { }); } - // TODO: - assert!(clk.frequency != 0); - assert!(m >= 1); - if let Some(p) = p.as_ref() { - assert!(*p >= 1); - assert!(*p <= 31); - } - if let Some(n) = n.as_ref() { - assert!(*n >= 1); - } - // A = floor(m / 4) + 1 let selp_a = (m / 4) + 1; // SELP = A if A < 31 diff --git a/examples/mcxa/src/bin/clkout.rs b/examples/mcxa/src/bin/clkout.rs index 94f7d7bbf..c0e8c330d 100644 --- a/examples/mcxa/src/bin/clkout.rs +++ b/examples/mcxa/src/bin/clkout.rs @@ -19,7 +19,8 @@ async fn main(_spawner: Spawner) { power: PoweredClock::NormalEnabledDeepSleepDisabled, }); cfg.clock_cfg.spll = Some(SpllConfig { - source: SpllSource::Sirc, // 12MHz + source: SpllSource::Sirc, + // 12MHz // 12 x 32 => 384MHz // 384 / (16 x 2) => 12.0MHz mode: SpllMode::Mode1b { -- cgit From 25f1963690a52a04ea0bc23e397b4fe6f585777a Mon Sep 17 00:00:00 2001 From: James Munns Date: Thu, 18 Dec 2025 19:35:44 +0100 Subject: SPLL needs to ensure LDO is active --- embassy-mcxa/src/clocks/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index 5a2d5ed5c..04559fd04 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs @@ -978,6 +978,9 @@ impl ClockOperator<'_> { return Ok(()); }; + // Ensure the LDO is active + self.ensure_ldo_active(); + // match on the source, ensure it is active already let res = match cfg.source { config::SpllSource::Sosc => self -- cgit