aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/clocks/periph_helpers.rs
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/periph_helpers.rs
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/periph_helpers.rs')
-rw-r--r--embassy-mcxa/src/clocks/periph_helpers.rs502
1 files changed, 502 insertions, 0 deletions
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}