aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/clocks/mod.rs
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-12-05 14:28:47 +0100
committerJames Munns <[email protected]>2025-12-05 14:28:47 +0100
commitb252db845e19603faf528cf93fe0c44757a27430 (patch)
tree99e646d17bed747df244dd607a15f5a67baa530a /embassy-mcxa/src/clocks/mod.rs
parent6a1eed83b9df8ffa81b93860f530f5bb3252d996 (diff)
Move
Diffstat (limited to 'embassy-mcxa/src/clocks/mod.rs')
-rw-r--r--embassy-mcxa/src/clocks/mod.rs950
1 files changed, 950 insertions, 0 deletions
diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs
new file mode 100644
index 000000000..ac30115f6
--- /dev/null
+++ b/embassy-mcxa/src/clocks/mod.rs
@@ -0,0 +1,950 @@
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 let input = operator.clocks.fro_hf_root.clone().unwrap();
95 operator.clocks.main_clk = Some(input.clone());
96 // We can also assume cpu/system clk == fro_hf because div is /1.
97 assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0);
98 operator.clocks.cpu_system_clk = Some(input);
99
100 critical_section::with(|cs| {
101 let mut clks = CLOCKS.borrow_ref_mut(cs);
102 assert!(clks.is_none(), "Clock setup race!");
103 *clks = Some(clocks);
104 });
105
106 Ok(())
107}
108
109/// Obtain the full clocks structure, calling the given closure in a critical section.
110///
111/// The given closure will be called with read-only access to the state of the system
112/// clocks. This can be used to query and return the state of a given clock.
113///
114/// As the caller's closure will be called in a critical section, care must be taken
115/// not to block or cause any other undue delays while accessing.
116///
117/// Calls to this function will not succeed until after a successful call to `init()`,
118/// and will always return None.
119pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> {
120 critical_section::with(|cs| {
121 let c = CLOCKS.borrow_ref(cs);
122 let c = c.as_ref()?;
123 Some(f(c))
124 })
125}
126
127//
128// Structs/Enums
129//
130
131/// The `Clocks` structure contains the initialized state of the core system clocks
132///
133/// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function
134/// at boot time.
135#[derive(Default, Debug, Clone)]
136#[non_exhaustive]
137pub struct Clocks {
138 /// The `clk_in` is a clock provided by an external oscillator
139 pub clk_in: Option<Clock>,
140
141 // FRO180M stuff
142 //
143 /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator
144 ///
145 /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`,
146 /// and `fro_hf_div`.
147 pub fro_hf_root: Option<Clock>,
148
149 /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate.
150 pub fro_hf: Option<Clock>,
151
152 /// `clk_45` is a 45MHz clock, sourced from `fro_hf`.
153 pub clk_45m: Option<Clock>,
154
155 /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`.
156 pub fro_hf_div: Option<Clock>,
157
158 //
159 // End FRO180M
160
161 // FRO12M stuff
162 //
163 /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator
164 ///
165 /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`,
166 /// `and `fro_lf_div`.
167 pub fro_12m_root: Option<Clock>,
168
169 /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate.
170 pub fro_12m: Option<Clock>,
171
172 /// `clk_1m` is a 1MHz clock, sourced from `fro_12m`
173 pub clk_1m: Option<Clock>,
174
175 /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m`
176 pub fro_lf_div: Option<Clock>,
177 //
178 // End FRO12M stuff
179 /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator.
180 ///
181 /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in
182 /// the system domain, such as the CMP and RTC.
183 pub clk_16k_vsys: Option<Clock>,
184
185 /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator.
186 ///
187 /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in
188 /// the VDD Core domain, such as the OSTimer or LPUarts.
189 pub clk_16k_vdd_core: Option<Clock>,
190
191 /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some
192 /// peripherals.
193 pub main_clk: Option<Clock>,
194
195 /// `CPU_CLK` or `SYSTEM_CLK` is the output of `main_clk`, run through the `AHBCLKDIV`
196 pub cpu_system_clk: Option<Clock>,
197
198 /// `pll1_clk` is the output of the main system PLL, `pll1`.
199 pub pll1_clk: Option<Clock>,
200}
201
202/// `ClockError` is the main error returned when configuring or checking clock state
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204#[cfg_attr(feature = "defmt", derive(defmt::Format))]
205#[non_exhaustive]
206pub enum ClockError {
207 /// The system clocks were never initialized by calling [`init()`]
208 NeverInitialized,
209 /// The [`init()`] function was called more than once
210 AlreadyInitialized,
211 /// The requested configuration was not possible to fulfill, as the system clocks
212 /// were not configured in a compatible way
213 BadConfig { clock: &'static str, reason: &'static str },
214 /// The requested configuration was not possible to fulfill, as the required system
215 /// clocks have not yet been implemented.
216 NotImplemented { clock: &'static str },
217 /// The requested peripheral could not be configured, as the steps necessary to
218 /// enable it have not yet been implemented.
219 UnimplementedConfig,
220}
221
222/// Information regarding a system clock
223#[derive(Debug, Clone)]
224pub struct Clock {
225 /// The frequency, in Hz, of the given clock
226 pub frequency: u32,
227 /// The power state of the clock, e.g. whether it is active in deep sleep mode
228 /// or not.
229 pub power: PoweredClock,
230}
231
232/// The power state of a given clock.
233///
234/// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep
235/// mode will be stopped. This means that any downstream usage, e.g. by peripherals,
236/// will also stop.
237///
238/// In the future, we will provide an API for entering Deep Sleep, and if there are
239/// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into
240/// Deep Sleep will be prevented, in order to avoid misbehaving peripherals.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub enum PoweredClock {
243 /// The given clock will NOT continue running in Deep Sleep mode
244 NormalEnabledDeepSleepDisabled,
245 /// The given clock WILL continue running in Deep Sleep mode
246 AlwaysEnabled,
247}
248
249/// The ClockOperator is a private helper type that contains the methods used
250/// during system clock initialization.
251///
252/// # SAFETY
253///
254/// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`,
255/// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function.
256struct ClockOperator<'a> {
257 /// A mutable reference to the current state of system clocks
258 clocks: &'a mut Clocks,
259 /// A reference to the requested configuration provided by the caller of [`init()`]
260 config: &'a ClocksConfig,
261
262 // We hold on to stolen peripherals
263 _mrcc0: pac::Mrcc0,
264 scg0: pac::Scg0,
265 syscon: pac::Syscon,
266 vbat0: pac::Vbat0,
267}
268
269/// Trait describing an AHB clock gate that can be toggled through MRCC.
270pub trait Gate {
271 type MrccPeriphConfig: SPConfHelper;
272
273 /// Enable the clock gate.
274 ///
275 /// # SAFETY
276 ///
277 /// The current peripheral must be disabled prior to calling this method
278 unsafe fn enable_clock();
279
280 /// Disable the clock gate.
281 ///
282 /// # SAFETY
283 ///
284 /// There must be no active user of this peripheral when calling this method
285 unsafe fn disable_clock();
286
287 /// Drive the peripheral into reset.
288 ///
289 /// # SAFETY
290 ///
291 /// There must be no active user of this peripheral when calling this method
292 unsafe fn assert_reset();
293
294 /// Drive the peripheral out of reset.
295 ///
296 /// # SAFETY
297 ///
298 /// There must be no active user of this peripheral when calling this method
299 unsafe fn release_reset();
300
301 /// Return whether the clock gate for this peripheral is currently enabled.
302 fn is_clock_enabled() -> bool;
303
304 /// Return whether the peripheral is currently held in reset.
305 fn is_reset_released() -> bool;
306}
307
308/// This is the primary helper method HAL drivers are expected to call when creating
309/// an instance of the peripheral.
310///
311/// This method:
312///
313/// 1. Enables the MRCC clock gate for this peripheral
314/// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error
315/// and re-disabling the peripheral if this fails.
316/// 3. Pulses the MRCC reset line, to reset the peripheral to the default state
317/// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account
318/// the selected upstream clock, as well as any division specified by `cfg`.
319///
320/// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method
321/// may return `Ok(0)`. In the future, this might be updated to return the correct
322/// "ambient" clock, e.g. the AHB/APB frequency.
323///
324/// # SAFETY
325///
326/// This peripheral must not yet be in use prior to calling `enable_and_reset`.
327#[inline]
328pub unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> {
329 let freq = enable::<G>(cfg).inspect_err(|_| disable::<G>())?;
330 pulse_reset::<G>();
331 Ok(freq)
332}
333
334/// Enable the clock gate for the given peripheral.
335///
336/// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need
337/// to control the duration of the pulse more directly.
338///
339/// # SAFETY
340///
341/// This peripheral must not yet be in use prior to calling `enable`.
342#[inline]
343pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> {
344 G::enable_clock();
345 while !G::is_clock_enabled() {}
346 core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags));
347
348 let freq = critical_section::with(|cs| {
349 let clocks = CLOCKS.borrow_ref(cs);
350 let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?;
351 cfg.post_enable_config(clocks)
352 });
353
354 freq.inspect_err(|_e| {
355 G::disable_clock();
356 })
357}
358
359/// Disable the clock gate for the given peripheral.
360///
361/// # SAFETY
362///
363/// This peripheral must no longer be in use prior to calling `enable`.
364#[allow(dead_code)]
365#[inline]
366pub unsafe fn disable<G: Gate>() {
367 G::disable_clock();
368}
369
370/// Check whether a gate is currently enabled.
371#[allow(dead_code)]
372#[inline]
373pub fn is_clock_enabled<G: Gate>() -> bool {
374 G::is_clock_enabled()
375}
376
377/// Release a reset line for the given peripheral set.
378///
379/// Prefer [`enable_and_reset`].
380///
381/// # SAFETY
382///
383/// This peripheral must not yet be in use prior to calling `release_reset`.
384#[inline]
385pub unsafe fn release_reset<G: Gate>() {
386 G::release_reset();
387}
388
389/// Assert a reset line for the given peripheral set.
390///
391/// Prefer [`enable_and_reset`].
392///
393/// # SAFETY
394///
395/// This peripheral must not yet be in use prior to calling `assert_reset`.
396#[inline]
397pub unsafe fn assert_reset<G: Gate>() {
398 G::assert_reset();
399}
400
401/// Check whether the peripheral is held in reset.
402///
403/// # Safety
404///
405/// Must be called with a valid peripheral gate type.
406#[inline]
407pub unsafe fn is_reset_released<G: Gate>() -> bool {
408 G::is_reset_released()
409}
410
411/// Pulse a reset line (assert then release) with a short delay.
412///
413/// Prefer [`enable_and_reset`].
414///
415/// # SAFETY
416///
417/// This peripheral must not yet be in use prior to calling `release_reset`.
418#[inline]
419pub unsafe fn pulse_reset<G: Gate>() {
420 G::assert_reset();
421 cortex_m::asm::nop();
422 cortex_m::asm::nop();
423 G::release_reset();
424}
425
426//
427// `impl`s for structs/enums
428//
429
430/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active".
431///
432/// These methods are intended to be used by HAL peripheral implementors to ensure that their
433/// selected clocks are active at a suitable level at time of construction. These methods
434/// return the frequency of the requested clock, in Hertz, or a [`ClockError`].
435impl Clocks {
436 /// Ensure the `fro_lf_div` clock is active and valid at the given power state.
437 pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
438 let Some(clk) = self.fro_lf_div.as_ref() else {
439 return Err(ClockError::BadConfig {
440 clock: "fro_lf_div",
441 reason: "required but not active",
442 });
443 };
444 if !clk.power.meets_requirement_of(at_level) {
445 return Err(ClockError::BadConfig {
446 clock: "fro_lf_div",
447 reason: "not low power active",
448 });
449 }
450 Ok(clk.frequency)
451 }
452
453 /// Ensure the `fro_hf` clock is active and valid at the given power state.
454 pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
455 let Some(clk) = self.fro_hf.as_ref() else {
456 return Err(ClockError::BadConfig {
457 clock: "fro_hf",
458 reason: "required but not active",
459 });
460 };
461 if !clk.power.meets_requirement_of(at_level) {
462 return Err(ClockError::BadConfig {
463 clock: "fro_hf",
464 reason: "not low power active",
465 });
466 }
467 Ok(clk.frequency)
468 }
469
470 /// Ensure the `fro_hf_div` clock is active and valid at the given power state.
471 pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
472 let Some(clk) = self.fro_hf_div.as_ref() else {
473 return Err(ClockError::BadConfig {
474 clock: "fro_hf_div",
475 reason: "required but not active",
476 });
477 };
478 if !clk.power.meets_requirement_of(at_level) {
479 return Err(ClockError::BadConfig {
480 clock: "fro_hf_div",
481 reason: "not low power active",
482 });
483 }
484 Ok(clk.frequency)
485 }
486
487 /// Ensure the `clk_in` clock is active and valid at the given power state.
488 pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
489 Err(ClockError::NotImplemented { clock: "clk_in" })
490 }
491
492 /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state.
493 pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
494 // NOTE: clk_16k is always active in low power mode
495 Ok(self
496 .clk_16k_vsys
497 .as_ref()
498 .ok_or(ClockError::BadConfig {
499 clock: "clk_16k_vsys",
500 reason: "required but not active",
501 })?
502 .frequency)
503 }
504
505 /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state.
506 pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
507 // NOTE: clk_16k is always active in low power mode
508 Ok(self
509 .clk_16k_vdd_core
510 .as_ref()
511 .ok_or(ClockError::BadConfig {
512 clock: "clk_16k_vdd_core",
513 reason: "required but not active",
514 })?
515 .frequency)
516 }
517
518 /// Ensure the `clk_1m` clock is active and valid at the given power state.
519 pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
520 let Some(clk) = self.clk_1m.as_ref() else {
521 return Err(ClockError::BadConfig {
522 clock: "clk_1m",
523 reason: "required but not active",
524 });
525 };
526 if !clk.power.meets_requirement_of(at_level) {
527 return Err(ClockError::BadConfig {
528 clock: "clk_1m",
529 reason: "not low power active",
530 });
531 }
532 Ok(clk.frequency)
533 }
534
535 /// Ensure the `pll1_clk` clock is active and valid at the given power state.
536 pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
537 Err(ClockError::NotImplemented { clock: "pll1_clk" })
538 }
539
540 /// Ensure the `pll1_clk_div` clock is active and valid at the given power state.
541 pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
542 Err(ClockError::NotImplemented { clock: "pll1_clk_div" })
543 }
544
545 /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active
546 pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
547 let Some(clk) = self.cpu_system_clk.as_ref() else {
548 return Err(ClockError::BadConfig {
549 clock: "cpu_system_clk",
550 reason: "required but not active",
551 });
552 };
553 // Can the main_clk ever be active in deep sleep? I think it is gated?
554 match at_level {
555 PoweredClock::NormalEnabledDeepSleepDisabled => {}
556 PoweredClock::AlwaysEnabled => {
557 return Err(ClockError::BadConfig {
558 clock: "main_clk",
559 reason: "not low power active",
560 });
561 }
562 }
563
564 Ok(clk.frequency)
565 }
566
567 pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
568 let freq = self.ensure_cpu_system_clk_active(at_level)?;
569
570 Ok(freq / 6)
571 }
572}
573
574impl PoweredClock {
575 /// Does THIS clock meet the power requirements of the OTHER clock?
576 pub fn meets_requirement_of(&self, other: &Self) -> bool {
577 match (self, other) {
578 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false,
579 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
580 (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
581 (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true,
582 }
583 }
584}
585
586impl ClockOperator<'_> {
587 /// Configure the FIRC/FRO180M clock family
588 ///
589 /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used
590 /// as the main clock used for the CPU, AHB, APB, etc.
591 fn configure_firc_clocks(&mut self) -> Result<(), ClockError> {
592 const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig {
593 clock: "firc",
594 reason: "For now, FIRC must be enabled and in default state!",
595 });
596
597 // Did the user give us a FIRC config?
598 let Some(firc) = self.config.firc.as_ref() else {
599 return HARDCODED_ERR;
600 };
601 // Is the FIRC set to 45MHz (should be reset default)
602 if !matches!(firc.frequency, FircFreqSel::Mhz45) {
603 return HARDCODED_ERR;
604 }
605 let base_freq = 45_000_000;
606
607 // Now, check if the FIRC as expected for our hardcoded value
608 let mut firc_ok = true;
609
610 // Is the hardware currently set to the default 45MHz?
611 //
612 // NOTE: the SVD currently has the wrong(?) values for these:
613 // 45 -> 48
614 // 60 -> 64
615 // 90 -> 96
616 // 180 -> 192
617 // Probably correct-ish, but for a different trim value?
618 firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s();
619
620 // Check some values in the CSR
621 let csr = self.scg0.firccsr().read();
622 // Is it enabled?
623 firc_ok &= csr.fircen().is_enabled();
624 // Is it accurate?
625 firc_ok &= csr.fircacc().is_enabled_and_valid();
626 // Is there no error?
627 firc_ok &= csr.fircerr().is_error_not_detected();
628 // Is the FIRC the system clock?
629 firc_ok &= csr.fircsel().is_firc();
630 // Is it valid?
631 firc_ok &= csr.fircvld().is_enabled_and_valid();
632
633 // Are we happy with the current (hardcoded) state?
634 if !firc_ok {
635 return HARDCODED_ERR;
636 }
637
638 // Note that the fro_hf_root is active
639 self.clocks.fro_hf_root = Some(Clock {
640 frequency: base_freq,
641 power: firc.power,
642 });
643
644 // Okay! Now we're past that, let's enable all the downstream clocks.
645 let FircConfig {
646 frequency: _,
647 power,
648 fro_hf_enabled,
649 clk_45m_enabled,
650 fro_hf_div,
651 } = firc;
652
653 // When is the FRO enabled?
654 let pow_set = match power {
655 PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes,
656 PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes,
657 };
658
659 // Do we enable the `fro_hf` output?
660 let fro_hf_set = if *fro_hf_enabled {
661 self.clocks.fro_hf = Some(Clock {
662 frequency: base_freq,
663 power: *power,
664 });
665 FircFclkPeriphEn::Enabled
666 } else {
667 FircFclkPeriphEn::Disabled
668 };
669
670 // Do we enable the `clk_45m` output?
671 let clk_45m_set = if *clk_45m_enabled {
672 self.clocks.clk_45m = Some(Clock {
673 frequency: 45_000_000,
674 power: *power,
675 });
676 FircSclkPeriphEn::Enabled
677 } else {
678 FircSclkPeriphEn::Disabled
679 };
680
681 self.scg0.firccsr().modify(|_r, w| {
682 w.fircsten().variant(pow_set);
683 w.firc_fclk_periph_en().variant(fro_hf_set);
684 w.firc_sclk_periph_en().variant(clk_45m_set);
685 w
686 });
687
688 // Do we enable the `fro_hf_div` output?
689 if let Some(d) = fro_hf_div.as_ref() {
690 // We need `fro_hf` to be enabled
691 if !*fro_hf_enabled {
692 return Err(ClockError::BadConfig {
693 clock: "fro_hf_div",
694 reason: "fro_hf not enabled",
695 });
696 }
697
698 // Halt and reset the div; then set our desired div.
699 self.syscon.frohfdiv().write(|w| {
700 w.halt().halt();
701 w.reset().asserted();
702 unsafe { w.div().bits(d.into_bits()) };
703 w
704 });
705 // Then unhalt it, and reset it
706 self.syscon.frohfdiv().write(|w| {
707 w.halt().run();
708 w.reset().released();
709 w
710 });
711
712 // Wait for clock to stabilize
713 while self.syscon.frohfdiv().read().unstab().is_ongoing() {}
714
715 // Store off the clock info
716 self.clocks.fro_hf_div = Some(Clock {
717 frequency: base_freq / d.into_divisor(),
718 power: *power,
719 });
720 }
721
722 Ok(())
723 }
724
725 /// Configure the SIRC/FRO12M clock family
726 fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> {
727 let SircConfig {
728 power,
729 fro_12m_enabled,
730 fro_lf_div,
731 } = &self.config.sirc;
732 let base_freq = 12_000_000;
733
734 // Allow writes
735 self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled());
736 self.clocks.fro_12m_root = Some(Clock {
737 frequency: base_freq,
738 power: *power,
739 });
740
741 let deep = match power {
742 PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled,
743 PoweredClock::AlwaysEnabled => Sircsten::Enabled,
744 };
745 let pclk = if *fro_12m_enabled {
746 self.clocks.fro_12m = Some(Clock {
747 frequency: base_freq,
748 power: *power,
749 });
750 self.clocks.clk_1m = Some(Clock {
751 frequency: base_freq / 12,
752 power: *power,
753 });
754 SircClkPeriphEn::Enabled
755 } else {
756 SircClkPeriphEn::Disabled
757 };
758
759 // Set sleep/peripheral usage
760 self.scg0.sirccsr().modify(|_r, w| {
761 w.sircsten().variant(deep);
762 w.sirc_clk_periph_en().variant(pclk);
763 w
764 });
765
766 while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {}
767 if self.scg0.sirccsr().read().sircerr().is_error_detected() {
768 return Err(ClockError::BadConfig {
769 clock: "sirc",
770 reason: "error set",
771 });
772 }
773
774 // reset lock
775 self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled());
776
777 // Do we enable the `fro_lf_div` output?
778 if let Some(d) = fro_lf_div.as_ref() {
779 // We need `fro_lf` to be enabled
780 if !*fro_12m_enabled {
781 return Err(ClockError::BadConfig {
782 clock: "fro_lf_div",
783 reason: "fro_12m not enabled",
784 });
785 }
786
787 // Halt and reset the div; then set our desired div.
788 self.syscon.frolfdiv().write(|w| {
789 w.halt().halt();
790 w.reset().asserted();
791 unsafe { w.div().bits(d.into_bits()) };
792 w
793 });
794 // Then unhalt it, and reset it
795 self.syscon.frolfdiv().modify(|_r, w| {
796 w.halt().run();
797 w.reset().released();
798 w
799 });
800
801 // Wait for clock to stabilize
802 while self.syscon.frolfdiv().read().unstab().is_ongoing() {}
803
804 // Store off the clock info
805 self.clocks.fro_lf_div = Some(Clock {
806 frequency: base_freq / d.into_divisor(),
807 power: *power,
808 });
809 }
810
811 Ok(())
812 }
813
814 /// Configure the FRO16K/clk_16k clock family
815 fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> {
816 let Some(fro16k) = self.config.fro16k.as_ref() else {
817 return Ok(());
818 };
819 // Enable FRO16K oscillator
820 self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit());
821
822 // Lock the control register
823 self.vbat0.frolcka().modify(|_, w| w.lock().set_bit());
824
825 let Fro16KConfig {
826 vsys_domain_active,
827 vdd_core_domain_active,
828 } = fro16k;
829
830 // Enable clock outputs to both VSYS and VDD_CORE domains
831 // Bit 0: clk_16k0 to VSYS domain
832 // Bit 1: clk_16k1 to VDD_CORE domain
833 //
834 // TODO: Define sub-fields for this register with a PAC patch?
835 let mut bits = 0;
836 if *vsys_domain_active {
837 bits |= 0b01;
838 self.clocks.clk_16k_vsys = Some(Clock {
839 frequency: 16_384,
840 power: PoweredClock::AlwaysEnabled,
841 });
842 }
843 if *vdd_core_domain_active {
844 bits |= 0b10;
845 self.clocks.clk_16k_vdd_core = Some(Clock {
846 frequency: 16_384,
847 power: PoweredClock::AlwaysEnabled,
848 });
849 }
850 self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) });
851
852 Ok(())
853 }
854}
855
856//
857// Macros/macro impls
858//
859
860/// This macro is used to implement the [`Gate`] trait for a given peripheral
861/// that is controlled by the MRCC peripheral.
862macro_rules! impl_cc_gate {
863 ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => {
864 impl Gate for crate::peripherals::$name {
865 type MrccPeriphConfig = $config;
866
867 #[inline]
868 unsafe fn enable_clock() {
869 let mrcc = unsafe { pac::Mrcc0::steal() };
870 mrcc.$clk_reg().modify(|_, w| w.$field().enabled());
871 }
872
873 #[inline]
874 unsafe fn disable_clock() {
875 let mrcc = unsafe { pac::Mrcc0::steal() };
876 mrcc.$clk_reg().modify(|_r, w| w.$field().disabled());
877 }
878
879 #[inline]
880 fn is_clock_enabled() -> bool {
881 let mrcc = unsafe { pac::Mrcc0::steal() };
882 mrcc.$clk_reg().read().$field().is_enabled()
883 }
884
885 #[inline]
886 unsafe fn release_reset() {
887 let mrcc = unsafe { pac::Mrcc0::steal() };
888 mrcc.$rst_reg().modify(|_, w| w.$field().enabled());
889 }
890
891 #[inline]
892 unsafe fn assert_reset() {
893 let mrcc = unsafe { pac::Mrcc0::steal() };
894 mrcc.$rst_reg().modify(|_, w| w.$field().disabled());
895 }
896
897 #[inline]
898 fn is_reset_released() -> bool {
899 let mrcc = unsafe { pac::Mrcc0::steal() };
900 mrcc.$rst_reg().read().$field().is_enabled()
901 }
902 }
903 };
904}
905
906/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait,
907/// for various low level peripherals.
908pub(crate) mod gate {
909 #[cfg(not(feature = "time"))]
910 use super::periph_helpers::OsTimerConfig;
911 use super::periph_helpers::{AdcConfig, Lpi2cConfig, LpuartConfig, NoConfig};
912 use super::*;
913
914 // These peripherals have no additional upstream clocks or configuration required
915 // other than enabling through the MRCC gate. Currently, these peripherals will
916 // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or
917 // [`SPConfHelper::post_enable_config()`].
918 impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig);
919 impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig);
920 impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig);
921 impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig);
922 impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig);
923
924 impl_cc_gate!(GPIO0, mrcc_glb_cc2, mrcc_glb_rst2, gpio0, NoConfig);
925 impl_cc_gate!(GPIO1, mrcc_glb_cc2, mrcc_glb_rst2, gpio1, NoConfig);
926 impl_cc_gate!(GPIO2, mrcc_glb_cc2, mrcc_glb_rst2, gpio2, NoConfig);
927 impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig);
928 impl_cc_gate!(GPIO4, mrcc_glb_cc2, mrcc_glb_rst2, gpio4, NoConfig);
929
930 // These peripherals DO have meaningful configuration, and could fail if the system
931 // clocks do not match their needs.
932 #[cfg(not(feature = "time"))]
933 impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig);
934
935 impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig);
936 impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig);
937 impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig);
938 impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig);
939
940 impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig);
941 impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig);
942 impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig);
943 impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig);
944 impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig);
945 impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig);
946 impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig);
947
948 // DMA0 peripheral - uses NoConfig since it has no selectable clock source
949 impl_cc_gate!(DMA0, mrcc_glb_cc0, mrcc_glb_rst0, dma0, NoConfig);
950}