From 144c659fe278959736a1d391f1582dbbfc651646 Mon Sep 17 00:00:00 2001 From: James Munns Date: Tue, 11 Nov 2025 20:27:14 +0100 Subject: Start implementing clocks This starts with the FIRC/FRO180M, though leaves it mostly hardcoded. --- src/clocks.rs | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) diff --git a/src/clocks.rs b/src/clocks.rs index 65a17cef6..74e0daeeb 100644 --- a/src/clocks.rs +++ b/src/clocks.rs @@ -1,5 +1,7 @@ //! Clock control helpers (no magic numbers, PAC field access only). //! Provides reusable gate abstractions for peripherals used by the examples. +use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; + use crate::pac; /// Trait describing an AHB clock gate that can be toggled through MRCC. @@ -132,3 +134,380 @@ pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); } + +// ============================================== + +/// This type represents a divider in the range 1..=256. +/// +/// At a hardware level, this is an 8-bit register from 0..=255, +/// which adds one. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div8(pub(super) u8); + +impl Div8 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source + /// by 1, and `Div8::from_raw(255)` will divide the source by + /// 256. + pub const fn from_raw(n: u8) -> Self { + Self(n) + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source + /// by 1, and `Div8::from_divisor(256)` will divide the source + /// by 256. + /// + /// Will return `None` if `n` is not in the range `1..=256`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u16) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > (u8::MAX as u16) { + return None; + } + Some(Self(n as u8)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +#[derive(Debug, Clone)] +pub struct Clock { + pub frequency: u32, + pub power: PoweredClock, +} + +#[derive(Debug, Clone, Copy)] +pub enum PoweredClock { + HighPowerOnly, + AlwaysEnabled, +} + +/// ```text +/// ┌─────────────────────────────────────────────────────────┐ +/// │ │ +/// │ ┌───────────┐ clk_out ┌─────────┐ │ +/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ +/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ +/// EXTAL ──────┼──▷│ │───────────▷│ │ │ +/// │ └───────────┘ └─────────┘ │ +/// │ │ +/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ +/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_45m │ +/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ +/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_1m │ +/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ │ +/// │ ┌──────────┐ │ +/// │ │000 │ │ +/// │ clk_in │ │ │ +/// │ ───────────────▷│001 │ │ +/// │ fro_12m │ │ │ +/// │ ───────────────▷│010 │ │ +/// │ fro_hf_root │ │ │ +/// │ ───────────────▷│011 │ main_clk │ +/// │ │ │───────────────────────────┼──────▷ +/// clk_16k ──────┼─────────────────▷│100 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│101 │ │ +/// │ pll1_clk │ │ │ +/// │ ───────────────▷│110 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│111 │ │ +/// │ └──────────┘ │ +/// │ ▲ │ +/// │ │ │ +/// │ SCG SCS │ +/// │ SCG-Lite │ +/// └─────────────────────────────────────────────────────────┘ +/// +/// +/// clk_in ┌─────┐ +/// ───────────────▷│00 │ +/// clk_45m │ │ +/// ───────────────▷│01 │ ┌───────────┐ pll1_clk +/// none │ │─────▷│ SPLL │───────────────▷ +/// ───────────────▷│10 │ └───────────┘ +/// fro_12m │ │ +/// ───────────────▷│11 │ +/// └─────┘ +/// ``` +pub struct ClocksConfig { + // FIRC, FRO180, 45/60/90/180M clock source + pub firc: Option, + pub sirc: Option, +} + +// FIRC/FRO180M + +/// ```text +/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf +/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ +/// │ │ │ ├────┤ clk_45m +/// │ │ └─────▷│GATE│──────────▷ +/// └───────────┘ └────┘ +/// ``` +pub struct FircConfig { + pub frequency: FircFreqSel, + pub power: PoweredClock, + /// Is the "fro_hf" gated clock enabled? + pub fro_hf_enabled: bool, + /// Is the "clk_45m" gated clock enabled? + pub clk_45m_enabled: bool, + /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! + pub fro_hf_div: Option, +} + +pub enum FircFreqSel { + Mhz45, + Mhz60, + Mhz90, + Mhz180, +} + +// SIRC/FRO12M + +/// ```text +/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m +/// │ FRO12M │────────┬─────▷│ CG │──────────▷ +/// │ │ │ ├────┤ clk_1m +/// │ │ └─────▷│1/12│──────────▷ +/// └───────────┘ └────┘ +/// ``` +pub struct SircConfig { + pub power: PoweredClock, + // peripheral output, aka sirc_12mhz + pub fro_12m_enabled: bool, + /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! + pub fro_lf_div: Option, +} + +#[derive(Default, Debug, Clone)] +pub struct Clocks { + pub clk_in: Option, + + // FRO180M stuff + // + pub fro_hf_root: Option, + pub fro_hf: Option, + pub clk_45m: Option, + pub fro_hf_div: Option, + // + // End FRO180M + + // FRO12M stuff + pub fro_12m_root: Option, + pub fro_12m: Option, + pub clk_1m: Option, + pub fro_lf_div: Option, + // + // End FRO12M stuff + pub main_clk: Option, + pub pll1_clk: Option, +} + +static CLOCKS: critical_section::Mutex> = critical_section::Mutex::new(None); + +pub enum ClockError { + AlreadyInitialized, + BadConfig { clock: &'static str, reason: &'static str }, +} + +struct ClockOperator<'a> { + clocks: &'a mut Clocks, + config: &'a ClocksConfig, + + _mrcc0: pac::Mrcc0, + scg0: pac::Scg0, + syscon: pac::Syscon, +} + +impl ClockOperator<'_> { + fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { + const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { + clock: "firc", + reason: "For now, FIRC must be enabled and in default state!", + }); + + // Did the user give us a FIRC config? + let Some(firc) = self.config.firc.as_ref() else { + return HARDCODED_ERR; + }; + // Is the FIRC set to 45MHz (should be reset default) + if !matches!(firc.frequency, FircFreqSel::Mhz45) { + return HARDCODED_ERR; + } + let base_freq = 45_000_000; + + // Is the FIRC as expected? + let mut firc_ok = true; + + // Is the hardware currently set to the default 45MHz? + // + // NOTE: the SVD currently has the wrong(?) values for these: + // 45 -> 48 + // 60 -> 64 + // 90 -> 96 + // 180 -> 192 + // Probably correct-ish, but for a different trim value? + firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); + + // Check some values in the CSR + let csr = self.scg0.firccsr().read(); + // Is it enabled? + firc_ok &= csr.fircen().is_enabled(); + // Is it accurate? + firc_ok &= csr.fircacc().is_enabled_and_valid(); + // Is there no error? + firc_ok &= csr.fircerr().is_error_not_detected(); + // Is the FIRC the system clock? + firc_ok &= csr.fircsel().is_firc(); + // Is it valid? + firc_ok &= csr.fircvld().is_enabled_and_valid(); + + // Are we happy with the current (hardcoded) state? + if !firc_ok { + return HARDCODED_ERR; + } + + // Note that the fro_hf_root is active + self.clocks.fro_hf_root = Some(Clock { + frequency: base_freq, + power: firc.power, + }); + + // Okay! Now we're past that, let's enable all the downstream clocks. + let FircConfig { + frequency: _, + power, + fro_hf_enabled, + clk_45m_enabled, + fro_hf_div, + } = firc; + + // When is the FRO enabled? + let pow_set = match power { + PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes, + PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, + }; + + // Do we enable the `fro_hf` output? + let fro_hf_set = if *fro_hf_enabled { + self.clocks.fro_hf = Some(Clock { + frequency: base_freq, + power: *power, + }); + FircFclkPeriphEn::Enabled + } else { + FircFclkPeriphEn::Disabled + }; + + // Do we enable the `clk_45m` output? + let clk_45m_set = if *clk_45m_enabled { + self.clocks.clk_45m = Some(Clock { + frequency: 45_000_000, + power: *power, + }); + FircSclkPeriphEn::Enabled + } else { + FircSclkPeriphEn::Disabled + }; + + self.scg0.firccsr().modify(|_r, w| { + w.fircsten().variant(pow_set); + w.firc_fclk_periph_en().variant(fro_hf_set); + w.firc_sclk_periph_en().variant(clk_45m_set); + w + }); + + // Do we enable the `fro_hf_div` output? + if let Some(d) = fro_hf_div.as_ref() { + // We need `fro_hf` to be enabled + if !*fro_hf_enabled { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "fro_hf not enabled", + }); + } + + // Halt and reset the div + self.syscon.frohfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + w + }); + // Then change the div, unhalt it, and reset it + self.syscon.frohfdiv().write(|w| { + unsafe { + w.div().bits(d.into_bits()); + } + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frohfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_hf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } +} + +pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { + critical_section::with(|cs| { + if CLOCKS.borrow(cs).is_some() { + Err(ClockError::AlreadyInitialized) + } else { + Ok(()) + } + })?; + + let mut clocks = Clocks::default(); + let mut operator = ClockOperator { + clocks: &mut clocks, + config: &settings, + + _mrcc0: unsafe { pac::Mrcc0::steal() }, + scg0: unsafe { pac::Scg0::steal() }, + syscon: unsafe { pac::Syscon::steal() }, + }; + + operator.configure_firc_clocks()?; + // TODO, SIRC, and everything downstream + + Ok(()) +} + +/// Obtain the full clocks structure, calling the given closure in a critical section +/// +/// NOTE: Clocks implements `Clone`, +pub fn with_clocks R>(f: F) -> Option { + critical_section::with(|cs| { + let c = CLOCKS.borrow(cs).as_ref()?; + Some(f(c)) + }) +} -- cgit From 1ec472aa864d8add5dd14246324d671217fab304 Mon Sep 17 00:00:00 2001 From: James Munns Date: Wed, 12 Nov 2025 17:00:40 +0100 Subject: add frolf support --- src/clocks.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/src/clocks.rs b/src/clocks.rs index 74e0daeeb..a00b3e4c5 100644 --- a/src/clocks.rs +++ b/src/clocks.rs @@ -1,6 +1,9 @@ //! Clock control helpers (no magic numbers, PAC field access only). //! Provides reusable gate abstractions for peripherals used by the examples. -use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; +use mcxa_pac::scg0::{ + firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}, + sirccsr::{SircClkPeriphEn, Sircsten}, +}; use crate::pac; @@ -249,10 +252,12 @@ pub enum PoweredClock { /// ───────────────▷│11 │ /// └─────┘ /// ``` +#[non_exhaustive] pub struct ClocksConfig { // FIRC, FRO180, 45/60/90/180M clock source pub firc: Option, - pub sirc: Option, + // NOTE: I don't think we *can* disable the SIRC? + pub sirc: SircConfig, } // FIRC/FRO180M @@ -264,6 +269,7 @@ pub struct ClocksConfig { /// │ │ └─────▷│GATE│──────────▷ /// └───────────┘ └────┘ /// ``` +#[non_exhaustive] pub struct FircConfig { pub frequency: FircFreqSel, pub power: PoweredClock, @@ -291,6 +297,7 @@ pub enum FircFreqSel { /// │ │ └─────▷│1/12│──────────▷ /// └───────────┘ └────┘ /// ``` +#[non_exhaustive] pub struct SircConfig { pub power: PoweredClock, // peripheral output, aka sirc_12mhz @@ -300,6 +307,7 @@ pub struct SircConfig { } #[derive(Default, Debug, Clone)] +#[non_exhaustive] pub struct Clocks { pub clk_in: Option, @@ -325,6 +333,7 @@ pub struct Clocks { static CLOCKS: critical_section::Mutex> = critical_section::Mutex::new(None); +#[non_exhaustive] pub enum ClockError { AlreadyInitialized, BadConfig { clock: &'static str, reason: &'static str }, @@ -475,6 +484,96 @@ impl ClockOperator<'_> { Ok(()) } + + fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { + let SircConfig { + power, + fro_12m_enabled, + fro_lf_div, + } = &self.config.sirc; + let base_freq = 12_000_000; + + // Allow writes + self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); + self.clocks.fro_12m_root = Some(Clock { + frequency: base_freq, + power: *power, + }); + + let deep = match power { + PoweredClock::HighPowerOnly => Sircsten::Disabled, + PoweredClock::AlwaysEnabled => Sircsten::Enabled, + }; + let pclk = if *fro_12m_enabled { + self.clocks.fro_12m = Some(Clock { + frequency: base_freq, + power: *power, + }); + self.clocks.clk_1m = Some(Clock { + frequency: base_freq / 12, + power: *power, + }); + SircClkPeriphEn::Enabled + } else { + SircClkPeriphEn::Disabled + }; + + // Set sleep/peripheral usage + self.scg0.sirccsr().modify(|_r, w| { + w.sircsten().variant(deep); + w.sirc_clk_periph_en().variant(pclk); + w + }); + + while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} + if self.scg0.sirccsr().read().sircerr().is_error_detected() { + return Err(ClockError::BadConfig { + clock: "sirc", + reason: "error set", + }); + } + + // reset lock + self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); + + // Do we enable the `fro_lf_div` output? + if let Some(d) = fro_lf_div.as_ref() { + // We need `fro_lf` to be enabled + if !*fro_12m_enabled { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "fro_12m not enabled", + }); + } + + // Halt and reset the div + self.syscon.frolfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + w + }); + // Then change the div, unhalt it, and reset it + self.syscon.frolfdiv().write(|w| { + unsafe { + w.div().bits(d.into_bits()); + } + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frolfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_lf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + todo!() + } } pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { @@ -497,7 +596,8 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { }; operator.configure_firc_clocks()?; - // TODO, SIRC, and everything downstream + operator.configure_sirc_clocks()?; + // TODO, everything downstream Ok(()) } -- cgit From c004f3616e2959df51ed4785ee0bba4e4c373fc6 Mon Sep 17 00:00:00 2001 From: James Munns Date: Wed, 12 Nov 2025 17:41:48 +0100 Subject: Comment out old clock gating logic, will be restored --- src/clocks.rs | 260 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 132 insertions(+), 128 deletions(-) diff --git a/src/clocks.rs b/src/clocks.rs index a00b3e4c5..c86ed1cd5 100644 --- a/src/clocks.rs +++ b/src/clocks.rs @@ -7,136 +7,140 @@ use mcxa_pac::scg0::{ use crate::pac; -/// Trait describing an AHB clock gate that can be toggled through MRCC. -pub trait Gate { - /// Enable the clock gate. - unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); - - /// Return whether the clock gate is currently enabled. - fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; -} - -/// Enable a clock gate for the given peripheral set. -#[inline] -pub unsafe fn enable(peripherals: &pac::Peripherals) { - let mrcc = &peripherals.mrcc0; - G::enable(mrcc); - while !G::is_enabled(mrcc) {} - core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); -} - -/// Check whether a gate is currently enabled. -#[inline] -pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { - G::is_enabled(&peripherals.mrcc0) +pub trait SPConfHelper { + fn post_enable_config(&self, clocks: &Clocks) -> Result; } -macro_rules! impl_cc_gate { - ($name:ident, $reg:ident, $field:ident) => { - pub struct $name; - - impl Gate for $name { - #[inline] - unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { - mrcc.$reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { - mrcc.$reg().read().$field().is_enabled() - } - } - }; -} - -pub mod gate { - use super::*; - - impl_cc_gate!(Port2, mrcc_glb_cc1, port2); - impl_cc_gate!(Port3, mrcc_glb_cc1, port3); - impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); - impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); - impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); - impl_cc_gate!(Port1, mrcc_glb_cc1, port1); - impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); -} - -/// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. -pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { - enable::(peripherals); - enable::(peripherals); -} - -/// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. -pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { - enable::(peripherals); - enable::(peripherals); -} - -/// Convenience helper enabling the OSTIMER0 clock gate. -pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { - enable::(peripherals); -} - -pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { - // Use FRO_LF_DIV (already running) MUX=0 DIV=0 - let mrcc = &peripherals.mrcc0; - mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); - mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); -} - -pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { - // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) - let sys = &peripherals.syscon; - sys.frolfdiv().modify(|_, w| { - // DIV defaults to 0; keep it explicit and clear HALT - unsafe { w.div().bits(0) }.halt().run() - }); -} - -/// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. -/// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. -pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { - // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. - // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. - // Read it anyway for future generality. - let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; - let base = 12_000_000u32; - base / (div + 1) -} - -/// Enable clock gate and release reset for OSTIMER0. -/// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). -pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { - let mrcc = &peripherals.mrcc0; - mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); -} - -pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { - let vbat = &peripherals.vbat0; - // Enable FRO16K oscillator - vbat.froctla().modify(|_, w| w.fro_en().set_bit()); - - // Lock the control register - vbat.frolcka().modify(|_, w| w.lock().set_bit()); - - // Enable clock outputs to both VSYS and VDD_CORE domains - // Bit 0: clk_16k0 to VSYS domain - // Bit 1: clk_16k1 to VDD_CORE domain - vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); -} - -pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { - enable::(peripherals); - enable::(peripherals); -} - -pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { - // Use FRO_LF_DIV (already running) MUX=0 DIV=0 - let mrcc = &peripherals.mrcc0; - mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); - mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); -} +// /// Trait describing an AHB clock gate that can be toggled through MRCC. +// pub trait Gate { +// /// Enable the clock gate. +// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); + +// /// Return whether the clock gate is currently enabled. +// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; +// } + +// /// Enable a clock gate for the given peripheral set. +// #[inline] +// pub unsafe fn enable(peripherals: &pac::Peripherals) { +// let mrcc = &peripherals.mrcc0; +// G::enable(mrcc); +// while !G::is_enabled(mrcc) {} +// core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); +// } + +// /// Check whether a gate is currently enabled. +// #[inline] +// pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { +// G::is_enabled(&peripherals.mrcc0) +// } + +// macro_rules! impl_cc_gate { +// ($name:ident, $reg:ident, $field:ident) => { +// pub struct $name; + +// impl Gate for $name { +// #[inline] +// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { +// mrcc.$reg().modify(|_, w| w.$field().enabled()); +// } + +// #[inline] +// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { +// mrcc.$reg().read().$field().is_enabled() +// } +// } +// }; +// } + +// pub mod gate { +// use super::*; + +// impl_cc_gate!(Port2, mrcc_glb_cc1, port2); +// impl_cc_gate!(Port3, mrcc_glb_cc1, port3); +// impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); +// impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); +// impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); +// impl_cc_gate!(Port1, mrcc_glb_cc1, port1); +// impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); +// } + +// /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. +// pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. +// pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// /// Convenience helper enabling the OSTIMER0 clock gate. +// pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// } + +// pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { +// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); +// mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); +// } + +// pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { +// // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) +// let sys = &peripherals.syscon; +// sys.frolfdiv().modify(|_, w| { +// // DIV defaults to 0; keep it explicit and clear HALT +// unsafe { w.div().bits(0) }.halt().run() +// }); +// } + +// /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. +// /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. +// pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { +// // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. +// // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. +// // Read it anyway for future generality. +// let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; +// let base = 12_000_000u32; +// base / (div + 1) +// } + +// /// Enable clock gate and release reset for OSTIMER0. +// /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). +// pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); +// } + +// pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { +// let vbat = &peripherals.vbat0; +// // Enable FRO16K oscillator +// vbat.froctla().modify(|_, w| w.fro_en().set_bit()); + +// // Lock the control register +// vbat.frolcka().modify(|_, w| w.lock().set_bit()); + +// // Enable clock outputs to both VSYS and VDD_CORE domains +// // Bit 0: clk_16k0 to VSYS domain +// // Bit 1: clk_16k1 to VDD_CORE domain +// vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); +// } + +// pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { +// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); +// mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); +// } // ============================================== -- cgit From a2e63ff090d240f8dea0d448477990a89db114be Mon Sep 17 00:00:00 2001 From: James Munns Date: Wed, 12 Nov 2025 17:42:21 +0100 Subject: Move clocks into module --- src/clocks.rs | 617 ------------------------------------------------------ src/clocks/mod.rs | 617 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 617 insertions(+), 617 deletions(-) delete mode 100644 src/clocks.rs create mode 100644 src/clocks/mod.rs diff --git a/src/clocks.rs b/src/clocks.rs deleted file mode 100644 index c86ed1cd5..000000000 --- a/src/clocks.rs +++ /dev/null @@ -1,617 +0,0 @@ -//! Clock control helpers (no magic numbers, PAC field access only). -//! Provides reusable gate abstractions for peripherals used by the examples. -use mcxa_pac::scg0::{ - firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}, - sirccsr::{SircClkPeriphEn, Sircsten}, -}; - -use crate::pac; - -pub trait SPConfHelper { - fn post_enable_config(&self, clocks: &Clocks) -> Result; -} - -// /// Trait describing an AHB clock gate that can be toggled through MRCC. -// pub trait Gate { -// /// Enable the clock gate. -// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); - -// /// Return whether the clock gate is currently enabled. -// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; -// } - -// /// Enable a clock gate for the given peripheral set. -// #[inline] -// pub unsafe fn enable(peripherals: &pac::Peripherals) { -// let mrcc = &peripherals.mrcc0; -// G::enable(mrcc); -// while !G::is_enabled(mrcc) {} -// core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); -// } - -// /// Check whether a gate is currently enabled. -// #[inline] -// pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { -// G::is_enabled(&peripherals.mrcc0) -// } - -// macro_rules! impl_cc_gate { -// ($name:ident, $reg:ident, $field:ident) => { -// pub struct $name; - -// impl Gate for $name { -// #[inline] -// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { -// mrcc.$reg().modify(|_, w| w.$field().enabled()); -// } - -// #[inline] -// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { -// mrcc.$reg().read().$field().is_enabled() -// } -// } -// }; -// } - -// pub mod gate { -// use super::*; - -// impl_cc_gate!(Port2, mrcc_glb_cc1, port2); -// impl_cc_gate!(Port3, mrcc_glb_cc1, port3); -// impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); -// impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); -// impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); -// impl_cc_gate!(Port1, mrcc_glb_cc1, port1); -// impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); -// } - -// /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. -// pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. -// pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// /// Convenience helper enabling the OSTIMER0 clock gate. -// pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// } - -// pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { -// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); -// mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); -// } - -// pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { -// // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) -// let sys = &peripherals.syscon; -// sys.frolfdiv().modify(|_, w| { -// // DIV defaults to 0; keep it explicit and clear HALT -// unsafe { w.div().bits(0) }.halt().run() -// }); -// } - -// /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. -// /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. -// pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { -// // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. -// // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. -// // Read it anyway for future generality. -// let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; -// let base = 12_000_000u32; -// base / (div + 1) -// } - -// /// Enable clock gate and release reset for OSTIMER0. -// /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). -// pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); -// } - -// pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { -// let vbat = &peripherals.vbat0; -// // Enable FRO16K oscillator -// vbat.froctla().modify(|_, w| w.fro_en().set_bit()); - -// // Lock the control register -// vbat.frolcka().modify(|_, w| w.lock().set_bit()); - -// // Enable clock outputs to both VSYS and VDD_CORE domains -// // Bit 0: clk_16k0 to VSYS domain -// // Bit 1: clk_16k1 to VDD_CORE domain -// vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); -// } - -// pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { -// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); -// mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); -// } - -// ============================================== - -/// This type represents a divider in the range 1..=256. -/// -/// At a hardware level, this is an 8-bit register from 0..=255, -/// which adds one. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Div8(pub(super) u8); - -impl Div8 { - /// Store a "raw" divisor value that will divide the source by - /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source - /// by 1, and `Div8::from_raw(255)` will divide the source by - /// 256. - pub const fn from_raw(n: u8) -> Self { - Self(n) - } - - /// Store a specific divisor value that will divide the source - /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source - /// by 1, and `Div8::from_divisor(256)` will divide the source - /// by 256. - /// - /// Will return `None` if `n` is not in the range `1..=256`. - /// Consider [`Self::from_raw`] for an infallible version. - pub const fn from_divisor(n: u16) -> Option { - let Some(n) = n.checked_sub(1) else { - return None; - }; - if n > (u8::MAX as u16) { - return None; - } - Some(Self(n as u8)) - } - - /// Convert into "raw" bits form - #[inline(always)] - pub const fn into_bits(self) -> u8 { - self.0 - } - - /// Convert into "divisor" form, as a u32 for convenient frequency math - #[inline(always)] - pub const fn into_divisor(self) -> u32 { - self.0 as u32 + 1 - } -} - -#[derive(Debug, Clone)] -pub struct Clock { - pub frequency: u32, - pub power: PoweredClock, -} - -#[derive(Debug, Clone, Copy)] -pub enum PoweredClock { - HighPowerOnly, - AlwaysEnabled, -} - -/// ```text -/// ┌─────────────────────────────────────────────────────────┐ -/// │ │ -/// │ ┌───────────┐ clk_out ┌─────────┐ │ -/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ -/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ -/// EXTAL ──────┼──▷│ │───────────▷│ │ │ -/// │ └───────────┘ └─────────┘ │ -/// │ │ -/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ -/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_45m │ -/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ -/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_1m │ -/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ │ -/// │ ┌──────────┐ │ -/// │ │000 │ │ -/// │ clk_in │ │ │ -/// │ ───────────────▷│001 │ │ -/// │ fro_12m │ │ │ -/// │ ───────────────▷│010 │ │ -/// │ fro_hf_root │ │ │ -/// │ ───────────────▷│011 │ main_clk │ -/// │ │ │───────────────────────────┼──────▷ -/// clk_16k ──────┼─────────────────▷│100 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│101 │ │ -/// │ pll1_clk │ │ │ -/// │ ───────────────▷│110 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│111 │ │ -/// │ └──────────┘ │ -/// │ ▲ │ -/// │ │ │ -/// │ SCG SCS │ -/// │ SCG-Lite │ -/// └─────────────────────────────────────────────────────────┘ -/// -/// -/// clk_in ┌─────┐ -/// ───────────────▷│00 │ -/// clk_45m │ │ -/// ───────────────▷│01 │ ┌───────────┐ pll1_clk -/// none │ │─────▷│ SPLL │───────────────▷ -/// ───────────────▷│10 │ └───────────┘ -/// fro_12m │ │ -/// ───────────────▷│11 │ -/// └─────┘ -/// ``` -#[non_exhaustive] -pub struct ClocksConfig { - // FIRC, FRO180, 45/60/90/180M clock source - pub firc: Option, - // NOTE: I don't think we *can* disable the SIRC? - pub sirc: SircConfig, -} - -// FIRC/FRO180M - -/// ```text -/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf -/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ -/// │ │ │ ├────┤ clk_45m -/// │ │ └─────▷│GATE│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct FircConfig { - pub frequency: FircFreqSel, - pub power: PoweredClock, - /// Is the "fro_hf" gated clock enabled? - pub fro_hf_enabled: bool, - /// Is the "clk_45m" gated clock enabled? - pub clk_45m_enabled: bool, - /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! - pub fro_hf_div: Option, -} - -pub enum FircFreqSel { - Mhz45, - Mhz60, - Mhz90, - Mhz180, -} - -// SIRC/FRO12M - -/// ```text -/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m -/// │ FRO12M │────────┬─────▷│ CG │──────────▷ -/// │ │ │ ├────┤ clk_1m -/// │ │ └─────▷│1/12│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct SircConfig { - pub power: PoweredClock, - // peripheral output, aka sirc_12mhz - pub fro_12m_enabled: bool, - /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! - pub fro_lf_div: Option, -} - -#[derive(Default, Debug, Clone)] -#[non_exhaustive] -pub struct Clocks { - pub clk_in: Option, - - // FRO180M stuff - // - pub fro_hf_root: Option, - pub fro_hf: Option, - pub clk_45m: Option, - pub fro_hf_div: Option, - // - // End FRO180M - - // FRO12M stuff - pub fro_12m_root: Option, - pub fro_12m: Option, - pub clk_1m: Option, - pub fro_lf_div: Option, - // - // End FRO12M stuff - pub main_clk: Option, - pub pll1_clk: Option, -} - -static CLOCKS: critical_section::Mutex> = critical_section::Mutex::new(None); - -#[non_exhaustive] -pub enum ClockError { - AlreadyInitialized, - BadConfig { clock: &'static str, reason: &'static str }, -} - -struct ClockOperator<'a> { - clocks: &'a mut Clocks, - config: &'a ClocksConfig, - - _mrcc0: pac::Mrcc0, - scg0: pac::Scg0, - syscon: pac::Syscon, -} - -impl ClockOperator<'_> { - fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { - const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { - clock: "firc", - reason: "For now, FIRC must be enabled and in default state!", - }); - - // Did the user give us a FIRC config? - let Some(firc) = self.config.firc.as_ref() else { - return HARDCODED_ERR; - }; - // Is the FIRC set to 45MHz (should be reset default) - if !matches!(firc.frequency, FircFreqSel::Mhz45) { - return HARDCODED_ERR; - } - let base_freq = 45_000_000; - - // Is the FIRC as expected? - let mut firc_ok = true; - - // Is the hardware currently set to the default 45MHz? - // - // NOTE: the SVD currently has the wrong(?) values for these: - // 45 -> 48 - // 60 -> 64 - // 90 -> 96 - // 180 -> 192 - // Probably correct-ish, but for a different trim value? - firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); - - // Check some values in the CSR - let csr = self.scg0.firccsr().read(); - // Is it enabled? - firc_ok &= csr.fircen().is_enabled(); - // Is it accurate? - firc_ok &= csr.fircacc().is_enabled_and_valid(); - // Is there no error? - firc_ok &= csr.fircerr().is_error_not_detected(); - // Is the FIRC the system clock? - firc_ok &= csr.fircsel().is_firc(); - // Is it valid? - firc_ok &= csr.fircvld().is_enabled_and_valid(); - - // Are we happy with the current (hardcoded) state? - if !firc_ok { - return HARDCODED_ERR; - } - - // Note that the fro_hf_root is active - self.clocks.fro_hf_root = Some(Clock { - frequency: base_freq, - power: firc.power, - }); - - // Okay! Now we're past that, let's enable all the downstream clocks. - let FircConfig { - frequency: _, - power, - fro_hf_enabled, - clk_45m_enabled, - fro_hf_div, - } = firc; - - // When is the FRO enabled? - let pow_set = match power { - PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes, - PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, - }; - - // Do we enable the `fro_hf` output? - let fro_hf_set = if *fro_hf_enabled { - self.clocks.fro_hf = Some(Clock { - frequency: base_freq, - power: *power, - }); - FircFclkPeriphEn::Enabled - } else { - FircFclkPeriphEn::Disabled - }; - - // Do we enable the `clk_45m` output? - let clk_45m_set = if *clk_45m_enabled { - self.clocks.clk_45m = Some(Clock { - frequency: 45_000_000, - power: *power, - }); - FircSclkPeriphEn::Enabled - } else { - FircSclkPeriphEn::Disabled - }; - - self.scg0.firccsr().modify(|_r, w| { - w.fircsten().variant(pow_set); - w.firc_fclk_periph_en().variant(fro_hf_set); - w.firc_sclk_periph_en().variant(clk_45m_set); - w - }); - - // Do we enable the `fro_hf_div` output? - if let Some(d) = fro_hf_div.as_ref() { - // We need `fro_hf` to be enabled - if !*fro_hf_enabled { - return Err(ClockError::BadConfig { - clock: "fro_hf_div", - reason: "fro_hf not enabled", - }); - } - - // Halt and reset the div - self.syscon.frohfdiv().write(|w| { - w.halt().halt(); - w.reset().asserted(); - w - }); - // Then change the div, unhalt it, and reset it - self.syscon.frohfdiv().write(|w| { - unsafe { - w.div().bits(d.into_bits()); - } - w.halt().run(); - w.reset().released(); - w - }); - - // Wait for clock to stabilize - while self.syscon.frohfdiv().read().unstab().is_ongoing() {} - - // Store off the clock info - self.clocks.fro_hf_div = Some(Clock { - frequency: base_freq / d.into_divisor(), - power: *power, - }); - } - - Ok(()) - } - - fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { - let SircConfig { - power, - fro_12m_enabled, - fro_lf_div, - } = &self.config.sirc; - let base_freq = 12_000_000; - - // Allow writes - self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); - self.clocks.fro_12m_root = Some(Clock { - frequency: base_freq, - power: *power, - }); - - let deep = match power { - PoweredClock::HighPowerOnly => Sircsten::Disabled, - PoweredClock::AlwaysEnabled => Sircsten::Enabled, - }; - let pclk = if *fro_12m_enabled { - self.clocks.fro_12m = Some(Clock { - frequency: base_freq, - power: *power, - }); - self.clocks.clk_1m = Some(Clock { - frequency: base_freq / 12, - power: *power, - }); - SircClkPeriphEn::Enabled - } else { - SircClkPeriphEn::Disabled - }; - - // Set sleep/peripheral usage - self.scg0.sirccsr().modify(|_r, w| { - w.sircsten().variant(deep); - w.sirc_clk_periph_en().variant(pclk); - w - }); - - while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} - if self.scg0.sirccsr().read().sircerr().is_error_detected() { - return Err(ClockError::BadConfig { - clock: "sirc", - reason: "error set", - }); - } - - // reset lock - self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); - - // Do we enable the `fro_lf_div` output? - if let Some(d) = fro_lf_div.as_ref() { - // We need `fro_lf` to be enabled - if !*fro_12m_enabled { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "fro_12m not enabled", - }); - } - - // Halt and reset the div - self.syscon.frolfdiv().write(|w| { - w.halt().halt(); - w.reset().asserted(); - w - }); - // Then change the div, unhalt it, and reset it - self.syscon.frolfdiv().write(|w| { - unsafe { - w.div().bits(d.into_bits()); - } - w.halt().run(); - w.reset().released(); - w - }); - - // Wait for clock to stabilize - while self.syscon.frolfdiv().read().unstab().is_ongoing() {} - - // Store off the clock info - self.clocks.fro_lf_div = Some(Clock { - frequency: base_freq / d.into_divisor(), - power: *power, - }); - } - - todo!() - } -} - -pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { - critical_section::with(|cs| { - if CLOCKS.borrow(cs).is_some() { - Err(ClockError::AlreadyInitialized) - } else { - Ok(()) - } - })?; - - let mut clocks = Clocks::default(); - let mut operator = ClockOperator { - clocks: &mut clocks, - config: &settings, - - _mrcc0: unsafe { pac::Mrcc0::steal() }, - scg0: unsafe { pac::Scg0::steal() }, - syscon: unsafe { pac::Syscon::steal() }, - }; - - operator.configure_firc_clocks()?; - operator.configure_sirc_clocks()?; - // TODO, everything downstream - - Ok(()) -} - -/// Obtain the full clocks structure, calling the given closure in a critical section -/// -/// NOTE: Clocks implements `Clone`, -pub fn with_clocks R>(f: F) -> Option { - critical_section::with(|cs| { - let c = CLOCKS.borrow(cs).as_ref()?; - Some(f(c)) - }) -} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs new file mode 100644 index 000000000..c86ed1cd5 --- /dev/null +++ b/src/clocks/mod.rs @@ -0,0 +1,617 @@ +//! Clock control helpers (no magic numbers, PAC field access only). +//! Provides reusable gate abstractions for peripherals used by the examples. +use mcxa_pac::scg0::{ + firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}, + sirccsr::{SircClkPeriphEn, Sircsten}, +}; + +use crate::pac; + +pub trait SPConfHelper { + fn post_enable_config(&self, clocks: &Clocks) -> Result; +} + +// /// Trait describing an AHB clock gate that can be toggled through MRCC. +// pub trait Gate { +// /// Enable the clock gate. +// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); + +// /// Return whether the clock gate is currently enabled. +// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; +// } + +// /// Enable a clock gate for the given peripheral set. +// #[inline] +// pub unsafe fn enable(peripherals: &pac::Peripherals) { +// let mrcc = &peripherals.mrcc0; +// G::enable(mrcc); +// while !G::is_enabled(mrcc) {} +// core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); +// } + +// /// Check whether a gate is currently enabled. +// #[inline] +// pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { +// G::is_enabled(&peripherals.mrcc0) +// } + +// macro_rules! impl_cc_gate { +// ($name:ident, $reg:ident, $field:ident) => { +// pub struct $name; + +// impl Gate for $name { +// #[inline] +// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { +// mrcc.$reg().modify(|_, w| w.$field().enabled()); +// } + +// #[inline] +// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { +// mrcc.$reg().read().$field().is_enabled() +// } +// } +// }; +// } + +// pub mod gate { +// use super::*; + +// impl_cc_gate!(Port2, mrcc_glb_cc1, port2); +// impl_cc_gate!(Port3, mrcc_glb_cc1, port3); +// impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); +// impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); +// impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); +// impl_cc_gate!(Port1, mrcc_glb_cc1, port1); +// impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); +// } + +// /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. +// pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. +// pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// /// Convenience helper enabling the OSTIMER0 clock gate. +// pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// } + +// pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { +// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); +// mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); +// } + +// pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { +// // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) +// let sys = &peripherals.syscon; +// sys.frolfdiv().modify(|_, w| { +// // DIV defaults to 0; keep it explicit and clear HALT +// unsafe { w.div().bits(0) }.halt().run() +// }); +// } + +// /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. +// /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. +// pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { +// // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. +// // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. +// // Read it anyway for future generality. +// let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; +// let base = 12_000_000u32; +// base / (div + 1) +// } + +// /// Enable clock gate and release reset for OSTIMER0. +// /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). +// pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); +// } + +// pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { +// let vbat = &peripherals.vbat0; +// // Enable FRO16K oscillator +// vbat.froctla().modify(|_, w| w.fro_en().set_bit()); + +// // Lock the control register +// vbat.frolcka().modify(|_, w| w.lock().set_bit()); + +// // Enable clock outputs to both VSYS and VDD_CORE domains +// // Bit 0: clk_16k0 to VSYS domain +// // Bit 1: clk_16k1 to VDD_CORE domain +// vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); +// } + +// pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { +// enable::(peripherals); +// enable::(peripherals); +// } + +// pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { +// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 +// let mrcc = &peripherals.mrcc0; +// mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); +// mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); +// } + +// ============================================== + +/// This type represents a divider in the range 1..=256. +/// +/// At a hardware level, this is an 8-bit register from 0..=255, +/// which adds one. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div8(pub(super) u8); + +impl Div8 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source + /// by 1, and `Div8::from_raw(255)` will divide the source by + /// 256. + pub const fn from_raw(n: u8) -> Self { + Self(n) + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source + /// by 1, and `Div8::from_divisor(256)` will divide the source + /// by 256. + /// + /// Will return `None` if `n` is not in the range `1..=256`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u16) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > (u8::MAX as u16) { + return None; + } + Some(Self(n as u8)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +#[derive(Debug, Clone)] +pub struct Clock { + pub frequency: u32, + pub power: PoweredClock, +} + +#[derive(Debug, Clone, Copy)] +pub enum PoweredClock { + HighPowerOnly, + AlwaysEnabled, +} + +/// ```text +/// ┌─────────────────────────────────────────────────────────┐ +/// │ │ +/// │ ┌───────────┐ clk_out ┌─────────┐ │ +/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ +/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ +/// EXTAL ──────┼──▷│ │───────────▷│ │ │ +/// │ └───────────┘ └─────────┘ │ +/// │ │ +/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ +/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_45m │ +/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ +/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_1m │ +/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ │ +/// │ ┌──────────┐ │ +/// │ │000 │ │ +/// │ clk_in │ │ │ +/// │ ───────────────▷│001 │ │ +/// │ fro_12m │ │ │ +/// │ ───────────────▷│010 │ │ +/// │ fro_hf_root │ │ │ +/// │ ───────────────▷│011 │ main_clk │ +/// │ │ │───────────────────────────┼──────▷ +/// clk_16k ──────┼─────────────────▷│100 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│101 │ │ +/// │ pll1_clk │ │ │ +/// │ ───────────────▷│110 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│111 │ │ +/// │ └──────────┘ │ +/// │ ▲ │ +/// │ │ │ +/// │ SCG SCS │ +/// │ SCG-Lite │ +/// └─────────────────────────────────────────────────────────┘ +/// +/// +/// clk_in ┌─────┐ +/// ───────────────▷│00 │ +/// clk_45m │ │ +/// ───────────────▷│01 │ ┌───────────┐ pll1_clk +/// none │ │─────▷│ SPLL │───────────────▷ +/// ───────────────▷│10 │ └───────────┘ +/// fro_12m │ │ +/// ───────────────▷│11 │ +/// └─────┘ +/// ``` +#[non_exhaustive] +pub struct ClocksConfig { + // FIRC, FRO180, 45/60/90/180M clock source + pub firc: Option, + // NOTE: I don't think we *can* disable the SIRC? + pub sirc: SircConfig, +} + +// FIRC/FRO180M + +/// ```text +/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf +/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ +/// │ │ │ ├────┤ clk_45m +/// │ │ └─────▷│GATE│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct FircConfig { + pub frequency: FircFreqSel, + pub power: PoweredClock, + /// Is the "fro_hf" gated clock enabled? + pub fro_hf_enabled: bool, + /// Is the "clk_45m" gated clock enabled? + pub clk_45m_enabled: bool, + /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! + pub fro_hf_div: Option, +} + +pub enum FircFreqSel { + Mhz45, + Mhz60, + Mhz90, + Mhz180, +} + +// SIRC/FRO12M + +/// ```text +/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m +/// │ FRO12M │────────┬─────▷│ CG │──────────▷ +/// │ │ │ ├────┤ clk_1m +/// │ │ └─────▷│1/12│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct SircConfig { + pub power: PoweredClock, + // peripheral output, aka sirc_12mhz + pub fro_12m_enabled: bool, + /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! + pub fro_lf_div: Option, +} + +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct Clocks { + pub clk_in: Option, + + // FRO180M stuff + // + pub fro_hf_root: Option, + pub fro_hf: Option, + pub clk_45m: Option, + pub fro_hf_div: Option, + // + // End FRO180M + + // FRO12M stuff + pub fro_12m_root: Option, + pub fro_12m: Option, + pub clk_1m: Option, + pub fro_lf_div: Option, + // + // End FRO12M stuff + pub main_clk: Option, + pub pll1_clk: Option, +} + +static CLOCKS: critical_section::Mutex> = critical_section::Mutex::new(None); + +#[non_exhaustive] +pub enum ClockError { + AlreadyInitialized, + BadConfig { clock: &'static str, reason: &'static str }, +} + +struct ClockOperator<'a> { + clocks: &'a mut Clocks, + config: &'a ClocksConfig, + + _mrcc0: pac::Mrcc0, + scg0: pac::Scg0, + syscon: pac::Syscon, +} + +impl ClockOperator<'_> { + fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { + const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { + clock: "firc", + reason: "For now, FIRC must be enabled and in default state!", + }); + + // Did the user give us a FIRC config? + let Some(firc) = self.config.firc.as_ref() else { + return HARDCODED_ERR; + }; + // Is the FIRC set to 45MHz (should be reset default) + if !matches!(firc.frequency, FircFreqSel::Mhz45) { + return HARDCODED_ERR; + } + let base_freq = 45_000_000; + + // Is the FIRC as expected? + let mut firc_ok = true; + + // Is the hardware currently set to the default 45MHz? + // + // NOTE: the SVD currently has the wrong(?) values for these: + // 45 -> 48 + // 60 -> 64 + // 90 -> 96 + // 180 -> 192 + // Probably correct-ish, but for a different trim value? + firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); + + // Check some values in the CSR + let csr = self.scg0.firccsr().read(); + // Is it enabled? + firc_ok &= csr.fircen().is_enabled(); + // Is it accurate? + firc_ok &= csr.fircacc().is_enabled_and_valid(); + // Is there no error? + firc_ok &= csr.fircerr().is_error_not_detected(); + // Is the FIRC the system clock? + firc_ok &= csr.fircsel().is_firc(); + // Is it valid? + firc_ok &= csr.fircvld().is_enabled_and_valid(); + + // Are we happy with the current (hardcoded) state? + if !firc_ok { + return HARDCODED_ERR; + } + + // Note that the fro_hf_root is active + self.clocks.fro_hf_root = Some(Clock { + frequency: base_freq, + power: firc.power, + }); + + // Okay! Now we're past that, let's enable all the downstream clocks. + let FircConfig { + frequency: _, + power, + fro_hf_enabled, + clk_45m_enabled, + fro_hf_div, + } = firc; + + // When is the FRO enabled? + let pow_set = match power { + PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes, + PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, + }; + + // Do we enable the `fro_hf` output? + let fro_hf_set = if *fro_hf_enabled { + self.clocks.fro_hf = Some(Clock { + frequency: base_freq, + power: *power, + }); + FircFclkPeriphEn::Enabled + } else { + FircFclkPeriphEn::Disabled + }; + + // Do we enable the `clk_45m` output? + let clk_45m_set = if *clk_45m_enabled { + self.clocks.clk_45m = Some(Clock { + frequency: 45_000_000, + power: *power, + }); + FircSclkPeriphEn::Enabled + } else { + FircSclkPeriphEn::Disabled + }; + + self.scg0.firccsr().modify(|_r, w| { + w.fircsten().variant(pow_set); + w.firc_fclk_periph_en().variant(fro_hf_set); + w.firc_sclk_periph_en().variant(clk_45m_set); + w + }); + + // Do we enable the `fro_hf_div` output? + if let Some(d) = fro_hf_div.as_ref() { + // We need `fro_hf` to be enabled + if !*fro_hf_enabled { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "fro_hf not enabled", + }); + } + + // Halt and reset the div + self.syscon.frohfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + w + }); + // Then change the div, unhalt it, and reset it + self.syscon.frohfdiv().write(|w| { + unsafe { + w.div().bits(d.into_bits()); + } + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frohfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_hf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + Ok(()) + } + + fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { + let SircConfig { + power, + fro_12m_enabled, + fro_lf_div, + } = &self.config.sirc; + let base_freq = 12_000_000; + + // Allow writes + self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); + self.clocks.fro_12m_root = Some(Clock { + frequency: base_freq, + power: *power, + }); + + let deep = match power { + PoweredClock::HighPowerOnly => Sircsten::Disabled, + PoweredClock::AlwaysEnabled => Sircsten::Enabled, + }; + let pclk = if *fro_12m_enabled { + self.clocks.fro_12m = Some(Clock { + frequency: base_freq, + power: *power, + }); + self.clocks.clk_1m = Some(Clock { + frequency: base_freq / 12, + power: *power, + }); + SircClkPeriphEn::Enabled + } else { + SircClkPeriphEn::Disabled + }; + + // Set sleep/peripheral usage + self.scg0.sirccsr().modify(|_r, w| { + w.sircsten().variant(deep); + w.sirc_clk_periph_en().variant(pclk); + w + }); + + while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} + if self.scg0.sirccsr().read().sircerr().is_error_detected() { + return Err(ClockError::BadConfig { + clock: "sirc", + reason: "error set", + }); + } + + // reset lock + self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); + + // Do we enable the `fro_lf_div` output? + if let Some(d) = fro_lf_div.as_ref() { + // We need `fro_lf` to be enabled + if !*fro_12m_enabled { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "fro_12m not enabled", + }); + } + + // Halt and reset the div + self.syscon.frolfdiv().write(|w| { + w.halt().halt(); + w.reset().asserted(); + w + }); + // Then change the div, unhalt it, and reset it + self.syscon.frolfdiv().write(|w| { + unsafe { + w.div().bits(d.into_bits()); + } + w.halt().run(); + w.reset().released(); + w + }); + + // Wait for clock to stabilize + while self.syscon.frolfdiv().read().unstab().is_ongoing() {} + + // Store off the clock info + self.clocks.fro_lf_div = Some(Clock { + frequency: base_freq / d.into_divisor(), + power: *power, + }); + } + + todo!() + } +} + +pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { + critical_section::with(|cs| { + if CLOCKS.borrow(cs).is_some() { + Err(ClockError::AlreadyInitialized) + } else { + Ok(()) + } + })?; + + let mut clocks = Clocks::default(); + let mut operator = ClockOperator { + clocks: &mut clocks, + config: &settings, + + _mrcc0: unsafe { pac::Mrcc0::steal() }, + scg0: unsafe { pac::Scg0::steal() }, + syscon: unsafe { pac::Syscon::steal() }, + }; + + operator.configure_firc_clocks()?; + operator.configure_sirc_clocks()?; + // TODO, everything downstream + + Ok(()) +} + +/// Obtain the full clocks structure, calling the given closure in a critical section +/// +/// NOTE: Clocks implements `Clone`, +pub fn with_clocks R>(f: F) -> Option { + critical_section::with(|cs| { + let c = CLOCKS.borrow(cs).as_ref()?; + Some(f(c)) + }) +} -- cgit From 2f08491f1a09df79bf1d41e26217d21b0bd7bdae Mon Sep 17 00:00:00 2001 From: James Munns Date: Wed, 12 Nov 2025 18:54:27 +0100 Subject: Start adding peripheral helpers --- Cargo.toml | 2 +- src/clocks/mod.rs | 69 +++++++++++++++++++++--- src/clocks/periph_helpers.rs | 125 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 src/clocks/periph_helpers.rs diff --git a/Cargo.toml b/Cargo.toml index 8ec547494..afcb2d3c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ embassy-time = "0.5.0" embassy-time-driver = "0.2.1" embedded-io = "0.6" heapless = "0.8" -mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt"], rev = "f0281344c605ab24c38979553b41a1655c50625c", version = "0.1.0" } +mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt"], rev = "f25b7aed0b8ad8e610012046e09340926cbd51a2", version = "0.1.0" } paste = "1.0.15" embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index c86ed1cd5..ad48a9cf2 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -6,10 +6,7 @@ use mcxa_pac::scg0::{ }; use crate::pac; - -pub trait SPConfHelper { - fn post_enable_config(&self, clocks: &Clocks) -> Result; -} +pub mod periph_helpers; // /// Trait describing an AHB clock gate that can be toggled through MRCC. // pub trait Gate { @@ -198,10 +195,22 @@ pub struct Clock { #[derive(Debug, Clone, Copy)] pub enum PoweredClock { - HighPowerOnly, + NormalEnabledDeepSleepDisabled, AlwaysEnabled, } +impl PoweredClock { + /// Does THIS clock meet the power requirements of the OTHER clock? + pub fn meets_requirement_of(&self, other: &Self) -> bool { + match (self, other) { + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, + (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, + (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, + } + } +} + /// ```text /// ┌─────────────────────────────────────────────────────────┐ /// │ │ @@ -341,6 +350,7 @@ static CLOCKS: critical_section::Mutex> = critical_section::Mutex pub enum ClockError { AlreadyInitialized, BadConfig { clock: &'static str, reason: &'static str }, + NotImplemented { clock: &'static str }, } struct ClockOperator<'a> { @@ -417,7 +427,7 @@ impl ClockOperator<'_> { // When is the FRO enabled? let pow_set = match power { - PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes, + PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, }; @@ -505,7 +515,7 @@ impl ClockOperator<'_> { }); let deep = match power { - PoweredClock::HighPowerOnly => Sircsten::Disabled, + PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, PoweredClock::AlwaysEnabled => Sircsten::Enabled, }; let pclk = if *fro_12m_enabled { @@ -615,3 +625,48 @@ pub fn with_clocks R>(f: F) -> Option { Some(f(c)) }) } + +impl Clocks { + pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.fro_lf_div.as_ref() else { + return Err(ClockError::BadConfig { clock: "fro_lf_div", reason: "required but not active" }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { clock: "fro_lf_div", reason: "not low power active" }); + } + Ok(clk.frequency) + } + + 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) + } + + pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "clk_in" }) + } + + pub fn ensure_clk_16k_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "clk_16k" }) + } + + 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) + } + + pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) + } + +} diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs new file mode 100644 index 000000000..c4d4e9e3b --- /dev/null +++ b/src/clocks/periph_helpers.rs @@ -0,0 +1,125 @@ +use super::{ClockError, Clocks, Div8, PoweredClock}; +use crate::pac; + +pub trait SPConfHelper { + fn post_enable_config(&self, clocks: &Clocks) -> Result; +} + +// config types + +pub enum LpuartClockSel { + /// FRO12M/FRO_LF/SIRC clock source, passed through divider + /// "fro_lf_div" + FroLfDiv, + /// FRO180M/FRO_HF/FIRC clock source, passed through divider + /// "fro_hf_div" + FroHfDiv, + /// SOSC/XTAL/EXTAL clock source + ClkIn, + /// FRO16K/clk_16k source + Clk16K, + /// clk_1m/FRO_LF divided by 12 + Clk1M, + /// Output of PLL1, passed through clock divider, + /// "pll1_clk_div", maybe "pll1_lf_div"? + Pll1ClkDiv, + /// Disabled + None, +} + +pub enum LpuartInstance { + Lpuart0, + Lpuart1, + Lpuart2, + Lpuart3, + Lpuart4, + Lpuart5, +} + +pub struct LpuartConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div8, + /// Which instance is this? + // NOTE: should not be user settable + instance: LpuartInstance, +} + +// impls + +impl SPConfHelper for LpuartConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + // check that source is suitable + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; + + let (clkdiv, clksel) = match self.instance { + LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), + LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), + LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), + LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), + LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), + LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), + }; + + let (freq, variant) = match self.source { + LpuartClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + LpuartClockSel::FroHfDiv => { + let freq = clocks.ensure_fro_hf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc2) + } + LpuartClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + LpuartClockSel::Clk16K => { + let freq = clocks.ensure_clk_16k_active(&self.power)?; + (freq, Mux::ClkrootFunc4) + } + LpuartClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + LpuartClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + LpuartClockSel::None => unsafe { + // no ClkrootFunc7, just write manually for now + clksel.write(|w| w.bits(0b111)); + clkdiv.modify(|_r, w| { + w.reset().on(); + w.halt().on(); + w + }); + return Ok(0); + }, + }; + + // set clksel + clksel.modify(|_r, w| w.mux().variant(variant)); + + // Set up clkdiv + clkdiv.modify(|_r, w| { + w.halt().on(); + w.reset().on(); + w + }); + clkdiv.modify(|_r, w| { + w.halt().off(); + w.reset().off(); + unsafe { w.div().bits(self.div.into_bits()) }; + w + }); + + while clkdiv.read().unstab().is_on() {} + + Ok(freq / self.div.into_divisor()) + } +} -- cgit From 4979747286af4cd7bab43f8048b0ce6f00cc5ab6 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 14:39:23 +0100 Subject: Plumb clocks changes to lpuart --- src/clocks/mod.rs | 301 ++++++++++++++++++++++++++++++++----------- src/clocks/periph_helpers.rs | 3 +- src/lib.rs | 14 +- src/lpuart/buffered.rs | 11 +- src/lpuart/mod.rs | 136 ++++++------------- 5 files changed, 291 insertions(+), 174 deletions(-) diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index ad48a9cf2..63a8fb64d 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -1,66 +1,157 @@ //! Clock control helpers (no magic numbers, PAC field access only). //! Provides reusable gate abstractions for peripherals used by the examples. +use core::cell::RefCell; + use mcxa_pac::scg0::{ firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}, sirccsr::{SircClkPeriphEn, Sircsten}, }; +use periph_helpers::SPConfHelper; use crate::pac; pub mod periph_helpers; -// /// Trait describing an AHB clock gate that can be toggled through MRCC. -// pub trait Gate { -// /// Enable the clock gate. -// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); +/// Trait describing an AHB clock gate that can be toggled through MRCC. +pub trait Gate { + type MrccPeriphConfig: SPConfHelper; -// /// Return whether the clock gate is currently enabled. -// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; -// } + /// Enable the clock gate. + unsafe fn enable_clock(); -// /// Enable a clock gate for the given peripheral set. -// #[inline] -// pub unsafe fn enable(peripherals: &pac::Peripherals) { -// let mrcc = &peripherals.mrcc0; -// G::enable(mrcc); -// while !G::is_enabled(mrcc) {} -// core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); -// } + /// Disable the clock gate. + unsafe fn disable_clock(); -// /// Check whether a gate is currently enabled. -// #[inline] -// pub fn is_enabled(peripherals: &pac::Peripherals) -> bool { -// G::is_enabled(&peripherals.mrcc0) -// } + /// Drive the peripheral into reset. + unsafe fn assert_reset(); -// macro_rules! impl_cc_gate { -// ($name:ident, $reg:ident, $field:ident) => { -// pub struct $name; - -// impl Gate for $name { -// #[inline] -// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { -// mrcc.$reg().modify(|_, w| w.$field().enabled()); -// } - -// #[inline] -// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { -// mrcc.$reg().read().$field().is_enabled() -// } -// } -// }; -// } + /// Drive the peripheral out of reset. + unsafe fn release_reset(); -// pub mod gate { -// use super::*; + /// Return whether the clock gate is currently enabled. + fn is_clock_enabled() -> bool; -// impl_cc_gate!(Port2, mrcc_glb_cc1, port2); -// impl_cc_gate!(Port3, mrcc_glb_cc1, port3); -// impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); -// impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); -// impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); -// impl_cc_gate!(Port1, mrcc_glb_cc1, port1); -// impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); -// } + /// . + fn is_reset_released() -> bool; +} + +#[inline] +pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { + let freq = enable::(cfg)?; + pulse_reset::(); + Ok(freq) +} + +/// Enable a clock gate for the given peripheral set. +#[inline] +pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { + G::enable_clock(); + while !G::is_clock_enabled() {} + core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); + + let freq = critical_section::with(|cs| { + let clocks = CLOCKS.borrow_ref(cs); + let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; + cfg.post_enable_config(clocks) + }); + + freq.inspect_err(|_e| { + G::disable_clock(); + }) +} + +pub unsafe fn disable() { + G::disable_clock(); +} + +/// Check whether a gate is currently enabled. +#[inline] +pub fn is_clock_enabled() -> bool { + G::is_clock_enabled() +} + +/// Release a reset line for the given peripheral set. +#[inline] +pub unsafe fn release_reset() { + G::release_reset(); +} + +/// Assert a reset line for the given peripheral set. +#[inline] +pub unsafe fn assert_reset() { + G::assert_reset(); +} + +/// Pulse a reset line (assert then release) with a short delay. +#[inline] +pub unsafe fn pulse_reset() { + G::assert_reset(); + cortex_m::asm::nop(); + cortex_m::asm::nop(); + G::release_reset(); +} + +macro_rules! impl_cc_gate { + ($name:ident, $reg:ident, $field:ident, $config:ty) => { + impl Gate for crate::peripherals::$name { + type MrccPeriphConfig = $config; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_r, w| w.$field().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().read().$field().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().read().$field().is_enabled() + } + } + }; +} + +pub struct UnimplementedConfig; +impl SPConfHelper for UnimplementedConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Err(ClockError::UnimplementedConfig) + } +} + +pub mod gate { + use super::{periph_helpers::LpuartConfig, *}; + + impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, UnimplementedConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, UnimplementedConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, UnimplementedConfig); + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, UnimplementedConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, UnimplementedConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, UnimplementedConfig); +} // /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. // pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { @@ -113,20 +204,6 @@ pub mod periph_helpers; // mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); // } -// pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { -// let vbat = &peripherals.vbat0; -// // Enable FRO16K oscillator -// vbat.froctla().modify(|_, w| w.fro_en().set_bit()); - -// // Lock the control register -// vbat.frolcka().modify(|_, w| w.lock().set_bit()); - -// // Enable clock outputs to both VSYS and VDD_CORE domains -// // Bit 0: clk_16k0 to VSYS domain -// // Bit 1: clk_16k1 to VDD_CORE domain -// vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); -// } - // pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { // enable::(peripherals); // enable::(peripherals); @@ -271,6 +348,7 @@ pub struct ClocksConfig { pub firc: Option, // NOTE: I don't think we *can* disable the SIRC? pub sirc: SircConfig, + pub fro16k: Option, } // FIRC/FRO180M @@ -340,17 +418,30 @@ pub struct Clocks { pub fro_lf_div: Option, // // End FRO12M stuff + + pub clk_16k_vsys: Option, + pub clk_16k_vdd_core: Option, pub main_clk: Option, pub pll1_clk: Option, } -static CLOCKS: critical_section::Mutex> = critical_section::Mutex::new(None); +#[non_exhaustive] +pub struct Fro16KConfig { + pub vsys_domain_active: bool, + pub vdd_core_domain_active: bool, +} + +static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum ClockError { + NeverInitialized, AlreadyInitialized, BadConfig { clock: &'static str, reason: &'static str }, NotImplemented { clock: &'static str }, + UnimplementedConfig, } struct ClockOperator<'a> { @@ -360,6 +451,7 @@ struct ClockOperator<'a> { _mrcc0: pac::Mrcc0, scg0: pac::Scg0, syscon: pac::Syscon, + vbat0: pac::Vbat0, } impl ClockOperator<'_> { @@ -586,13 +678,52 @@ impl ClockOperator<'_> { }); } - todo!() + Ok(()) + } + + fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { + let Some(fro16k) = self.config.fro16k.as_ref() else { + return Ok(()); + }; + // Enable FRO16K oscillator + self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); + + // Lock the control register + self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); + + let Fro16KConfig { vsys_domain_active, vdd_core_domain_active } = fro16k; + + // Enable clock outputs to both VSYS and VDD_CORE domains + // Bit 0: clk_16k0 to VSYS domain + // Bit 1: clk_16k1 to VDD_CORE domain + // + // TODO: Define sub-fields for this register with a PAC patch? + let mut bits = 0; + if *vsys_domain_active { + bits |= 0b01; + self.clocks.clk_16k_vsys = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + if *vdd_core_domain_active { + bits |= 0b10; + self.clocks.clk_16k_vdd_core = Some(Clock { + frequency: 16_384, + power: PoweredClock::AlwaysEnabled, + }); + } + self.vbat0.froclke().modify(|_r, w| { + unsafe { w.clke().bits(bits) } + }); + + Ok(()) } } pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { critical_section::with(|cs| { - if CLOCKS.borrow(cs).is_some() { + if CLOCKS.borrow_ref(cs).is_some() { Err(ClockError::AlreadyInitialized) } else { Ok(()) @@ -607,12 +738,20 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { _mrcc0: unsafe { pac::Mrcc0::steal() }, scg0: unsafe { pac::Scg0::steal() }, syscon: unsafe { pac::Syscon::steal() }, + vbat0: unsafe { pac::Vbat0::steal() }, }; operator.configure_firc_clocks()?; operator.configure_sirc_clocks()?; + operator.configure_fro16k_clocks()?; // TODO, everything downstream + critical_section::with(|cs| { + let mut clks = CLOCKS.borrow_ref_mut(cs); + assert!(clks.is_none(), "Clock setup race!"); + *clks = Some(clocks); + }); + Ok(()) } @@ -621,7 +760,8 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { /// NOTE: Clocks implements `Clone`, pub fn with_clocks R>(f: F) -> Option { critical_section::with(|cs| { - let c = CLOCKS.borrow(cs).as_ref()?; + let c = CLOCKS.borrow_ref(cs); + let c = c.as_ref()?; Some(f(c)) }) } @@ -629,20 +769,32 @@ pub fn with_clocks R>(f: F) -> Option { impl Clocks { pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { let Some(clk) = self.fro_lf_div.as_ref() else { - return Err(ClockError::BadConfig { clock: "fro_lf_div", reason: "required but not active" }); + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "required but not active", + }); }; if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { clock: "fro_lf_div", reason: "not low power active" }); + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "not low power active", + }); } Ok(clk.frequency) } 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" }); + 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" }); + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "not low power active", + }); } Ok(clk.frequency) } @@ -657,10 +809,16 @@ impl Clocks { 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" }); + 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" }); + return Err(ClockError::BadConfig { + clock: "clk_1m", + reason: "not low power active", + }); } Ok(clk.frequency) } @@ -668,5 +826,4 @@ impl Clocks { pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) } - } diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs index c4d4e9e3b..4d186acf8 100644 --- a/src/clocks/periph_helpers.rs +++ b/src/clocks/periph_helpers.rs @@ -7,6 +7,7 @@ pub trait SPConfHelper { // config types +#[derive(Debug, Clone, Copy)] pub enum LpuartClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider /// "fro_lf_div" @@ -45,7 +46,7 @@ pub struct LpuartConfig { pub div: Div8, /// Which instance is this? // NOTE: should not be user settable - instance: LpuartInstance, + pub(crate) instance: LpuartInstance, } // impls diff --git a/src/lib.rs b/src/lib.rs index 9899564d8..e3e603ef2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,19 @@ pub mod ostimer; pub mod rtc; pub mod uart; -embassy_hal_internal::peripherals!(LPUART2, OSTIMER0, GPIO, RTC0, ADC1,); +embassy_hal_internal::peripherals!( + PORT1, + PORT2, + PORT3, + LPUART2, + OSTIMER0, + GPIO, + PIO2_2, + PIO2_3, + GPIO3, + RTC0, + ADC1, +); /// Get access to the PAC Peripherals for low-level register access. /// This is a lazy-initialized singleton that can be called after init(). diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs index a617c10a5..7b575c086 100644 --- a/src/lpuart/buffered.rs +++ b/src/lpuart/buffered.rs @@ -212,10 +212,19 @@ impl<'a> BufferedLpuart<'a> { config.enable_tx = true; config.enable_rx = true; + // Enable clocks + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; + // Perform standard initialization perform_software_reset(regs); disable_transceiver(regs); - configure_baudrate(regs, config.baudrate_bps, config.clock)?; + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; configure_frame_format(regs, &config); configure_control_settings(regs, &config); configure_fifo(regs, &config); diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 35b531421..41ec951ec 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -3,6 +3,8 @@ use core::marker::PhantomData; use embassy_hal_internal::{Peri, PeripheralType}; use paste::paste; +use crate::clocks::periph_helpers::{LpuartClockSel, LpuartConfig}; +use crate::clocks::{enable_and_reset, ClockError, Div8, Gate, PoweredClock}; use crate::pac::lpuart0::baud::Sbns as StopBits; use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; @@ -18,70 +20,7 @@ pub mod buffered; // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API // Pin and Clock initialization is currently done at the examples level. -// --- START LIB --- - -// Use our own instance of Peripherals, until we align `lib.rs` with the EMBASSY-IMXRT approach -// Inlined peripherals_definition! to bypass the `Peripherals::take_with_cs()` check -// SHOULD NOT BE USED IN THE FINAL VERSION -pub mod lib { - // embassy_hal_internal::peripherals!(LPUART2, PIO2_2, PIO2_3) - - embassy_hal_internal::peripherals_definition!(LPUART2, PIO2_2, PIO2_3,); - #[doc = r" Struct containing all the peripheral singletons."] - #[doc = r""] - #[doc = r" To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]."] - #[allow(non_snake_case)] - pub struct Peripherals { - #[doc = concat!(stringify!(LPUART2)," peripheral")] - pub LPUART2: embassy_hal_internal::Peri<'static, peripherals::LPUART2>, - #[doc = concat!(stringify!(PIO2_2)," peripheral")] - pub PIO2_2: embassy_hal_internal::Peri<'static, peripherals::PIO2_2>, - #[doc = concat!(stringify!(PIO2_3)," peripheral")] - pub PIO2_3: embassy_hal_internal::Peri<'static, peripherals::PIO2_3>, - } - impl Peripherals { - #[doc = r"Returns all the peripherals *once*"] - #[inline] - pub(crate) fn take() -> Self { - critical_section::with(Self::take_with_cs) - } - #[doc = r"Returns all the peripherals *once*"] - #[inline] - pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { - #[no_mangle] - static mut _EMBASSY_DEVICE_PERIPHERALS2: bool = false; // ALIGN: Temporary fix to use stub Peripherals - unsafe { - if _EMBASSY_DEVICE_PERIPHERALS2 { - panic!("init called more than once!") - } - _EMBASSY_DEVICE_PERIPHERALS2 = true; - Self::steal() - } - } - } - impl Peripherals { - #[doc = r" Unsafely create an instance of this peripheral out of thin air."] - #[doc = r""] - #[doc = r" # Safety"] - #[doc = r""] - #[doc = r" You must ensure that you're only using one instance of this type at a time."] - #[inline] - pub unsafe fn steal() -> Self { - Self { - LPUART2: peripherals::LPUART2::steal(), - PIO2_2: peripherals::PIO2_2::steal(), - PIO2_3: peripherals::PIO2_3::steal(), - } - } - } - - /// Initialize HAL - pub fn init() -> Peripherals { - Peripherals::take() - } -} -// --- END LIB --- // --- START GPIO --- @@ -101,13 +40,13 @@ mod gpio { macro_rules! impl_gpio_pin { ($($pin:ident),*) => { $( - impl SealedPin for super::lib::peripherals::$pin {} + impl SealedPin for crate::peripherals::$pin {} - impl GpioPin for super::lib::peripherals::$pin {} + impl GpioPin for crate::peripherals::$pin {} - impl From for AnyPin { + impl From for AnyPin { // TODO: AJM: any reason we aren't using $pin? - fn from(_val: super::lib::peripherals::$pin) -> Self { + fn from(_val: crate::peripherals::$pin) -> Self { AnyPin } } @@ -143,18 +82,6 @@ use dma::Channel; // --- END DMA --- -// --- START CLOCK --- -mod clock { - #[derive(Debug, Clone, Copy)] - pub enum Clock { - FroLf, // Low-Frequency Free-Running Oscillator - } -} - -use clock::Clock; - -// --- END CLOCK --- - // ============================================================================ // MISC // ============================================================================ @@ -182,7 +109,8 @@ pub struct Info { /// Trait for LPUART peripheral instances #[allow(private_bounds)] -pub trait Instance: SealedInstance + PeripheralType + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; type Interrupt: interrupt::typelevel::Interrupt; } @@ -190,7 +118,7 @@ macro_rules! impl_instance { ($($n:expr),*) => { $( paste!{ - impl SealedInstance for lib::peripherals::[] { + impl SealedInstance for crate::peripherals::[] { fn info() -> Info { Info { regs: unsafe { &*pac::[]::ptr() }, @@ -208,7 +136,9 @@ macro_rules! impl_instance { } } - impl Instance for lib::peripherals::[] { + impl Instance for crate::peripherals::[] { + const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance + = crate::clocks::periph_helpers::LpuartInstance::[]; type Interrupt = crate::interrupt::typelevel::[]; } } @@ -236,8 +166,7 @@ pub fn disable_transceiver(regs: Regs) { } /// Calculate and configure baudrate settings -pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock: Clock) -> Result<()> { - let clock_freq = get_fc_freq(clock)?; +pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock_freq: u32) -> Result<()> { let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; // Configure BAUD register @@ -408,16 +337,6 @@ pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> Ok((osr, sbr)) } -pub fn get_fc_freq(clock: Clock) -> Result { - // This is a placeholder - actual implementation would query the clock system - // In real implementation, this would get the LPUART clock frequency - match clock { - Clock::FroLf => Ok(12_000_000), // Low frequency oscillator - #[allow(unreachable_patterns)] - _ => Err(Error::InvalidArgument), - } -} - /// Wait for all transmit operations to complete pub fn wait_for_tx_complete(regs: Regs) { // Wait for TX FIFO to empty @@ -504,7 +423,7 @@ macro_rules! impl_pin_trait { ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { paste! { $( - impl [<$mode:camel Pin>] for lib::peripherals::$pin { + impl [<$mode:camel Pin>] for crate::peripherals::$pin { fn [](&self) { let _alt = gpio::Alt::$alt; // todo!("Configure pin for LPUART function") @@ -553,6 +472,8 @@ pub enum Error { TxFifoFull, /// TX Busy TxBusy, + /// Clock Error + ClockSetup(ClockError), } /// A specialized Result type for LPUART operations @@ -565,10 +486,14 @@ pub type Result = core::result::Result; /// Lpuart config #[derive(Debug, Clone, Copy)] pub struct Config { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Clock source + pub source: LpuartClockSel, + /// Clock divisor + pub div: Div8, /// Baud rate in bits per second pub baudrate_bps: u32, - /// Clock - pub clock: Clock, /// Parity configuration pub parity_mode: Option, /// Number of data bits @@ -605,7 +530,6 @@ impl Default for Config { fn default() -> Self { Self { baudrate_bps: 115_200u32, - clock: Clock::FroLf, parity_mode: None, data_bits_count: DataBits::Data8, msb_firs: MsbFirst::LsbFirst, @@ -621,6 +545,9 @@ impl Default for Config { enable_tx: false, enable_rx: false, swap_txd_rxd: false, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: LpuartClockSel::FroLfDiv, + div: const { Div8::from_divisor(1).unwrap() }, } } } @@ -706,10 +633,21 @@ impl<'a, M: Mode> Lpuart<'a, M> { ) -> Result<()> { let regs = T::info().regs; + // Enable clocks + let conf = LpuartConfig { + power: config.power, + source: config.source, + div: config.div, + instance: T::CLOCK_INSTANCE, + }; + let clock_freq = unsafe { + enable_and_reset::(&conf).map_err(Error::ClockSetup)? + }; + // Perform initialization sequence perform_software_reset(regs); disable_transceiver(regs); - configure_baudrate(regs, config.baudrate_bps, config.clock)?; + configure_baudrate(regs, config.baudrate_bps, clock_freq)?; configure_frame_format(regs, &config); configure_control_settings(regs, &config); configure_fifo(regs, &config); -- cgit From 9f889d015ef5d373343ad6c769824e28be780e53 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 15:35:20 +0100 Subject: Implement SPConfHelper for OSTimer and ADC --- src/board.rs | 14 ---- src/clocks/mod.rs | 71 ++++++++++++++---- src/clocks/periph_helpers.rs | 171 ++++++++++++++++++++++++++++++++++++++++++- src/lpuart/mod.rs | 12 +-- 4 files changed, 229 insertions(+), 39 deletions(-) delete mode 100644 src/board.rs diff --git a/src/board.rs b/src/board.rs deleted file mode 100644 index fa679e82c..000000000 --- a/src/board.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::{clocks, pac, pins}; - -/// Initialize clocks and pin muxing for UART2 debug console. -pub unsafe fn init_uart2(p: &pac::Peripherals) { - clocks::ensure_frolf_running(p); - clocks::enable_uart2_port2(p); - pins::configure_uart2_pins_port2(); - clocks::select_uart2_clock(p); -} - -/// Initialize clocks for the LED GPIO/PORT used by the blink example. -pub unsafe fn init_led(p: &pac::Peripherals) { - clocks::enable_led_port(p); -} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 63a8fb64d..db41db628 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -141,16 +141,23 @@ impl SPConfHelper for UnimplementedConfig { } } +pub struct NoConfig; +impl SPConfHelper for NoConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Ok(0) + } +} + pub mod gate { - use super::{periph_helpers::LpuartConfig, *}; + use super::{periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}, *}; - impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, UnimplementedConfig); - impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, UnimplementedConfig); - impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, UnimplementedConfig); - impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, UnimplementedConfig); + impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); - impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, UnimplementedConfig); - impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, UnimplementedConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); } // /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. @@ -418,7 +425,6 @@ pub struct Clocks { pub fro_lf_div: Option, // // End FRO12M stuff - pub clk_16k_vsys: Option, pub clk_16k_vdd_core: Option, pub main_clk: Option, @@ -691,7 +697,10 @@ impl ClockOperator<'_> { // Lock the control register self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); - let Fro16KConfig { vsys_domain_active, vdd_core_domain_active } = fro16k; + let Fro16KConfig { + vsys_domain_active, + vdd_core_domain_active, + } = fro16k; // Enable clock outputs to both VSYS and VDD_CORE domains // Bit 0: clk_16k0 to VSYS domain @@ -713,9 +722,7 @@ impl ClockOperator<'_> { power: PoweredClock::AlwaysEnabled, }); } - self.vbat0.froclke().modify(|_r, w| { - unsafe { w.clke().bits(bits) } - }); + self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); Ok(()) } @@ -783,6 +790,22 @@ impl Clocks { Ok(clk.frequency) } + 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) + } + 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 { @@ -803,8 +826,28 @@ impl Clocks { Err(ClockError::NotImplemented { clock: "clk_in" }) } - pub fn ensure_clk_16k_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "clk_16k" }) + pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vsys + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vsys", + reason: "required but not active", + })? + .frequency) + } + + pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vdd_core + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vdd_core", + reason: "required but not active", + })? + .frequency) } pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result { diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs index 4d186acf8..de767ef87 100644 --- a/src/clocks/periph_helpers.rs +++ b/src/clocks/periph_helpers.rs @@ -1,4 +1,4 @@ -use super::{ClockError, Clocks, Div8, PoweredClock}; +use super::{ClockError, Clocks, PoweredClock}; use crate::pac; pub trait SPConfHelper { @@ -7,6 +7,59 @@ pub trait SPConfHelper { // config types +/// This type represents a divider in the range 1..=16. +/// +/// At a hardware level, this is an 8-bit register from 0..=15, +/// which adds one. +/// +/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain +/// seems to use 4 bit dividers! +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div4(pub(super) u8); + +impl Div4 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source + /// by 1, and `Div4::from_raw(15)` will divide the source by + /// 16. + pub const fn from_raw(n: u8) -> Option { + if n > 0b1111 { + None + } else { + Some(Self(n)) + } + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source + /// by 1, and `Div4::from_divisor(16)` will divide the source + /// by 16. + /// + /// Will return `None` if `n` is not in the range `1..=16`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u8) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > 0b1111 { + return None; + } + Some(Self(n)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + #[derive(Debug, Clone, Copy)] pub enum LpuartClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider @@ -43,12 +96,41 @@ pub struct LpuartConfig { /// Clock source pub source: LpuartClockSel, /// Clock divisor - pub div: Div8, + pub div: Div4, /// Which instance is this? // NOTE: should not be user settable pub(crate) instance: LpuartInstance, } +pub enum OstimerClockSel { + /// 16k clock, sourced from FRO16K (Vdd Core) + Clk16kVddCore, + /// 1 MHz Clock sourced from FRO12M + Clk1M, + /// Disabled + None, +} + +pub struct OsTimerConfig { + pub power: PoweredClock, + pub source: OstimerClockSel, +} + +pub enum AdcClockSel { + FroLfDiv, + FroHf, + ClkIn, + Clk1M, + Pll1ClkDiv, + None, +} + +pub struct AdcConfig { + pub power: PoweredClock, + pub source: AdcClockSel, + pub div: Div4, +} + // impls impl SPConfHelper for LpuartConfig { @@ -80,7 +162,7 @@ impl SPConfHelper for LpuartConfig { (freq, Mux::ClkrootFunc3) } LpuartClockSel::Clk16K => { - let freq = clocks.ensure_clk_16k_active(&self.power)?; + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; (freq, Mux::ClkrootFunc4) } LpuartClockSel::Clk1M => { @@ -124,3 +206,86 @@ impl SPConfHelper for LpuartConfig { Ok(freq / self.div.into_divisor()) } } + +impl SPConfHelper for OsTimerConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + Ok(match self.source { + OstimerClockSel::Clk16kVddCore => { + let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); + freq + } + OstimerClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); + freq + } + OstimerClockSel::None => { + mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); + 0 + } + }) + } +} + +impl SPConfHelper for AdcConfig { + fn post_enable_config(&self, clocks: &Clocks) -> Result { + use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; + let mrcc0 = unsafe { pac::Mrcc0::steal() }; + let (freq, variant) = match self.source { + AdcClockSel::FroLfDiv => { + let freq = clocks.ensure_fro_lf_div_active(&self.power)?; + (freq, Mux::ClkrootFunc0) + } + AdcClockSel::FroHf => { + let freq = clocks.ensure_fro_hf_active(&self.power)?; + (freq, Mux::ClkrootFunc1) + } + AdcClockSel::ClkIn => { + let freq = clocks.ensure_clk_in_active(&self.power)?; + (freq, Mux::ClkrootFunc3) + } + AdcClockSel::Clk1M => { + let freq = clocks.ensure_clk_1m_active(&self.power)?; + (freq, Mux::ClkrootFunc5) + } + AdcClockSel::Pll1ClkDiv => { + let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; + (freq, Mux::ClkrootFunc6) + } + AdcClockSel::None => { + mrcc0.mrcc_adc_clksel().write(|w| unsafe { + // no ClkrootFunc7, just write manually for now + w.mux().bits(0b111) + }); + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.reset().on(); + w.halt().on(); + w + }); + return Ok(0); + } + }; + + // set clksel + mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant)); + + // Set up clkdiv + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.halt().on(); + w.reset().on(); + w + }); + mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { + w.halt().off(); + w.reset().off(); + unsafe { w.div().bits(self.div.into_bits()) }; + w + }); + + while mrcc0.mrcc_adc_clkdiv().read().unstab().is_on() {} + + Ok(freq / self.div.into_divisor()) + } +} diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 41ec951ec..35b4db24c 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -4,7 +4,7 @@ use embassy_hal_internal::{Peri, PeripheralType}; use paste::paste; use crate::clocks::periph_helpers::{LpuartClockSel, LpuartConfig}; -use crate::clocks::{enable_and_reset, ClockError, Div8, Gate, PoweredClock}; +use crate::clocks::{enable_and_reset, ClockError, periph_helpers::Div4, Gate, PoweredClock}; use crate::pac::lpuart0::baud::Sbns as StopBits; use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; @@ -20,8 +20,6 @@ pub mod buffered; // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API // Pin and Clock initialization is currently done at the examples level. - - // --- START GPIO --- mod gpio { @@ -491,7 +489,7 @@ pub struct Config { /// Clock source pub source: LpuartClockSel, /// Clock divisor - pub div: Div8, + pub div: Div4, /// Baud rate in bits per second pub baudrate_bps: u32, /// Parity configuration @@ -547,7 +545,7 @@ impl Default for Config { swap_txd_rxd: false, power: PoweredClock::NormalEnabledDeepSleepDisabled, source: LpuartClockSel::FroLfDiv, - div: const { Div8::from_divisor(1).unwrap() }, + div: const { Div4::from_divisor(1).unwrap() }, } } } @@ -640,9 +638,7 @@ impl<'a, M: Mode> Lpuart<'a, M> { div: config.div, instance: T::CLOCK_INSTANCE, }; - let clock_freq = unsafe { - enable_and_reset::(&conf).map_err(Error::ClockSetup)? - }; + let clock_freq = unsafe { enable_and_reset::(&conf).map_err(Error::ClockSetup)? }; // Perform initialization sequence perform_software_reset(regs); -- cgit From 9b91d886e6a5067d6a553715a053db653af55ca6 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 15:37:48 +0100 Subject: Cargo fmt --- src/clocks/mod.rs | 9 ++++----- src/lib.rs | 14 +------------- src/lpuart/mod.rs | 4 ++-- 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index db41db628..24e118e38 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -2,10 +2,8 @@ //! Provides reusable gate abstractions for peripherals used by the examples. use core::cell::RefCell; -use mcxa_pac::scg0::{ - firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}, - sirccsr::{SircClkPeriphEn, Sircsten}, -}; +use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; +use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; use periph_helpers::SPConfHelper; use crate::pac; @@ -149,7 +147,8 @@ impl SPConfHelper for NoConfig { } pub mod gate { - use super::{periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}, *}; + use super::periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}; + use super::*; impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); diff --git a/src/lib.rs b/src/lib.rs index e3e603ef2..fd7d3cd07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,19 +16,7 @@ pub mod ostimer; pub mod rtc; pub mod uart; -embassy_hal_internal::peripherals!( - PORT1, - PORT2, - PORT3, - LPUART2, - OSTIMER0, - GPIO, - PIO2_2, - PIO2_3, - GPIO3, - RTC0, - ADC1, -); +embassy_hal_internal::peripherals!(PORT1, PORT2, PORT3, LPUART2, OSTIMER0, GPIO, PIO2_2, PIO2_3, GPIO3, RTC0, ADC1,); /// Get access to the PAC Peripherals for low-level register access. /// This is a lazy-initialized singleton that can be called after init(). diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 35b4db24c..be69372a2 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -3,8 +3,8 @@ use core::marker::PhantomData; use embassy_hal_internal::{Peri, PeripheralType}; use paste::paste; -use crate::clocks::periph_helpers::{LpuartClockSel, LpuartConfig}; -use crate::clocks::{enable_and_reset, ClockError, periph_helpers::Div4, Gate, PoweredClock}; +use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; +use crate::clocks::{enable_and_reset, ClockError, Gate, PoweredClock}; use crate::pac::lpuart0::baud::Sbns as StopBits; use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; -- cgit From e799d6c8956ed3ea5ced65d58c3065a22927ad10 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 17:29:31 +0100 Subject: More work on examples --- examples/adc_interrupt.rs | 15 ++++-- examples/common/mod.rs | 38 +++++--------- examples/lpuart_buffered.rs | 5 +- examples/ostimer_alarm.rs | 7 +-- examples/ostimer_counter.rs | 8 ++- examples/ostimer_race_test.rs | 5 +- examples/uart_interrupt.rs | 100 ++++++++++++++++++------------------- src/adc.rs | 73 +++++++++++++++++---------- src/clocks/mod.rs | 12 ++++- src/clocks/periph_helpers.rs | 8 +++ src/lib.rs | 1 - src/lpuart/mod.rs | 2 +- src/ostimer.rs | 62 +++++++++++++---------- src/reset.rs | 112 ------------------------------------------ 14 files changed, 186 insertions(+), 262 deletions(-) delete mode 100644 src/reset.rs diff --git a/examples/adc_interrupt.rs b/examples/adc_interrupt.rs index dc82cfd30..3be85ac75 100644 --- a/examples/adc_interrupt.rs +++ b/examples/adc_interrupt.rs @@ -2,6 +2,8 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa276::clocks::periph_helpers::{AdcClockSel, Div4}; +use embassy_mcxa276::clocks::PoweredClock; use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; use hal::uart; use mcxa_pac::adc1::cfg::{Pwrsel, Refsel}; @@ -30,10 +32,10 @@ async fn main(_spawner: Spawner) { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); + // let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; + // let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); - uart.write_str_blocking("\r\n=== ADC interrupt Example ===\r\n"); + // uart.write_str_blocking("\r\n=== ADC interrupt Example ===\r\n"); unsafe { common::init_adc(hal::pac()); @@ -50,6 +52,9 @@ async fn main(_spawner: Spawner) { enable_conv_pause: false, conv_pause_delay: 0, fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), }; let adc = hal::adc::Adc::::new(p.ADC1, adc_config); @@ -66,7 +71,7 @@ async fn main(_spawner: Spawner) { conv_trigger_config.enable_hardware_trigger = false; adc.set_conv_trigger_config(0, &conv_trigger_config); - uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); + // uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); adc.enable_interrupt(0x1); @@ -83,7 +88,7 @@ async fn main(_spawner: Spawner) { while !adc.is_interrupt_triggered() { // Wait until the interrupt is triggered } - uart.write_str_blocking("\r\n*** ADC interrupt TRIGGERED! ***\r\n"); + // uart.write_str_blocking("\r\n*** ADC interrupt TRIGGERED! ***\r\n"); //TBD need to print the value } } diff --git a/examples/common/mod.rs b/examples/common/mod.rs index 8c52c8e86..7b197a590 100644 --- a/examples/common/mod.rs +++ b/examples/common/mod.rs @@ -1,45 +1,31 @@ //! Shared board-specific helpers for the FRDM-MCXA276 examples. //! These live with the examples so the HAL stays generic. -use hal::{clocks, pins, reset}; +use hal::{clocks, pins}; use {embassy_mcxa276 as hal, panic_probe as _}; /// Initialize clocks and pin muxing for UART2 debug console. /// Safe to call multiple times; writes are idempotent for our use. #[allow(dead_code)] -pub unsafe fn init_uart2(p: &mcxa_pac::Peripherals) { - clocks::ensure_frolf_running(p); - clocks::enable_uart2_port2(p); - reset::release_reset_port2(p); - reset::release_reset_lpuart2(p); +pub unsafe fn init_uart2(_p: &mcxa_pac::Peripherals) { + // NOTE: Lpuart has been updated to properly enable + reset its own clocks. + // GPIO has not. + _ = clocks::enable_and_reset::(&clocks::NoConfig); pins::configure_uart2_pins_port2(); - clocks::select_uart2_clock(p); } /// Initialize clocks for the LED GPIO/PORT used by the blink example. #[allow(dead_code)] -pub unsafe fn init_led(p: &mcxa_pac::Peripherals) { - clocks::enable_led_port(p); - reset::release_reset_gpio3(p); - reset::release_reset_port3(p); -} - -/// Initialize clocks for OSTIMER0 (1 MHz source). -#[allow(dead_code)] -pub unsafe fn init_ostimer0(p: &mcxa_pac::Peripherals) { - clocks::ensure_frolf_running(p); - clocks::enable_ostimer0(p); - reset::release_reset_ostimer0(p); - clocks::select_ostimer0_clock_1m(p); +pub unsafe fn init_led(_p: &mcxa_pac::Peripherals) { + _ = clocks::enable_and_reset::(&clocks::NoConfig); + _ = clocks::enable_and_reset::(&clocks::NoConfig); } /// Initialize clocks and pin muxing for ADC. #[allow(dead_code)] -pub unsafe fn init_adc(p: &mcxa_pac::Peripherals) { - clocks::ensure_frolf_running(p); - clocks::enable_adc(p); - reset::release_reset_port1(p); - reset::release_reset_adc1(p); +pub unsafe fn init_adc(_p: &mcxa_pac::Peripherals) { + // NOTE: Lpuart has been updated to properly enable + reset its own clocks. + // GPIO has not. + _ = clocks::enable_and_reset::(&clocks::NoConfig); pins::configure_adc_pins(); - clocks::select_adc_clock(p); } diff --git a/examples/lpuart_buffered.rs b/examples/lpuart_buffered.rs index 35d311143..6ae690c56 100644 --- a/examples/lpuart_buffered.rs +++ b/examples/lpuart_buffered.rs @@ -12,18 +12,17 @@ mod common; // Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver bind_interrupts!(struct Irqs { - LPUART2 => lpuart::buffered::BufferedInterruptHandler::; + LPUART2 => lpuart::buffered::BufferedInterruptHandler::; }); // Wrapper function for the interrupt handler unsafe extern "C" fn lpuart2_handler() { - lpuart::buffered::BufferedInterruptHandler::::on_interrupt(); + lpuart::buffered::BufferedInterruptHandler::::on_interrupt(); } #[embassy_executor::main] async fn main(_spawner: Spawner) { let _p = hal::init(hal::config::Config::default()); - let p2 = lpuart::lib::init(); unsafe { hal::interrupt::install_irq_handler(mcxa_pac::Interrupt::LPUART2, lpuart2_handler); diff --git a/examples/ostimer_alarm.rs b/examples/ostimer_alarm.rs index 78ca4bbc5..4f29a2c7c 100644 --- a/examples/ostimer_alarm.rs +++ b/examples/ostimer_alarm.rs @@ -9,7 +9,7 @@ use {cortex_m, embassy_mcxa276 as hal}; mod common; -use embassy_mcxa276::bind_interrupts; +use embassy_mcxa276::{bind_interrupts, clocks::{periph_helpers::OstimerClockSel, PoweredClock}}; use {defmt_rtt as _, panic_probe as _}; // Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. @@ -50,9 +50,10 @@ async fn main(_spawner: Spawner) { // Create OSTIMER instance let config = hal::ostimer::Config { init_match_max: true, - clock_frequency_hz: 1_000_000, // 1MHz + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, }; - let ostimer = hal::ostimer::Ostimer::::new(p.OSTIMER0, config, hal::pac()); + let ostimer = hal::ostimer::Ostimer::::new(p.OSTIMER0, config); // Create alarm with callback let alarm = hal::ostimer::Alarm::new() diff --git a/examples/ostimer_counter.rs b/examples/ostimer_counter.rs index e95140a88..069e879d8 100644 --- a/examples/ostimer_counter.rs +++ b/examples/ostimer_counter.rs @@ -7,6 +7,7 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa276::clocks::{periph_helpers::OstimerClockSel, PoweredClock}; use embassy_time::{Duration, Timer}; use hal::bind_interrupts; use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; @@ -22,9 +23,6 @@ async fn main(_spawner: Spawner) { let p = hal::init(Default::default()); // Enable/clock OSTIMER0 and UART2 before touching their registers - unsafe { - common::init_ostimer0(hal::pac()); - } unsafe { common::init_uart2(hal::pac()); } @@ -44,9 +42,9 @@ async fn main(_spawner: Spawner) { p.OSTIMER0, hal::ostimer::Config { init_match_max: true, - clock_frequency_hz: 1_000_000, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, }, - hal::pac(), ); // Read initial counter value diff --git a/examples/ostimer_race_test.rs b/examples/ostimer_race_test.rs index 368a3e52f..6e3d4ac21 100644 --- a/examples/ostimer_race_test.rs +++ b/examples/ostimer_race_test.rs @@ -12,6 +12,7 @@ use core::sync::atomic::{AtomicU32, Ordering}; use embassy_executor::Spawner; +use embassy_mcxa276::clocks::{periph_helpers::OstimerClockSel, PoweredClock}; use embassy_time::{Duration, Timer}; use hal::bind_interrupts; use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; @@ -98,9 +99,9 @@ async fn main(_spawner: Spawner) { p.OSTIMER0, hal::ostimer::Config { init_match_max: true, - clock_frequency_hz: 1_000_000, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, }, - hal::pac(), ); uart.write_str_blocking("OSTIMER instance created\n"); diff --git a/examples/uart_interrupt.rs b/examples/uart_interrupt.rs index bd734f859..190a4d850 100644 --- a/examples/uart_interrupt.rs +++ b/examples/uart_interrupt.rs @@ -2,68 +2,68 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa276 as hal; -use hal::interrupt::typelevel::Handler; -use hal::uart; +// use embassy_mcxa276 as hal; +// use hal::interrupt::typelevel::Handler; +// use hal::uart; -mod common; +// mod common; -use embassy_mcxa276::bind_interrupts; -use {defmt_rtt as _, panic_probe as _}; +// use embassy_mcxa276::bind_interrupts; +// use {defmt_rtt as _, panic_probe as _}; -// Bind LPUART2 interrupt to our handler -bind_interrupts!(struct Irqs { - LPUART2 => hal::uart::UartInterruptHandler; -}); +// // Bind LPUART2 interrupt to our handler +// bind_interrupts!(struct Irqs { +// LPUART2 => hal::uart::UartInterruptHandler; +// }); -#[used] -#[no_mangle] -static KEEP_LPUART2: unsafe extern "C" fn() = LPUART2; +// #[used] +// #[no_mangle] +// static KEEP_LPUART2: unsafe extern "C" fn() = LPUART2; -// Wrapper function for the interrupt handler -unsafe extern "C" fn lpuart2_handler() { - hal::uart::UartInterruptHandler::on_interrupt(); -} +// // Wrapper function for the interrupt handler +// unsafe extern "C" fn lpuart2_handler() { +// hal::uart::UartInterruptHandler::on_interrupt(); +// } #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = hal::init(hal::config::Config::default()); +// let _p = hal::init(hal::config::Config::default()); - // Enable/clock UART2 before touching its registers - unsafe { - common::init_uart2(hal::pac()); - } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(_p.LPUART2, uart::Config::new(src)); +// // Enable/clock UART2 before touching its registers +// unsafe { +// common::init_uart2(hal::pac()); +// } +// let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; +// let uart = uart::Uart::::new(_p.LPUART2, uart::Config::new(src)); - // Configure LPUART2 interrupt for UART operation BEFORE any UART usage - hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::from(3)); +// // Configure LPUART2 interrupt for UART operation BEFORE any UART usage +// hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::from(3)); - // Manually install the interrupt handler and enable RX IRQs in the peripheral - unsafe { - hal::interrupt::LPUART2.install_handler(lpuart2_handler); - // Enable RX interrupts so the handler actually fires on incoming bytes - uart.enable_rx_interrupts(); - } +// // Manually install the interrupt handler and enable RX IRQs in the peripheral +// unsafe { +// hal::interrupt::LPUART2.install_handler(lpuart2_handler); +// // Enable RX interrupts so the handler actually fires on incoming bytes +// uart.enable_rx_interrupts(); +// } - // Print welcome message - uart.write_str_blocking("UART interrupt echo demo starting...\r\n"); - uart.write_str_blocking("Type characters to echo them back.\r\n"); +// // Print welcome message +// uart.write_str_blocking("UART interrupt echo demo starting...\r\n"); +// uart.write_str_blocking("Type characters to echo them back.\r\n"); - // Log using defmt if enabled - defmt::info!("UART interrupt echo demo starting..."); +// // Log using defmt if enabled +// defmt::info!("UART interrupt echo demo starting..."); - loop { - // Check if we have received any data - if uart.rx_data_available() { - if let Some(byte) = uart.try_read_byte() { - // Echo it back - uart.write_byte(byte); - uart.write_str_blocking(" (received)\r\n"); - } - } else { - // No data available, wait a bit before checking again - cortex_m::asm::delay(12_000_000); // ~1 second at 12MHz - } - } +// loop { +// // Check if we have received any data +// if uart.rx_data_available() { +// if let Some(byte) = uart.try_read_byte() { +// // Echo it back +// uart.write_byte(byte); +// uart.write_str_blocking(" (received)\r\n"); +// } +// } else { +// // No data available, wait a bit before checking again +// cortex_m::asm::delay(12_000_000); // ~1 second at 12MHz +// } +// } } diff --git a/src/adc.rs b/src/adc.rs index 655bf934f..b5ec5983f 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,6 +1,10 @@ //! ADC driver use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; +use crate::clocks::{enable_and_reset, Gate, PoweredClock}; use crate::pac; use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; @@ -12,7 +16,7 @@ type Regs = pac::adc1::RegisterBlock; static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); // Token-based instance pattern like embassy-imxrt -pub trait Instance { +pub trait Instance: Gate + PeripheralType { fn ptr() -> *const Regs; } @@ -26,12 +30,12 @@ impl Instance for crate::peripherals::ADC1 { } // Also implement Instance for the Peri wrapper type -impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Adc1::ptr() - } -} +// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { +// #[inline(always)] +// fn ptr() -> *const Regs { +// pac::Adc1::ptr() +// } +// } #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] @@ -60,6 +64,29 @@ pub struct LpadcConfig { pub enable_conv_pause: bool, pub conv_pause_delay: u16, pub fifo_watermark: u8, + pub power: PoweredClock, + pub source: AdcClockSel, + pub div: Div4, +} + +impl Default for LpadcConfig { + fn default() -> Self { + LpadcConfig { + enable_in_doze_mode: true, + conversion_average_mode: CalAvgs::NoAverage, + enable_analog_preliminary: false, + power_up_delay: 0x80, + reference_voltage_source: Refsel::Option1, + power_level_mode: Pwrsel::Lowest, + trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, + enable_conv_pause: false, + conv_pause_delay: 0, + fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -94,15 +121,24 @@ pub struct ConvResult { pub conv_value: u16, } -pub struct Adc { - _inst: core::marker::PhantomData, +pub struct Adc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, } -impl Adc { +impl<'a, I: Instance> Adc<'a, I> { /// initialize ADC - pub fn new(_inst: impl Instance, config: LpadcConfig) -> Self { + pub fn new(_inst: Peri<'a, I>, config: LpadcConfig) -> Self { let adc = unsafe { &*I::ptr() }; + let _clock_freq = unsafe { + enable_and_reset::(&AdcConfig { + power: config.power, + source: config.source, + div: config.div, + }) + .expect("Adc Init should not fail") + }; + /* Reset the module. */ adc.ctrl().modify(|_, w| w.rst().held_in_reset()); adc.ctrl().modify(|_, w| w.rst().released_from_reset()); @@ -194,21 +230,6 @@ impl Adc { adc.ctrl().modify(|_, w| w.adcen().disabled()); } - pub fn get_default_config() -> LpadcConfig { - LpadcConfig { - enable_in_doze_mode: true, - conversion_average_mode: CalAvgs::NoAverage, - enable_analog_preliminary: false, - power_up_delay: 0x80, - reference_voltage_source: Refsel::Option1, - power_level_mode: Pwrsel::Lowest, - trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, - enable_conv_pause: false, - conv_pause_delay: 0, - fifo_watermark: 0, - } - } - pub fn do_offset_calibration(&self) { let adc = unsafe { &*I::ptr() }; // Enable calibration mode diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 24e118e38..e04f63b8e 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -79,6 +79,11 @@ pub unsafe fn assert_reset() { G::assert_reset(); } +#[inline] +pub unsafe fn is_reset_released() -> bool { + G::is_reset_released() +} + /// Pulse a reset line (assert then release) with a short delay. #[inline] pub unsafe fn pulse_reset() { @@ -150,12 +155,15 @@ pub mod gate { use super::periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}; use super::*; + // These peripherals have no additional upstream clocks or configuration required + // other than enabling through the MRCC gate. impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); - impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); } @@ -276,7 +284,7 @@ pub struct Clock { pub power: PoweredClock, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PoweredClock { NormalEnabledDeepSleepDisabled, AlwaysEnabled, diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs index de767ef87..1657bd7eb 100644 --- a/src/clocks/periph_helpers.rs +++ b/src/clocks/periph_helpers.rs @@ -18,6 +18,11 @@ pub trait SPConfHelper { pub struct Div4(pub(super) u8); impl Div4 { + /// Divide by one, or no division + pub const fn no_div() -> Self { + Self(0) + } + /// Store a "raw" divisor value that will divide the source by /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source /// by 1, and `Div4::from_raw(15)` will divide the source by @@ -81,6 +86,7 @@ pub enum LpuartClockSel { None, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LpuartInstance { Lpuart0, Lpuart1, @@ -102,6 +108,7 @@ pub struct LpuartConfig { pub(crate) instance: LpuartInstance, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum OstimerClockSel { /// 16k clock, sourced from FRO16K (Vdd Core) Clk16kVddCore, @@ -116,6 +123,7 @@ pub struct OsTimerConfig { pub source: OstimerClockSel, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AdcClockSel { FroLfDiv, FroHf, diff --git a/src/lib.rs b/src/lib.rs index fd7d3cd07..4120c1e84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ pub mod clocks; // still provide clock helpers pub mod gpio; pub mod pins; // pin mux helpers -pub mod reset; // reset control helpers pub mod adc; pub mod config; diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index be69372a2..9e8b25ff6 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -545,7 +545,7 @@ impl Default for Config { swap_txd_rxd: false, power: PoweredClock::NormalEnabledDeepSleepDisabled, source: LpuartClockSel::FroLfDiv, - div: const { Div4::from_divisor(1).unwrap() }, + div: Div4::no_div(), } } } diff --git a/src/ostimer.rs b/src/ostimer.rs index 8bc68389a..efa534194 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs @@ -29,8 +29,13 @@ use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; +use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; use crate::interrupt::InterruptExt; use crate::pac; +use crate::peripherals::OSTIMER0; // PAC defines the shared RegisterBlock under `ostimer0`. type Regs = pac::ostimer0::RegisterBlock; @@ -197,16 +202,16 @@ impl<'d> Alarm<'d> { pub struct Config { /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. pub init_match_max: bool, - /// OSTIMER clock frequency in Hz (must match the actual hardware clock) - pub clock_frequency_hz: u64, + pub power: PoweredClock, + pub source: OstimerClockSel, } impl Default for Config { fn default() -> Self { Self { init_match_max: true, - // Default to 1MHz - user should override this with actual frequency - clock_frequency_hz: 1_000_000, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: OstimerClockSel::Clk1M, } } } @@ -222,8 +227,16 @@ impl<'d, I: Instance> Ostimer<'d, I> { /// Construct OSTIMER handle. /// Requires clocks for the instance to be enabled by the board before calling. /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. - pub fn new(_inst: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { - assert!(cfg.clock_frequency_hz > 0, "OSTIMER frequency must be greater than 0"); + pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { + let clock_freq = unsafe { + enable_and_reset::(&OsTimerConfig { + power: cfg.power, + source: cfg.source, + }) + .expect("Enabling OsTimer clock should not fail") + }; + + assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); if cfg.init_match_max { let r: &Regs = unsafe { &*I::ptr() }; @@ -233,7 +246,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { Self { _inst: core::marker::PhantomData, - clock_frequency_hz: cfg.clock_frequency_hz, + clock_frequency_hz: clock_freq as u64, _phantom: core::marker::PhantomData, } } @@ -260,7 +273,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { /// # Safety /// This operation will reset the entire OSTIMER peripheral. Any active alarms /// or time_driver operations will be disrupted. Use with caution. - pub fn reset(&self, peripherals: &crate::pac::Peripherals) { + pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { critical_section::with(|_| { let r: &Regs = unsafe { &*I::ptr() }; @@ -270,19 +283,17 @@ impl<'d, I: Instance> Ostimer<'d, I> { .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); unsafe { - crate::reset::assert::(peripherals); - } + assert_reset::(); - for _ in 0..RESET_STABILIZE_SPINS { - cortex_m::asm::nop(); - } + for _ in 0..RESET_STABILIZE_SPINS { + cortex_m::asm::nop(); + } - unsafe { - crate::reset::release::(peripherals); - } + release_reset::(); - while !::is_released(&peripherals.mrcc0) { - cortex_m::asm::nop(); + while !is_reset_released::() { + cortex_m::asm::nop(); + } } for _ in 0..RESET_STABILIZE_SPINS { @@ -469,14 +480,13 @@ fn now_ticks_read() -> u64 { // Read high then low to minimize incoherent snapshots let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; - // Combine and convert from Gray code to binary let gray = lo | (hi << EVTIMER_HI_SHIFT); gray_to_bin(gray) } // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance -pub trait Instance { +pub trait Instance: Gate + PeripheralType { fn ptr() -> *const Regs; } @@ -491,12 +501,12 @@ impl Instance for crate::peripherals::OSTIMER0 { } // Also implement Instance for the Peri wrapper type -impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Ostimer0::ptr() - } -} +// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { +// #[inline(always)] +// fn ptr() -> *const Regs { +// pac::Ostimer0::ptr() +// } +// } #[inline(always)] fn bin_to_gray(x: u64) -> u64 { diff --git a/src/reset.rs b/src/reset.rs deleted file mode 100644 index 1c131d1cc..000000000 --- a/src/reset.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Reset control helpers built on PAC field writers. -use crate::pac; - -/// Trait describing a reset line that can be asserted/deasserted. -pub trait ResetLine { - /// Drive the peripheral out of reset. - unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock); - - /// Drive the peripheral into reset. - unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock); - - /// Check whether the peripheral is currently released. - fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool; -} - -/// Release a reset line for the given peripheral set. -#[inline] -pub unsafe fn release(peripherals: &pac::Peripherals) { - R::release(&peripherals.mrcc0); -} - -/// Assert a reset line for the given peripheral set. -#[inline] -pub unsafe fn assert(peripherals: &pac::Peripherals) { - R::assert(&peripherals.mrcc0); -} - -/// Pulse a reset line (assert then release) with a short delay. -#[inline] -pub unsafe fn pulse(peripherals: &pac::Peripherals) { - let mrcc = &peripherals.mrcc0; - R::assert(mrcc); - cortex_m::asm::nop(); - cortex_m::asm::nop(); - R::release(mrcc); -} - -macro_rules! impl_reset_line { - ($name:ident, $reg:ident, $field:ident) => { - pub struct $name; - - impl ResetLine for $name { - #[inline] - unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock) { - mrcc.$reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock) { - mrcc.$reg().modify(|_, w| w.$field().disabled()); - } - - #[inline] - fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool { - mrcc.$reg().read().$field().is_enabled() - } - } - }; -} - -pub mod line { - use super::*; - - impl_reset_line!(Port2, mrcc_glb_rst1, port2); - impl_reset_line!(Port3, mrcc_glb_rst1, port3); - impl_reset_line!(Gpio3, mrcc_glb_rst2, gpio3); - impl_reset_line!(Lpuart2, mrcc_glb_rst0, lpuart2); - impl_reset_line!(Ostimer0, mrcc_glb_rst1, ostimer0); - impl_reset_line!(Port1, mrcc_glb_rst1, port1); - impl_reset_line!(Adc1, mrcc_glb_rst1, adc1); -} - -#[inline] -pub unsafe fn release_reset_port2(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_port3(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_gpio3(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_lpuart2(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_ostimer0(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -/// Convenience shim retained for existing call sites. -#[inline] -pub unsafe fn reset_ostimer0(peripherals: &pac::Peripherals) { - pulse::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_port1(peripherals: &pac::Peripherals) { - release::(peripherals); -} - -#[inline] -pub unsafe fn release_reset_adc1(peripherals: &pac::Peripherals) { - release::(peripherals); -} -- cgit From 8cdccae3c6c4a805cf5003b1a859734c105d76e8 Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 18:43:27 +0100 Subject: Continue working on examples --- Cargo.toml | 3 - examples/adc_interrupt.rs | 29 ++-- examples/adc_polling.rs | 30 +++- examples/blink.rs | 4 - examples/hello.rs | 33 +++-- examples/lpuart_buffered.rs | 9 +- examples/lpuart_polling.rs | 11 +- examples/ostimer_alarm.rs | 32 +++-- examples/ostimer_async.rs | 29 ++-- examples/ostimer_counter.rs | 26 +++- examples/ostimer_race_test.rs | 71 ++++++---- examples/rtc_alarm.rs | 28 ++-- examples/uart_interrupt.rs | 69 --------- src/lib.rs | 2 - src/lpuart/mod.rs | 20 +++ src/ostimer.rs | 10 +- src/rtc.rs | 32 +++-- src/uart.rs | 316 ------------------------------------------ 18 files changed, 248 insertions(+), 506 deletions(-) delete mode 100644 examples/uart_interrupt.rs delete mode 100644 src/uart.rs diff --git a/Cargo.toml b/Cargo.toml index 19d1c7174..259becfe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,9 +57,6 @@ name = "hello" [[example]] name = "blink" -[[example]] -name = "uart_interrupt" - [[example]] name = "ostimer_alarm" diff --git a/examples/adc_interrupt.rs b/examples/adc_interrupt.rs index 3be85ac75..536152539 100644 --- a/examples/adc_interrupt.rs +++ b/examples/adc_interrupt.rs @@ -4,13 +4,13 @@ use embassy_executor::Spawner; use embassy_mcxa276::clocks::periph_helpers::{AdcClockSel, Div4}; use embassy_mcxa276::clocks::PoweredClock; +use embassy_mcxa276::lpuart::{Config, Lpuart}; use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; -use hal::uart; use mcxa_pac::adc1::cfg::{Pwrsel, Refsel}; use mcxa_pac::adc1::cmdl1::{Adch, Mode}; use mcxa_pac::adc1::ctrl::CalAvgs; use mcxa_pac::adc1::tctrl::Tcmd; -use {cortex_m, embassy_mcxa276 as hal}; +use {embassy_mcxa276 as hal}; mod common; use hal::{bind_interrupts, InterruptExt}; @@ -28,14 +28,25 @@ static KEEP_ADC: unsafe extern "C" fn() = ADC1; async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - - // let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - // let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); - - // uart.write_str_blocking("\r\n=== ADC interrupt Example ===\r\n"); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); unsafe { common::init_adc(hal::pac()); @@ -71,7 +82,7 @@ async fn main(_spawner: Spawner) { conv_trigger_config.enable_hardware_trigger = false; adc.set_conv_trigger_config(0, &conv_trigger_config); - // uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); + uart.write_str_blocking("\r\n=== ADC configuration done... ===\r\n"); adc.enable_interrupt(0x1); @@ -88,7 +99,7 @@ async fn main(_spawner: Spawner) { while !adc.is_interrupt_triggered() { // Wait until the interrupt is triggered } - // uart.write_str_blocking("\r\n*** ADC interrupt TRIGGERED! ***\r\n"); + uart.write_str_blocking("\r\n*** ADC interrupt TRIGGERED! ***\r\n"); //TBD need to print the value } } diff --git a/examples/adc_polling.rs b/examples/adc_polling.rs index 4b5f9422d..2fe4153db 100644 --- a/examples/adc_polling.rs +++ b/examples/adc_polling.rs @@ -2,9 +2,11 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa276::clocks::periph_helpers::{AdcClockSel, Div4}; +use embassy_mcxa276::clocks::PoweredClock; +use embassy_mcxa276::lpuart::{Config, Lpuart}; use embassy_mcxa276 as hal; use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; -use hal::uart; use mcxa_pac::adc1::cfg::{Pwrsel, Refsel}; use mcxa_pac::adc1::cmdl1::{Adch, Mode}; use mcxa_pac::adc1::ctrl::CalAvgs; @@ -27,10 +29,27 @@ async fn main(_spawner: Spawner) { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX + unsafe { + common::init_uart2(hal::pac()); + } + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); - uart.write_str_blocking("\r\n=== ADC polling Example ===\r\n"); + uart.blocking_write(b"\r\n=== ADC polling Example ===\r\n").unwrap(); unsafe { common::init_adc(hal::pac()); @@ -47,6 +66,9 @@ async fn main(_spawner: Spawner) { enable_conv_pause: false, conv_pause_delay: 0, fifo_watermark: 0, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + source: AdcClockSel::FroLfDiv, + div: Div4::no_div(), }; let adc = hal::adc::Adc::::new(p.ADC1, adc_config); diff --git a/examples/blink.rs b/examples/blink.rs index 564353d5c..0f489abb9 100644 --- a/examples/blink.rs +++ b/examples/blink.rs @@ -28,10 +28,6 @@ async fn main(_spawner: Spawner) { unsafe { common::init_led(hal::pac()); } - // Initialize OSTIMER for async timing - unsafe { - common::init_ostimer0(hal::pac()); - } // Initialize embassy-time global driver backed by OSTIMER0 hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); diff --git a/examples/hello.rs b/examples/hello.rs index e39adaced..dbb53fdcf 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -2,15 +2,14 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa276 as hal; -use hal::uart; +use embassy_mcxa276::{self as hal, lpuart::{Blocking, Config, Lpuart}}; mod common; use {defmt_rtt as _, panic_probe as _}; /// Simple helper to write a byte as hex to UART -fn write_hex_byte(uart: &hal::uart::Uart, byte: u8) { +fn write_hex_byte(uart: &mut Lpuart<'_, Blocking>, byte: u8) { const HEX_DIGITS: &[u8] = b"0123456789ABCDEF"; uart.write_byte(HEX_DIGITS[(byte >> 4) as usize]); uart.write_byte(HEX_DIGITS[(byte & 0xF) as usize]); @@ -22,15 +21,25 @@ async fn main(_spawner: Spawner) { defmt::info!("boot"); - // Board-level init for UART2 clocks and pins. + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - - // Get UART source frequency from clock configuration - // Using hardcoded frequency for now - dynamic detection may have issues - let src = 12_000_000; // FRO_LF_DIV at 12MHz with DIV=0 - let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); // Print welcome message before any async delays to guarantee early console output uart.write_str_blocking("\r\n=== MCXA276 UART Echo Demo ===\r\n"); @@ -69,12 +78,12 @@ async fn main(_spawner: Spawner) { let num_str = &command[4..]; if let Ok(num) = parse_u8(num_str) { uart.write_str_blocking("Hex: 0x"); - write_hex_byte(&uart, num); + write_hex_byte(&mut uart, num); uart.write_str_blocking("\r\n"); } else { uart.write_str_blocking("Invalid number for hex command\r\n"); } - } else if command.len() > 0 { + } else if !command.is_empty() { uart.write_str_blocking("Unknown command: "); uart.write_str_blocking(core::str::from_utf8(command).unwrap_or("")); uart.write_str_blocking("\r\n"); @@ -103,7 +112,7 @@ async fn main(_spawner: Spawner) { fn parse_u8(bytes: &[u8]) -> Result { let mut result = 0u8; for &b in bytes { - if b >= b'0' && b <= b'9' { + if b.is_ascii_digit() { result = result.checked_mul(10).ok_or(())?; result = result.checked_add(b - b'0').ok_or(())?; } else { diff --git a/examples/lpuart_buffered.rs b/examples/lpuart_buffered.rs index 6ae690c56..9e297ca67 100644 --- a/examples/lpuart_buffered.rs +++ b/examples/lpuart_buffered.rs @@ -22,7 +22,7 @@ unsafe extern "C" fn lpuart2_handler() { #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = hal::init(hal::config::Config::default()); + let p = hal::init(hal::config::Config::default()); unsafe { hal::interrupt::install_irq_handler(mcxa_pac::Interrupt::LPUART2, lpuart2_handler); @@ -33,7 +33,6 @@ async fn main(_spawner: Spawner) { unsafe { common::init_uart2(hal::pac()); - common::init_ostimer0(hal::pac()); } // UART configuration (enable both TX and RX) @@ -51,9 +50,9 @@ async fn main(_spawner: Spawner) { // Create a buffered LPUART2 instance with both TX and RX let mut uart = BufferedLpuart::new( - p2.LPUART2, - p2.PIO2_2, // TX pin - p2.PIO2_3, // RX pin + p.LPUART2, + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin Irqs, &mut tx_buf, &mut rx_buf, diff --git a/examples/lpuart_polling.rs b/examples/lpuart_polling.rs index 067c7eb53..c9630dca5 100644 --- a/examples/lpuart_polling.rs +++ b/examples/lpuart_polling.rs @@ -4,14 +4,13 @@ use embassy_executor::Spawner; use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; -use crate::hal::lpuart::{lib, Config, Lpuart}; +use crate::hal::lpuart::{Config, Lpuart}; mod common; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = hal::init(hal::config::Config::default()); - let p2 = lib::init(); + let p = hal::init(hal::config::Config::default()); defmt::info!("boot"); @@ -30,9 +29,9 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX let lpuart = Lpuart::new_blocking( - p2.LPUART2, // Peripheral - p2.PIO2_2, // TX pin - p2.PIO2_3, // RX pin + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin config, ) .unwrap(); diff --git a/examples/ostimer_alarm.rs b/examples/ostimer_alarm.rs index 4f29a2c7c..f3a84d312 100644 --- a/examples/ostimer_alarm.rs +++ b/examples/ostimer_alarm.rs @@ -4,12 +4,15 @@ use core::sync::atomic::{AtomicBool, Ordering}; use embassy_executor::Spawner; -use hal::uart; -use {cortex_m, embassy_mcxa276 as hal}; +use embassy_mcxa276 as hal; mod common; -use embassy_mcxa276::{bind_interrupts, clocks::{periph_helpers::OstimerClockSel, PoweredClock}}; +use embassy_mcxa276::{ + bind_interrupts, + clocks::{periph_helpers::OstimerClockSel, PoweredClock}, + lpuart::{Config, Lpuart}, +}; use {defmt_rtt as _, panic_probe as _}; // Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. @@ -33,15 +36,26 @@ fn alarm_callback() { async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); - // Enable/clock OSTIMER0 and UART2 before touching their registers - unsafe { - common::init_ostimer0(hal::pac()); - } + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); + uart.write_str_blocking("OSTIMER Alarm Example\n"); // Initialize embassy-time global driver backed by OSTIMER0 diff --git a/examples/ostimer_async.rs b/examples/ostimer_async.rs index 27e14e022..2642a633d 100644 --- a/examples/ostimer_async.rs +++ b/examples/ostimer_async.rs @@ -2,8 +2,7 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa276 as hal; -use hal::uart; +use embassy_mcxa276::{self as hal, lpuart::{Config, Lpuart}}; mod common; @@ -22,18 +21,28 @@ static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = hal::init(hal::config::Config::default()); + let p = hal::init(hal::config::Config::default()); - // Enable/clock OSTIMER0 and UART2 before touching their registers - unsafe { - common::init_ostimer0(hal::pac()); - } + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(_p.LPUART2, uart::Config::new(src)); - uart.write_str_blocking("boot\n"); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); + uart.blocking_write(b"boot\n").unwrap(); // Avoid mass NVIC writes here; DefaultHandler now safely returns. diff --git a/examples/ostimer_counter.rs b/examples/ostimer_counter.rs index 069e879d8..590c5a14b 100644 --- a/examples/ostimer_counter.rs +++ b/examples/ostimer_counter.rs @@ -7,7 +7,10 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa276::clocks::{periph_helpers::OstimerClockSel, PoweredClock}; +use embassy_mcxa276::{ + clocks::{periph_helpers::OstimerClockSel, PoweredClock}, + lpuart::{Blocking, Config, Lpuart}, +}; use embassy_time::{Duration, Timer}; use hal::bind_interrupts; use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; @@ -22,12 +25,25 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let p = hal::init(Default::default()); - // Enable/clock OSTIMER0 and UART2 before touching their registers + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let mut uart = hal::uart::Uart::::new(p.LPUART2, hal::uart::Config::new(src)); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); uart.write_str_blocking("OSTIMER Counter Reading and Reset Example\n"); @@ -90,7 +106,7 @@ async fn main(_spawner: Spawner) { } // Helper function to write a u64 value as decimal string -fn write_u64(uart: &mut hal::uart::Uart, value: u64) { +fn write_u64(uart: &mut Lpuart<'_, Blocking>, value: u64) { if value == 0 { uart.write_str_blocking("0"); return; diff --git a/examples/ostimer_race_test.rs b/examples/ostimer_race_test.rs index 6e3d4ac21..131d10f64 100644 --- a/examples/ostimer_race_test.rs +++ b/examples/ostimer_race_test.rs @@ -12,7 +12,10 @@ use core::sync::atomic::{AtomicU32, Ordering}; use embassy_executor::Spawner; -use embassy_mcxa276::clocks::{periph_helpers::OstimerClockSel, PoweredClock}; +use embassy_mcxa276::{ + clocks::{periph_helpers::OstimerClockSel, PoweredClock}, + lpuart::{Blocking, Config, Lpuart}, +}; use embassy_time::{Duration, Timer}; use hal::bind_interrupts; use {defmt_rtt as _, embassy_mcxa276 as hal, panic_probe as _}; @@ -43,7 +46,7 @@ fn alarm_callback() { } } -fn report_default_handler(uart: &mut hal::uart::Uart) { +fn report_default_handler(uart: &mut Lpuart<'_, Blocking>) { let snapshot = hal::interrupt::default_handler_snapshot(); if snapshot.count == 0 { return; @@ -72,15 +75,25 @@ fn report_default_handler(uart: &mut hal::uart::Uart) { async fn main(_spawner: Spawner) { let p = hal::init(Default::default()); - // Enable/clock OSTIMER0 and UART2 before touching their registers - unsafe { - common::init_ostimer0(hal::pac()); - } + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let mut uart = hal::uart::Uart::::new(p.LPUART2, hal::uart::Config::new(src)); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); uart.write_str_blocking("OSTIMER Race Condition Test Starting...\n"); @@ -140,7 +153,7 @@ async fn main(_spawner: Spawner) { // Test rapid alarm scheduling to stress interrupt handling async fn test_rapid_alarms( ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut hal::uart::Uart, + uart: &mut Lpuart<'_, Blocking>, ) { let initial_count = ALARM_CALLBACK_COUNT.load(Ordering::SeqCst); @@ -177,7 +190,7 @@ async fn test_rapid_alarms( // Test reading counter while interrupts are firing async fn test_counter_reading_during_interrupts( ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut hal::uart::Uart, + uart: &mut Lpuart<'_, Blocking>, ) { let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); @@ -238,7 +251,7 @@ async fn test_counter_reading_during_interrupts( // Test concurrent timer operations (embassy-time + alarms) async fn test_concurrent_operations( ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut hal::uart::Uart, + uart: &mut Lpuart<'_, Blocking>, ) { let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); @@ -267,7 +280,7 @@ async fn test_concurrent_operations( // Test timer reset during active operations async fn test_reset_during_operation( ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut hal::uart::Uart, + uart: &mut Lpuart<'_, Blocking>, peripherals: &mcxa_pac::Peripherals, ) { let initial_counter = ostimer.now(); @@ -308,7 +321,7 @@ async fn test_reset_during_operation( } // Helper function to write a u32 value as decimal string -fn write_u32(uart: &mut hal::uart::Uart, value: u32) { +fn write_u32(uart: &mut Lpuart<'_, Blocking>, value: u32) { if value == 0 { uart.write_str_blocking("0"); return; @@ -343,7 +356,7 @@ fn write_u32(uart: &mut hal::uart::Uart, value: u32) { } } -fn write_hex32(uart: &mut hal::uart::Uart, value: u32) { +fn write_hex32(uart: &mut Lpuart<'_, Blocking>, value: u32) { let mut buf = [b'0'; 8]; let mut tmp = value; for i in (0..8).rev() { @@ -355,15 +368,13 @@ fn write_hex32(uart: &mut hal::uart::Uart, value: u32) { }; tmp >>= 4; } - for b in &buf { - uart.write_byte(*b); - } + uart.blocking_write(&buf).unwrap(); } // Helper function to write a u64 value as decimal string -fn write_u64(uart: &mut hal::uart::Uart, value: u64) { +fn write_u64(uart: &mut Lpuart<'_, Blocking>, value: u64) { if value == 0 { - uart.write_str_blocking("0"); + uart.blocking_write(b"0").unwrap(); return; } @@ -381,17 +392,17 @@ fn write_u64(uart: &mut hal::uart::Uart, value: u64) { while i > 0 { i -= 1; match buffer[i] { - b'0' => uart.write_str_blocking("0"), - b'1' => uart.write_str_blocking("1"), - b'2' => uart.write_str_blocking("2"), - b'3' => uart.write_str_blocking("3"), - b'4' => uart.write_str_blocking("4"), - b'5' => uart.write_str_blocking("5"), - b'6' => uart.write_str_blocking("6"), - b'7' => uart.write_str_blocking("7"), - b'8' => uart.write_str_blocking("8"), - b'9' => uart.write_str_blocking("9"), - _ => uart.write_str_blocking("?"), + b'0' => uart.blocking_write(b"0").unwrap(), + b'1' => uart.blocking_write(b"1").unwrap(), + b'2' => uart.blocking_write(b"2").unwrap(), + b'3' => uart.blocking_write(b"3").unwrap(), + b'4' => uart.blocking_write(b"4").unwrap(), + b'5' => uart.blocking_write(b"5").unwrap(), + b'6' => uart.blocking_write(b"6").unwrap(), + b'7' => uart.blocking_write(b"7").unwrap(), + b'8' => uart.blocking_write(b"8").unwrap(), + b'9' => uart.blocking_write(b"9").unwrap(), + _ => uart.blocking_write(b"?").unwrap(), } } } diff --git a/examples/rtc_alarm.rs b/examples/rtc_alarm.rs index c27fd4c55..1cda37054 100644 --- a/examples/rtc_alarm.rs +++ b/examples/rtc_alarm.rs @@ -2,13 +2,14 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa276::lpuart::{Config, Lpuart}; use hal::rtc::{RtcDateTime, RtcInterruptEnable}; -use hal::{uart, InterruptExt}; -use {cortex_m, embassy_mcxa276 as hal}; +use hal::InterruptExt; +use {embassy_mcxa276 as hal}; mod common; -type MyRtc = hal::rtc::Rtc; +type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; use embassy_mcxa276::bind_interrupts; use {defmt_rtt as _, panic_probe as _}; @@ -25,17 +26,28 @@ static KEEP_RTC: unsafe extern "C" fn() = RTC; async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); + // Create UART configuration + let config = Config { + baudrate_bps: 115_200, + enable_tx: true, + enable_rx: true, + ..Default::default() + }; + + // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { common::init_uart2(hal::pac()); } - - let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; - let uart = uart::Uart::::new(p.LPUART2, uart::Config::new(src)); + let mut uart = Lpuart::new_blocking( + p.LPUART2, // Peripheral + p.PIO2_2, // TX pin + p.PIO2_3, // RX pin + config, + ) + .unwrap(); uart.write_str_blocking("\r\n=== RTC Alarm Example ===\r\n"); - unsafe { hal::clocks::init_fro16k(hal::pac()) }; - let rtc_config = hal::rtc::get_default_config(); let rtc = MyRtc::new(p.RTC0, rtc_config); diff --git a/examples/uart_interrupt.rs b/examples/uart_interrupt.rs deleted file mode 100644 index 190a4d850..000000000 --- a/examples/uart_interrupt.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -// use embassy_mcxa276 as hal; -// use hal::interrupt::typelevel::Handler; -// use hal::uart; - -// mod common; - -// use embassy_mcxa276::bind_interrupts; -// use {defmt_rtt as _, panic_probe as _}; - -// // Bind LPUART2 interrupt to our handler -// bind_interrupts!(struct Irqs { -// LPUART2 => hal::uart::UartInterruptHandler; -// }); - -// #[used] -// #[no_mangle] -// static KEEP_LPUART2: unsafe extern "C" fn() = LPUART2; - -// // Wrapper function for the interrupt handler -// unsafe extern "C" fn lpuart2_handler() { -// hal::uart::UartInterruptHandler::on_interrupt(); -// } - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { -// let _p = hal::init(hal::config::Config::default()); - -// // Enable/clock UART2 before touching its registers -// unsafe { -// common::init_uart2(hal::pac()); -// } -// let src = unsafe { hal::clocks::uart2_src_hz(hal::pac()) }; -// let uart = uart::Uart::::new(_p.LPUART2, uart::Config::new(src)); - -// // Configure LPUART2 interrupt for UART operation BEFORE any UART usage -// hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::from(3)); - -// // Manually install the interrupt handler and enable RX IRQs in the peripheral -// unsafe { -// hal::interrupt::LPUART2.install_handler(lpuart2_handler); -// // Enable RX interrupts so the handler actually fires on incoming bytes -// uart.enable_rx_interrupts(); -// } - -// // Print welcome message -// uart.write_str_blocking("UART interrupt echo demo starting...\r\n"); -// uart.write_str_blocking("Type characters to echo them back.\r\n"); - -// // Log using defmt if enabled -// defmt::info!("UART interrupt echo demo starting..."); - -// loop { -// // Check if we have received any data -// if uart.rx_data_available() { -// if let Some(byte) = uart.try_read_byte() { -// // Echo it back -// uart.write_byte(byte); -// uart.write_str_blocking(" (received)\r\n"); -// } -// } else { -// // No data available, wait a bit before checking again -// cortex_m::asm::delay(12_000_000); // ~1 second at 12MHz -// } -// } -} diff --git a/src/lib.rs b/src/lib.rs index 4120c1e84..ec2cb31e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ pub mod interrupt; pub mod lpuart; pub mod ostimer; pub mod rtc; -pub mod uart; embassy_hal_internal::peripherals!(PORT1, PORT2, PORT3, LPUART2, OSTIMER0, GPIO, PIO2_2, PIO2_3, GPIO3, RTC0, ADC1,); @@ -45,7 +44,6 @@ pub use mcxa_pac as pac; pub(crate) use mcxa_pac as pac; pub use ostimer::Ostimer0 as Ostimer0Token; pub use rtc::Rtc0 as Rtc0Token; -pub use uart::Lpuart2 as Uart2Token; /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 9e8b25ff6..b3d7c4885 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs @@ -772,6 +772,10 @@ impl<'a> LpuartTx<'a, Blocking> { Ok(()) } + pub fn write_str_blocking(&mut self, buf: &str) { + let _ = self.blocking_write(buf.as_bytes()); + } + /// Write data to LPUART TX without blocking. pub fn write(&mut self, buf: &[u8]) -> Result<()> { for x in buf { @@ -901,6 +905,22 @@ impl<'a> Lpuart<'a, Blocking> { self.tx.blocking_write(buf) } + pub fn write_byte(&mut self, byte: u8) { + _ = self.tx.write_byte(byte); + } + + pub fn read_byte_blocking(&mut self) -> u8 { + loop { + if let Ok(b) = self.rx.read_byte() { + return b; + } + } + } + + pub fn write_str_blocking(&mut self, buf: &str) { + self.tx.write_str_blocking(buf); + } + /// Write data to LPUART TX without blocking pub fn write(&mut self, buf: &[u8]) -> Result<()> { self.tx.write(buf) diff --git a/src/ostimer.rs b/src/ostimer.rs index efa534194..ebdf7d45d 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs @@ -534,7 +534,7 @@ pub mod time_driver { bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, }; - use crate::pac; + use crate::{clocks::{enable_and_reset, periph_helpers::{OsTimerConfig, OstimerClockSel}, PoweredClock}, pac, peripherals::OSTIMER0}; pub struct Driver; static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); @@ -621,6 +621,14 @@ pub mod time_driver { /// Note: The frequency parameter is currently accepted for API compatibility. /// The embassy_time_driver macro handles driver registration automatically. pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { + let _clock_freq = unsafe { + enable_and_reset::(&OsTimerConfig { + power: PoweredClock::AlwaysEnabled, + source: OstimerClockSel::Clk1M, + }) + .expect("Enabling OsTimer clock should not fail") + }; + // Mask/clear at peripheral and set default MATCH let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; super::prime_match_registers(r); diff --git a/src/rtc.rs b/src/rtc.rs index facb9cf8c..f526e82ac 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,6 +1,9 @@ //! RTC DateTime driver. use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::clocks::with_clocks; use crate::pac; use crate::pac::rtc0::cr::Um; @@ -9,7 +12,7 @@ type Regs = pac::rtc0::RegisterBlock; static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); // Token-based instance pattern like embassy-imxrt -pub trait Instance { +pub trait Instance: PeripheralType { fn ptr() -> *const Regs; } @@ -22,14 +25,6 @@ impl Instance for crate::peripherals::RTC0 { } } -// Also implement Instance for the Peri wrapper type -impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Rtc0::ptr() - } -} - const DAYS_IN_A_YEAR: u32 = 365; const SECONDS_IN_A_DAY: u32 = 86400; const SECONDS_IN_A_HOUR: u32 = 3600; @@ -157,15 +152,26 @@ pub fn get_default_config() -> RtcConfig { } } /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) -pub struct Rtc { - _inst: core::marker::PhantomData, +pub struct Rtc<'a, I: Instance> { + _inst: core::marker::PhantomData<&'a mut I>, } -impl Rtc { +impl<'a, I: Instance> Rtc<'a, I> { /// initialize RTC - pub fn new(_inst: impl Instance, config: RtcConfig) -> Self { + pub fn new(_inst: Peri<'a, I>, config: RtcConfig) -> Self { let rtc = unsafe { &*I::ptr() }; + // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock + // on the vsys domain is active + let clocks = with_clocks(|c| { + c.clk_16k_vsys.clone() + }); + match clocks { + None => panic!("Clocks have not been initialized"), + Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), + Some(Some(_)) => {} + } + /* RTC reset */ rtc.cr().modify(|_, w| w.swr().set_bit()); rtc.cr().modify(|_, w| w.swr().clear_bit()); diff --git a/src/uart.rs b/src/uart.rs deleted file mode 100644 index 3705959d3..000000000 --- a/src/uart.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. -//! WARNING: This is a narrow implementation only for debug console (115200 8N1). - -// TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs -// are complete prior to release. -#![allow(clippy::missing_safety_doc)] - -use core::cell::RefCell; - -use cortex_m::interrupt::Mutex; -use embassy_sync::signal::Signal; - -use crate::pac; - -// svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. -type Regs = pac::lpuart0::RegisterBlock; - -// Token-based instance pattern like embassy-imxrt -pub trait Instance { - fn ptr() -> *const Regs; -} - -/// Token for LPUART2 provided by embassy-hal-internal peripherals macro. -pub type Lpuart2 = crate::peripherals::LPUART2; -impl Instance for crate::peripherals::LPUART2 { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Lpuart2::ptr() - } -} - -// Also implement Instance for the Peri wrapper type -impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { - #[inline(always)] - fn ptr() -> *const Regs { - pac::Lpuart2::ptr() - } -} - -/// UART configuration (explicit src_hz; no hardcoded frequencies) -#[derive(Copy, Clone)] -pub struct Config { - pub src_hz: u32, - pub baud: u32, - pub parity: Parity, - pub stop_bits: StopBits, -} - -#[derive(Copy, Clone)] -pub enum Parity { - None, - Even, - Odd, -} -#[derive(Copy, Clone)] -pub enum StopBits { - One, - Two, -} - -impl Config { - pub fn new(src_hz: u32) -> Self { - Self { - src_hz, - baud: 115_200, - parity: Parity::None, - stop_bits: StopBits::One, - } - } -} - -/// Compute a valid (OSR, SBR) tuple for given source clock and baud. -/// Uses a functional fold approach to find the best OSR/SBR combination -/// with minimal baud rate error. -fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { - let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( - (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) - |(best_osr, best_sbr, best_err), osr| { - let denom = baud.saturating_mul(osr); - if denom == 0 { - return (best_osr, best_sbr, best_err); - } - - let sbr = (src_hz + denom / 2) / denom; // round - if sbr == 0 || sbr > 0x1FFF { - return (best_osr, best_sbr, best_err); - } - - let actual = src_hz / (osr * sbr); - let err = actual.abs_diff(baud); - - // Update best if this is better, or same error but higher OSR - if err < best_err || (err == best_err && osr as u8 > best_osr) { - (osr as u8, sbr as u16, err) - } else { - (best_osr, best_sbr, best_err) - } - }, - ); - (best_osr, best_sbr) -} - -/// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) -pub struct Uart { - _inst: core::marker::PhantomData, -} - -impl Uart { - /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. - pub fn new(_inst: impl Instance, cfg: Config) -> Self { - let l = unsafe { &*I::ptr() }; - // 1) software reset pulse - l.global().write(|w| w.rst().reset()); - cortex_m::asm::delay(3); // Short delay for reset to take effect - l.global().write(|w| w.rst().no_effect()); - cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset - // 2) BAUD - let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); - l.baud().modify(|_, w| { - let w = match cfg.stop_bits { - StopBits::One => w.sbns().one(), - StopBits::Two => w.sbns().two(), - }; - // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants - let raw_osr = osr.saturating_sub(1); - unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } - }); - // 3) CTRL baseline and parity - l.ctrl().write(|w| { - let w = w.ilt().from_stop().idlecfg().idle_2(); - let w = match cfg.parity { - Parity::None => w.pe().disabled(), - Parity::Even => w.pe().enabled().pt().even(), - Parity::Odd => w.pe().enabled().pt().odd(), - }; - w.re().enabled().te().enabled().rie().disabled() - }); - // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 - l.fifo().modify(|_, w| { - w.txfe() - .disabled() - .rxfe() - .disabled() - .txflush() - .txfifo_rst() - .rxflush() - .rxfifo_rst() - }); - l.water() - .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); - Self { - _inst: core::marker::PhantomData, - } - } - - /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. - pub unsafe fn enable_rx_interrupts(&self) { - let l = &*I::ptr(); - l.ctrl().modify(|_, w| w.rie().enabled()); - } - - #[inline(never)] - pub fn write_byte(&self, b: u8) { - let l = unsafe { &*I::ptr() }; - // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) - const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block - let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; - for _ in 0..120000 { - if l.water().read().txcount().bits() == 0 { - unsafe { core::ptr::write_volatile(data_ptr, b) }; - return; - } - } - // If timeout, skip the write to avoid hanging - } - - #[inline(never)] - pub fn write_str_blocking(&self, s: &str) { - for &b in s.as_bytes() { - if b == b'\n' { - self.write_byte(b'\r'); - } - self.write_byte(b); - } - } - pub fn read_byte_blocking(&self) -> u8 { - let l = unsafe { &*I::ptr() }; - while !l.stat().read().rdrf().is_rxdata() {} - (l.data().read().bits() & 0xFF) as u8 - } -} - -// Simple ring buffer for UART RX data -const RX_BUFFER_SIZE: usize = 256; -pub struct RingBuffer { - buffer: [u8; RX_BUFFER_SIZE], - read_idx: usize, - write_idx: usize, - count: usize, -} - -impl Default for RingBuffer { - fn default() -> Self { - Self::new() - } -} - -impl RingBuffer { - pub const fn new() -> Self { - Self { - buffer: [0; RX_BUFFER_SIZE], - read_idx: 0, - write_idx: 0, - count: 0, - } - } - - pub fn push(&mut self, data: u8) -> bool { - if self.count >= RX_BUFFER_SIZE { - return false; // Buffer full - } - self.buffer[self.write_idx] = data; - self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; - self.count += 1; - true - } - - pub fn pop(&mut self) -> Option { - if self.count == 0 { - return None; - } - let data = self.buffer[self.read_idx]; - self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; - self.count -= 1; - Some(data) - } - - pub fn is_empty(&self) -> bool { - self.count == 0 - } - - pub fn len(&self) -> usize { - self.count - } -} - -// Global RX buffer shared between interrupt handler and UART instance -static RX_BUFFER: Mutex> = Mutex::new(RefCell::new(RingBuffer::new())); -static RX_SIGNAL: Signal = Signal::new(); - -// Debug counter for interrupt handler calls -static mut INTERRUPT_COUNT: u32 = 0; - -impl Uart { - /// Read a byte asynchronously using interrupts - pub async fn read_byte_async(&self) -> u8 { - loop { - // Check if we have data in the buffer - let byte = cortex_m::interrupt::free(|cs| { - let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); - buffer.pop() - }); - - if let Some(byte) = byte { - return byte; - } - - // Wait for the interrupt signal - RX_SIGNAL.wait().await; - } - } - - /// Check if there's data available in the RX buffer - pub fn rx_data_available(&self) -> bool { - cortex_m::interrupt::free(|cs| { - let buffer = RX_BUFFER.borrow(cs).borrow(); - !buffer.is_empty() - }) - } - - /// Try to read a byte from RX buffer (non-blocking) - pub fn try_read_byte(&self) -> Option { - cortex_m::interrupt::free(|cs| { - let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); - buffer.pop() - }) - } -} - -/// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. -pub struct UartInterruptHandler; - -impl crate::interrupt::typelevel::Handler for UartInterruptHandler { - unsafe fn on_interrupt() { - INTERRUPT_COUNT += 1; - - let lpuart = &*pac::Lpuart2::ptr(); - - // Check if we have RX data - if lpuart.stat().read().rdrf().is_rxdata() { - // Read the data byte - let data = (lpuart.data().read().bits() & 0xFF) as u8; - - // Store in ring buffer - cortex_m::interrupt::free(|cs| { - let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); - if buffer.push(data) { - // Data added successfully, signal waiting tasks - RX_SIGNAL.signal(()); - } - }); - } - // Always clear any error flags that might cause spurious interrupts - let _ = lpuart.stat().read(); - } -} -- cgit From 1f589e9428542cd94bc630b623accf04d9c36edf Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 19:08:16 +0100 Subject: A little follow-up cleanup --- examples/src/bin/adc_interrupt.rs | 5 +++-- examples/src/bin/adc_polling.rs | 8 ++++---- examples/src/bin/blink.rs | 4 ++-- examples/src/bin/hello.rs | 2 +- examples/src/bin/lpuart_buffered.rs | 7 ++++--- examples/src/bin/lpuart_polling.rs | 4 ++-- examples/src/bin/ostimer_alarm.rs | 4 ++-- examples/src/bin/ostimer_async.rs | 9 +++++---- examples/src/bin/ostimer_counter.rs | 2 +- examples/src/bin/ostimer_race_test.rs | 2 +- examples/src/bin/rtc_alarm.rs | 9 +++++---- examples/src/lib.rs | 10 ++++------ 12 files changed, 34 insertions(+), 32 deletions(-) diff --git a/examples/src/bin/adc_interrupt.rs b/examples/src/bin/adc_interrupt.rs index be08ebf8c..6812ba5d3 100644 --- a/examples/src/bin/adc_interrupt.rs +++ b/examples/src/bin/adc_interrupt.rs @@ -2,6 +2,7 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa_examples::init_adc_pins; use hal::adc::{LpadcConfig, TriggerPriorityPolicy}; use hal::clocks::periph_helpers::{AdcClockSel, Div4}; use hal::clocks::PoweredClock; @@ -35,7 +36,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - embassy_mcxa_examples::init_uart2(hal::pac()); + embassy_mcxa_examples::init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral @@ -47,7 +48,7 @@ async fn main(_spawner: Spawner) { uart.write_str_blocking("\r\n=== ADC interrupt Example ===\r\n"); unsafe { - embassy_mcxa_examples::init_adc(hal::pac()); + init_adc_pins(hal::pac()); } let adc_config = LpadcConfig { diff --git a/examples/src/bin/adc_polling.rs b/examples/src/bin/adc_polling.rs index 723f1e044..421306e9b 100644 --- a/examples/src/bin/adc_polling.rs +++ b/examples/src/bin/adc_polling.rs @@ -4,7 +4,7 @@ use core::fmt::Write; use embassy_executor::Spawner; -use embassy_mcxa_examples::{init_adc, init_uart2}; +use embassy_mcxa_examples::{init_adc_pins, init_uart2_pins}; use hal::adc::{ConvResult, LpadcConfig, TriggerPriorityPolicy}; use hal::clocks::periph_helpers::{AdcClockSel, Div4}; use hal::clocks::PoweredClock; @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } // Create UART configuration @@ -36,7 +36,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral @@ -49,7 +49,7 @@ async fn main(_spawner: Spawner) { uart.write_str_blocking("\r\n=== ADC polling Example ===\r\n"); unsafe { - init_adc(hal::pac()); + init_adc_pins(hal::pac()); } let adc_config = LpadcConfig { diff --git a/examples/src/bin/blink.rs b/examples/src/bin/blink.rs index ee59ac591..8c48e79f1 100644 --- a/examples/src/bin/blink.rs +++ b/examples/src/bin/blink.rs @@ -4,7 +4,7 @@ use embassy_executor::Spawner; use embassy_mcxa as hal; use embassy_mcxa::bind_interrupts; -use embassy_mcxa_examples::init_led; +use embassy_mcxa_examples::init_led_gpio_clocks; use embassy_time::{Duration, Timer}; use hal::gpio::pins::PIO3_18; use hal::gpio::{Level, Output}; @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { let _p = hal::init(hal::config::Config::default()); unsafe { - init_led(hal::pac()); + init_led_gpio_clocks(hal::pac()); } // Initialize embassy-time global driver backed by OSTIMER0 diff --git a/examples/src/bin/hello.rs b/examples/src/bin/hello.rs index ff7afa01d..207c157c3 100644 --- a/examples/src/bin/hello.rs +++ b/examples/src/bin/hello.rs @@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - embassy_mcxa_examples::init_uart2(hal::pac()); + embassy_mcxa_examples::init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral diff --git a/examples/src/bin/lpuart_buffered.rs b/examples/src/bin/lpuart_buffered.rs index e96ab7b81..642d4af65 100644 --- a/examples/src/bin/lpuart_buffered.rs +++ b/examples/src/bin/lpuart_buffered.rs @@ -5,8 +5,9 @@ use embassy_executor::Spawner; use embassy_mcxa as hal; use embassy_mcxa::interrupt::typelevel::Handler; use embassy_mcxa::lpuart::buffered::BufferedLpuart; +use embassy_mcxa::lpuart::Config; use embassy_mcxa::{bind_interrupts, lpuart}; -use embassy_mcxa_examples::init_uart2; +use embassy_mcxa_examples::init_uart2_pins; use embedded_io_async::{Read, Write}; // Bind OS_EVENT for timers plus LPUART2 IRQ for the buffered driver @@ -31,11 +32,11 @@ async fn main(_spawner: Spawner) { hal::interrupt::LPUART2.configure_for_uart(hal::interrupt::Priority::P3); unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } // UART configuration (enable both TX and RX) - let config = lpuart::Config { + let config = Config { baudrate_bps: 115_200, enable_tx: true, enable_rx: true, diff --git a/examples/src/bin/lpuart_polling.rs b/examples/src/bin/lpuart_polling.rs index bdcfa0776..bea82c33e 100644 --- a/examples/src/bin/lpuart_polling.rs +++ b/examples/src/bin/lpuart_polling.rs @@ -2,7 +2,7 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa_examples::init_uart2; +use embassy_mcxa_examples::init_uart2_pins; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; use crate::hal::lpuart::{Config, Lpuart}; @@ -15,7 +15,7 @@ async fn main(_spawner: Spawner) { // Board-level init for UART2 clocks and pins. unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } // Create UART configuration diff --git a/examples/src/bin/ostimer_alarm.rs b/examples/src/bin/ostimer_alarm.rs index 9e858b60b..36b1f403a 100644 --- a/examples/src/bin/ostimer_alarm.rs +++ b/examples/src/bin/ostimer_alarm.rs @@ -8,7 +8,7 @@ use embassy_mcxa::bind_interrupts; use embassy_mcxa::clocks::periph_helpers::OstimerClockSel; use embassy_mcxa::clocks::PoweredClock; use embassy_mcxa::lpuart::{Config, Lpuart}; -use embassy_mcxa_examples::init_uart2; +use embassy_mcxa_examples::init_uart2_pins; use {cortex_m, defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; // Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. @@ -42,7 +42,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral diff --git a/examples/src/bin/ostimer_async.rs b/examples/src/bin/ostimer_async.rs index 4e692a744..881f09374 100644 --- a/examples/src/bin/ostimer_async.rs +++ b/examples/src/bin/ostimer_async.rs @@ -3,8 +3,9 @@ use embassy_executor::Spawner; use embassy_mcxa::bind_interrupts; -use embassy_mcxa_examples::init_uart2; +use embassy_mcxa_examples::init_uart2_pins; use embassy_time::{Duration, Timer}; +use hal::lpuart::{Config, Lpuart}; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; // Bind only OS_EVENT, and retain the symbol explicitly so it can’t be GC’ed. @@ -21,7 +22,7 @@ async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); // Create UART configuration - let config = hal::lpuart::Config { + let config = Config { baudrate_bps: 115_200, enable_tx: true, enable_rx: true, @@ -30,9 +31,9 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - init_uart2(hal::pac()); + init_uart2_pins(hal::pac()); } - let mut uart = hal::lpuart::Lpuart::new_blocking( + let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral p.PIO2_2, // TX pin p.PIO2_3, // RX pin diff --git a/examples/src/bin/ostimer_counter.rs b/examples/src/bin/ostimer_counter.rs index 47543160b..2fbc251b9 100644 --- a/examples/src/bin/ostimer_counter.rs +++ b/examples/src/bin/ostimer_counter.rs @@ -32,7 +32,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - embassy_mcxa_examples::init_uart2(hal::pac()); + embassy_mcxa_examples::init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral diff --git a/examples/src/bin/ostimer_race_test.rs b/examples/src/bin/ostimer_race_test.rs index c2ecff969..168a952cd 100644 --- a/examples/src/bin/ostimer_race_test.rs +++ b/examples/src/bin/ostimer_race_test.rs @@ -82,7 +82,7 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - embassy_mcxa_examples::init_uart2(hal::pac()); + embassy_mcxa_examples::init_uart2_pins(hal::pac()); } let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral diff --git a/examples/src/bin/rtc_alarm.rs b/examples/src/bin/rtc_alarm.rs index e2eaa6ae2..40a1207df 100644 --- a/examples/src/bin/rtc_alarm.rs +++ b/examples/src/bin/rtc_alarm.rs @@ -2,9 +2,10 @@ #![no_main] use embassy_executor::Spawner; +use embassy_mcxa as hal; +use hal::lpuart::{Config, Lpuart}; use hal::rtc::{RtcDateTime, RtcInterruptEnable}; use hal::InterruptExt; -use {cortex_m, embassy_mcxa as hal}; type MyRtc = hal::rtc::Rtc<'static, hal::rtc::Rtc0>; @@ -24,7 +25,7 @@ async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); // Create UART configuration - let config = hal::lpuart::Config { + let config = Config { baudrate_bps: 115_200, enable_tx: true, enable_rx: true, @@ -33,9 +34,9 @@ async fn main(_spawner: Spawner) { // Create UART instance using LPUART2 with PIO2_2 as TX and PIO2_3 as RX unsafe { - embassy_mcxa_examples::init_uart2(hal::pac()); + embassy_mcxa_examples::init_uart2_pins(hal::pac()); } - let mut uart = hal::lpuart::Lpuart::new_blocking( + let mut uart = Lpuart::new_blocking( p.LPUART2, // Peripheral p.PIO2_2, // TX pin p.PIO2_3, // RX pin diff --git a/examples/src/lib.rs b/examples/src/lib.rs index a45ab708d..be4761c32 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![allow(clippy::missing_safety_doc)] //! Shared board-specific helpers for the FRDM-MCXA276 examples. //! These live with the examples so the HAL stays generic. @@ -8,8 +9,7 @@ use {embassy_mcxa as hal, panic_probe as _}; /// Initialize clocks and pin muxing for UART2 debug console. /// Safe to call multiple times; writes are idempotent for our use. -#[allow(dead_code)] -pub unsafe fn init_uart2(_p: &hal::pac::Peripherals) { +pub unsafe fn init_uart2_pins(_p: &hal::pac::Peripherals) { // NOTE: Lpuart has been updated to properly enable + reset its own clocks. // GPIO has not. _ = clocks::enable_and_reset::(&clocks::NoConfig); @@ -17,15 +17,13 @@ pub unsafe fn init_uart2(_p: &hal::pac::Peripherals) { } /// Initialize clocks for the LED GPIO/PORT used by the blink example. -#[allow(dead_code)] -pub unsafe fn init_led(_p: &hal::pac::Peripherals) { +pub unsafe fn init_led_gpio_clocks(_p: &hal::pac::Peripherals) { _ = clocks::enable_and_reset::(&clocks::NoConfig); _ = clocks::enable_and_reset::(&clocks::NoConfig); } /// Initialize clocks and pin muxing for ADC. -#[allow(dead_code)] -pub unsafe fn init_adc(_p: &hal::pac::Peripherals) { +pub unsafe fn init_adc_pins(_p: &hal::pac::Peripherals) { // NOTE: Lpuart has been updated to properly enable + reset its own clocks. // GPIO has not. _ = clocks::enable_and_reset::(&clocks::NoConfig); -- cgit From 728340ba05e9a16ceb472b147737c38c144d292d Mon Sep 17 00:00:00 2001 From: James Munns Date: Fri, 14 Nov 2025 19:11:06 +0100 Subject: Remove redundant import --- examples/src/bin/ostimer_alarm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/bin/ostimer_alarm.rs b/examples/src/bin/ostimer_alarm.rs index 36b1f403a..03fb93319 100644 --- a/examples/src/bin/ostimer_alarm.rs +++ b/examples/src/bin/ostimer_alarm.rs @@ -9,7 +9,7 @@ use embassy_mcxa::clocks::periph_helpers::OstimerClockSel; use embassy_mcxa::clocks::PoweredClock; use embassy_mcxa::lpuart::{Config, Lpuart}; use embassy_mcxa_examples::init_uart2_pins; -use {cortex_m, defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; // Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. bind_interrupts!(struct Irqs { -- cgit From 6e6ba60beb4faf17142938e1efff4b9d30e715c3 Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 17 Nov 2025 14:43:06 +0100 Subject: Docs pass and organization cleanup --- src/clocks/config.rs | 199 +++++++++ src/clocks/mod.rs | 983 ++++++++++++++++++++++--------------------- src/clocks/periph_helpers.rs | 160 +++++-- src/config.rs | 3 + 4 files changed, 822 insertions(+), 523 deletions(-) create mode 100644 src/clocks/config.rs diff --git a/src/clocks/config.rs b/src/clocks/config.rs new file mode 100644 index 000000000..a517afcca --- /dev/null +++ b/src/clocks/config.rs @@ -0,0 +1,199 @@ +//! Clock Configuration +//! +//! This module holds configuration types used for the system clocks. For +//! configuration of individual peripherals, see [`super::periph_helpers`]. + +use super::PoweredClock; + +/// This type represents a divider in the range 1..=256. +/// +/// At a hardware level, this is an 8-bit register from 0..=255, +/// which adds one. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct Div8(pub(super) u8); + +impl Div8 { + /// Store a "raw" divisor value that will divide the source by + /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source + /// by 1, and `Div8::from_raw(255)` will divide the source by + /// 256. + pub const fn from_raw(n: u8) -> Self { + Self(n) + } + + /// Store a specific divisor value that will divide the source + /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source + /// by 1, and `Div8::from_divisor(256)` will divide the source + /// by 256. + /// + /// Will return `None` if `n` is not in the range `1..=256`. + /// Consider [`Self::from_raw`] for an infallible version. + pub const fn from_divisor(n: u16) -> Option { + let Some(n) = n.checked_sub(1) else { + return None; + }; + if n > (u8::MAX as u16) { + return None; + } + Some(Self(n as u8)) + } + + /// Convert into "raw" bits form + #[inline(always)] + pub const fn into_bits(self) -> u8 { + self.0 + } + + /// Convert into "divisor" form, as a u32 for convenient frequency math + #[inline(always)] + pub const fn into_divisor(self) -> u32 { + self.0 as u32 + 1 + } +} + +/// ```text +/// ┌─────────────────────────────────────────────────────────┐ +/// │ │ +/// │ ┌───────────┐ clk_out ┌─────────┐ │ +/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ +/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ +/// EXTAL ──────┼──▷│ │───────────▷│ │ │ +/// │ └───────────┘ └─────────┘ │ +/// │ │ +/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ +/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_45m │ +/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ +/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ +/// │ │ │ │ ├────┤ clk_1m │ +/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ +/// │ └───────────┘ └────┘ │ +/// │ │ +/// │ ┌──────────┐ │ +/// │ │000 │ │ +/// │ clk_in │ │ │ +/// │ ───────────────▷│001 │ │ +/// │ fro_12m │ │ │ +/// │ ───────────────▷│010 │ │ +/// │ fro_hf_root │ │ │ +/// │ ───────────────▷│011 │ main_clk │ +/// │ │ │───────────────────────────┼──────▷ +/// clk_16k ──────┼─────────────────▷│100 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│101 │ │ +/// │ pll1_clk │ │ │ +/// │ ───────────────▷│110 │ │ +/// │ none │ │ │ +/// │ ───────────────▷│111 │ │ +/// │ └──────────┘ │ +/// │ ▲ │ +/// │ │ │ +/// │ SCG SCS │ +/// │ SCG-Lite │ +/// └─────────────────────────────────────────────────────────┘ +/// +/// +/// clk_in ┌─────┐ +/// ───────────────▷│00 │ +/// clk_45m │ │ +/// ───────────────▷│01 │ ┌───────────┐ pll1_clk +/// none │ │─────▷│ SPLL │───────────────▷ +/// ───────────────▷│10 │ └───────────┘ +/// fro_12m │ │ +/// ───────────────▷│11 │ +/// └─────┘ +/// ``` +#[non_exhaustive] +pub struct ClocksConfig { + /// FIRC, FRO180, 45/60/90/180M clock source + pub firc: Option, + /// SIRC, FRO12M, clk_12m clock source + // NOTE: I don't think we *can* disable the SIRC? + pub sirc: SircConfig, + /// FRO16K clock source + pub fro16k: Option, +} + +// FIRC/FRO180M + +/// ```text +/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf +/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ +/// │ │ │ ├────┤ clk_45m +/// │ │ └─────▷│GATE│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct FircConfig { + /// Selected clock frequency + pub frequency: FircFreqSel, + /// Selected power state of the clock + pub power: PoweredClock, + /// Is the "fro_hf" gated clock enabled? + pub fro_hf_enabled: bool, + /// Is the "clk_45m" gated clock enabled? + pub clk_45m_enabled: bool, + /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! + pub fro_hf_div: Option, +} + +/// Selected FIRC frequency +pub enum FircFreqSel { + /// 45MHz Output + Mhz45, + /// 60MHz Output + Mhz60, + /// 90MHz Output + Mhz90, + /// 180MHz Output + Mhz180, +} + +// SIRC/FRO12M + +/// ```text +/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m +/// │ FRO12M │────────┬─────▷│ CG │──────────▷ +/// │ │ │ ├────┤ clk_1m +/// │ │ └─────▷│1/12│──────────▷ +/// └───────────┘ └────┘ +/// ``` +#[non_exhaustive] +pub struct SircConfig { + pub power: PoweredClock, + // peripheral output, aka sirc_12mhz + pub fro_12m_enabled: bool, + /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! + pub fro_lf_div: Option, +} + +#[non_exhaustive] +pub struct Fro16KConfig { + pub vsys_domain_active: bool, + pub vdd_core_domain_active: bool, +} + +impl Default for ClocksConfig { + fn default() -> Self { + Self { + firc: Some(FircConfig { + frequency: FircFreqSel::Mhz45, + power: PoweredClock::NormalEnabledDeepSleepDisabled, + fro_hf_enabled: true, + clk_45m_enabled: true, + fro_hf_div: None, + }), + sirc: SircConfig { + power: PoweredClock::AlwaysEnabled, + fro_12m_enabled: true, + fro_lf_div: None, + }, + fro16k: Some(Fro16KConfig { + vsys_domain_active: true, + vdd_core_domain_active: true, + }), + } + } +} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index e04f63b8e..c6606b1b6 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -1,47 +1,335 @@ -//! Clock control helpers (no magic numbers, PAC field access only). -//! Provides reusable gate abstractions for peripherals used by the examples. +//! # Clock Module +//! +//! For the MCX-A, we separate clock and peripheral control into two main stages: +//! +//! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the +//! core system clocks, including external and internal oscillators. This +//! configuration is then largely static for the duration of the program. +//! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver +//! is responsible for two main things: +//! * Ensuring that any required "upstream" core system clocks necessary for +//! clocking the peripheral is active and configured to a reasonable value +//! * Enabling the clock gates for that peripheral, and resetting the peripheral +//! +//! From a user perspective, only step 1 is visible. Step 2 is automatically handled +//! by HAL drivers, using interfaces defined in this module. +//! +//! It is also possible to *view* the state of the clock configuration after [`init()`] +//! has been called, using the [`with_clocks()`] function, which provides a view of the +//! [`Clocks`] structure. +//! +//! ## For HAL driver implementors +//! +//! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or +//! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This +//! selection, as well as often any pre-scaler division from that source clock, is made +//! through MRCC registers. +//! +//! Any peripheral that is controlled through the MRCC register can automatically implement +//! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need +//! to define the configuration surface and steps necessary to fully configure that peripheral +//! from a clocks perspective by: +//! +//! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects +//! or divisions available to the HAL driver +//! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the +//! necessary input clocks are reasonable + use core::cell::RefCell; +use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; use periph_helpers::SPConfHelper; use crate::pac; +pub mod config; pub mod periph_helpers; +// +// Statics/Consts +// + +/// The state of system core clocks. +/// +/// Initialized by [`init()`], and then unchanged for the remainder of the program. +static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); + +// +// Free functions +// + +/// Initialize the core system clocks with the given [`ClocksConfig`]. +/// +/// This function should be called EXACTLY once at start-up, usually via a +/// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will +/// return an error. +pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { + critical_section::with(|cs| { + if CLOCKS.borrow_ref(cs).is_some() { + Err(ClockError::AlreadyInitialized) + } else { + Ok(()) + } + })?; + + let mut clocks = Clocks::default(); + let mut operator = ClockOperator { + clocks: &mut clocks, + config: &settings, + + _mrcc0: unsafe { pac::Mrcc0::steal() }, + scg0: unsafe { pac::Scg0::steal() }, + syscon: unsafe { pac::Syscon::steal() }, + vbat0: unsafe { pac::Vbat0::steal() }, + }; + + operator.configure_firc_clocks()?; + operator.configure_sirc_clocks()?; + operator.configure_fro16k_clocks()?; + // TODO, everything downstream + + critical_section::with(|cs| { + let mut clks = CLOCKS.borrow_ref_mut(cs); + assert!(clks.is_none(), "Clock setup race!"); + *clks = Some(clocks); + }); + + Ok(()) +} + +/// Obtain the full clocks structure, calling the given closure in a critical section. +/// +/// The given closure will be called with read-only access to the state of the system +/// clocks. This can be used to query and return the state of a given clock. +/// +/// As the caller's closure will be called in a critical section, care must be taken +/// not to block or cause any other undue delays while accessing. +/// +/// Calls to this function will not succeed until after a successful call to `init()`, +/// and will always return None. +pub fn with_clocks R>(f: F) -> Option { + critical_section::with(|cs| { + let c = CLOCKS.borrow_ref(cs); + let c = c.as_ref()?; + Some(f(c)) + }) +} + +// +// Structs/Enums +// + +/// The `Clocks` structure contains the initialized state of the core system clocks +/// +/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function +/// at boot time. +#[derive(Default, Debug, Clone)] +#[non_exhaustive] +pub struct Clocks { + /// The `clk_in` is a clock provided by an external oscillator + pub clk_in: Option, + + // FRO180M stuff + // + /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, + /// and `fro_hf_div`. + pub fro_hf_root: Option, + + /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. + pub fro_hf: Option, + + /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. + pub clk_45m: Option, + + /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. + pub fro_hf_div: Option, + + // + // End FRO180M + + // FRO12M stuff + // + /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator + /// + /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, + /// `and `fro_lf_div`. + pub fro_12m_root: Option, + + /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. + pub fro_12m: Option, + + /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` + pub clk_1m: Option, + + /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` + pub fro_lf_div: Option, + // + // End FRO12M stuff + /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in + /// the system domain, such as the CMP and RTC. + pub clk_16k_vsys: Option, + + /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. + /// + /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in + /// the VDD Core domain, such as the OSTimer or LPUarts. + pub clk_16k_vdd_core: Option, + + /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some + /// peripherals. + pub main_clk: Option, + + /// `pll1_clk` is the output of the main system PLL, `pll1`. + pub pll1_clk: Option, +} + +/// `ClockError` is the main error returned when configuring or checking clock state +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ClockError { + /// The system clocks were never initialized by calling [`init()`] + NeverInitialized, + /// The [`init()`] function was called more than once + AlreadyInitialized, + /// The requested configuration was not possible to fulfill, as the system clocks + /// were not configured in a compatible way + BadConfig { clock: &'static str, reason: &'static str }, + /// The requested configuration was not possible to fulfill, as the required system + /// clocks have not yet been implemented. + NotImplemented { clock: &'static str }, + /// The requested peripheral could not be configured, as the steps necessary to + /// enable it have not yet been implemented. + UnimplementedConfig, +} + +/// Information regarding a system clock +#[derive(Debug, Clone)] +pub struct Clock { + /// The frequency, in Hz, of the given clock + pub frequency: u32, + /// The power state of the clock, e.g. whether it is active in deep sleep mode + /// or not. + pub power: PoweredClock, +} + +/// The power state of a given clock. +/// +/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep +/// mode will be stopped. This means that any downstream usage, e.g. by peripherals, +/// will also stop. +/// +/// In the future, we will provide an API for entering Deep Sleep, and if there are +/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into +/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PoweredClock { + /// The given clock will NOT continue running in Deep Sleep mode + NormalEnabledDeepSleepDisabled, + /// The given clock WILL continue running in Deep Sleep mode + AlwaysEnabled, +} + +/// The ClockOperator is a private helper type that contains the methods used +/// during system clock initialization. +/// +/// # SAFETY +/// +/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, +/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. +struct ClockOperator<'a> { + /// A mutable reference to the current state of system clocks + clocks: &'a mut Clocks, + /// A reference to the requested configuration provided by the caller of [`init()`] + config: &'a ClocksConfig, + + // We hold on to stolen peripherals + _mrcc0: pac::Mrcc0, + scg0: pac::Scg0, + syscon: pac::Syscon, + vbat0: pac::Vbat0, +} + /// Trait describing an AHB clock gate that can be toggled through MRCC. pub trait Gate { type MrccPeriphConfig: SPConfHelper; /// Enable the clock gate. + /// + /// # SAFETY + /// + /// The current peripheral must be disabled prior to calling this method unsafe fn enable_clock(); /// Disable the clock gate. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method unsafe fn disable_clock(); /// Drive the peripheral into reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method unsafe fn assert_reset(); /// Drive the peripheral out of reset. + /// + /// # SAFETY + /// + /// There must be no active user of this peripheral when calling this method unsafe fn release_reset(); - /// Return whether the clock gate is currently enabled. + /// Return whether the clock gate for this peripheral is currently enabled. fn is_clock_enabled() -> bool; - /// . + /// Return whether the peripheral is currently held in reset. fn is_reset_released() -> bool; } +/// This is the primary helper method HAL drivers are expected to call when creating +/// an instance of the peripheral. +/// +/// This method: +/// +/// 1. Enables the MRCC clock gate for this peripheral +/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error +/// and re-disabling the peripheral if this fails. +/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state +/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account +/// the selected upstream clock, as well as any division specified by `cfg`. +/// +/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method +/// may return `Ok(0)`. In the future, this might be updated to return the correct +/// "ambient" clock, e.g. the AHB/APB frequency. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable_and_reset`. #[inline] -pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { - let freq = enable::(cfg)?; +pub(crate) unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { + let freq = enable::(cfg).inspect_err(|_| disable::())?; pulse_reset::(); Ok(freq) } -/// Enable a clock gate for the given peripheral set. +/// Enable the clock gate for the given peripheral. +/// +/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need +/// to control the duration of the pulse more directly. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `enable`. #[inline] -pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { +pub(crate) unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { G::enable_clock(); while !G::is_clock_enabled() {} core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); @@ -57,237 +345,182 @@ pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result() { +/// Disable the clock gate for the given peripheral. +/// +/// # SAFETY +/// +/// This peripheral must no longer be in use prior to calling `enable`. +#[allow(dead_code)] +#[inline] +pub(crate) unsafe fn disable() { G::disable_clock(); } /// Check whether a gate is currently enabled. +#[allow(dead_code)] #[inline] -pub fn is_clock_enabled() -> bool { +pub(crate) fn is_clock_enabled() -> bool { G::is_clock_enabled() } /// Release a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] -pub unsafe fn release_reset() { +pub(crate) unsafe fn release_reset() { G::release_reset(); } /// Assert a reset line for the given peripheral set. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `assert_reset`. #[inline] -pub unsafe fn assert_reset() { +pub(crate) unsafe fn assert_reset() { G::assert_reset(); } +/// Check whether the peripheral is held in reset. #[inline] -pub unsafe fn is_reset_released() -> bool { +pub(crate) unsafe fn is_reset_released() -> bool { G::is_reset_released() } /// Pulse a reset line (assert then release) with a short delay. +/// +/// Prefer [`enable_and_reset`]. +/// +/// # SAFETY +/// +/// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] -pub unsafe fn pulse_reset() { +pub(crate) unsafe fn pulse_reset() { G::assert_reset(); cortex_m::asm::nop(); cortex_m::asm::nop(); G::release_reset(); } -macro_rules! impl_cc_gate { - ($name:ident, $reg:ident, $field:ident, $config:ty) => { - impl Gate for crate::peripherals::$name { - type MrccPeriphConfig = $config; - - #[inline] - unsafe fn enable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - unsafe fn disable_clock() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_r, w| w.$field().disabled()); - } - - #[inline] - fn is_clock_enabled() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().read().$field().is_enabled() - } - - #[inline] - unsafe fn release_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().enabled()); - } - - #[inline] - unsafe fn assert_reset() { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().disabled()); - } +// +// `impl`s for structs/enums +// - #[inline] - fn is_reset_released() -> bool { - let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().read().$field().is_enabled() - } +/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". +/// +/// These methods are intended to be used by HAL peripheral implementors to ensure that their +/// 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 { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "required but not active", + }); + }; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_lf_div", + reason: "not low power active", + }); } - }; -} - -pub struct UnimplementedConfig; -impl SPConfHelper for UnimplementedConfig { - fn post_enable_config(&self, _clocks: &Clocks) -> Result { - Err(ClockError::UnimplementedConfig) - } -} - -pub struct NoConfig; -impl SPConfHelper for NoConfig { - fn post_enable_config(&self, _clocks: &Clocks) -> Result { - Ok(0) + Ok(clk.frequency) } -} - -pub mod gate { - use super::periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}; - use super::*; - // These peripherals have no additional upstream clocks or configuration required - // other than enabling through the MRCC gate. - impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); - impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); - impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); - impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); - - impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); - impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); - impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); -} - -// /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. -// pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. -// pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// /// Convenience helper enabling the OSTIMER0 clock gate. -// pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// } - -// pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { -// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); -// mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); -// } - -// pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { -// // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) -// let sys = &peripherals.syscon; -// sys.frolfdiv().modify(|_, w| { -// // DIV defaults to 0; keep it explicit and clear HALT -// unsafe { w.div().bits(0) }.halt().run() -// }); -// } - -// /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. -// /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. -// pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { -// // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. -// // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. -// // Read it anyway for future generality. -// let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; -// let base = 12_000_000u32; -// base / (div + 1) -// } - -// /// Enable clock gate and release reset for OSTIMER0. -// /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). -// pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); -// } - -// pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { -// enable::(peripherals); -// enable::(peripherals); -// } - -// pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { -// // Use FRO_LF_DIV (already running) MUX=0 DIV=0 -// let mrcc = &peripherals.mrcc0; -// mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); -// mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); -// } - -// ============================================== - -/// This type represents a divider in the range 1..=256. -/// -/// At a hardware level, this is an 8-bit register from 0..=255, -/// which adds one. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Div8(pub(super) u8); - -impl Div8 { - /// Store a "raw" divisor value that will divide the source by - /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source - /// by 1, and `Div8::from_raw(255)` will divide the source by - /// 256. - pub const fn from_raw(n: u8) -> Self { - Self(n) + /// Ensure the `fro_hf` clock is active and valid at the given power state. + 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) } - /// Store a specific divisor value that will divide the source - /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source - /// by 1, and `Div8::from_divisor(256)` will divide the source - /// by 256. - /// - /// Will return `None` if `n` is not in the range `1..=256`. - /// Consider [`Self::from_raw`] for an infallible version. - pub const fn from_divisor(n: u16) -> Option { - let Some(n) = n.checked_sub(1) else { - return None; + /// Ensure the `fro_hf_div` clock is active and valid at the given power state. + 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 n > (u8::MAX as u16) { - return None; + if !clk.power.meets_requirement_of(at_level) { + return Err(ClockError::BadConfig { + clock: "fro_hf_div", + reason: "not low power active", + }); } - Some(Self(n as u8)) + Ok(clk.frequency) } - /// Convert into "raw" bits form - #[inline(always)] - pub const fn into_bits(self) -> u8 { - self.0 + /// Ensure the `clk_in` clock is active and valid at the given power state. + pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "clk_in" }) } - /// Convert into "divisor" form, as a u32 for convenient frequency math - #[inline(always)] - pub const fn into_divisor(self) -> u32 { - self.0 as u32 + 1 + /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vsys + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vsys", + reason: "required but not active", + })? + .frequency) } -} -#[derive(Debug, Clone)] -pub struct Clock { - pub frequency: u32, - pub power: PoweredClock, -} + /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. + pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { + // NOTE: clk_16k is always active in low power mode + Ok(self + .clk_16k_vdd_core + .as_ref() + .ok_or(ClockError::BadConfig { + clock: "clk_16k_vdd_core", + reason: "required but not active", + })? + .frequency) + } + + /// Ensure the `clk_1m` clock is active and valid at the given power state. + 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) + } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PoweredClock { - NormalEnabledDeepSleepDisabled, - AlwaysEnabled, + /// 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" }) + } } impl PoweredClock { @@ -302,172 +535,11 @@ impl PoweredClock { } } -/// ```text -/// ┌─────────────────────────────────────────────────────────┐ -/// │ │ -/// │ ┌───────────┐ clk_out ┌─────────┐ │ -/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ -/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ -/// EXTAL ──────┼──▷│ │───────────▷│ │ │ -/// │ └───────────┘ └─────────┘ │ -/// │ │ -/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ -/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_45m │ -/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ -/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ -/// │ │ │ │ ├────┤ clk_1m │ -/// │ │ │ └─────▷│1/12│────────────────────┼──────▷ -/// │ └───────────┘ └────┘ │ -/// │ │ -/// │ ┌──────────┐ │ -/// │ │000 │ │ -/// │ clk_in │ │ │ -/// │ ───────────────▷│001 │ │ -/// │ fro_12m │ │ │ -/// │ ───────────────▷│010 │ │ -/// │ fro_hf_root │ │ │ -/// │ ───────────────▷│011 │ main_clk │ -/// │ │ │───────────────────────────┼──────▷ -/// clk_16k ──────┼─────────────────▷│100 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│101 │ │ -/// │ pll1_clk │ │ │ -/// │ ───────────────▷│110 │ │ -/// │ none │ │ │ -/// │ ───────────────▷│111 │ │ -/// │ └──────────┘ │ -/// │ ▲ │ -/// │ │ │ -/// │ SCG SCS │ -/// │ SCG-Lite │ -/// └─────────────────────────────────────────────────────────┘ -/// -/// -/// clk_in ┌─────┐ -/// ───────────────▷│00 │ -/// clk_45m │ │ -/// ───────────────▷│01 │ ┌───────────┐ pll1_clk -/// none │ │─────▷│ SPLL │───────────────▷ -/// ───────────────▷│10 │ └───────────┘ -/// fro_12m │ │ -/// ───────────────▷│11 │ -/// └─────┘ -/// ``` -#[non_exhaustive] -pub struct ClocksConfig { - // FIRC, FRO180, 45/60/90/180M clock source - pub firc: Option, - // NOTE: I don't think we *can* disable the SIRC? - pub sirc: SircConfig, - pub fro16k: Option, -} - -// FIRC/FRO180M - -/// ```text -/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf -/// │ FRO180M ├───────┬─────▷│GATE│──────────▷ -/// │ │ │ ├────┤ clk_45m -/// │ │ └─────▷│GATE│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct FircConfig { - pub frequency: FircFreqSel, - pub power: PoweredClock, - /// Is the "fro_hf" gated clock enabled? - pub fro_hf_enabled: bool, - /// Is the "clk_45m" gated clock enabled? - pub clk_45m_enabled: bool, - /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! - pub fro_hf_div: Option, -} - -pub enum FircFreqSel { - Mhz45, - Mhz60, - Mhz90, - Mhz180, -} - -// SIRC/FRO12M - -/// ```text -/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m -/// │ FRO12M │────────┬─────▷│ CG │──────────▷ -/// │ │ │ ├────┤ clk_1m -/// │ │ └─────▷│1/12│──────────▷ -/// └───────────┘ └────┘ -/// ``` -#[non_exhaustive] -pub struct SircConfig { - pub power: PoweredClock, - // peripheral output, aka sirc_12mhz - pub fro_12m_enabled: bool, - /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! - pub fro_lf_div: Option, -} - -#[derive(Default, Debug, Clone)] -#[non_exhaustive] -pub struct Clocks { - pub clk_in: Option, - - // FRO180M stuff - // - pub fro_hf_root: Option, - pub fro_hf: Option, - pub clk_45m: Option, - pub fro_hf_div: Option, - // - // End FRO180M - - // FRO12M stuff - pub fro_12m_root: Option, - pub fro_12m: Option, - pub clk_1m: Option, - pub fro_lf_div: Option, - // - // End FRO12M stuff - pub clk_16k_vsys: Option, - pub clk_16k_vdd_core: Option, - pub main_clk: Option, - pub pll1_clk: Option, -} - -#[non_exhaustive] -pub struct Fro16KConfig { - pub vsys_domain_active: bool, - pub vdd_core_domain_active: bool, -} - -static CLOCKS: critical_section::Mutex>> = critical_section::Mutex::new(RefCell::new(None)); - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub enum ClockError { - NeverInitialized, - AlreadyInitialized, - BadConfig { clock: &'static str, reason: &'static str }, - NotImplemented { clock: &'static str }, - UnimplementedConfig, -} - -struct ClockOperator<'a> { - clocks: &'a mut Clocks, - config: &'a ClocksConfig, - - _mrcc0: pac::Mrcc0, - scg0: pac::Scg0, - syscon: pac::Syscon, - vbat0: pac::Vbat0, -} - impl ClockOperator<'_> { + /// Configure the FIRC/FRO180M clock family + /// + /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used + /// as the main clock used for the CPU, AHB, APB, etc. fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { clock: "firc", @@ -484,7 +556,7 @@ impl ClockOperator<'_> { } let base_freq = 45_000_000; - // Is the FIRC as expected? + // Now, check if the FIRC as expected for our hardcoded value let mut firc_ok = true; // Is the hardware currently set to the default 45MHz? @@ -604,6 +676,7 @@ impl ClockOperator<'_> { Ok(()) } + /// Configure the SIRC/FRO12M clock family fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { let SircConfig { power, @@ -694,6 +767,7 @@ impl ClockOperator<'_> { Ok(()) } + /// Configure the FRO16K/clk_16k clock family fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { let Some(fro16k) = self.config.fro16k.as_ref() else { return Ok(()); @@ -735,145 +809,74 @@ impl ClockOperator<'_> { } } -pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { - critical_section::with(|cs| { - if CLOCKS.borrow_ref(cs).is_some() { - Err(ClockError::AlreadyInitialized) - } else { - Ok(()) - } - })?; - - let mut clocks = Clocks::default(); - let mut operator = ClockOperator { - clocks: &mut clocks, - config: &settings, - - _mrcc0: unsafe { pac::Mrcc0::steal() }, - scg0: unsafe { pac::Scg0::steal() }, - syscon: unsafe { pac::Syscon::steal() }, - vbat0: unsafe { pac::Vbat0::steal() }, - }; +// +// Macros/macro impls +// - operator.configure_firc_clocks()?; - operator.configure_sirc_clocks()?; - operator.configure_fro16k_clocks()?; - // TODO, everything downstream +/// This macro is used to implement the [`Gate`] trait for a given peripheral +/// that is controlled by the MRCC peripheral. +macro_rules! impl_cc_gate { + ($name:ident, $reg:ident, $field:ident, $config:ty) => { + impl Gate for crate::peripherals::$name { + type MrccPeriphConfig = $config; - critical_section::with(|cs| { - let mut clks = CLOCKS.borrow_ref_mut(cs); - assert!(clks.is_none(), "Clock setup race!"); - *clks = Some(clocks); - }); + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } - Ok(()) -} + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_r, w| w.$field().disabled()); + } -/// Obtain the full clocks structure, calling the given closure in a critical section -/// -/// NOTE: Clocks implements `Clone`, -pub fn with_clocks R>(f: F) -> Option { - critical_section::with(|cs| { - let c = CLOCKS.borrow_ref(cs); - let c = c.as_ref()?; - Some(f(c)) - }) -} + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().read().$field().is_enabled() + } -impl Clocks { - pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result { - let Some(clk) = self.fro_lf_div.as_ref() else { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "required but not active", - }); - }; - if !clk.power.meets_requirement_of(at_level) { - return Err(ClockError::BadConfig { - clock: "fro_lf_div", - reason: "not low power active", - }); - } - Ok(clk.frequency) - } + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().enabled()); + } - 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) - } + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().modify(|_, w| w.$field().disabled()); + } - 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", - }); + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.$reg().read().$field().is_enabled() + } } - Ok(clk.frequency) - } - - pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "clk_in" }) - } - - pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result { - // NOTE: clk_16k is always active in low power mode - Ok(self - .clk_16k_vsys - .as_ref() - .ok_or(ClockError::BadConfig { - clock: "clk_16k_vsys", - reason: "required but not active", - })? - .frequency) - } + }; +} - pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result { - // NOTE: clk_16k is always active in low power mode - Ok(self - .clk_16k_vdd_core - .as_ref() - .ok_or(ClockError::BadConfig { - clock: "clk_16k_vdd_core", - reason: "required but not active", - })? - .frequency) - } +/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, +/// for various low level peripherals. +pub(crate) mod gate { + use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig}; + use super::*; - 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) - } + // These peripherals have no additional upstream clocks or configuration required + // other than enabling through the MRCC gate. Currently, these peripherals will + // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or + // [`SPConfHelper::post_enable_config()`]. + impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); - pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result { - Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) - } + // These peripherals DO have meaningful configuration, and could fail if the system + // clocks do not match their needs. + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); } diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs index 1657bd7eb..e5b234c5b 100644 --- a/src/clocks/periph_helpers.rs +++ b/src/clocks/periph_helpers.rs @@ -1,7 +1,43 @@ +//! Peripheral Helpers +//! +//! The purpose of this module is to define the per-peripheral special handling +//! required from a clocking perspective. Different peripherals have different +//! selectable source clocks, and some peripherals have additional pre-dividers +//! that can be used. +//! +//! See the docs of [`SPConfHelper`] for more details. + use super::{ClockError, Clocks, PoweredClock}; use crate::pac; +/// Sealed Peripheral Configuration Helper +/// +/// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the +/// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should +/// fix the name, or actually do the sealing of peripherals. +/// +/// This trait serves to act as a per-peripheral customization for clocking behavior. +/// +/// This trait should be implemented on a configuration type for a given peripheral, and +/// provide the methods that will be called by the higher level operations like +/// `embassy_mcxa::clocks::enable_and_reset()`. pub trait SPConfHelper { + /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated), + /// but BEFORE the peripheral reset line is reset. + /// + /// This function should check that any relevant upstream clocks are enabled, are in a + /// reasonable power state, and that the requested configuration can be made. If any of + /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. + /// + /// This function SHOULD NOT make any changes to the system clock configuration, even + /// unsafely, as this should remain static for the duration of the program. + /// + /// This function WILL be called in a critical section, care should be taken not to delay + /// for an unreasonable amount of time. + /// + /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency + /// fed into the peripheral, taking into account the selected source clock, as well as + /// any pre-divisors. fn post_enable_config(&self, clocks: &Clocks) -> Result; } @@ -65,6 +101,33 @@ impl Div4 { } } +/// A basic type that always returns an error when `post_enable_config` is called. +/// +/// Should only be used as a placeholder. +pub struct UnimplementedConfig; + +impl SPConfHelper for UnimplementedConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Err(ClockError::UnimplementedConfig) + } +} + +/// A basic type that always returns `Ok(0)` when `post_enable_config` is called. +/// +/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` +/// peripherals, which have no selectable/configurable source clock. +pub struct NoConfig; +impl SPConfHelper for NoConfig { + fn post_enable_config(&self, _clocks: &Clocks) -> Result { + Ok(0) + } +} + +// +// LPUart +// + +/// Selectable clocks for Lpuart peripherals #[derive(Debug, Clone, Copy)] pub enum LpuartClockSel { /// FRO12M/FRO_LF/SIRC clock source, passed through divider @@ -86,16 +149,26 @@ pub enum LpuartClockSel { None, } +/// Which instance of the Lpuart is this? +/// +/// Should not be directly selectable by end-users. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LpuartInstance { + /// Instance 0 Lpuart0, + /// Instance 1 Lpuart1, + /// Instance 2 Lpuart2, + /// Instance 3 Lpuart3, + /// Instance 4 Lpuart4, + /// Instance 5 Lpuart5, } +/// Top level configuration for `Lpuart` instances. pub struct LpuartConfig { /// Power state required for this peripheral pub power: PoweredClock, @@ -108,39 +181,6 @@ pub struct LpuartConfig { pub(crate) instance: LpuartInstance, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum OstimerClockSel { - /// 16k clock, sourced from FRO16K (Vdd Core) - Clk16kVddCore, - /// 1 MHz Clock sourced from FRO12M - Clk1M, - /// Disabled - None, -} - -pub struct OsTimerConfig { - pub power: PoweredClock, - pub source: OstimerClockSel, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum AdcClockSel { - FroLfDiv, - FroHf, - ClkIn, - Clk1M, - Pll1ClkDiv, - None, -} - -pub struct AdcConfig { - pub power: PoweredClock, - pub source: AdcClockSel, - pub div: Div4, -} - -// impls - impl SPConfHelper for LpuartConfig { fn post_enable_config(&self, clocks: &Clocks) -> Result { // check that source is suitable @@ -215,6 +255,29 @@ impl SPConfHelper for LpuartConfig { } } +// +// OSTimer +// + +/// Selectable clocks for the OSTimer peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum OstimerClockSel { + /// 16k clock, sourced from FRO16K (Vdd Core) + Clk16kVddCore, + /// 1 MHz Clock sourced from FRO12M + Clk1M, + /// Disabled + None, +} + +/// Top level configuration for the `OSTimer` peripheral +pub struct OsTimerConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock source for this peripheral + pub source: OstimerClockSel, +} + impl SPConfHelper for OsTimerConfig { fn post_enable_config(&self, clocks: &Clocks) -> Result { let mrcc0 = unsafe { pac::Mrcc0::steal() }; @@ -237,6 +300,37 @@ impl SPConfHelper for OsTimerConfig { } } +// +// Adc +// + +/// Selectable clocks for the ADC peripheral +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AdcClockSel { + /// Divided `fro_lf`/`clk_12m`/FRO12M source + FroLfDiv, + /// Gated `fro_hf`/`FRO180M` source + FroHf, + /// External Clock Source + ClkIn, + /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` + Clk1M, + /// Internal PLL output, with configurable divisor + Pll1ClkDiv, + /// No clock/disabled + None, +} + +/// Top level configuration for the ADC peripheral +pub struct AdcConfig { + /// Power state required for this peripheral + pub power: PoweredClock, + /// Selected clock-source for this peripheral + pub source: AdcClockSel, + /// Pre-divisor, applied to the upstream clock output + pub div: Div4, +} + impl SPConfHelper for AdcConfig { fn post_enable_config(&self, clocks: &Clocks) -> Result { use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; diff --git a/src/config.rs b/src/config.rs index 93aed5a99..0939c11f1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ // HAL configuration (minimal), mirroring embassy-imxrt style +use crate::clocks::config::ClocksConfig; use crate::interrupt::Priority; #[non_exhaustive] @@ -7,6 +8,7 @@ pub struct Config { pub time_interrupt_priority: Priority, pub rtc_interrupt_priority: Priority, pub adc_interrupt_priority: Priority, + pub clock_cfg: ClocksConfig, } impl Default for Config { @@ -15,6 +17,7 @@ impl Default for Config { time_interrupt_priority: Priority::from(0), rtc_interrupt_priority: Priority::from(0), adc_interrupt_priority: Priority::from(0), + clock_cfg: ClocksConfig::default(), } } } -- cgit From a8ca36cdbe06e83019279e441ac386a448c12edb Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 17 Nov 2025 14:58:26 +0100 Subject: Fill in main_clk state --- src/clocks/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index c6606b1b6..40acb11ec 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -87,7 +87,12 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { operator.configure_firc_clocks()?; operator.configure_sirc_clocks()?; operator.configure_fro16k_clocks()?; - // TODO, everything downstream + + // For now, just use FIRC as the main/cpu clock, which should already be + // the case on reset + assert!(operator.scg0.rccr().read().scs().is_firc()); + assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); + operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap()); critical_section::with(|cs| { let mut clks = CLOCKS.borrow_ref_mut(cs); -- cgit From a0c8e2d0299f3ae8eb24cd264d2b8e87f2bce464 Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 17 Nov 2025 15:02:26 +0100 Subject: Restore examples --- examples/src/lib.rs | 8 ++++---- src/clocks/mod.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/src/lib.rs b/examples/src/lib.rs index be4761c32..2018a3c25 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -12,20 +12,20 @@ use {embassy_mcxa as hal, panic_probe as _}; pub unsafe fn init_uart2_pins(_p: &hal::pac::Peripherals) { // NOTE: Lpuart has been updated to properly enable + reset its own clocks. // GPIO has not. - _ = clocks::enable_and_reset::(&clocks::NoConfig); + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); pins::configure_uart2_pins_port2(); } /// Initialize clocks for the LED GPIO/PORT used by the blink example. pub unsafe fn init_led_gpio_clocks(_p: &hal::pac::Peripherals) { - _ = clocks::enable_and_reset::(&clocks::NoConfig); - _ = clocks::enable_and_reset::(&clocks::NoConfig); + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); } /// Initialize clocks and pin muxing for ADC. pub unsafe fn init_adc_pins(_p: &hal::pac::Peripherals) { // NOTE: Lpuart has been updated to properly enable + reset its own clocks. // GPIO has not. - _ = clocks::enable_and_reset::(&clocks::NoConfig); + _ = clocks::enable_and_reset::(&clocks::periph_helpers::NoConfig); pins::configure_adc_pins(); } diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 40acb11ec..74a1f1a1f 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -319,7 +319,7 @@ pub trait Gate { /// /// This peripheral must not yet be in use prior to calling `enable_and_reset`. #[inline] -pub(crate) unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { +pub unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Result { let freq = enable::(cfg).inspect_err(|_| disable::())?; pulse_reset::(); Ok(freq) @@ -334,7 +334,7 @@ pub(crate) unsafe fn enable_and_reset(cfg: &G::MrccPeriphConfig) -> Res /// /// This peripheral must not yet be in use prior to calling `enable`. #[inline] -pub(crate) unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { +pub unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result { G::enable_clock(); while !G::is_clock_enabled() {} core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); @@ -357,14 +357,14 @@ pub(crate) unsafe fn enable(cfg: &G::MrccPeriphConfig) -> Result() { +pub unsafe fn disable() { G::disable_clock(); } /// Check whether a gate is currently enabled. #[allow(dead_code)] #[inline] -pub(crate) fn is_clock_enabled() -> bool { +pub fn is_clock_enabled() -> bool { G::is_clock_enabled() } @@ -376,7 +376,7 @@ pub(crate) fn is_clock_enabled() -> bool { /// /// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] -pub(crate) unsafe fn release_reset() { +pub unsafe fn release_reset() { G::release_reset(); } @@ -388,13 +388,13 @@ pub(crate) unsafe fn release_reset() { /// /// This peripheral must not yet be in use prior to calling `assert_reset`. #[inline] -pub(crate) unsafe fn assert_reset() { +pub unsafe fn assert_reset() { G::assert_reset(); } /// Check whether the peripheral is held in reset. #[inline] -pub(crate) unsafe fn is_reset_released() -> bool { +pub unsafe fn is_reset_released() -> bool { G::is_reset_released() } @@ -406,7 +406,7 @@ pub(crate) unsafe fn is_reset_released() -> bool { /// /// This peripheral must not yet be in use prior to calling `release_reset`. #[inline] -pub(crate) unsafe fn pulse_reset() { +pub unsafe fn pulse_reset() { G::assert_reset(); cortex_m::asm::nop(); cortex_m::asm::nop(); -- cgit From 02285c2153d22f2c0c93a4ce920cdebc03f18658 Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 17 Nov 2025 16:38:32 +0100 Subject: Correct clk/rst field logic --- examples/memory.x | 4 ++-- examples/src/lib.rs | 2 +- src/clocks/mod.rs | 28 ++++++++++++++-------------- src/lib.rs | 5 ++++- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/examples/memory.x b/examples/memory.x index f4263a412..528545b64 100644 --- a/examples/memory.x +++ b/examples/memory.x @@ -1,5 +1,5 @@ MEMORY { - FLASH : ORIGIN = 0x20000000, LENGTH = 64K - RAM : ORIGIN = 0x20010000, LENGTH = 64K + FLASH : ORIGIN = 0x20000000, LENGTH = 80K + RAM : ORIGIN = 0x20014000, LENGTH = 48K } diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 2018a3c25..4bb334da5 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -5,7 +5,7 @@ //! These live with the examples so the HAL stays generic. use hal::{clocks, pins}; -use {embassy_mcxa as hal, panic_probe as _}; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; /// Initialize clocks and pin muxing for UART2 debug console. /// Safe to call multiple times; writes are idempotent for our use. diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 74a1f1a1f..e02840592 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -821,44 +821,44 @@ impl ClockOperator<'_> { /// This macro is used to implement the [`Gate`] trait for a given peripheral /// that is controlled by the MRCC peripheral. macro_rules! impl_cc_gate { - ($name:ident, $reg:ident, $field:ident, $config:ty) => { + ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { impl Gate for crate::peripherals::$name { type MrccPeriphConfig = $config; #[inline] unsafe fn enable_clock() { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().enabled()); + mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); } #[inline] unsafe fn disable_clock() { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_r, w| w.$field().disabled()); + mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); } #[inline] fn is_clock_enabled() -> bool { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().read().$field().is_enabled() + mrcc.$clk_reg().read().$field().is_enabled() } #[inline] unsafe fn release_reset() { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().enabled()); + mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); } #[inline] unsafe fn assert_reset() { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().modify(|_, w| w.$field().disabled()); + mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); } #[inline] fn is_reset_released() -> bool { let mrcc = unsafe { pac::Mrcc0::steal() }; - mrcc.$reg().read().$field().is_enabled() + mrcc.$rst_reg().read().$field().is_enabled() } } }; @@ -874,14 +874,14 @@ pub(crate) mod gate { // other than enabling through the MRCC gate. Currently, these peripherals will // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or // [`SPConfHelper::post_enable_config()`]. - impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); - impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); - impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); - impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); + impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); + impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); + impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); + impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); // These peripherals DO have meaningful configuration, and could fail if the system // clocks do not match their needs. - impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); - impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); - impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); + impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); + impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); } diff --git a/src/lib.rs b/src/lib.rs index 1bf54a98b..86c0dc45b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,6 @@ pub use rtc::Rtc0 as Rtc0Token; /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). -#[allow(unused_variables)] pub fn init(cfg: crate::config::Config) -> Peripherals { let peripherals = Peripherals::take(); // Apply user-configured priority early; enabling is left to examples/apps @@ -56,6 +55,10 @@ pub fn init(cfg: crate::config::Config) -> Peripherals { crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); // Apply user-configured priority early; enabling is left to examples/apps crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); + + // Configure clocks + crate::clocks::init(cfg.clock_cfg).unwrap(); + peripherals } -- cgit From c8942aec2478ff077b55da0e86801f8a6a88a7de Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 17 Nov 2025 16:37:41 -0800 Subject: Prune unnecessary exemptions Signed-off-by: Felipe Balbi --- supply-chain/config.toml | 40 ----- supply-chain/imports.lock | 414 +--------------------------------------------- 2 files changed, 1 insertion(+), 453 deletions(-) diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 36a513ee2..173392c16 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -13,10 +13,6 @@ url = "https://raw.githubusercontent.com/google/rust-crate-audits/main/audits.to [imports.mozilla] url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" -[[exemptions.az]] -version = "1.2.1" -criteria = "safe-to-deploy" - [[exemptions.bare-metal]] version = "0.2.5" criteria = "safe-to-deploy" @@ -25,14 +21,6 @@ criteria = "safe-to-deploy" version = "0.13.2" criteria = "safe-to-deploy" -[[exemptions.bitfield]] -version = "0.15.0" -criteria = "safe-to-deploy" - -[[exemptions.chrono]] -version = "0.4.40" -criteria = "safe-to-deploy" - [[exemptions.cortex-m]] version = "0.7.7" criteria = "safe-to-deploy" @@ -117,10 +105,6 @@ criteria = "safe-to-deploy" version = "0.4.1" criteria = "safe-to-deploy" -[[exemptions.fixed]] -version = "1.29.0" -criteria = "safe-to-deploy" - [[exemptions.futures-core]] version = "0.3.31" criteria = "safe-to-deploy" @@ -137,26 +121,10 @@ criteria = "safe-to-deploy" version = "0.8.0" criteria = "safe-to-deploy" -[[exemptions.itertools]] -version = "0.11.0" -criteria = "safe-to-deploy" - -[[exemptions.log]] -version = "0.4.27" -criteria = "safe-to-deploy" - -[[exemptions.mimxrt600-fcb]] -version = "0.2.1" -criteria = "safe-to-deploy" - [[exemptions.paste]] version = "1.0.15" criteria = "safe-to-deploy" -[[exemptions.portable-atomic]] -version = "1.11.0" -criteria = "safe-to-run" - [[exemptions.proc-macro-error-attr2]] version = "2.0.0" criteria = "safe-to-deploy" @@ -177,14 +145,6 @@ criteria = "safe-to-deploy" version = "0.7.0" criteria = "safe-to-deploy" -[[exemptions.static_cell]] -version = "2.1.0" -criteria = "safe-to-run" - -[[exemptions.typenum]] -version = "1.18.0" -criteria = "safe-to-deploy" - [[exemptions.vcell]] version = "0.1.3" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 3f541e59f..aa62839e2 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -3,13 +3,6 @@ [audits.OpenDevicePartnership.audits] -[[audits.google.audits.autocfg]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "1.4.0" -notes = "Contains no unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.bitflags]] who = "Lukasz Anforowicz " criteria = "safe-to-deploy" @@ -26,67 +19,6 @@ Additional review comments can be found at https://crrev.com/c/4723145/31 """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.bytemuck]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.16.3" -notes = """ -Review notes from the original audit (of 1.14.3) may be found in -https://crrev.com/c/5362675. Note that this audit has initially missed UB risk -that was fixed in 1.16.2 - see https://github.com/Lokathor/bytemuck/pull/258. -Because of this, the original audit has been edited to certify version `1.16.3` -instead (see also https://crrev.com/c/5771867). -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.16.3 -> 1.17.1" -notes = "Unsafe review comments can be found in https://crrev.com/c/5813463" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.17.1 -> 1.18.0" -notes = "No code changes - just altering feature flag arrangements" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.18.0 -> 1.19.0" -notes = "No code changes - just comment changes and adding the track_caller attribute." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.19.0 -> 1.20.0" -notes = "`unsafe` review can be found at https://crrev.com/c/6096767" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.20.0 -> 1.21.0" -notes = "Unsafe review at https://chromium-review.googlesource.com/c/chromium/src/+/6111154/" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.bytemuck]] -who = "Daniel Cheng " -criteria = "safe-to-deploy" -delta = "1.21.0 -> 1.22.0" -notes = """ -This adds new instances of unsafe, but the uses are justified: -- BoxBytes is essentially a Box<[u8], which is Send + Sync, so also marking BoxBytes as Send + Sync is justified. -- core::num::Saturating meets the criteria for Zeroable + Pod, so marking it as such is justified. - -See https://crrev.com/c/6321863 for more audit notes. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.byteorder]] who = "danakj " criteria = "safe-to-deploy" @@ -94,40 +26,6 @@ version = "1.5.0" notes = "Unsafe review in https://crrev.com/c/5838022" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.cfg-if]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -version = "1.0.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.either]] -who = "Manish Goregaokar " -criteria = "safe-to-deploy" -version = "1.13.0" -notes = "Unsafe code pertaining to wrapping Pin APIs. Mostly passes invariants down." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.either]] -who = "Daniel Cheng " -criteria = "safe-to-deploy" -delta = "1.13.0 -> 1.14.0" -notes = """ -Inheriting ub-risk-1 from the baseline review of 1.13.0. While the delta has some diffs in unsafe code, they are either: -- migrating code to use helper macros -- migrating match patterns to take advantage of default bindings mode from RFC 2005 -Either way, the result is code that does exactly the same thing and does not change the risk of UB. - -See https://crrev.com/c/6323164 for more audit details. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.either]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.14.0 -> 1.15.0" -notes = "The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = \"std\")]`." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.nb]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -153,320 +51,10 @@ version = "0.2.19" notes = "Contains a single line of float-to-int unsafe with decent safety comments" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.proc-macro2]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -version = "1.0.78" -notes = """ -Grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits -(except for a benign \"fs\" hit in a doc comment) - -Notes from the `unsafe` review can be found in https://crrev.com/c/5385745. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.78 -> 1.0.79" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Adrian Taylor " -criteria = "safe-to-deploy" -delta = "1.0.79 -> 1.0.80" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.80 -> 1.0.81" -notes = "Comment changes only" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "danakj " -criteria = "safe-to-deploy" -delta = "1.0.81 -> 1.0.82" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Dustin J. Mitchell " -criteria = "safe-to-deploy" -delta = "1.0.82 -> 1.0.83" -notes = "Substantive change is replacing String with Box, saving memory." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.83 -> 1.0.84" -notes = "Only doc comment changes in `src/lib.rs`." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "danakj@chromium.org" -criteria = "safe-to-deploy" -delta = "1.0.84 -> 1.0.85" -notes = "Test-only changes." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Lukasz Anforowicz " -criteria = "safe-to-deploy" -delta = "1.0.85 -> 1.0.86" -notes = """ -Comment-only changes in `build.rs`. -Reordering of `Cargo.toml` entries. -Just bumping up the version number in `lib.rs`. -Config-related changes in `test_size.rs`. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "danakj " -criteria = "safe-to-deploy" -delta = "1.0.86 -> 1.0.87" -notes = "No new unsafe interactions." -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - -[[audits.google.audits.proc-macro2]] -who = "Liza Burakova