aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/clocks
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-12-04 18:46:39 +0100
committerJames Munns <[email protected]>2025-12-04 18:47:31 +0100
commitc3bc8fe8c0db112e5f2f66580104fc49b02890d2 (patch)
treeeea7adc15021697ea980289edd6235daaa12d6ee /embassy-mcxa/src/clocks
parent5182b2ed54f991b40b45fd2de75f597358ff9c3e (diff)
parent277ab0d2e8714edf37a0ee84cda1059d9944ecef (diff)
Import embassy-mcxa repo
Merge remote-tracking branch 'james-e-mcxa/james/upstream' into james/upstream-mcxa
Diffstat (limited to 'embassy-mcxa/src/clocks')
-rw-r--r--embassy-mcxa/src/clocks/config.rs204
-rw-r--r--embassy-mcxa/src/clocks/mod.rs943
-rw-r--r--embassy-mcxa/src/clocks/periph_helpers.rs502
3 files changed, 1649 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..9c9e6ef3d
--- /dev/null
+++ b/embassy-mcxa/src/clocks/mod.rs
@@ -0,0 +1,943 @@
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#[inline]
403pub unsafe fn is_reset_released<G: Gate>() -> bool {
404 G::is_reset_released()
405}
406
407/// Pulse a reset line (assert then release) with a short delay.
408///
409/// Prefer [`enable_and_reset`].
410///
411/// # SAFETY
412///
413/// This peripheral must not yet be in use prior to calling `release_reset`.
414#[inline]
415pub unsafe fn pulse_reset<G: Gate>() {
416 G::assert_reset();
417 cortex_m::asm::nop();
418 cortex_m::asm::nop();
419 G::release_reset();
420}
421
422//
423// `impl`s for structs/enums
424//
425
426/// The [`Clocks`] type's methods generally take the form of "ensure X clock is active".
427///
428/// These methods are intended to be used by HAL peripheral implementors to ensure that their
429/// selected clocks are active at a suitable level at time of construction. These methods
430/// return the frequency of the requested clock, in Hertz, or a [`ClockError`].
431impl Clocks {
432 /// Ensure the `fro_lf_div` clock is active and valid at the given power state.
433 pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
434 let Some(clk) = self.fro_lf_div.as_ref() else {
435 return Err(ClockError::BadConfig {
436 clock: "fro_lf_div",
437 reason: "required but not active",
438 });
439 };
440 if !clk.power.meets_requirement_of(at_level) {
441 return Err(ClockError::BadConfig {
442 clock: "fro_lf_div",
443 reason: "not low power active",
444 });
445 }
446 Ok(clk.frequency)
447 }
448
449 /// Ensure the `fro_hf` clock is active and valid at the given power state.
450 pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
451 let Some(clk) = self.fro_hf.as_ref() else {
452 return Err(ClockError::BadConfig {
453 clock: "fro_hf",
454 reason: "required but not active",
455 });
456 };
457 if !clk.power.meets_requirement_of(at_level) {
458 return Err(ClockError::BadConfig {
459 clock: "fro_hf",
460 reason: "not low power active",
461 });
462 }
463 Ok(clk.frequency)
464 }
465
466 /// Ensure the `fro_hf_div` clock is active and valid at the given power state.
467 pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
468 let Some(clk) = self.fro_hf_div.as_ref() else {
469 return Err(ClockError::BadConfig {
470 clock: "fro_hf_div",
471 reason: "required but not active",
472 });
473 };
474 if !clk.power.meets_requirement_of(at_level) {
475 return Err(ClockError::BadConfig {
476 clock: "fro_hf_div",
477 reason: "not low power active",
478 });
479 }
480 Ok(clk.frequency)
481 }
482
483 /// Ensure the `clk_in` clock is active and valid at the given power state.
484 pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
485 Err(ClockError::NotImplemented { clock: "clk_in" })
486 }
487
488 /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state.
489 pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
490 // NOTE: clk_16k is always active in low power mode
491 Ok(self
492 .clk_16k_vsys
493 .as_ref()
494 .ok_or(ClockError::BadConfig {
495 clock: "clk_16k_vsys",
496 reason: "required but not active",
497 })?
498 .frequency)
499 }
500
501 /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state.
502 pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
503 // NOTE: clk_16k is always active in low power mode
504 Ok(self
505 .clk_16k_vdd_core
506 .as_ref()
507 .ok_or(ClockError::BadConfig {
508 clock: "clk_16k_vdd_core",
509 reason: "required but not active",
510 })?
511 .frequency)
512 }
513
514 /// Ensure the `clk_1m` clock is active and valid at the given power state.
515 pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
516 let Some(clk) = self.clk_1m.as_ref() else {
517 return Err(ClockError::BadConfig {
518 clock: "clk_1m",
519 reason: "required but not active",
520 });
521 };
522 if !clk.power.meets_requirement_of(at_level) {
523 return Err(ClockError::BadConfig {
524 clock: "clk_1m",
525 reason: "not low power active",
526 });
527 }
528 Ok(clk.frequency)
529 }
530
531 /// Ensure the `pll1_clk` clock is active and valid at the given power state.
532 pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
533 Err(ClockError::NotImplemented { clock: "pll1_clk" })
534 }
535
536 /// Ensure the `pll1_clk_div` clock is active and valid at the given power state.
537 pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> {
538 Err(ClockError::NotImplemented { clock: "pll1_clk_div" })
539 }
540
541 /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active
542 pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
543 let Some(clk) = self.cpu_system_clk.as_ref() else {
544 return Err(ClockError::BadConfig {
545 clock: "cpu_system_clk",
546 reason: "required but not active",
547 });
548 };
549 // Can the main_clk ever be active in deep sleep? I think it is gated?
550 match at_level {
551 PoweredClock::NormalEnabledDeepSleepDisabled => {}
552 PoweredClock::AlwaysEnabled => {
553 return Err(ClockError::BadConfig {
554 clock: "main_clk",
555 reason: "not low power active",
556 });
557 }
558 }
559
560 Ok(clk.frequency)
561 }
562
563 pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> {
564 let freq = self.ensure_cpu_system_clk_active(at_level)?;
565
566 Ok(freq / 6)
567 }
568}
569
570impl PoweredClock {
571 /// Does THIS clock meet the power requirements of the OTHER clock?
572 pub fn meets_requirement_of(&self, other: &Self) -> bool {
573 match (self, other) {
574 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false,
575 (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
576 (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true,
577 (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true,
578 }
579 }
580}
581
582impl ClockOperator<'_> {
583 /// Configure the FIRC/FRO180M clock family
584 ///
585 /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used
586 /// as the main clock used for the CPU, AHB, APB, etc.
587 fn configure_firc_clocks(&mut self) -> Result<(), ClockError> {
588 const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig {
589 clock: "firc",
590 reason: "For now, FIRC must be enabled and in default state!",
591 });
592
593 // Did the user give us a FIRC config?
594 let Some(firc) = self.config.firc.as_ref() else {
595 return HARDCODED_ERR;
596 };
597 // Is the FIRC set to 45MHz (should be reset default)
598 if !matches!(firc.frequency, FircFreqSel::Mhz45) {
599 return HARDCODED_ERR;
600 }
601 let base_freq = 45_000_000;
602
603 // Now, check if the FIRC as expected for our hardcoded value
604 let mut firc_ok = true;
605
606 // Is the hardware currently set to the default 45MHz?
607 //
608 // NOTE: the SVD currently has the wrong(?) values for these:
609 // 45 -> 48
610 // 60 -> 64
611 // 90 -> 96
612 // 180 -> 192
613 // Probably correct-ish, but for a different trim value?
614 firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s();
615
616 // Check some values in the CSR
617 let csr = self.scg0.firccsr().read();
618 // Is it enabled?
619 firc_ok &= csr.fircen().is_enabled();
620 // Is it accurate?
621 firc_ok &= csr.fircacc().is_enabled_and_valid();
622 // Is there no error?
623 firc_ok &= csr.fircerr().is_error_not_detected();
624 // Is the FIRC the system clock?
625 firc_ok &= csr.fircsel().is_firc();
626 // Is it valid?
627 firc_ok &= csr.fircvld().is_enabled_and_valid();
628
629 // Are we happy with the current (hardcoded) state?
630 if !firc_ok {
631 return HARDCODED_ERR;
632 }
633
634 // Note that the fro_hf_root is active
635 self.clocks.fro_hf_root = Some(Clock {
636 frequency: base_freq,
637 power: firc.power,
638 });
639
640 // Okay! Now we're past that, let's enable all the downstream clocks.
641 let FircConfig {
642 frequency: _,
643 power,
644 fro_hf_enabled,
645 clk_45m_enabled,
646 fro_hf_div,
647 } = firc;
648
649 // When is the FRO enabled?
650 let pow_set = match power {
651 PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes,
652 PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes,
653 };
654
655 // Do we enable the `fro_hf` output?
656 let fro_hf_set = if *fro_hf_enabled {
657 self.clocks.fro_hf = Some(Clock {
658 frequency: base_freq,
659 power: *power,
660 });
661 FircFclkPeriphEn::Enabled
662 } else {
663 FircFclkPeriphEn::Disabled
664 };
665
666 // Do we enable the `clk_45m` output?
667 let clk_45m_set = if *clk_45m_enabled {
668 self.clocks.clk_45m = Some(Clock {
669 frequency: 45_000_000,
670 power: *power,
671 });
672 FircSclkPeriphEn::Enabled
673 } else {
674 FircSclkPeriphEn::Disabled
675 };
676
677 self.scg0.firccsr().modify(|_r, w| {
678 w.fircsten().variant(pow_set);
679 w.firc_fclk_periph_en().variant(fro_hf_set);
680 w.firc_sclk_periph_en().variant(clk_45m_set);
681 w
682 });
683
684 // Do we enable the `fro_hf_div` output?
685 if let Some(d) = fro_hf_div.as_ref() {
686 // We need `fro_hf` to be enabled
687 if !*fro_hf_enabled {
688 return Err(ClockError::BadConfig {
689 clock: "fro_hf_div",
690 reason: "fro_hf not enabled",
691 });
692 }
693
694 // Halt and reset the div; then set our desired div.
695 self.syscon.frohfdiv().write(|w| {
696 w.halt().halt();
697 w.reset().asserted();
698 unsafe { w.div().bits(d.into_bits()) };
699 w
700 });
701 // Then unhalt it, and reset it
702 self.syscon.frohfdiv().write(|w| {
703 w.halt().run();
704 w.reset().released();
705 w
706 });
707
708 // Wait for clock to stabilize
709 while self.syscon.frohfdiv().read().unstab().is_ongoing() {}
710
711 // Store off the clock info
712 self.clocks.fro_hf_div = Some(Clock {
713 frequency: base_freq / d.into_divisor(),
714 power: *power,
715 });
716 }
717
718 Ok(())
719 }
720
721 /// Configure the SIRC/FRO12M clock family
722 fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> {
723 let SircConfig {
724 power,
725 fro_12m_enabled,
726 fro_lf_div,
727 } = &self.config.sirc;
728 let base_freq = 12_000_000;
729
730 // Allow writes
731 self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled());
732 self.clocks.fro_12m_root = Some(Clock {
733 frequency: base_freq,
734 power: *power,
735 });
736
737 let deep = match power {
738 PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled,
739 PoweredClock::AlwaysEnabled => Sircsten::Enabled,
740 };
741 let pclk = if *fro_12m_enabled {
742 self.clocks.fro_12m = Some(Clock {
743 frequency: base_freq,
744 power: *power,
745 });
746 self.clocks.clk_1m = Some(Clock {
747 frequency: base_freq / 12,
748 power: *power,
749 });
750 SircClkPeriphEn::Enabled
751 } else {
752 SircClkPeriphEn::Disabled
753 };
754
755 // Set sleep/peripheral usage
756 self.scg0.sirccsr().modify(|_r, w| {
757 w.sircsten().variant(deep);
758 w.sirc_clk_periph_en().variant(pclk);
759 w
760 });
761
762 while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {}
763 if self.scg0.sirccsr().read().sircerr().is_error_detected() {
764 return Err(ClockError::BadConfig {
765 clock: "sirc",
766 reason: "error set",
767 });
768 }
769
770 // reset lock
771 self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled());
772
773 // Do we enable the `fro_lf_div` output?
774 if let Some(d) = fro_lf_div.as_ref() {
775 // We need `fro_lf` to be enabled
776 if !*fro_12m_enabled {
777 return Err(ClockError::BadConfig {
778 clock: "fro_lf_div",
779 reason: "fro_12m not enabled",
780 });
781 }
782
783 // Halt and reset the div; then set our desired div.
784 self.syscon.frolfdiv().write(|w| {
785 w.halt().halt();
786 w.reset().asserted();
787 unsafe { w.div().bits(d.into_bits()) };
788 w
789 });
790 // Then unhalt it, and reset it
791 self.syscon.frolfdiv().modify(|_r, w| {
792 w.halt().run();
793 w.reset().released();
794 w
795 });
796
797 // Wait for clock to stabilize
798 while self.syscon.frolfdiv().read().unstab().is_ongoing() {}
799
800 // Store off the clock info
801 self.clocks.fro_lf_div = Some(Clock {
802 frequency: base_freq / d.into_divisor(),
803 power: *power,
804 });
805 }
806
807 Ok(())
808 }
809
810 /// Configure the FRO16K/clk_16k clock family
811 fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> {
812 let Some(fro16k) = self.config.fro16k.as_ref() else {
813 return Ok(());
814 };
815 // Enable FRO16K oscillator
816 self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit());
817
818 // Lock the control register
819 self.vbat0.frolcka().modify(|_, w| w.lock().set_bit());
820
821 let Fro16KConfig {
822 vsys_domain_active,
823 vdd_core_domain_active,
824 } = fro16k;
825
826 // Enable clock outputs to both VSYS and VDD_CORE domains
827 // Bit 0: clk_16k0 to VSYS domain
828 // Bit 1: clk_16k1 to VDD_CORE domain
829 //
830 // TODO: Define sub-fields for this register with a PAC patch?
831 let mut bits = 0;
832 if *vsys_domain_active {
833 bits |= 0b01;
834 self.clocks.clk_16k_vsys = Some(Clock {
835 frequency: 16_384,
836 power: PoweredClock::AlwaysEnabled,
837 });
838 }
839 if *vdd_core_domain_active {
840 bits |= 0b10;
841 self.clocks.clk_16k_vdd_core = Some(Clock {
842 frequency: 16_384,
843 power: PoweredClock::AlwaysEnabled,
844 });
845 }
846 self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) });
847
848 Ok(())
849 }
850}
851
852//
853// Macros/macro impls
854//
855
856/// This macro is used to implement the [`Gate`] trait for a given peripheral
857/// that is controlled by the MRCC peripheral.
858macro_rules! impl_cc_gate {
859 ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => {
860 impl Gate for crate::peripherals::$name {
861 type MrccPeriphConfig = $config;
862
863 #[inline]
864 unsafe fn enable_clock() {
865 let mrcc = unsafe { pac::Mrcc0::steal() };
866 mrcc.$clk_reg().modify(|_, w| w.$field().enabled());
867 }
868
869 #[inline]
870 unsafe fn disable_clock() {
871 let mrcc = unsafe { pac::Mrcc0::steal() };
872 mrcc.$clk_reg().modify(|_r, w| w.$field().disabled());
873 }
874
875 #[inline]
876 fn is_clock_enabled() -> bool {
877 let mrcc = unsafe { pac::Mrcc0::steal() };
878 mrcc.$clk_reg().read().$field().is_enabled()
879 }
880
881 #[inline]
882 unsafe fn release_reset() {
883 let mrcc = unsafe { pac::Mrcc0::steal() };
884 mrcc.$rst_reg().modify(|_, w| w.$field().enabled());
885 }
886
887 #[inline]
888 unsafe fn assert_reset() {
889 let mrcc = unsafe { pac::Mrcc0::steal() };
890 mrcc.$rst_reg().modify(|_, w| w.$field().disabled());
891 }
892
893 #[inline]
894 fn is_reset_released() -> bool {
895 let mrcc = unsafe { pac::Mrcc0::steal() };
896 mrcc.$rst_reg().read().$field().is_enabled()
897 }
898 }
899 };
900}
901
902/// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait,
903/// for various low level peripherals.
904pub(crate) mod gate {
905 #[cfg(not(feature = "time"))]
906 use super::periph_helpers::OsTimerConfig;
907 use super::periph_helpers::{AdcConfig, Lpi2cConfig, LpuartConfig, NoConfig};
908 use super::*;
909
910 // These peripherals have no additional upstream clocks or configuration required
911 // other than enabling through the MRCC gate. Currently, these peripherals will
912 // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or
913 // [`SPConfHelper::post_enable_config()`].
914 impl_cc_gate!(PORT0, mrcc_glb_cc1, mrcc_glb_rst1, port0, NoConfig);
915 impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig);
916 impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig);
917 impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig);
918 impl_cc_gate!(PORT4, mrcc_glb_cc1, mrcc_glb_rst1, port4, NoConfig);
919
920 impl_cc_gate!(GPIO0, mrcc_glb_cc2, mrcc_glb_rst2, gpio0, NoConfig);
921 impl_cc_gate!(GPIO1, mrcc_glb_cc2, mrcc_glb_rst2, gpio1, NoConfig);
922 impl_cc_gate!(GPIO2, mrcc_glb_cc2, mrcc_glb_rst2, gpio2, NoConfig);
923 impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig);
924 impl_cc_gate!(GPIO4, mrcc_glb_cc2, mrcc_glb_rst2, gpio4, NoConfig);
925
926 // These peripherals DO have meaningful configuration, and could fail if the system
927 // clocks do not match their needs.
928 #[cfg(not(feature = "time"))]
929 impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig);
930
931 impl_cc_gate!(LPI2C0, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c0, Lpi2cConfig);
932 impl_cc_gate!(LPI2C1, mrcc_glb_cc0, mrcc_glb_rst0, lpi2c1, Lpi2cConfig);
933 impl_cc_gate!(LPI2C2, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c2, Lpi2cConfig);
934 impl_cc_gate!(LPI2C3, mrcc_glb_cc1, mrcc_glb_rst1, lpi2c3, Lpi2cConfig);
935
936 impl_cc_gate!(LPUART0, mrcc_glb_cc0, mrcc_glb_rst0, lpuart0, LpuartConfig);
937 impl_cc_gate!(LPUART1, mrcc_glb_cc0, mrcc_glb_rst0, lpuart1, LpuartConfig);
938 impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig);
939 impl_cc_gate!(LPUART3, mrcc_glb_cc0, mrcc_glb_rst0, lpuart3, LpuartConfig);
940 impl_cc_gate!(LPUART4, mrcc_glb_cc0, mrcc_glb_rst0, lpuart4, LpuartConfig);
941 impl_cc_gate!(LPUART5, mrcc_glb_cc1, mrcc_glb_rst1, lpuart5, LpuartConfig);
942 impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig);
943}
diff --git a/embassy-mcxa/src/clocks/periph_helpers.rs b/embassy-mcxa/src/clocks/periph_helpers.rs
new file mode 100644
index 000000000..1ea7a99ed
--- /dev/null
+++ b/embassy-mcxa/src/clocks/periph_helpers.rs
@@ -0,0 +1,502 @@
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 {
112 None
113 } else {
114 Some(Self(n))
115 }
116 }
117
118 /// Store a specific divisor value that will divide the source
119 /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source
120 /// by 1, and `Div4::from_divisor(16)` will divide the source
121 /// by 16.
122 ///
123 /// Will return `None` if `n` is not in the range `1..=16`.
124 /// Consider [`Self::from_raw`] for an infallible version.
125 pub const fn from_divisor(n: u8) -> Option<Self> {
126 let Some(n) = n.checked_sub(1) else {
127 return None;
128 };
129 if n > 0b1111 {
130 return None;
131 }
132 Some(Self(n))
133 }
134
135 /// Convert into "raw" bits form
136 #[inline(always)]
137 pub const fn into_bits(self) -> u8 {
138 self.0
139 }
140
141 /// Convert into "divisor" form, as a u32 for convenient frequency math
142 #[inline(always)]
143 pub const fn into_divisor(self) -> u32 {
144 self.0 as u32 + 1
145 }
146}
147
148/// A basic type that always returns an error when `post_enable_config` is called.
149///
150/// Should only be used as a placeholder.
151pub struct UnimplementedConfig;
152
153impl SPConfHelper for UnimplementedConfig {
154 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
155 Err(ClockError::UnimplementedConfig)
156 }
157}
158
159/// A basic type that always returns `Ok(0)` when `post_enable_config` is called.
160///
161/// This should only be used for peripherals that are "ambiently" clocked, like `PORTn`
162/// peripherals, which have no selectable/configurable source clock.
163pub struct NoConfig;
164impl SPConfHelper for NoConfig {
165 fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> {
166 Ok(0)
167 }
168}
169
170//
171// LPI2c
172//
173
174/// Selectable clocks for `Lpi2c` peripherals
175#[derive(Debug, Clone, Copy)]
176pub enum Lpi2cClockSel {
177 /// FRO12M/FRO_LF/SIRC clock source, passed through divider
178 /// "fro_lf_div"
179 FroLfDiv,
180 /// FRO180M/FRO_HF/FIRC clock source, passed through divider
181 /// "fro_hf_div"
182 FroHfDiv,
183 /// SOSC/XTAL/EXTAL clock source
184 ClkIn,
185 /// clk_1m/FRO_LF divided by 12
186 Clk1M,
187 /// Output of PLL1, passed through clock divider,
188 /// "pll1_clk_div", maybe "pll1_lf_div"?
189 Pll1ClkDiv,
190 /// Disabled
191 None,
192}
193
194/// Which instance of the `Lpi2c` is this?
195///
196/// Should not be directly selectable by end-users.
197#[derive(Copy, Clone, Debug, PartialEq, Eq)]
198pub enum Lpi2cInstance {
199 /// Instance 0
200 Lpi2c0,
201 /// Instance 1
202 Lpi2c1,
203 /// Instance 2
204 Lpi2c2,
205 /// Instance 3
206 Lpi2c3,
207}
208
209/// Top level configuration for `Lpi2c` instances.
210pub struct Lpi2cConfig {
211 /// Power state required for this peripheral
212 pub power: PoweredClock,
213 /// Clock source
214 pub source: Lpi2cClockSel,
215 /// Clock divisor
216 pub div: Div4,
217 /// Which instance is this?
218 // NOTE: should not be user settable
219 pub(crate) instance: Lpi2cInstance,
220}
221
222impl SPConfHelper for Lpi2cConfig {
223 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
224 // check that source is suitable
225 let mrcc0 = unsafe { pac::Mrcc0::steal() };
226 use mcxa_pac::mrcc0::mrcc_lpi2c0_clksel::Mux;
227
228 let (clkdiv, clksel) = match self.instance {
229 Lpi2cInstance::Lpi2c0 => (mrcc0.mrcc_lpi2c0_clkdiv(), mrcc0.mrcc_lpi2c0_clksel()),
230 Lpi2cInstance::Lpi2c1 => (mrcc0.mrcc_lpi2c1_clkdiv(), mrcc0.mrcc_lpi2c1_clksel()),
231 Lpi2cInstance::Lpi2c2 => (mrcc0.mrcc_lpi2c2_clkdiv(), mrcc0.mrcc_lpi2c2_clksel()),
232 Lpi2cInstance::Lpi2c3 => (mrcc0.mrcc_lpi2c3_clkdiv(), mrcc0.mrcc_lpi2c3_clksel()),
233 };
234
235 let (freq, variant) = match self.source {
236 Lpi2cClockSel::FroLfDiv => {
237 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
238 (freq, Mux::ClkrootFunc0)
239 }
240 Lpi2cClockSel::FroHfDiv => {
241 let freq = clocks.ensure_fro_hf_div_active(&self.power)?;
242 (freq, Mux::ClkrootFunc2)
243 }
244 Lpi2cClockSel::ClkIn => {
245 let freq = clocks.ensure_clk_in_active(&self.power)?;
246 (freq, Mux::ClkrootFunc3)
247 }
248 Lpi2cClockSel::Clk1M => {
249 let freq = clocks.ensure_clk_1m_active(&self.power)?;
250 (freq, Mux::ClkrootFunc5)
251 }
252 Lpi2cClockSel::Pll1ClkDiv => {
253 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
254 (freq, Mux::ClkrootFunc6)
255 }
256 Lpi2cClockSel::None => unsafe {
257 // no ClkrootFunc7, just write manually for now
258 clksel.write(|w| w.bits(0b111));
259 clkdiv.modify(|_r, w| w.reset().asserted().halt().asserted());
260 return Ok(0);
261 },
262 };
263
264 apply_div4!(self, clksel, clkdiv, variant, freq)
265 }
266}
267
268//
269// LPUart
270//
271
272/// Selectable clocks for Lpuart peripherals
273#[derive(Debug, Clone, Copy)]
274pub enum LpuartClockSel {
275 /// FRO12M/FRO_LF/SIRC clock source, passed through divider
276 /// "fro_lf_div"
277 FroLfDiv,
278 /// FRO180M/FRO_HF/FIRC clock source, passed through divider
279 /// "fro_hf_div"
280 FroHfDiv,
281 /// SOSC/XTAL/EXTAL clock source
282 ClkIn,
283 /// FRO16K/clk_16k source
284 Clk16K,
285 /// clk_1m/FRO_LF divided by 12
286 Clk1M,
287 /// Output of PLL1, passed through clock divider,
288 /// "pll1_clk_div", maybe "pll1_lf_div"?
289 Pll1ClkDiv,
290 /// Disabled
291 None,
292}
293
294/// Which instance of the Lpuart is this?
295///
296/// Should not be directly selectable by end-users.
297#[derive(Copy, Clone, Debug, PartialEq, Eq)]
298pub enum LpuartInstance {
299 /// Instance 0
300 Lpuart0,
301 /// Instance 1
302 Lpuart1,
303 /// Instance 2
304 Lpuart2,
305 /// Instance 3
306 Lpuart3,
307 /// Instance 4
308 Lpuart4,
309 /// Instance 5
310 Lpuart5,
311}
312
313/// Top level configuration for `Lpuart` instances.
314pub struct LpuartConfig {
315 /// Power state required for this peripheral
316 pub power: PoweredClock,
317 /// Clock source
318 pub source: LpuartClockSel,
319 /// Clock divisor
320 pub div: Div4,
321 /// Which instance is this?
322 // NOTE: should not be user settable
323 pub(crate) instance: LpuartInstance,
324}
325
326impl SPConfHelper for LpuartConfig {
327 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
328 // check that source is suitable
329 let mrcc0 = unsafe { pac::Mrcc0::steal() };
330 use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux;
331
332 let (clkdiv, clksel) = match self.instance {
333 LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()),
334 LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()),
335 LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()),
336 LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()),
337 LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()),
338 LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()),
339 };
340
341 let (freq, variant) = match self.source {
342 LpuartClockSel::FroLfDiv => {
343 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
344 (freq, Mux::ClkrootFunc0)
345 }
346 LpuartClockSel::FroHfDiv => {
347 let freq = clocks.ensure_fro_hf_div_active(&self.power)?;
348 (freq, Mux::ClkrootFunc2)
349 }
350 LpuartClockSel::ClkIn => {
351 let freq = clocks.ensure_clk_in_active(&self.power)?;
352 (freq, Mux::ClkrootFunc3)
353 }
354 LpuartClockSel::Clk16K => {
355 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
356 (freq, Mux::ClkrootFunc4)
357 }
358 LpuartClockSel::Clk1M => {
359 let freq = clocks.ensure_clk_1m_active(&self.power)?;
360 (freq, Mux::ClkrootFunc5)
361 }
362 LpuartClockSel::Pll1ClkDiv => {
363 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
364 (freq, Mux::ClkrootFunc6)
365 }
366 LpuartClockSel::None => unsafe {
367 // no ClkrootFunc7, just write manually for now
368 clksel.write(|w| w.bits(0b111));
369 clkdiv.modify(|_r, w| {
370 w.reset().asserted();
371 w.halt().asserted();
372 w
373 });
374 return Ok(0);
375 },
376 };
377
378 // set clksel
379 apply_div4!(self, clksel, clkdiv, variant, freq)
380 }
381}
382
383//
384// OSTimer
385//
386
387/// Selectable clocks for the OSTimer peripheral
388#[derive(Copy, Clone, Debug, PartialEq, Eq)]
389pub enum OstimerClockSel {
390 /// 16k clock, sourced from FRO16K (Vdd Core)
391 Clk16kVddCore,
392 /// 1 MHz Clock sourced from FRO12M
393 Clk1M,
394 /// Disabled
395 None,
396}
397
398/// Top level configuration for the `OSTimer` peripheral
399pub struct OsTimerConfig {
400 /// Power state required for this peripheral
401 pub power: PoweredClock,
402 /// Selected clock source for this peripheral
403 pub source: OstimerClockSel,
404}
405
406impl SPConfHelper for OsTimerConfig {
407 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
408 let mrcc0 = unsafe { pac::Mrcc0::steal() };
409 Ok(match self.source {
410 OstimerClockSel::Clk16kVddCore => {
411 let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?;
412 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k());
413 freq
414 }
415 OstimerClockSel::Clk1M => {
416 let freq = clocks.ensure_clk_1m_active(&self.power)?;
417 mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m());
418 freq
419 }
420 OstimerClockSel::None => {
421 mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) });
422 0
423 }
424 })
425 }
426}
427
428//
429// Adc
430//
431
432/// Selectable clocks for the ADC peripheral
433#[derive(Copy, Clone, Debug, PartialEq, Eq)]
434pub enum AdcClockSel {
435 /// Divided `fro_lf`/`clk_12m`/FRO12M source
436 FroLfDiv,
437 /// Gated `fro_hf`/`FRO180M` source
438 FroHf,
439 /// External Clock Source
440 ClkIn,
441 /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m`
442 Clk1M,
443 /// Internal PLL output, with configurable divisor
444 Pll1ClkDiv,
445 /// No clock/disabled
446 None,
447}
448
449/// Top level configuration for the ADC peripheral
450pub struct AdcConfig {
451 /// Power state required for this peripheral
452 pub power: PoweredClock,
453 /// Selected clock-source for this peripheral
454 pub source: AdcClockSel,
455 /// Pre-divisor, applied to the upstream clock output
456 pub div: Div4,
457}
458
459impl SPConfHelper for AdcConfig {
460 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> {
461 use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux;
462 let mrcc0 = unsafe { pac::Mrcc0::steal() };
463 let (freq, variant) = match self.source {
464 AdcClockSel::FroLfDiv => {
465 let freq = clocks.ensure_fro_lf_div_active(&self.power)?;
466 (freq, Mux::ClkrootFunc0)
467 }
468 AdcClockSel::FroHf => {
469 let freq = clocks.ensure_fro_hf_active(&self.power)?;
470 (freq, Mux::ClkrootFunc1)
471 }
472 AdcClockSel::ClkIn => {
473 let freq = clocks.ensure_clk_in_active(&self.power)?;
474 (freq, Mux::ClkrootFunc3)
475 }
476 AdcClockSel::Clk1M => {
477 let freq = clocks.ensure_clk_1m_active(&self.power)?;
478 (freq, Mux::ClkrootFunc5)
479 }
480 AdcClockSel::Pll1ClkDiv => {
481 let freq = clocks.ensure_pll1_clk_div_active(&self.power)?;
482 (freq, Mux::ClkrootFunc6)
483 }
484 AdcClockSel::None => {
485 mrcc0.mrcc_adc_clksel().write(|w| unsafe {
486 // no ClkrootFunc7, just write manually for now
487 w.mux().bits(0b111)
488 });
489 mrcc0.mrcc_adc_clkdiv().modify(|_r, w| {
490 w.reset().asserted();
491 w.halt().asserted();
492 w
493 });
494 return Ok(0);
495 }
496 };
497 let clksel = mrcc0.mrcc_adc_clksel();
498 let clkdiv = mrcc0.mrcc_adc_clkdiv();
499
500 apply_div4!(self, clksel, clkdiv, variant, freq)
501 }
502}