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 (limited to 'src') 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