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/clocks/mod.rs | 71 ++++++++++++++---- src/clocks/periph_helpers.rs | 171 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 225 insertions(+), 17 deletions(-) (limited to 'src/clocks') 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()) + } +} -- cgit