aboutsummaryrefslogtreecommitdiff
path: root/src/clocks
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-11-18 14:19:09 +0100
committerGitHub <[email protected]>2025-11-18 14:19:09 +0100
commit5b1149a52dbec9e3bdd10dc341dc0751ab4798a6 (patch)
tree3ff7098471cf660a54a707464a0e2feb2080b09e /src/clocks
parent62e297c130ac26afe4d7d5752bb79709bd370e39 (diff)
parentc8942aec2478ff077b55da0e86801f8a6a88a7de (diff)
Merge pull request #11 from jamesmunns/james/impl-clocks
Implement initial `clock` driver. This PR introduces an initial two-phase clock driver system: 1. The first stage is responsible for initializing the core/system clocks at the time of `embassy_mcxa::init()` 2. The second stage is done on creation of peripherals This work is limited to currently used clocks and peripherals, but has room for expansion for later peripherals. This model is based on the preliminary refactoring performed for the `embassy-imxrt` crate.
Diffstat (limited to 'src/clocks')
-rw-r--r--src/clocks/config.rs199
-rw-r--r--src/clocks/mod.rs887
-rw-r--r--src/clocks/periph_helpers.rs393
3 files changed, 1479 insertions, 0 deletions
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 @@
1//! Clock Configuration
2//!
3//! This module holds configuration types used for the system clocks. For
4//! configuration of individual peripherals, see [`super::periph_helpers`].
5
6use super::PoweredClock;
7
8/// This type represents a divider in the range 1..=256.
9///
10/// At a hardware level, this is an 8-bit register from 0..=255,
11/// which adds one.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub struct Div8(pub(super) u8);
14
15impl Div8 {
16 /// Store a "raw" divisor value that will divide the source by
17 /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source
18 /// by 1, and `Div8::from_raw(255)` will divide the source by
19 /// 256.
20 pub const fn from_raw(n: u8) -> Self {
21 Self(n)
22 }
23
24 /// Store a specific divisor value that will divide the source
25 /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source
26 /// by 1, and `Div8::from_divisor(256)` will divide the source
27 /// by 256.
28 ///
29 /// Will return `None` if `n` is not in the range `1..=256`.
30 /// Consider [`Self::from_raw`] for an infallible version.
31 pub const fn from_divisor(n: u16) -> Option<Self> {
32 let Some(n) = n.checked_sub(1) else {
33 return None;
34 };
35 if n > (u8::MAX as u16) {
36 return None;
37 }
38 Some(Self(n as u8))
39 }
40
41 /// Convert into "raw" bits form
42 #[inline(always)]
43 pub const fn into_bits(self) -> u8 {
44 self.0
45 }
46
47 /// Convert into "divisor" form, as a u32 for convenient frequency math
48 #[inline(always)]
49 pub const fn into_divisor(self) -> u32 {
50 self.0 as u32 + 1
51 }
52}
53
54/// ```text
55/// ┌─────────────────────────────────────────────────────────┐
56/// │ │
57/// │ ┌───────────┐ clk_out ┌─────────┐ │
58/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │
59/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷
60/// EXTAL ──────┼──▷│ │───────────▷│ │ │
61/// │ └───────────┘ └─────────┘ │
62/// │ │
63/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │
64/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷
65/// │ │ │ │ ├────┤ clk_45m │
66/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷
67/// │ └───────────┘ └────┘ │
68/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │
69/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷
70/// │ │ │ │ ├────┤ clk_1m │
71/// │ │ │ └─────▷│1/12│────────────────────┼──────▷
72/// │ └───────────┘ └────┘ │
73/// │ │
74/// │ ┌──────────┐ │
75/// │ │000 │ │
76/// │ clk_in │ │ │
77/// │ ───────────────▷│001 │ │
78/// │ fro_12m │ │ │
79/// │ ───────────────▷│010 │ │
80/// │ fro_hf_root │ │ │
81/// │ ───────────────▷│011 │ main_clk │
82/// │ │ │───────────────────────────┼──────▷
83/// clk_16k ──────┼─────────────────▷│100 │ │
84/// │ none │ │ │
85/// │ ───────────────▷│101 │ │
86/// │ pll1_clk │ │ │
87/// │ ───────────────▷│110 │ │
88/// │ none │ │ │
89/// │ ───────────────▷│111 │ │
90/// │ └──────────┘ │
91/// │ ▲ │
92/// │ │ │
93/// │ SCG SCS │
94/// │ SCG-Lite │
95/// └─────────────────────────────────────────────────────────┘
96///
97///
98/// clk_in ┌─────┐
99/// ───────────────▷│00 │
100/// clk_45m │ │
101/// ───────────────▷│01 │ ┌───────────┐ pll1_clk
102/// none │ │─────▷│ SPLL │───────────────▷
103/// ───────────────▷│10 │ └───────────┘
104/// fro_12m │ │
105/// ───────────────▷│11 │
106/// └─────┘
107/// ```
108#[non_exhaustive]
109pub struct ClocksConfig {
110 /// FIRC, FRO180, 45/60/90/180M clock source
111 pub firc: Option<FircConfig>,
112 /// SIRC, FRO12M, clk_12m clock source
113 // NOTE: I don't think we *can* disable the SIRC?
114 pub sirc: SircConfig,
115 /// FRO16K clock source
116 pub fro16k: Option<Fro16KConfig>,
117}
118
119// FIRC/FRO180M
120
121/// ```text
122/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf
123/// │ FRO180M ├───────┬─────▷│GATE│──────────▷
124/// │ │ │ ├────┤ clk_45m
125/// │ │ └─────▷│GATE│──────────▷
126/// └───────────┘ └────┘
127/// ```
128#[non_exhaustive]
129pub struct FircConfig {
130 /// Selected clock frequency
131 pub frequency: FircFreqSel,
132 /// Selected power state of the clock
133 pub power: PoweredClock,
134 /// Is the "fro_hf" gated clock enabled?
135 pub fro_hf_enabled: bool,
136 /// Is the "clk_45m" gated clock enabled?
137 pub clk_45m_enabled: bool,
138 /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`!
139 pub fro_hf_div: Option<Div8>,
140}
141
142/// Selected FIRC frequency
143pub enum FircFreqSel {
144 /// 45MHz Output
145 Mhz45,
146 /// 60MHz Output
147 Mhz60,
148 /// 90MHz Output
149 Mhz90,
150 /// 180MHz Output
151 Mhz180,
152}
153
154// SIRC/FRO12M
155
156/// ```text
157/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m
158/// │ FRO12M │────────┬─────▷│ CG │──────────▷
159/// │ │ │ ├────┤ clk_1m
160/// │ │ └─────▷│1/12│──────────▷
161/// └───────────┘ └────┘
162/// ```
163#[non_exhaustive]
164pub struct SircConfig {
165 pub power: PoweredClock,
166 // peripheral output, aka sirc_12mhz
167 pub fro_12m_enabled: bool,
168 /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`!
169 pub fro_lf_div: Option<Div8>,
170}
171
172#[non_exhaustive]
173pub struct Fro16KConfig {
174 pub vsys_domain_active: bool,
175 pub vdd_core_domain_active: bool,
176}
177
178impl Default for ClocksConfig {
179 fn default() -> Self {
180 Self {
181 firc: Some(FircConfig {
182 frequency: FircFreqSel::Mhz45,
183 power: PoweredClock::NormalEnabledDeepSleepDisabled,
184 fro_hf_enabled: true,
185 clk_45m_enabled: true,
186 fro_hf_div: None,
187 }),
188 sirc: SircConfig {
189 power: PoweredClock::AlwaysEnabled,
190 fro_12m_enabled: true,
191 fro_lf_div: None,
192 },
193 fro16k: Some(Fro16KConfig {
194 vsys_domain_active: true,
195 vdd_core_domain_active: true,
196 }),
197 }
198 }
199}
diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs
new file mode 100644
index 000000000..e02840592
--- /dev/null
+++ b/src/clocks/mod.rs
@@ -0,0 +1,887 @@
1//! # Clock Module
2//!
3//! For the MCX-A, we separate clock and peripheral control into two main stages:
4//!
5//! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the
6//! core system clocks, including external and internal oscillators. This
7//! configuration is then largely static for the duration of the program.
8//! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver
9//! is responsible for two main things:
10//! * Ensuring that any required "upstream" core system clocks necessary for
11//! clocking the peripheral is active and configured to a reasonable value
12//! * Enabling the clock gates for that peripheral, and resetting the peripheral
13//!
14//! From a user perspective, only step 1 is visible. Step 2 is automatically handled
15//! by HAL drivers, using interfaces defined in this module.
16//!
17//! It is also possible to *view* the state of the clock configuration after [`init()`]
18//! has been called, using the [`with_clocks()`] function, which provides a view of the
19//! [`Clocks`] structure.
20//!
21//! ## For HAL driver implementors
22//!
23//! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or
24//! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This
25//! selection, as well as often any pre-scaler division from that source clock, is made
26//! through MRCC registers.
27//!
28//! Any peripheral that is controlled through the MRCC register can automatically implement
29//! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need
30//! to define the configuration surface and steps necessary to fully configure that peripheral
31//! from a clocks perspective by:
32//!
33//! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects
34//! or divisions available to the HAL driver
35//! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the
36//! necessary input clocks are reasonable
37
38use core::cell::RefCell;
39
40use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig};
41use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten};
42use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten};
43use periph_helpers::SPConfHelper;
44
45use crate::pac;
46pub mod config;
47pub mod periph_helpers;
48
49//
50// Statics/Consts
51//
52
53/// The state of system core clocks.
54///
55/// Initialized by [`init()`], and then unchanged for the remainder of the program.
56static CLOCKS: critical_section::Mutex<RefCell<Option<Clocks>>> = critical_section::Mutex::new(RefCell::new(None));
57
58//
59// Free functions
60//
61
62/// Initialize the core system clocks with the given [`ClocksConfig`].
63///
64/// This function should be called EXACTLY once at start-up, usually via a
65/// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will
66/// return an error.
67pub fn init(settings: ClocksConfig) -> Result<(), ClockError> {
68 critical_section::with(|cs| {
69 if CLOCKS.borrow_ref(cs).is_some() {
70 Err(ClockError::AlreadyInitialized)
71 } else {
72 Ok(())
73 }
74 })?;
75
76 let mut clocks = Clocks::default();
77 let mut operator = ClockOperator {
78 clocks: &mut clocks,
79 config: &settings,
80
81 _mrcc0: unsafe { pac::Mrcc0::steal() },
82 scg0: unsafe { pac::Scg0::steal() },
83 syscon: unsafe { pac::Syscon::steal() },
84 vbat0: unsafe { pac::Vbat0::steal() },
85 };
86
87 operator.configure_firc_clocks()?;
88 operator.configure_sirc_clocks()?;
89 operator.configure_fro16k_clocks()?;
90
91 // For now, just use FIRC as the main/cpu clock, which should already be
92 // the case on reset
93 assert!(operator.scg0.rccr().read().scs().is_firc());
94 assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0);
95 operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap());
96
97 critical_section::with(|cs| {
98 let mut clks = CLOCKS.borrow_ref_mut(cs);
99 assert!(clks.is_none(), "Clock setup race!");
100 *clks = Some(clocks);
101 });
102
103 Ok(())
104}
105
106/// Obtain the full clocks structure, calling the given closure in a critical section.
107///
108/// The given closure will be called with read-only access to the state of the system
109/// clocks. This can be used to query and return the state of a given clock.
110///
111/// As the caller's closure will be called in a critical section, care must be taken
112/// not to block or cause any other undue delays while accessing.
113///
114/// Calls to this function will not succeed until after a successful call to `init()`,
115/// and will always return None.
116pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> {
117 critical_section::with(|cs| {
118 let c = CLOCKS.borrow_ref(cs);
119 let c = c.as_ref()?;
120 Some(f(c))
121 })
122}
123
124//
125// Structs/Enums
126//
127
128/// The `Clocks` structure contains the initialized state of the core system clocks
129///
130/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function
131/// at boot time.
132#[derive(Default, Debug, Clone)]
133#[non_exhaustive]
134pub struct Clocks {
135 /// The `clk_in` is a clock provided by an external oscillator
136 pub clk_in: Option<Clock>,
137
138 // FRO180M stuff
139 //
140 /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator
141 ///
142 /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`,
143 /// and `fro_hf_div`.
144 pub fro_hf_root: Option<Clock>,
145
146 /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate.
147 pub fro_hf: Option<Clock>,
148
149 /// `clk_45` is a 45MHz clock, sourced from `fro_hf`.
150 pub clk_45m: Option<Clock>,
151
152 /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`.
153 pub fro_hf_div: Option<Clock>,
154
155 //
156 // End FRO180M
157
158 // FRO12M stuff
159 //
160 /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator
161 ///
162 /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`,
163 /// `and `fro_lf_div`.
164 pub fro_12m_root: Option<Clock>,
165
166 /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate.
167 pub fro_12m: Option<Clock>,
168
169 /// `clk_1m` is a 1MHz clock, sourced from `fro_12m`
170 pub clk_1m: Option<Clock>,
171
172 /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m`
173 pub fro_lf_div: Option<Clock>,
174 //
175 // End FRO12M stuff
176 /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator.
177 ///
178 /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in
179 /// the system domain, such as the CMP and RTC.
180 pub clk_16k_vsys: Option<Clock>,
181
182 /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator.
183 ///
184 /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in
185 /// the VDD Core domain, such as the OSTimer or LPUarts.
186 pub clk_16k_vdd_core: Option<Clock>,
187
188 /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some
189 /// peripherals.
190 pub main_clk: Option<Clock>,
191
192 /// `pll1_clk` is the output of the main system PLL, `pll1`.
193 pub pll1_clk: Option<Clock>,
194}
195
196/// `ClockError` is the main error returned when configuring or checking clock state
197#[derive(Debug, Copy, Clone, Eq, PartialEq)]
198#[cfg_attr(feature = "defmt", derive(defmt::Format))]
199#[non_exhaustive]
200pub enum ClockError {
201 /// The system clocks were never initialized by calling [`init()`]
202 NeverInitialized,
203 /// The [`init()`] function was called more than once
204 AlreadyInitialized,
205 /// The requested configuration was not possible to fulfill, as the system clocks
206 /// were not configured in a compatible way
207 BadConfig { clock: &'static str, reason: &'static str },
208 /// The requested configuration was not possible to fulfill, as the required system
209 /// clocks have not yet been implemented.
210 NotImplemented { clock: &'static str },
211 /// The requested peripheral could not be configured, as the steps necessary to
212 /// enable it have not yet been implemented.
213 UnimplementedConfig,
214}
215
216/// Information regarding a system clock
217#[derive(Debug, Clone)]
218pub struct Clock {
219 /// The frequency, in Hz, of the given clock
220 pub frequency: u32,
221 /// The power state of the clock, e.g. whether it is active in deep sleep mode
222 /// or not.
223 pub power: PoweredClock,
224}
225
226/// The power state of a given clock.
227///
228/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep
229/// mode will be stopped. This means that any downstream usage, e.g. by peripherals,
230/// will also stop.
231///
232/// In the future, we will provide an API for entering Deep Sleep, and if there are
233/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into
234/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals.
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236pub enum PoweredClock {
237 /// The given clock will NOT continue running in Deep Sleep mode
238 NormalEnabledDeepSleepDisabled,
239 /// The given clock WILL continue running in Deep Sleep mode
240 AlwaysEnabled,
241}
242
243/// The ClockOperator is a private helper type that contains the methods used
244/// during system clock initialization.
245///
246/// # SAFETY
247///
248/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`,
249/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function.
250struct ClockOperator<'a> {
251 /// A mutable reference to the current state of system clocks
252 clocks: &'a mut Clocks,
253 /// A reference to the requested configuration provided by the caller of [`init()`]
254 config: &'a ClocksConfig,
255
256 // We hold on to stolen peripherals
257 _mrcc0: pac::Mrcc0,
258 scg0: pac::Scg0,
259 syscon: pac::Syscon,
260 vbat0: pac::Vbat0,
261}
262
263/// Trait describing an AHB clock gate that can be toggled through MRCC.
264pub trait Gate {
265 type MrccPeriphConfig: SPConfHelper;
266
267 /// Enable the clock gate.
268 ///
269 /// # SAFETY
270 ///
271 /// The current peripheral must be disabled prior to calling this method
272 unsafe fn enable_clock();
273
274 /// Disable the clock gate.
275 ///
276 /// # SAFETY
277 ///
278 /// There must be no active user of this peripheral when calling this method
279 unsafe fn disable_clock();
280
281 /// Drive the peripheral into reset.
282 ///
283 /// # SAFETY
284 ///
285 /// There must be no active user of this peripheral when calling this method
286 unsafe fn assert_reset();
287
288 /// Drive the peripheral out of reset.
289 ///
290 /// # SAFETY
291 ///
292 /// There must be no active user of this peripheral when calling this method
293 unsafe fn release_reset();
294
295 /// Return whether the clock gate for this peripheral is currently enabled.
296 fn is_clock_enabled() -> bool;
297
298 /// Return whether the peripheral is currently held in reset.
299 fn is_reset_released() -> bool;
300}
301
302/// This is the primary helper method HAL drivers are expected to call when creating
303/// an instance of the peripheral.
304///
305/// This method:
306///
307/// 1. Enables the MRCC clock gate for this peripheral
308/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error
309/// and re-disabling the peripheral if this fails.
310/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state
311/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account
312/// the selected upstream clock, as well as any division specified by `cfg`.
313///
314/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method
315/// may return `Ok(0)`. In the future, this might be updated to return the correct
316/// "ambient" clock, e.g. the AHB/APB frequency.
317///
318/// # SAFETY
319///
320/// This peripheral must not yet be in use prior to calling `enable_and_reset`.
321#[inline]
322pub unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> {
323 let freq = enable::<G>(cfg).inspect_err(|_| disable::<G>())?;
324 pulse_reset::<G>();
325 Ok(freq)
326}
327
328/// Enable the clock gate for the given peripheral.
329///
330/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need
331/// to control the duration of the pulse more directly.
332///
333/// # SAFETY
334///
335/// This peripheral must not yet be in use prior to calling `enable`.
336#[inline]
337pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> {
338 G::enable_clock();
339 while !G::is_clock_enabled() {}
340 core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags));
341
342 let freq = critical_section::with(|cs| {
343 let clocks = CLOCKS.borrow_ref(cs);
344 let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?;
345 cfg.post_enable_config(clocks)
346 });
347
348 freq.inspect_err(|_e| {
349 G::disable_clock();
350 })
351}
352
353/// Disable the clock gate for the given peripheral.
354///
355/// # SAFETY
356///
357/// This peripheral must no longer be in use prior to calling `enable`.
358#[allow(dead_code)]
359#[inline]
360pub unsafe fn disable<G: Gate>() {
361 G::disable_clock();
362}
363
364/// Check whether a gate is currently enabled.
365#[allow(dead_code)]
366#[inline]
367pub fn is_clock_enabled<G: Gate>() -> bool {
368 G::is_clock_enabled()
369}
370
371/// Release a reset line for the given peripheral set.
372///
373/// Prefer [`enable_and_reset`].
374///
375/// # SAFETY
376///
377/// This peripheral must not yet be in use prior to calling `release_reset`.
378#[inline]
379pub unsafe fn release_reset<G: Gate>() {
380 G::release_reset();
381}
382
383/// Assert a reset line for the given peripheral set.
384///
385/// Prefer [`enable_and_reset`].
386///
387/// # SAFETY
388///
389/// This peripheral must not yet be in use prior to calling `assert_reset`.
390#[inline]
391pub unsafe fn assert_reset<G: Gate>() {
392 G::assert_reset();
393}
394
395/// Check whether the peripheral is held in reset.
396#[inline]
397pub unsafe fn is_reset_released<G: Gate>() -> bool {
398 G::is_reset_released()
399}
400
401/// Pulse a reset line (assert then release) with a short delay.
402///
403/// Prefer [`enable_and_reset`].
404///
405/// # SAFETY
406///
407/// This peripheral must not yet be in use prior to calling `release_reset`.
408#[inline]
409pub unsafe fn pulse_reset<G: Gate>() {
410 G::assert_reset();
411 cortex_m::asm::nop();
412 cortex_m::asm::nop();
413 G::release_reset();
414}
415
416//
417// `impl`s for structs/enums
418//
419
420/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active".
421///
422/// These methods are intended to be used by HAL peripheral implementors to ensure that their
423/// selected clocks are active at a suitable level at time of construction. These methods
424/// return the frequency of the requested clock, in Hertz, or a [`ClockError`].
425impl Clocks {
426 /// Ensure the `fro_lf_div` clock is active and valid at the given power state.
427 pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
428 let Some(clk) = self.fro_lf_div.as_ref() else {
429 return Err(ClockError::BadConfig {
430 clock: "fro_lf_div",
431 reason: "required but not active",
432 });
433 };
434 if !clk.power.meets_requirement_of(at_level) {
435 return Err(ClockError::BadConfig {
436 clock: "fro_lf_div",
437 reason: "not low power active",
438 });
439 }
440 Ok(clk.frequency)
441 }
442
443 /// Ensure the `fro_hf` clock is active and valid at the given power state.
444 pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
445 let Some(clk) = self.fro_hf.as_ref() else {
446 return Err(ClockError::BadConfig {
447 clock: "fro_hf",
448 reason: "required but not active",
449 });
450 };
451 if !clk.power.meets_requirement_of(at_level) {
452 return Err(ClockError::BadConfig {
453 clock: "fro_hf",
454 reason: "not low power active",
455 });
456 }
457 Ok(clk.frequency)
458 }
459
460 /// Ensure the `fro_hf_div` clock is active and valid at the given power state.
461 pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
462 let Some(clk) = self.fro_hf_div.as_ref() else {
463 return Err(ClockError::BadConfig {
464 clock: "fro_hf_div",
465 reason: "required but not active",
466 });
467 };
468 if !clk.power.meets_requirement_of(at_level) {
469 return Err(ClockError::BadConfig {
470 clock: "fro_hf_div",
471 reason: "not low power active",
472 });
473 }
474 Ok(clk.frequency)
475 }
476
477 /// Ensure the `clk_in` clock is active and valid at the given power state.
478 pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
479 Err(ClockError::NotImplemented { clock: "clk_in" })
480 }
481
482 /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state.
483 pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
484 // NOTE: clk_16k is always active in low power mode
485 Ok(self
486 .clk_16k_vsys
487 .as_ref()
488 .ok_or(ClockError::BadConfig {
489 clock: "clk_16k_vsys",
490 reason: "required but not active",
491 })?
492 .frequency)
493 }
494
495 /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state.
496 pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
497 // NOTE: clk_16k is always active in low power mode
498 Ok(self
499 .clk_16k_vdd_core
500 .as_ref()
501 .ok_or(ClockError::BadConfig {
502 clock: "clk_16k_vdd_core",
503 reason: "required but not active",
504 })?
505 .frequency)
506 }
507
508 /// Ensure the `clk_1m` clock is active and valid at the given power state.
509 pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
510 let Some(clk) = self.clk_1m.as_ref() else {
511 return Err(ClockError::BadConfig {
512 clock: "clk_1m",
513 reason: "required but not active",
514 });
515 };
516 if !clk.power.meets_requirement_of(at_level) {
517 return Err(ClockError::BadConfig {
518 clock: "clk_1m",
519 reason: "not low power active",
520 });
521 }
522 Ok(clk.frequency)
523 }
524
525 /// Ensure the `pll1_clk_div` clock is active and valid at the given power state.
526 pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
527 Err(ClockError::NotImplemented { clock: "pll1_clk_div" })
528 }
529}
530
531impl PoweredClock {
532 /// Does THIS clock meet the power requirements of the OTHER clock?
533 pub fn meets_requirement_of(&self, other: &Self) -> bool {
534 match (self, other) {
535 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false,
536 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
537 (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
538 (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true,
539 }
540 }
541}
542
543impl ClockOperator<'_> {
544 /// Configure the FIRC/FRO180M clock family
545 ///
546 /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used
547 /// as the main clock used for the CPU, AHB, APB, etc.
548 fn configure_firc_clocks(&mut self) -> Result<(), ClockError> {
549 const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig {
550 clock: "firc",
551 reason: "For now, FIRC must be enabled and in default state!",
552 });
553
554 // Did the user give us a FIRC config?
555 let Some(firc) = self.config.firc.as_ref() else {
556 return HARDCODED_ERR;
557 };
558 // Is the FIRC set to 45MHz (should be reset default)
559 if !matches!(firc.frequency, FircFreqSel::Mhz45) {
560 return HARDCODED_ERR;
561 }
562 let base_freq = 45_000_000;
563
564 // Now, check if the FIRC as expected for our hardcoded value
565 let mut firc_ok = true;
566
567 // Is the hardware currently set to the default 45MHz?
568 //
569 // NOTE: the SVD currently has the wrong(?) values for these:
570 // 45 -> 48
571 // 60 -> 64
572 // 90 -> 96
573 // 180 -> 192
574 // Probably correct-ish, but for a different trim value?
575 firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s();
576
577 // Check some values in the CSR
578 let csr = self.scg0.firccsr().read();
579 // Is it enabled?
580 firc_ok &= csr.fircen().is_enabled();
581 // Is it accurate?
582 firc_ok &= csr.fircacc().is_enabled_and_valid();
583 // Is there no error?
584 firc_ok &= csr.fircerr().is_error_not_detected();
585 // Is the FIRC the system clock?
586 firc_ok &= csr.fircsel().is_firc();
587 // Is it valid?
588 firc_ok &= csr.fircvld().is_enabled_and_valid();
589
590 // Are we happy with the current (hardcoded) state?
591 if !firc_ok {
592 return HARDCODED_ERR;
593 }
594
595 // Note that the fro_hf_root is active
596 self.clocks.fro_hf_root = Some(Clock {
597 frequency: base_freq,
598 power: firc.power,
599 });
600
601 // Okay! Now we're past that, let's enable all the downstream clocks.
602 let FircConfig {
603 frequency: _,
604 power,
605 fro_hf_enabled,
606 clk_45m_enabled,
607 fro_hf_div,
608 } = firc;
609
610 // When is the FRO enabled?
611 let pow_set = match power {
612 PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes,
613 PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes,
614 };
615
616 // Do we enable the `fro_hf` output?
617 let fro_hf_set = if *fro_hf_enabled {
618 self.clocks.fro_hf = Some(Clock {
619 frequency: base_freq,
620 power: *power,
621 });
622 FircFclkPeriphEn::Enabled
623 } else {
624 FircFclkPeriphEn::Disabled
625 };
626
627 // Do we enable the `clk_45m` output?
628 let clk_45m_set = if *clk_45m_enabled {
629 self.clocks.clk_45m = Some(Clock {
630 frequency: 45_000_000,
631 power: *power,
632 });
633 FircSclkPeriphEn::Enabled
634 } else {
635 FircSclkPeriphEn::Disabled
636 };
637
638 self.scg0.firccsr().modify(|_r, w| {
639 w.fircsten().variant(pow_set);
640 w.firc_fclk_periph_en().variant(fro_hf_set);
641 w.firc_sclk_periph_en().variant(clk_45m_set);
642 w
643 });
644
645 // Do we enable the `fro_hf_div` output?
646 if let Some(d) = fro_hf_div.as_ref() {
647 // We need `fro_hf` to be enabled
648 if !*fro_hf_enabled {
649 return Err(ClockError::BadConfig {
650 clock: "fro_hf_div",
651 reason: "fro_hf not enabled",
652 });
653 }
654
655 // Halt and reset the div
656 self.syscon.frohfdiv().write(|w| {
657 w.halt().halt();
658 w.reset().asserted();
659 w
660 });
661 // Then change the div, unhalt it, and reset it
662 self.syscon.frohfdiv().write(|w| {
663 unsafe {
664 w.div().bits(d.into_bits());
665 }
666 w.halt().run();
667 w.reset().released();
668 w
669 });
670
671 // Wait for clock to stabilize
672 while self.syscon.frohfdiv().read().unstab().is_ongoing() {}
673
674 // Store off the clock info
675 self.clocks.fro_hf_div = Some(Clock {
676 frequency: base_freq / d.into_divisor(),
677 power: *power,
678 });
679 }
680
681 Ok(())
682 }
683
684 /// Configure the SIRC/FRO12M clock family
685 fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> {
686 let SircConfig {
687 power,
688 fro_12m_enabled,
689 fro_lf_div,
690 } = &self.config.sirc;
691 let base_freq = 12_000_000;
692
693 // Allow writes
694 self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled());
695 self.clocks.fro_12m_root = Some(Clock {
696 frequency: base_freq,
697 power: *power,
698 });
699
700 let deep = match power {
701 PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled,
702 PoweredClock::AlwaysEnabled => Sircsten::Enabled,
703 };
704 let pclk = if *fro_12m_enabled {
705 self.clocks.fro_12m = Some(Clock {
706 frequency: base_freq,
707 power: *power,
708 });
709 self.clocks.clk_1m = Some(Clock {
710 frequency: base_freq / 12,
711 power: *power,
712 });
713 SircClkPeriphEn::Enabled
714 } else {
715 SircClkPeriphEn::Disabled
716 };
717
718 // Set sleep/peripheral usage
719 self.scg0.sirccsr().modify(|_r, w| {
720 w.sircsten().variant(deep);
721 w.sirc_clk_periph_en().variant(pclk);
722 w
723 });
724
725 while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {}
726 if self.scg0.sirccsr().read().sircerr().is_error_detected() {
727 return Err(ClockError::BadConfig {
728 clock: "sirc",
729 reason: "error set",
730 });
731 }
732
733 // reset lock
734 self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled());
735
736 // Do we enable the `fro_lf_div` output?
737 if let Some(d) = fro_lf_div.as_ref() {
738 // We need `fro_lf` to be enabled
739 if !*fro_12m_enabled {
740 return Err(ClockError::BadConfig {
741 clock: "fro_lf_div",
742 reason: "fro_12m not enabled",
743 });
744 }
745
746 // Halt and reset the div
747 self.syscon.frolfdiv().write(|w| {
748 w.halt().halt();
749 w.reset().asserted();
750 w
751 });
752 // Then change the div, unhalt it, and reset it
753 self.syscon.frolfdiv().write(|w| {
754 unsafe {
755 w.div().bits(d.into_bits());
756 }
757 w.halt().run();
758 w.reset().released();
759 w
760 });
761
762 // Wait for clock to stabilize
763 while self.syscon.frolfdiv().read().unstab().is_ongoing() {}
764
765 // Store off the clock info
766 self.clocks.fro_lf_div = Some(Clock {
767 frequency: base_freq / d.into_divisor(),
768 power: *power,
769 });
770 }
771
772 Ok(())
773 }
774
775 /// Configure the FRO16K/clk_16k clock family
776 fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> {
777 let Some(fro16k) = self.config.fro16k.as_ref() else {
778 return Ok(());
779 };
780 // Enable FRO16K oscillator
781 self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit());
782
783 // Lock the control register
784 self.vbat0.frolcka().modify(|_, w| w.lock().set_bit());
785
786 let Fro16KConfig {
787 vsys_domain_active,
788 vdd_core_domain_active,
789 } = fro16k;
790
791 // Enable clock outputs to both VSYS and VDD_CORE domains
792 // Bit 0: clk_16k0 to VSYS domain
793 // Bit 1: clk_16k1 to VDD_CORE domain
794 //
795 // TODO: Define sub-fields for this register with a PAC patch?
796 let mut bits = 0;
797 if *vsys_domain_active {
798 bits |= 0b01;
799 self.clocks.clk_16k_vsys = Some(Clock {
800 frequency: 16_384,
801 power: PoweredClock::AlwaysEnabled,
802 });
803 }
804 if *vdd_core_domain_active {
805 bits |= 0b10;
806 self.clocks.clk_16k_vdd_core = Some(Clock {
807 frequency: 16_384,
808 power: PoweredClock::AlwaysEnabled,
809 });
810 }
811 self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) });
812
813 Ok(())
814 }
815}
816
817//
818// Macros/macro impls
819//
820
821/// This macro is used to implement the [`Gate`] trait for a given peripheral
822/// that is controlled by the MRCC peripheral.
823macro_rules! impl_cc_gate {
824 ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => {
825 impl Gate for crate::peripherals::$name {
826 type MrccPeriphConfig = $config;
827
828 #[inline]
829 unsafe fn enable_clock() {
830 let mrcc = unsafe { pac::Mrcc0::steal() };
831 mrcc.$clk_reg().modify(|_, w| w.$field().enabled());
832 }
833
834 #[inline]
835 unsafe fn disable_clock() {
836 let mrcc = unsafe { pac::Mrcc0::steal() };
837 mrcc.$clk_reg().modify(|_r, w| w.$field().disabled());
838 }
839
840 #[inline]
841 fn is_clock_enabled() -> bool {
842 let mrcc = unsafe { pac::Mrcc0::steal() };
843 mrcc.$clk_reg().read().$field().is_enabled()
844 }
845
846 #[inline]
847 unsafe fn release_reset() {
848 let mrcc = unsafe { pac::Mrcc0::steal() };
849 mrcc.$rst_reg().modify(|_, w| w.$field().enabled());
850 }
851
852 #[inline]
853 unsafe fn assert_reset() {
854 let mrcc = unsafe { pac::Mrcc0::steal() };
855 mrcc.$rst_reg().modify(|_, w| w.$field().disabled());
856 }
857
858 #[inline]
859 fn is_reset_released() -> bool {
860 let mrcc = unsafe { pac::Mrcc0::steal() };
861 mrcc.$rst_reg().read().$field().is_enabled()
862 }
863 }
864 };
865}
866
867/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait,
868/// for various low level peripherals.
869pub(crate) mod gate {
870 use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig};
871 use super::*;
872
873 // These peripherals have no additional upstream clocks or configuration required
874 // other than enabling through the MRCC gate. Currently, these peripherals will
875 // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or
876 // [`SPConfHelper::post_enable_config()`].
877 impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig);
878 impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig);
879 impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig);
880 impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig);
881
882 // These peripherals DO have meaningful configuration, and could fail if the system
883 // clocks do not match their needs.
884 impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig);
885 impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig);
886 impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig);
887}
diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs
new file mode 100644
index 000000000..e5b234c5b
--- /dev/null
+++ b/src/clocks/periph_helpers.rs
@@ -0,0 +1,393 @@
1//! Peripheral Helpers
2//!
3//! The purpose of this module is to define the per-peripheral special handling
4//! required from a clocking perspective. Different peripherals have different
5//! selectable source clocks, and some peripherals have additional pre-dividers
6//! that can be used.
7//!
8//! See the docs of [`SPConfHelper`] for more details.
9
10use super::{ClockError, Clocks, PoweredClock};
11use crate::pac;
12
13/// Sealed Peripheral Configuration Helper
14///
15/// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the
16/// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should
17/// fix the name, or actually do the sealing of peripherals.
18///
19/// This trait serves to act as a per-peripheral customization for clocking behavior.
20///
21/// This trait should be implemented on a configuration type for a given peripheral, and
22/// provide the methods that will be called by the higher level operations like
23/// `embassy_mcxa::clocks::enable_and_reset()`.
24pub trait SPConfHelper {
25 /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated),
26 /// but BEFORE the peripheral reset line is reset.
27 ///
28 /// This function should check that any relevant upstream clocks are enabled, are in a
29 /// reasonable power state, and that the requested configuration can be made. If any of
30 /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`.
31 ///
32 /// This function SHOULD NOT make any changes to the system clock configuration, even
33 /// unsafely, as this should remain static for the duration of the program.
34 ///
35 /// This function WILL be called in a critical section, care should be taken not to delay
36 /// for an unreasonable amount of time.
37 ///
38 /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency
39 /// fed into the peripheral, taking into account the selected source clock, as well as
40 /// any pre-divisors.
41 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError>;
42}
43
44// config types
45
46/// This type represents a divider in the range 1..=16.
47///
48/// At a hardware level, this is an 8-bit register from 0..=15,
49/// which adds one.
50///
51/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain
52/// seems to use 4 bit dividers!
53#[derive(Copy, Clone, Debug, PartialEq, Eq)]
54pub struct Div4(pub(super) u8);
55
56impl Div4 {
57 /// Divide by one, or no division
58 pub const fn no_div() -> Self {
59 Self(0)
60 }
61
62 /// Store a "raw" divisor value that will divide the source by
63 /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source
64 /// by 1, and `Div4::from_raw(15)` will divide the source by
65 /// 16.
66 pub const fn from_raw(n: u8) -> Option<Self> {
67 if n > 0b1111 {
68 None
69 } else {
70 Some(Self(n))
71 }
72 }
73
74 /// Store a specific divisor value that will divide the source
75 /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source
76 /// by 1, and `Div4::from_divisor(16)` will divide the source
77 /// by 16.
78 ///
79 /// Will return `None` if `n` is not in the range `1..=16`.
80 /// Consider [`Self::from_raw`] for an infallible version.
81 pub const fn from_divisor(n: u8) -> Option<Self> {
82 let Some(n) = n.checked_sub(1) else {
83 return None;
84 };
85 if n > 0b1111 {
86 return None;
87 }
88 Some(Self(n))
89 }
90
91 /// Convert into "raw" bits form
92 #[inline(always)]
93 pub const fn into_bits(self) -> u8 {
94 self.0
95 }
96
97 /// Convert into "divisor" form, as a u32 for convenient frequency math
98 #[inline(always)]
99 pub const fn into_divisor(self) -> u32 {
100 self.0 as u32 + 1
101 }
102}
103
104/// A basic type that always returns an error when `post_enable_config` is called.
105///
106/// Should only be used as a placeholder.
107pub struct UnimplementedConfig;
108
109impl SPConfHelper for UnimplementedConfig {
110 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
111 Err(ClockError::UnimplementedConfig)
112 }
113}
114
115/// A basic type that always returns `Ok(0)` when `post_enable_config` is called.
116///
117/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn`
118/// peripherals, which have no selectable/configurable source clock.
119pub struct NoConfig;
120impl SPConfHelper for NoConfig {
121 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
122 Ok(0)
123 }
124}
125
126//
127// LPUart
128//
129
130/// Selectable clocks for Lpuart peripherals
131#[derive(Debug, Clone, Copy)]
132pub enum LpuartClockSel {
133 /// FRO12M/FRO_LF/SIRC clock source, passed through divider
134 /// "fro_lf_div"
135 FroLfDiv,
136 /// FRO180M/FRO_HF/FIRC clock source, passed through divider
137 /// "fro_hf_div"
138 FroHfDiv,
139 /// SOSC/XTAL/EXTAL clock source
140 ClkIn,
141 /// FRO16K/clk_16k source
142 Clk16K,
143 /// clk_1m/FRO_LF divided by 12
144 Clk1M,
145 /// Output of PLL1, passed through clock divider,
146 /// "pll1_clk_div", maybe "pll1_lf_div"?
147 Pll1ClkDiv,
148 /// Disabled
149 None,
150}
151
152/// Which instance of the Lpuart is this?
153///
154/// Should not be directly selectable by end-users.
155#[derive(Copy, Clone, Debug, PartialEq, Eq)]
156pub enum LpuartInstance {
157 /// Instance 0
158 Lpuart0,
159 /// Instance 1
160 Lpuart1,
161 /// Instance 2
162 Lpuart2,
163 /// Instance 3
164 Lpuart3,
165 /// Instance 4
166 Lpuart4,
167 /// Instance 5
168 Lpuart5,
169}
170
171/// Top level configuration for `Lpuart` instances.
172pub struct LpuartConfig {
173 /// Power state required for this peripheral
174 pub power: PoweredClock,
175 /// Clock source
176 pub source: LpuartClockSel,
177 /// Clock divisor
178 pub div: Div4,
179 /// Which instance is this?
180 // NOTE: should not be user settable
181 pub(crate) instance: LpuartInstance,
182}
183
184impl SPConfHelper for LpuartConfig {
185 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
186 // check that source is suitable
187 let mrcc0 = unsafe { pac::Mrcc0::steal() };
188 use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux;
189
190 let (clkdiv, clksel) = match self.instance {
191 LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()),
192 LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()),
193 LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()),
194 LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()),
195 LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()),
196 LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()),
197 };
198
199 let (freq, variant) = match self.source {
200 LpuartClockSel::FroLfDiv => {
201 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
202 (freq, Mux::ClkrootFunc0)
203 }
204 LpuartClockSel::FroHfDiv => {
205 let freq = clocks.ensure_fro_hf_div_active(&self.power)?;
206 (freq, Mux::ClkrootFunc2)
207 }
208 LpuartClockSel::ClkIn => {
209 let freq = clocks.ensure_clk_in_active(&self.power)?;
210 (freq, Mux::ClkrootFunc3)
211 }
212 LpuartClockSel::Clk16K => {
213 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
214 (freq, Mux::ClkrootFunc4)
215 }
216 LpuartClockSel::Clk1M => {
217 let freq = clocks.ensure_clk_1m_active(&self.power)?;
218 (freq, Mux::ClkrootFunc5)
219 }
220 LpuartClockSel::Pll1ClkDiv => {
221 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
222 (freq, Mux::ClkrootFunc6)
223 }
224 LpuartClockSel::None => unsafe {
225 // no ClkrootFunc7, just write manually for now
226 clksel.write(|w| w.bits(0b111));
227 clkdiv.modify(|_r, w| {
228 w.reset().on();
229 w.halt().on();
230 w
231 });
232 return Ok(0);
233 },
234 };
235
236 // set clksel
237 clksel.modify(|_r, w| w.mux().variant(variant));
238
239 // Set up clkdiv
240 clkdiv.modify(|_r, w| {
241 w.halt().on();
242 w.reset().on();
243 w
244 });
245 clkdiv.modify(|_r, w| {
246 w.halt().off();
247 w.reset().off();
248 unsafe { w.div().bits(self.div.into_bits()) };
249 w
250 });
251
252 while clkdiv.read().unstab().is_on() {}
253
254 Ok(freq / self.div.into_divisor())
255 }
256}
257
258//
259// OSTimer
260//
261
262/// Selectable clocks for the OSTimer peripheral
263#[derive(Copy, Clone, Debug, PartialEq, Eq)]
264pub enum OstimerClockSel {
265 /// 16k clock, sourced from FRO16K (Vdd Core)
266 Clk16kVddCore,
267 /// 1 MHz Clock sourced from FRO12M
268 Clk1M,
269 /// Disabled
270 None,
271}
272
273/// Top level configuration for the `OSTimer` peripheral
274pub struct OsTimerConfig {
275 /// Power state required for this peripheral
276 pub power: PoweredClock,
277 /// Selected clock source for this peripheral
278 pub source: OstimerClockSel,
279}
280
281impl SPConfHelper for OsTimerConfig {
282 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
283 let mrcc0 = unsafe { pac::Mrcc0::steal() };
284 Ok(match self.source {
285 OstimerClockSel::Clk16kVddCore => {
286 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
287 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k());
288 freq
289 }
290 OstimerClockSel::Clk1M => {
291 let freq = clocks.ensure_clk_1m_active(&self.power)?;
292 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m());
293 freq
294 }
295 OstimerClockSel::None => {
296 mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) });
297 0
298 }
299 })
300 }
301}
302
303//
304// Adc
305//
306
307/// Selectable clocks for the ADC peripheral
308#[derive(Copy, Clone, Debug, PartialEq, Eq)]
309pub enum AdcClockSel {
310 /// Divided `fro_lf`/`clk_12m`/FRO12M source
311 FroLfDiv,
312 /// Gated `fro_hf`/`FRO180M` source
313 FroHf,
314 /// External Clock Source
315 ClkIn,
316 /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m`
317 Clk1M,
318 /// Internal PLL output, with configurable divisor
319 Pll1ClkDiv,
320 /// No clock/disabled
321 None,
322}
323
324/// Top level configuration for the ADC peripheral
325pub struct AdcConfig {
326 /// Power state required for this peripheral
327 pub power: PoweredClock,
328 /// Selected clock-source for this peripheral
329 pub source: AdcClockSel,
330 /// Pre-divisor, applied to the upstream clock output
331 pub div: Div4,
332}
333
334impl SPConfHelper for AdcConfig {
335 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
336 use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux;
337 let mrcc0 = unsafe { pac::Mrcc0::steal() };
338 let (freq, variant) = match self.source {
339 AdcClockSel::FroLfDiv => {
340 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
341 (freq, Mux::ClkrootFunc0)
342 }
343 AdcClockSel::FroHf => {
344 let freq = clocks.ensure_fro_hf_active(&self.power)?;
345 (freq, Mux::ClkrootFunc1)
346 }
347 AdcClockSel::ClkIn => {
348 let freq = clocks.ensure_clk_in_active(&self.power)?;
349 (freq, Mux::ClkrootFunc3)
350 }
351 AdcClockSel::Clk1M => {
352 let freq = clocks.ensure_clk_1m_active(&self.power)?;
353 (freq, Mux::ClkrootFunc5)
354 }
355 AdcClockSel::Pll1ClkDiv => {
356 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
357 (freq, Mux::ClkrootFunc6)
358 }
359 AdcClockSel::None => {
360 mrcc0.mrcc_adc_clksel().write(|w| unsafe {
361 // no ClkrootFunc7, just write manually for now
362 w.mux().bits(0b111)
363 });
364 mrcc0.mrcc_adc_clkdiv().modify(|_r, w| {
365 w.reset().on();
366 w.halt().on();
367 w
368 });
369 return Ok(0);
370 }
371 };
372
373 // set clksel
374 mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant));
375
376 // Set up clkdiv
377 mrcc0.mrcc_adc_clkdiv().modify(|_r, w| {
378 w.halt().on();
379 w.reset().on();
380 w
381 });
382 mrcc0.mrcc_adc_clkdiv().modify(|_r, w| {
383 w.halt().off();
384 w.reset().off();
385 unsafe { w.div().bits(self.div.into_bits()) };
386 w
387 });
388
389 while mrcc0.mrcc_adc_clkdiv().read().unstab().is_on() {}
390
391 Ok(freq / self.div.into_divisor())
392 }
393}