From c552cf2434b847b7a8af06e15982eb29d07ad2fa Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 24 Nov 2025 17:37:57 +0100 Subject: Introduce clkout peripheral (#36) Introduces a ClockOut peripheral, allowing for ensuring that internal clocks are operating correctly. Also fixes some incorrect PLL/Div4 usage. --- examples/Cargo.toml | 2 +- examples/src/bin/clkout.rs | 69 ++++++++++++++++++ src/clkout.rs | 169 +++++++++++++++++++++++++++++++++++++++++++++ src/clocks/mod.rs | 43 +++++++++++- src/gpio.rs | 6 +- src/lib.rs | 5 ++ 6 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 examples/src/bin/clkout.rs create mode 100644 src/clkout.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d1c6a2071..4f15a6aff 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -cortex-m-rt = { version = "0.7" } +cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] } critical-section = "1.2.0" defmt = "1.0" defmt-rtt = "1.0" diff --git a/examples/src/bin/clkout.rs b/examples/src/bin/clkout.rs new file mode 100644 index 000000000..bfd963540 --- /dev/null +++ b/examples/src/bin/clkout.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4}; +use embassy_mcxa::clocks::PoweredClock; +use embassy_mcxa::gpio::{DriveStrength, SlewRate}; +use embassy_mcxa::{Level, Output}; +use embassy_time::Timer; +use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; + +/// Demonstrate CLKOUT, using Pin P4.2 +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = hal::init(hal::config::Config::default()); + let mut pin = p.P4_2; + let mut clkout = p.CLKOUT; + + loop { + defmt::info!("Set Low..."); + let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + defmt::info!("Set High..."); + output.set_high(); + Timer::after_millis(400).await; + + defmt::info!("Set Low..."); + output.set_low(); + Timer::after_millis(500).await; + + defmt::info!("16k..."); + // Run Clock Out with the 16K clock + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Clk16K, + div: Div4::no_div(), + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + Timer::after_millis(3000).await; + + defmt::info!("Set Low..."); + drop(_clock_out); + + let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow); + Timer::after_millis(500).await; + + // Run Clock Out with the 12M clock, divided by 3 + defmt::info!("4M..."); + let _clock_out = ClockOut::new( + clkout.reborrow(), + pin.reborrow(), + Config { + sel: ClockOutSel::Fro12M, + div: const { Div4::from_divisor(3).unwrap() }, + level: PoweredClock::NormalEnabledDeepSleepDisabled, + }, + ) + .unwrap(); + + // Let it run for 3 seconds... + Timer::after_millis(3000).await; + } +} diff --git a/src/clkout.rs b/src/clkout.rs new file mode 100644 index 000000000..88c731df1 --- /dev/null +++ b/src/clkout.rs @@ -0,0 +1,169 @@ +//! CLKOUT pseudo-peripheral +//! +//! CLKOUT is a part of the clock generation subsystem, and can be used +//! either to generate arbitrary waveforms, or to debug the state of +//! internal oscillators. + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; + +pub use crate::clocks::periph_helpers::Div4; +use crate::clocks::{with_clocks, ClockError, PoweredClock}; +use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; +use crate::peripherals::CLKOUT; + +/// A peripheral representing the CLKOUT pseudo-peripheral +pub struct ClockOut<'a> { + _p: PhantomData<&'a mut CLKOUT>, + freq: u32, +} + +/// Selected clock source to output +pub enum ClockOutSel { + /// 12MHz Internal Oscillator + Fro12M, + /// FRO180M Internal Oscillator, via divisor + FroHfDiv, + /// External Oscillator + ClkIn, + /// 16KHz oscillator + Clk16K, + /// Output of PLL1 + Pll1Clk, + /// Main System CPU clock, divided by 6 + SlowClk, +} + +/// Configuration for the ClockOut +pub struct Config { + /// Selected Source Clock + pub sel: ClockOutSel, + /// Selected division level + pub div: Div4, + /// Selected power level + pub level: PoweredClock, +} + +impl<'a> ClockOut<'a> { + /// Create a new ClockOut pin. On success, the clock signal will begin immediately + /// on the given pin. + pub fn new( + _peri: Peri<'a, CLKOUT>, + pin: Peri<'a, impl sealed::ClockOutPin>, + cfg: Config, + ) -> Result { + // There's no MRCC enable bit, so we check the validity of the clocks here + // + // TODO: Should we check that the frequency is suitably low? + let (freq, mux) = check_sel(cfg.sel, cfg.level)?; + + // All good! Apply requested config, starting with the pin. + pin.mux(); + + setup_clkout(mux, cfg.div); + + Ok(Self { + _p: PhantomData, + freq: freq / cfg.div.into_divisor(), + }) + } + + /// Frequency of the clkout pin + #[inline] + pub fn frequency(&self) -> u32 { + self.freq + } +} + +impl Drop for ClockOut<'_> { + fn drop(&mut self) { + disable_clkout(); + } +} + +/// Check whether the given clock selection is valid +fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { + let res = with_clocks(|c| { + Ok(match sel { + ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), + ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), + ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), + ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), + ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), + ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), + }) + }); + let Some(res) = res else { + return Err(ClockError::NeverInitialized); + }; + res +} + +/// Set up the clkout pin using the given mux and div settings +fn setup_clkout(mux: Mux, div: Div4) { + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + + mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); + + // Set up clkdiv + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().set_bit(); + w.reset().set_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.halt().clear_bit(); + w.reset().clear_bit(); + unsafe { w.div().bits(div.into_bits()) }; + w + }); + + while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} +} + +/// Stop the clkout +fn disable_clkout() { + // Stop the output by selecting the "none" clock + // + // TODO: restore the pin to hi-z or something? + let mrcc = unsafe { crate::pac::Mrcc0::steal() }; + mrcc.mrcc_clkout_clkdiv().write(|w| { + w.reset().set_bit(); + w.halt().set_bit(); + unsafe { + w.div().bits(0); + } + w + }); + mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); +} + +mod sealed { + use embassy_hal_internal::PeripheralType; + + use crate::gpio::{Pull, SealedPin}; + + /// Sealed marker trait for clockout pins + pub trait ClockOutPin: PeripheralType { + /// Set the given pin to the correct muxing state + fn mux(&self); + } + + macro_rules! impl_pin { + ($pin:ident, $func:ident) => { + impl ClockOutPin for crate::peripherals::$pin { + fn mux(&self) { + self.set_function(crate::pac::port0::pcr0::Mux::$func); + self.set_pull(Pull::Disabled); + } + } + }; + } + + impl_pin!(P0_6, Mux12); + impl_pin!(P3_6, Mux1); + impl_pin!(P3_8, Mux12); + impl_pin!(P4_2, Mux1); +} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 0b4535dc4..948355079 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -91,8 +91,11 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { // 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()); + let input = operator.clocks.fro_hf_root.clone().unwrap(); + operator.clocks.main_clk = Some(input.clone()); + // We can also assume cpu/system clk == fro_hf because div is /1. assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); - operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap()); + operator.clocks.cpu_system_clk = Some(input); critical_section::with(|cs| { let mut clks = CLOCKS.borrow_ref_mut(cs); @@ -189,6 +192,9 @@ pub struct Clocks { /// peripherals. pub main_clk: Option, + /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV` + pub cpu_system_clk: Option, + /// `pll1_clk` is the output of the main system PLL, `pll1`. pub pll1_clk: Option, } @@ -522,10 +528,43 @@ impl Clocks { Ok(clk.frequency) } + /// Ensure the `pll1_clk` clock is active and valid at the given power state. + pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result { + Err(ClockError::NotImplemented { clock: "pll1_clk" }) + } + /// 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" }) } + + /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active + pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result { + let Some(clk) = self.cpu_system_clk.as_ref() else { + return Err(ClockError::BadConfig { + clock: "cpu_system_clk", + reason: "required but not active", + }); + }; + // Can the main_clk ever be active in deep sleep? I think it is gated? + match at_level { + PoweredClock::NormalEnabledDeepSleepDisabled => {} + PoweredClock::AlwaysEnabled => { + return Err(ClockError::BadConfig { + clock: "main_clk", + reason: "not low power active", + }) + } + } + + Ok(clk.frequency) + } + + pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result { + let freq = self.ensure_cpu_system_clk_active(at_level)?; + + Ok(freq / 6) + } } impl PoweredClock { @@ -749,7 +788,7 @@ impl ClockOperator<'_> { w }); // Then unhalt it, and reset it - self.syscon.frolfdiv().write(|w| { + self.syscon.frolfdiv().modify(|_r, w| { w.halt().run(); w.reset().released(); w diff --git a/src/gpio.rs b/src/gpio.rs index 7dba7ab4f..4f79aff93 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -102,7 +102,7 @@ pub struct AnyPin { impl AnyPin { /// Create an `AnyPin` from raw components. - pub fn new( + fn new( port: usize, pin: usize, gpio: &'static crate::pac::gpio0::RegisterBlock, @@ -151,7 +151,7 @@ impl AnyPin { embassy_hal_internal::impl_peripheral!(AnyPin); -trait SealedPin { +pub(crate) trait SealedPin { fn pin_port(&self) -> usize; fn port(&self) -> usize { @@ -297,7 +297,7 @@ macro_rules! impl_pin { } } - impl crate::peripherals::$peri { + impl crate::peripherals::$peri { /// Convenience helper to obtain a type-erased handle to this pin. pub fn degrade(&self) -> AnyPin { AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) diff --git a/src/lib.rs b/src/lib.rs index 175642f75..6f3a63c94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod gpio; pub mod pins; // pin mux helpers pub mod adc; +pub mod clkout; pub mod config; pub mod interrupt; pub mod lpuart; @@ -33,6 +34,10 @@ embassy_hal_internal::peripherals!( CDOG0, CDOG1, + // CLKOUT is not specifically a peripheral (it's part of SYSCON), + // but we still want it to be a singleton. + CLKOUT, + CMC, CMP0, CMP1, -- cgit