aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/clocks
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-mcxa/src/clocks')
-rw-r--r--embassy-mcxa/src/clocks/config.rs204
-rw-r--r--embassy-mcxa/src/clocks/mod.rs957
-rw-r--r--embassy-mcxa/src/clocks/periph_helpers.rs499
3 files changed, 1660 insertions, 0 deletions
diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs
new file mode 100644
index 000000000..0563b8917
--- /dev/null
+++ b/embassy-mcxa/src/clocks/config.rs
@@ -0,0 +1,204 @@
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 /// Divide by one, or no division
25 pub const fn no_div() -> Self {
26 Self(0)
27 }
28
29 /// Store a specific divisor value that will divide the source
30 /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source
31 /// by 1, and `Div8::from_divisor(256)` will divide the source
32 /// by 256.
33 ///
34 /// Will return `None` if `n` is not in the range `1..=256`.
35 /// Consider [`Self::from_raw`] for an infallible version.
36 pub const fn from_divisor(n: u16) -> Option<Self> {
37 let Some(n) = n.checked_sub(1) else {
38 return None;
39 };
40 if n > (u8::MAX as u16) {
41 return None;
42 }
43 Some(Self(n as u8))
44 }
45
46 /// Convert into "raw" bits form
47 #[inline(always)]
48 pub const fn into_bits(self) -> u8 {
49 self.0
50 }
51
52 /// Convert into "divisor" form, as a u32 for convenient frequency math
53 #[inline(always)]
54 pub const fn into_divisor(self) -> u32 {
55 self.0 as u32 + 1
56 }
57}
58
59/// ```text
60/// ┌─────────────────────────────────────────────────────────┐
61/// │ │
62/// │ ┌───────────┐ clk_out ┌─────────┐ │
63/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │
64/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷
65/// EXTAL ──────┼──▷│ │───────────▷│ │ │
66/// │ └───────────┘ └─────────┘ │
67/// │ │
68/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │
69/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷
70/// │ │ │ │ ├────┤ clk_45m │
71/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷
72/// │ └───────────┘ └────┘ │
73/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │
74/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷
75/// │ │ │ │ ├────┤ clk_1m │
76/// │ │ │ └─────▷│1/12│────────────────────┼──────▷
77/// │ └───────────┘ └────┘ │
78/// │ │
79/// │ ┌──────────┐ │
80/// │ │000 │ │
81/// │ clk_in │ │ │
82/// │ ───────────────▷│001 │ │
83/// │ fro_12m │ │ │
84/// │ ───────────────▷│010 │ │
85/// │ fro_hf_root │ │ │
86/// │ ───────────────▷│011 │ main_clk │
87/// │ │ │───────────────────────────┼──────▷
88/// clk_16k ──────┼─────────────────▷│100 │ │
89/// │ none │ │ │
90/// │ ───────────────▷│101 │ │
91/// │ pll1_clk │ │ │
92/// │ ───────────────▷│110 │ │
93/// │ none │ │ │
94/// │ ───────────────▷│111 │ │
95/// │ └──────────┘ │
96/// │ ▲ │
97/// │ │ │
98/// │ SCG SCS │
99/// │ SCG-Lite │
100/// └─────────────────────────────────────────────────────────┘
101///
102///
103/// clk_in ┌─────┐
104/// ───────────────▷│00 │
105/// clk_45m │ │
106/// ───────────────▷│01 │ ┌───────────┐ pll1_clk
107/// none │ │─────▷│ SPLL │───────────────▷
108/// ───────────────▷│10 │ └───────────┘
109/// fro_12m │ │
110/// ───────────────▷│11 │
111/// └─────┘
112/// ```
113#[non_exhaustive]
114pub struct ClocksConfig {
115 /// FIRC, FRO180, 45/60/90/180M clock source
116 pub firc: Option<FircConfig>,
117 /// SIRC, FRO12M, clk_12m clock source
118 // NOTE: I don't think we *can* disable the SIRC?
119 pub sirc: SircConfig,
120 /// FRO16K clock source
121 pub fro16k: Option<Fro16KConfig>,
122}
123
124// FIRC/FRO180M
125
126/// ```text
127/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf
128/// │ FRO180M ├───────┬─────▷│GATE│──────────▷
129/// │ │ │ ├────┤ clk_45m
130/// │ │ └─────▷│GATE│──────────▷
131/// └───────────┘ └────┘
132/// ```
133#[non_exhaustive]
134pub struct FircConfig {
135 /// Selected clock frequency
136 pub frequency: FircFreqSel,
137 /// Selected power state of the clock
138 pub power: PoweredClock,
139 /// Is the "fro_hf" gated clock enabled?
140 pub fro_hf_enabled: bool,
141 /// Is the "clk_45m" gated clock enabled?
142 pub clk_45m_enabled: bool,
143 /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`!
144 pub fro_hf_div: Option<Div8>,
145}
146
147/// Selected FIRC frequency
148pub enum FircFreqSel {
149 /// 45MHz Output
150 Mhz45,
151 /// 60MHz Output
152 Mhz60,
153 /// 90MHz Output
154 Mhz90,
155 /// 180MHz Output
156 Mhz180,
157}
158
159// SIRC/FRO12M
160
161/// ```text
162/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m
163/// │ FRO12M │────────┬─────▷│ CG │──────────▷
164/// │ │ │ ├────┤ clk_1m
165/// │ │ └─────▷│1/12│──────────▷
166/// └───────────┘ └────┘
167/// ```
168#[non_exhaustive]
169pub struct SircConfig {
170 pub power: PoweredClock,
171 // peripheral output, aka sirc_12mhz
172 pub fro_12m_enabled: bool,
173 /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`!
174 pub fro_lf_div: Option<Div8>,
175}
176
177#[non_exhaustive]
178pub struct Fro16KConfig {
179 pub vsys_domain_active: bool,
180 pub vdd_core_domain_active: bool,
181}
182
183impl Default for ClocksConfig {
184 fn default() -> Self {
185 Self {
186 firc: Some(FircConfig {
187 frequency: FircFreqSel::Mhz45,
188 power: PoweredClock::NormalEnabledDeepSleepDisabled,
189 fro_hf_enabled: true,
190 clk_45m_enabled: true,
191 fro_hf_div: None,
192 }),
193 sirc: SircConfig {
194 power: PoweredClock::AlwaysEnabled,
195 fro_12m_enabled: true,
196 fro_lf_div: None,
197 },
198 fro16k: Some(Fro16KConfig {
199 vsys_domain_active: true,
200 vdd_core_domain_active: true,
201 }),
202 }
203 }
204}
diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs
new file mode 100644
index 000000000..037f0a656
--- /dev/null
+++ b/embassy-mcxa/src/clocks/mod.rs
@@ -0,0 +1,957 @@
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 impl_cc_gate!(CRC0, mrcc_glb_cc0, mrcc_glb_rst0, crc0, NoConfig);
931
932 // These peripherals DO have meaningful configuration, and could fail if the system
933 // clocks do not match their needs.
934 #[cfg(not(feature = "time"))]
935 impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig);
936
937 impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig);
938 impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig);
939 impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig);
940 impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig);
941
942 impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig);
943 impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig);
944 impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig);
945 impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig);
946 impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig);
947 impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig);
948 impl_cc_gate!(ADC0, mrcc_glb_cc1, mrcc_glb_rst1, adc0, AdcConfig);
949 impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig);
950 impl_cc_gate!(ADC2, mrcc_glb_cc1, mrcc_glb_rst1, adc2, AdcConfig);
951 impl_cc_gate!(ADC3, mrcc_glb_cc1, mrcc_glb_rst1, adc3, AdcConfig);
952
953 // DMA0 peripheral - uses NoConfig since it has no selectable clock source
954 impl_cc_gate!(DMA0, mrcc_glb_cc0, mrcc_glb_rst0, dma0, NoConfig);
955 // TRNG peripheral - uses NoConfig since it has no selectable clock source
956 impl_cc_gate!(TRNG0, mrcc_glb_cc1, mrcc_glb_rst1, trng0, NoConfig);
957}
diff --git a/embassy-mcxa/src/clocks/periph_helpers.rs b/embassy-mcxa/src/clocks/periph_helpers.rs
new file mode 100644
index 000000000..f2f51c60c
--- /dev/null
+++ b/embassy-mcxa/src/clocks/periph_helpers.rs
@@ -0,0 +1,499 @@
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/// Copy and paste macro that:
45///
46/// * Sets the clocksel mux to `$selvar`
47/// * Resets and halts the div, and applies the calculated div4 bits
48/// * Releases reset + halt
49/// * Waits for the div to stabilize
50/// * Returns `Ok($freq / $conf.div.into_divisor())`
51///
52/// Assumes:
53///
54/// * self is a configuration struct that has a field called `div`, which
55/// is a `Div4`
56///
57/// usage:
58///
59/// ```rust
60/// apply_div4!(self, clksel, clkdiv, variant, freq)
61/// ```
62///
63/// In the future if we make all the clksel+clkdiv pairs into commonly derivedFrom
64/// registers, or if we put some kind of simple trait around those regs, we could
65/// do this with something other than a macro, but for now, this is harm-reduction
66/// to avoid incorrect copy + paste
67macro_rules! apply_div4 {
68 ($conf:ident, $selreg:ident, $divreg:ident, $selvar:ident, $freq:ident) => {{
69 // set clksel
70 $selreg.modify(|_r, w| w.mux().variant($selvar));
71
72 // Set up clkdiv
73 $divreg.modify(|_r, w| {
74 unsafe { w.div().bits($conf.div.into_bits()) }
75 .halt()
76 .asserted()
77 .reset()
78 .asserted()
79 });
80 $divreg.modify(|_r, w| w.halt().deasserted().reset().deasserted());
81
82 while $divreg.read().unstab().is_unstable() {}
83
84 Ok($freq / $conf.div.into_divisor())
85 }};
86}
87
88// config types
89
90/// This type represents a divider in the range 1..=16.
91///
92/// At a hardware level, this is an 8-bit register from 0..=15,
93/// which adds one.
94///
95/// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain
96/// seems to use 4 bit dividers!
97#[derive(Copy, Clone, Debug, PartialEq, Eq)]
98pub struct Div4(pub(super) u8);
99
100impl Div4 {
101 /// Divide by one, or no division
102 pub const fn no_div() -> Self {
103 Self(0)
104 }
105
106 /// Store a "raw" divisor value that will divide the source by
107 /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source
108 /// by 1, and `Div4::from_raw(15)` will divide the source by
109 /// 16.
110 pub const fn from_raw(n: u8) -> Option<Self> {
111 if n > 0b1111 { None } else { Some(Self(n)) }
112 }
113
114 /// Store a specific divisor value that will divide the source
115 /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source
116 /// by 1, and `Div4::from_divisor(16)` will divide the source
117 /// by 16.
118 ///
119 /// Will return `None` if `n` is not in the range `1..=16`.
120 /// Consider [`Self::from_raw`] for an infallible version.
121 pub const fn from_divisor(n: u8) -> Option<Self> {
122 let Some(n) = n.checked_sub(1) else {
123 return None;
124 };
125 if n > 0b1111 {
126 return None;
127 }
128 Some(Self(n))
129 }
130
131 /// Convert into "raw" bits form
132 #[inline(always)]
133 pub const fn into_bits(self) -> u8 {
134 self.0
135 }
136
137 /// Convert into "divisor" form, as a u32 for convenient frequency math
138 #[inline(always)]
139 pub const fn into_divisor(self) -> u32 {
140 self.0 as u32 + 1
141 }
142}
143
144/// A basic type that always returns an error when `post_enable_config` is called.
145///
146/// Should only be used as a placeholder.
147pub struct UnimplementedConfig;
148
149impl SPConfHelper for UnimplementedConfig {
150 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
151 Err(ClockError::UnimplementedConfig)
152 }
153}
154
155/// A basic type that always returns `Ok(0)` when `post_enable_config` is called.
156///
157/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn`
158/// peripherals, which have no selectable/configurable source clock.
159pub struct NoConfig;
160impl SPConfHelper for NoConfig {
161 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
162 Ok(0)
163 }
164}
165
166//
167// LPI2c
168//
169
170/// Selectable clocks for `Lpi2c` peripherals
171#[derive(Debug, Clone, Copy)]
172pub enum Lpi2cClockSel {
173 /// FRO12M/FRO_LF/SIRC clock source, passed through divider
174 /// "fro_lf_div"
175 FroLfDiv,
176 /// FRO180M/FRO_HF/FIRC clock source, passed through divider
177 /// "fro_hf_div"
178 FroHfDiv,
179 /// SOSC/XTAL/EXTAL clock source
180 ClkIn,
181 /// clk_1m/FRO_LF divided by 12
182 Clk1M,
183 /// Output of PLL1, passed through clock divider,
184 /// "pll1_clk_div", maybe "pll1_lf_div"?
185 Pll1ClkDiv,
186 /// Disabled
187 None,
188}
189
190/// Which instance of the `Lpi2c` is this?
191///
192/// Should not be directly selectable by end-users.
193#[derive(Copy, Clone, Debug, PartialEq, Eq)]
194pub enum Lpi2cInstance {
195 /// Instance 0
196 Lpi2c0,
197 /// Instance 1
198 Lpi2c1,
199 /// Instance 2
200 Lpi2c2,
201 /// Instance 3
202 Lpi2c3,
203}
204
205/// Top level configuration for `Lpi2c` instances.
206pub struct Lpi2cConfig {
207 /// Power state required for this peripheral
208 pub power: PoweredClock,
209 /// Clock source
210 pub source: Lpi2cClockSel,
211 /// Clock divisor
212 pub div: Div4,
213 /// Which instance is this?
214 // NOTE: should not be user settable
215 pub(crate) instance: Lpi2cInstance,
216}
217
218impl SPConfHelper for Lpi2cConfig {
219 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
220 // check that source is suitable
221 let mrcc0 = unsafe { pac::Mrcc0::steal() };
222 use mcxa_pac::mrcc0::mrcc_lpi2c0_clksel::Mux;
223
224 let (clkdiv, clksel) = match self.instance {
225 Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()),
226 Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()),
227 Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()),
228 Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()),
229 };
230
231 let (freq, variant) = match self.source {
232 Lpi2cClockSel::FroLfDiv => {
233 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
234 (freq, Mux::ClkrootFunc0)
235 }
236 Lpi2cClockSel::FroHfDiv => {
237 let freq = clocks.ensure_fro_hf_div_active(&self.power)?;
238 (freq, Mux::ClkrootFunc2)
239 }
240 Lpi2cClockSel::ClkIn => {
241 let freq = clocks.ensure_clk_in_active(&self.power)?;
242 (freq, Mux::ClkrootFunc3)
243 }
244 Lpi2cClockSel::Clk1M => {
245 let freq = clocks.ensure_clk_1m_active(&self.power)?;
246 (freq, Mux::ClkrootFunc5)
247 }
248 Lpi2cClockSel::Pll1ClkDiv => {
249 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
250 (freq, Mux::ClkrootFunc6)
251 }
252 Lpi2cClockSel::None => unsafe {
253 // no ClkrootFunc7, just write manually for now
254 clksel.write(|w| w.bits(0b111));
255 clkdiv.modify(|_r, w| w.reset().asserted().halt().asserted());
256 return Ok(0);
257 },
258 };
259
260 apply_div4!(self, clksel, clkdiv, variant, freq)
261 }
262}
263
264//
265// LPUart
266//
267
268/// Selectable clocks for Lpuart peripherals
269#[derive(Debug, Clone, Copy)]
270pub enum LpuartClockSel {
271 /// FRO12M/FRO_LF/SIRC clock source, passed through divider
272 /// "fro_lf_div"
273 FroLfDiv,
274 /// FRO180M/FRO_HF/FIRC clock source, passed through divider
275 /// "fro_hf_div"
276 FroHfDiv,
277 /// SOSC/XTAL/EXTAL clock source
278 ClkIn,
279 /// FRO16K/clk_16k source
280 Clk16K,
281 /// clk_1m/FRO_LF divided by 12
282 Clk1M,
283 /// Output of PLL1, passed through clock divider,
284 /// "pll1_clk_div", maybe "pll1_lf_div"?
285 Pll1ClkDiv,
286 /// Disabled
287 None,
288}
289
290/// Which instance of the Lpuart is this?
291///
292/// Should not be directly selectable by end-users.
293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
294pub enum LpuartInstance {
295 /// Instance 0
296 Lpuart0,
297 /// Instance 1
298 Lpuart1,
299 /// Instance 2
300 Lpuart2,
301 /// Instance 3
302 Lpuart3,
303 /// Instance 4
304 Lpuart4,
305 /// Instance 5
306 Lpuart5,
307}
308
309/// Top level configuration for `Lpuart` instances.
310pub struct LpuartConfig {
311 /// Power state required for this peripheral
312 pub power: PoweredClock,
313 /// Clock source
314 pub source: LpuartClockSel,
315 /// Clock divisor
316 pub div: Div4,
317 /// Which instance is this?
318 // NOTE: should not be user settable
319 pub(crate) instance: LpuartInstance,
320}
321
322impl SPConfHelper for LpuartConfig {
323 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
324 // check that source is suitable
325 let mrcc0 = unsafe { pac::Mrcc0::steal() };
326 use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux;
327
328 let (clkdiv, clksel) = match self.instance {
329 LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()),
330 LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()),
331 LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()),
332 LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()),
333 LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()),
334 LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()),
335 };
336
337 let (freq, variant) = match self.source {
338 LpuartClockSel::FroLfDiv => {
339 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
340 (freq, Mux::ClkrootFunc0)
341 }
342 LpuartClockSel::FroHfDiv => {
343 let freq = clocks.ensure_fro_hf_div_active(&self.power)?;
344 (freq, Mux::ClkrootFunc2)
345 }
346 LpuartClockSel::ClkIn => {
347 let freq = clocks.ensure_clk_in_active(&self.power)?;
348 (freq, Mux::ClkrootFunc3)
349 }
350 LpuartClockSel::Clk16K => {
351 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
352 (freq, Mux::ClkrootFunc4)
353 }
354 LpuartClockSel::Clk1M => {
355 let freq = clocks.ensure_clk_1m_active(&self.power)?;
356 (freq, Mux::ClkrootFunc5)
357 }
358 LpuartClockSel::Pll1ClkDiv => {
359 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
360 (freq, Mux::ClkrootFunc6)
361 }
362 LpuartClockSel::None => unsafe {
363 // no ClkrootFunc7, just write manually for now
364 clksel.write(|w| w.bits(0b111));
365 clkdiv.modify(|_r, w| {
366 w.reset().asserted();
367 w.halt().asserted();
368 w
369 });
370 return Ok(0);
371 },
372 };
373
374 // set clksel
375 apply_div4!(self, clksel, clkdiv, variant, freq)
376 }
377}
378
379//
380// OSTimer
381//
382
383/// Selectable clocks for the OSTimer peripheral
384#[derive(Copy, Clone, Debug, PartialEq, Eq)]
385pub enum OstimerClockSel {
386 /// 16k clock, sourced from FRO16K (Vdd Core)
387 Clk16kVddCore,
388 /// 1 MHz Clock sourced from FRO12M
389 Clk1M,
390 /// Disabled
391 None,
392}
393
394/// Top level configuration for the `OSTimer` peripheral
395pub struct OsTimerConfig {
396 /// Power state required for this peripheral
397 pub power: PoweredClock,
398 /// Selected clock source for this peripheral
399 pub source: OstimerClockSel,
400}
401
402impl SPConfHelper for OsTimerConfig {
403 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
404 let mrcc0 = unsafe { pac::Mrcc0::steal() };
405 Ok(match self.source {
406 OstimerClockSel::Clk16kVddCore => {
407 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
408 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k());
409 freq
410 }
411 OstimerClockSel::Clk1M => {
412 let freq = clocks.ensure_clk_1m_active(&self.power)?;
413 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m());
414 freq
415 }
416 OstimerClockSel::None => {
417 mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) });
418 0
419 }
420 })
421 }
422}
423
424//
425// Adc
426//
427
428/// Selectable clocks for the ADC peripheral
429#[derive(Copy, Clone, Debug, PartialEq, Eq)]
430#[cfg_attr(feature = "defmt", derive(defmt::Format))]
431pub enum AdcClockSel {
432 /// Divided `fro_lf`/`clk_12m`/FRO12M source
433 FroLfDiv,
434 /// Gated `fro_hf`/`FRO180M` source
435 FroHf,
436 /// External Clock Source
437 ClkIn,
438 /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m`
439 Clk1M,
440 /// Internal PLL output, with configurable divisor
441 Pll1ClkDiv,
442 /// No clock/disabled
443 None,
444}
445
446/// Top level configuration for the ADC peripheral
447pub struct AdcConfig {
448 /// Power state required for this peripheral
449 pub power: PoweredClock,
450 /// Selected clock-source for this peripheral
451 pub source: AdcClockSel,
452 /// Pre-divisor, applied to the upstream clock output
453 pub div: Div4,
454}
455
456impl SPConfHelper for AdcConfig {
457 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
458 use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux;
459 let mrcc0 = unsafe { pac::Mrcc0::steal() };
460 let (freq, variant) = match self.source {
461 AdcClockSel::FroLfDiv => {
462 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
463 (freq, Mux::ClkrootFunc0)
464 }
465 AdcClockSel::FroHf => {
466 let freq = clocks.ensure_fro_hf_active(&self.power)?;
467 (freq, Mux::ClkrootFunc1)
468 }
469 AdcClockSel::ClkIn => {
470 let freq = clocks.ensure_clk_in_active(&self.power)?;
471 (freq, Mux::ClkrootFunc3)
472 }
473 AdcClockSel::Clk1M => {
474 let freq = clocks.ensure_clk_1m_active(&self.power)?;
475 (freq, Mux::ClkrootFunc5)
476 }
477 AdcClockSel::Pll1ClkDiv => {
478 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
479 (freq, Mux::ClkrootFunc6)
480 }
481 AdcClockSel::None => {
482 mrcc0.mrcc_adc_clksel().write(|w| unsafe {
483 // no ClkrootFunc7, just write manually for now
484 w.mux().bits(0b111)
485 });
486 mrcc0.mrcc_adc_clkdiv().modify(|_r, w| {
487 w.reset().asserted();
488 w.halt().asserted();
489 w
490 });
491 return Ok(0);
492 }
493 };
494 let clksel = mrcc0.mrcc_adc_clksel();
495 let clkdiv = mrcc0.mrcc_adc_clkdiv();
496
497 apply_div4!(self, clksel, clkdiv, variant, freq)
498 }
499}