aboutsummaryrefslogtreecommitdiff
path: root/embassy-mcxa/src/clocks/periph_helpers.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-mcxa/src/clocks/periph_helpers.rs')
-rw-r--r--embassy-mcxa/src/clocks/periph_helpers.rs499
1 files changed, 499 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..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}