aboutsummaryrefslogtreecommitdiff
path: root/src/clocks
diff options
context:
space:
mode:
Diffstat (limited to 'src/clocks')
-rw-r--r--src/clocks/mod.rs617
1 files changed, 617 insertions, 0 deletions
diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs
new file mode 100644
index 000000000..c86ed1cd5
--- /dev/null
+++ b/src/clocks/mod.rs
@@ -0,0 +1,617 @@
1//! Clock control helpers (no magic numbers, PAC field access only).
2//! Provides reusable gate abstractions for peripherals used by the examples.
3use mcxa_pac::scg0::{
4 firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten},
5 sirccsr::{SircClkPeriphEn, Sircsten},
6};
7
8use crate::pac;
9
10pub trait SPConfHelper {
11 fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError>;
12}
13
14// /// Trait describing an AHB clock gate that can be toggled through MRCC.
15// pub trait Gate {
16// /// Enable the clock gate.
17// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock);
18
19// /// Return whether the clock gate is currently enabled.
20// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool;
21// }
22
23// /// Enable a clock gate for the given peripheral set.
24// #[inline]
25// pub unsafe fn enable<G: Gate>(peripherals: &pac::Peripherals) {
26// let mrcc = &peripherals.mrcc0;
27// G::enable(mrcc);
28// while !G::is_enabled(mrcc) {}
29// core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags));
30// }
31
32// /// Check whether a gate is currently enabled.
33// #[inline]
34// pub fn is_enabled<G: Gate>(peripherals: &pac::Peripherals) -> bool {
35// G::is_enabled(&peripherals.mrcc0)
36// }
37
38// macro_rules! impl_cc_gate {
39// ($name:ident, $reg:ident, $field:ident) => {
40// pub struct $name;
41
42// impl Gate for $name {
43// #[inline]
44// unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) {
45// mrcc.$reg().modify(|_, w| w.$field().enabled());
46// }
47
48// #[inline]
49// fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool {
50// mrcc.$reg().read().$field().is_enabled()
51// }
52// }
53// };
54// }
55
56// pub mod gate {
57// use super::*;
58
59// impl_cc_gate!(Port2, mrcc_glb_cc1, port2);
60// impl_cc_gate!(Port3, mrcc_glb_cc1, port3);
61// impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0);
62// impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2);
63// impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3);
64// impl_cc_gate!(Port1, mrcc_glb_cc1, port1);
65// impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1);
66// }
67
68// /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART.
69// pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) {
70// enable::<gate::Port2>(peripherals);
71// enable::<gate::Lpuart2>(peripherals);
72// }
73
74// /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples.
75// pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) {
76// enable::<gate::Port3>(peripherals);
77// enable::<gate::Gpio3>(peripherals);
78// }
79
80// /// Convenience helper enabling the OSTIMER0 clock gate.
81// pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) {
82// enable::<gate::Ostimer0>(peripherals);
83// }
84
85// pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) {
86// // Use FRO_LF_DIV (already running) MUX=0 DIV=0
87// let mrcc = &peripherals.mrcc0;
88// mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0());
89// mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) });
90// }
91
92// pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) {
93// // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it)
94// let sys = &peripherals.syscon;
95// sys.frolfdiv().modify(|_, w| {
96// // DIV defaults to 0; keep it explicit and clear HALT
97// unsafe { w.div().bits(0) }.halt().run()
98// });
99// }
100
101// /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2.
102// /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV.
103// pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 {
104// // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts.
105// // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000.
106// // Read it anyway for future generality.
107// let div = peripherals.syscon.frolfdiv().read().div().bits() as u32;
108// let base = 12_000_000u32;
109// base / (div + 1)
110// }
111
112// /// Enable clock gate and release reset for OSTIMER0.
113// /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration).
114// pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) {
115// let mrcc = &peripherals.mrcc0;
116// mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m());
117// }
118
119// pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) {
120// let vbat = &peripherals.vbat0;
121// // Enable FRO16K oscillator
122// vbat.froctla().modify(|_, w| w.fro_en().set_bit());
123
124// // Lock the control register
125// vbat.frolcka().modify(|_, w| w.lock().set_bit());
126
127// // Enable clock outputs to both VSYS and VDD_CORE domains
128// // Bit 0: clk_16k0 to VSYS domain
129// // Bit 1: clk_16k1 to VDD_CORE domain
130// vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) });
131// }
132
133// pub unsafe fn enable_adc(peripherals: &pac::Peripherals) {
134// enable::<gate::Port1>(peripherals);
135// enable::<gate::Adc1>(peripherals);
136// }
137
138// pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) {
139// // Use FRO_LF_DIV (already running) MUX=0 DIV=0
140// let mrcc = &peripherals.mrcc0;
141// mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0());
142// mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) });
143// }
144
145// ==============================================
146
147/// This type represents a divider in the range 1..=256.
148///
149/// At a hardware level, this is an 8-bit register from 0..=255,
150/// which adds one.
151#[derive(Copy, Clone, Debug, PartialEq, Eq)]
152pub struct Div8(pub(super) u8);
153
154impl Div8 {
155 /// Store a "raw" divisor value that will divide the source by
156 /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source
157 /// by 1, and `Div8::from_raw(255)` will divide the source by
158 /// 256.
159 pub const fn from_raw(n: u8) -> Self {
160 Self(n)
161 }
162
163 /// Store a specific divisor value that will divide the source
164 /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source
165 /// by 1, and `Div8::from_divisor(256)` will divide the source
166 /// by 256.
167 ///
168 /// Will return `None` if `n` is not in the range `1..=256`.
169 /// Consider [`Self::from_raw`] for an infallible version.
170 pub const fn from_divisor(n: u16) -> Option<Self> {
171 let Some(n) = n.checked_sub(1) else {
172 return None;
173 };
174 if n > (u8::MAX as u16) {
175 return None;
176 }
177 Some(Self(n as u8))
178 }
179
180 /// Convert into "raw" bits form
181 #[inline(always)]
182 pub const fn into_bits(self) -> u8 {
183 self.0
184 }
185
186 /// Convert into "divisor" form, as a u32 for convenient frequency math
187 #[inline(always)]
188 pub const fn into_divisor(self) -> u32 {
189 self.0 as u32 + 1
190 }
191}
192
193#[derive(Debug, Clone)]
194pub struct Clock {
195 pub frequency: u32,
196 pub power: PoweredClock,
197}
198
199#[derive(Debug, Clone, Copy)]
200pub enum PoweredClock {
201 HighPowerOnly,
202 AlwaysEnabled,
203}
204
205/// ```text
206/// ┌─────────────────────────────────────────────────────────┐
207/// │ │
208/// │ ┌───────────┐ clk_out ┌─────────┐ │
209/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │
210/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷
211/// EXTAL ──────┼──▷│ │───────────▷│ │ │
212/// │ └───────────┘ └─────────┘ │
213/// │ │
214/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │
215/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷
216/// │ │ │ │ ├────┤ clk_45m │
217/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷
218/// │ └───────────┘ └────┘ │
219/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │
220/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷
221/// │ │ │ │ ├────┤ clk_1m │
222/// │ │ │ └─────▷│1/12│────────────────────┼──────▷
223/// │ └───────────┘ └────┘ │
224/// │ │
225/// │ ┌──────────┐ │
226/// │ │000 │ │
227/// │ clk_in │ │ │
228/// │ ───────────────▷│001 │ │
229/// │ fro_12m │ │ │
230/// │ ───────────────▷│010 │ │
231/// │ fro_hf_root │ │ │
232/// │ ───────────────▷│011 │ main_clk │
233/// │ │ │───────────────────────────┼──────▷
234/// clk_16k ──────┼─────────────────▷│100 │ │
235/// │ none │ │ │
236/// │ ───────────────▷│101 │ │
237/// │ pll1_clk │ │ │
238/// │ ───────────────▷│110 │ │
239/// │ none │ │ │
240/// │ ───────────────▷│111 │ │
241/// │ └──────────┘ │
242/// │ ▲ │
243/// │ │ │
244/// │ SCG SCS │
245/// │ SCG-Lite │
246/// └─────────────────────────────────────────────────────────┘
247///
248///
249/// clk_in ┌─────┐
250/// ───────────────▷│00 │
251/// clk_45m │ │
252/// ───────────────▷│01 │ ┌───────────┐ pll1_clk
253/// none │ │─────▷│ SPLL │───────────────▷
254/// ───────────────▷│10 │ └───────────┘
255/// fro_12m │ │
256/// ───────────────▷│11 │
257/// └─────┘
258/// ```
259#[non_exhaustive]
260pub struct ClocksConfig {
261 // FIRC, FRO180, 45/60/90/180M clock source
262 pub firc: Option<FircConfig>,
263 // NOTE: I don't think we *can* disable the SIRC?
264 pub sirc: SircConfig,
265}
266
267// FIRC/FRO180M
268
269/// ```text
270/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf
271/// │ FRO180M ├───────┬─────▷│GATE│──────────▷
272/// │ │ │ ├────┤ clk_45m
273/// │ │ └─────▷│GATE│──────────▷
274/// └───────────┘ └────┘
275/// ```
276#[non_exhaustive]
277pub struct FircConfig {
278 pub frequency: FircFreqSel,
279 pub power: PoweredClock,
280 /// Is the "fro_hf" gated clock enabled?
281 pub fro_hf_enabled: bool,
282 /// Is the "clk_45m" gated clock enabled?
283 pub clk_45m_enabled: bool,
284 /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`!
285 pub fro_hf_div: Option<Div8>,
286}
287
288pub enum FircFreqSel {
289 Mhz45,
290 Mhz60,
291 Mhz90,
292 Mhz180,
293}
294
295// SIRC/FRO12M
296
297/// ```text
298/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m
299/// │ FRO12M │────────┬─────▷│ CG │──────────▷
300/// │ │ │ ├────┤ clk_1m
301/// │ │ └─────▷│1/12│──────────▷
302/// └───────────┘ └────┘
303/// ```
304#[non_exhaustive]
305pub struct SircConfig {
306 pub power: PoweredClock,
307 // peripheral output, aka sirc_12mhz
308 pub fro_12m_enabled: bool,
309 /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`!
310 pub fro_lf_div: Option<Div8>,
311}
312
313#[derive(Default, Debug, Clone)]
314#[non_exhaustive]
315pub struct Clocks {
316 pub clk_in: Option<Clock>,
317
318 // FRO180M stuff
319 //
320 pub fro_hf_root: Option<Clock>,
321 pub fro_hf: Option<Clock>,
322 pub clk_45m: Option<Clock>,
323 pub fro_hf_div: Option<Clock>,
324 //
325 // End FRO180M
326
327 // FRO12M stuff
328 pub fro_12m_root: Option<Clock>,
329 pub fro_12m: Option<Clock>,
330 pub clk_1m: Option<Clock>,
331 pub fro_lf_div: Option<Clock>,
332 //
333 // End FRO12M stuff
334 pub main_clk: Option<Clock>,
335 pub pll1_clk: Option<Clock>,
336}
337
338static CLOCKS: critical_section::Mutex<Option<Clocks>> = critical_section::Mutex::new(None);
339
340#[non_exhaustive]
341pub enum ClockError {
342 AlreadyInitialized,
343 BadConfig { clock: &'static str, reason: &'static str },
344}
345
346struct ClockOperator<'a> {
347 clocks: &'a mut Clocks,
348 config: &'a ClocksConfig,
349
350 _mrcc0: pac::Mrcc0,
351 scg0: pac::Scg0,
352 syscon: pac::Syscon,
353}
354
355impl ClockOperator<'_> {
356 fn configure_firc_clocks(&mut self) -> Result<(), ClockError> {
357 const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig {
358 clock: "firc",
359 reason: "For now, FIRC must be enabled and in default state!",
360 });
361
362 // Did the user give us a FIRC config?
363 let Some(firc) = self.config.firc.as_ref() else {
364 return HARDCODED_ERR;
365 };
366 // Is the FIRC set to 45MHz (should be reset default)
367 if !matches!(firc.frequency, FircFreqSel::Mhz45) {
368 return HARDCODED_ERR;
369 }
370 let base_freq = 45_000_000;
371
372 // Is the FIRC as expected?
373 let mut firc_ok = true;
374
375 // Is the hardware currently set to the default 45MHz?
376 //
377 // NOTE: the SVD currently has the wrong(?) values for these:
378 // 45 -> 48
379 // 60 -> 64
380 // 90 -> 96
381 // 180 -> 192
382 // Probably correct-ish, but for a different trim value?
383 firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s();
384
385 // Check some values in the CSR
386 let csr = self.scg0.firccsr().read();
387 // Is it enabled?
388 firc_ok &= csr.fircen().is_enabled();
389 // Is it accurate?
390 firc_ok &= csr.fircacc().is_enabled_and_valid();
391 // Is there no error?
392 firc_ok &= csr.fircerr().is_error_not_detected();
393 // Is the FIRC the system clock?
394 firc_ok &= csr.fircsel().is_firc();
395 // Is it valid?
396 firc_ok &= csr.fircvld().is_enabled_and_valid();
397
398 // Are we happy with the current (hardcoded) state?
399 if !firc_ok {
400 return HARDCODED_ERR;
401 }
402
403 // Note that the fro_hf_root is active
404 self.clocks.fro_hf_root = Some(Clock {
405 frequency: base_freq,
406 power: firc.power,
407 });
408
409 // Okay! Now we're past that, let's enable all the downstream clocks.
410 let FircConfig {
411 frequency: _,
412 power,
413 fro_hf_enabled,
414 clk_45m_enabled,
415 fro_hf_div,
416 } = firc;
417
418 // When is the FRO enabled?
419 let pow_set = match power {
420 PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes,
421 PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes,
422 };
423
424 // Do we enable the `fro_hf` output?
425 let fro_hf_set = if *fro_hf_enabled {
426 self.clocks.fro_hf = Some(Clock {
427 frequency: base_freq,
428 power: *power,
429 });
430 FircFclkPeriphEn::Enabled
431 } else {
432 FircFclkPeriphEn::Disabled
433 };
434
435 // Do we enable the `clk_45m` output?
436 let clk_45m_set = if *clk_45m_enabled {
437 self.clocks.clk_45m = Some(Clock {
438 frequency: 45_000_000,
439 power: *power,
440 });
441 FircSclkPeriphEn::Enabled
442 } else {
443 FircSclkPeriphEn::Disabled
444 };
445
446 self.scg0.firccsr().modify(|_r, w| {
447 w.fircsten().variant(pow_set);
448 w.firc_fclk_periph_en().variant(fro_hf_set);
449 w.firc_sclk_periph_en().variant(clk_45m_set);
450 w
451 });
452
453 // Do we enable the `fro_hf_div` output?
454 if let Some(d) = fro_hf_div.as_ref() {
455 // We need `fro_hf` to be enabled
456 if !*fro_hf_enabled {
457 return Err(ClockError::BadConfig {
458 clock: "fro_hf_div",
459 reason: "fro_hf not enabled",
460 });
461 }
462
463 // Halt and reset the div
464 self.syscon.frohfdiv().write(|w| {
465 w.halt().halt();
466 w.reset().asserted();
467 w
468 });
469 // Then change the div, unhalt it, and reset it
470 self.syscon.frohfdiv().write(|w| {
471 unsafe {
472 w.div().bits(d.into_bits());
473 }
474 w.halt().run();
475 w.reset().released();
476 w
477 });
478
479 // Wait for clock to stabilize
480 while self.syscon.frohfdiv().read().unstab().is_ongoing() {}
481
482 // Store off the clock info
483 self.clocks.fro_hf_div = Some(Clock {
484 frequency: base_freq / d.into_divisor(),
485 power: *power,
486 });
487 }
488
489 Ok(())
490 }
491
492 fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> {
493 let SircConfig {
494 power,
495 fro_12m_enabled,
496 fro_lf_div,
497 } = &self.config.sirc;
498 let base_freq = 12_000_000;
499
500 // Allow writes
501 self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled());
502 self.clocks.fro_12m_root = Some(Clock {
503 frequency: base_freq,
504 power: *power,
505 });
506
507 let deep = match power {
508 PoweredClock::HighPowerOnly => Sircsten::Disabled,
509 PoweredClock::AlwaysEnabled => Sircsten::Enabled,
510 };
511 let pclk = if *fro_12m_enabled {
512 self.clocks.fro_12m = Some(Clock {
513 frequency: base_freq,
514 power: *power,
515 });
516 self.clocks.clk_1m = Some(Clock {
517 frequency: base_freq / 12,
518 power: *power,
519 });
520 SircClkPeriphEn::Enabled
521 } else {
522 SircClkPeriphEn::Disabled
523 };
524
525 // Set sleep/peripheral usage
526 self.scg0.sirccsr().modify(|_r, w| {
527 w.sircsten().variant(deep);
528 w.sirc_clk_periph_en().variant(pclk);
529 w
530 });
531
532 while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {}
533 if self.scg0.sirccsr().read().sircerr().is_error_detected() {
534 return Err(ClockError::BadConfig {
535 clock: "sirc",
536 reason: "error set",
537 });
538 }
539
540 // reset lock
541 self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled());
542
543 // Do we enable the `fro_lf_div` output?
544 if let Some(d) = fro_lf_div.as_ref() {
545 // We need `fro_lf` to be enabled
546 if !*fro_12m_enabled {
547 return Err(ClockError::BadConfig {
548 clock: "fro_lf_div",
549 reason: "fro_12m not enabled",
550 });
551 }
552
553 // Halt and reset the div
554 self.syscon.frolfdiv().write(|w| {
555 w.halt().halt();
556 w.reset().asserted();
557 w
558 });
559 // Then change the div, unhalt it, and reset it
560 self.syscon.frolfdiv().write(|w| {
561 unsafe {
562 w.div().bits(d.into_bits());
563 }
564 w.halt().run();
565 w.reset().released();
566 w
567 });
568
569 // Wait for clock to stabilize
570 while self.syscon.frolfdiv().read().unstab().is_ongoing() {}
571
572 // Store off the clock info
573 self.clocks.fro_lf_div = Some(Clock {
574 frequency: base_freq / d.into_divisor(),
575 power: *power,
576 });
577 }
578
579 todo!()
580 }
581}
582
583pub fn init(settings: ClocksConfig) -> Result<(), ClockError> {
584 critical_section::with(|cs| {
585 if CLOCKS.borrow(cs).is_some() {
586 Err(ClockError::AlreadyInitialized)
587 } else {
588 Ok(())
589 }
590 })?;
591
592 let mut clocks = Clocks::default();
593 let mut operator = ClockOperator {
594 clocks: &mut clocks,
595 config: &settings,
596
597 _mrcc0: unsafe { pac::Mrcc0::steal() },
598 scg0: unsafe { pac::Scg0::steal() },
599 syscon: unsafe { pac::Syscon::steal() },
600 };
601
602 operator.configure_firc_clocks()?;
603 operator.configure_sirc_clocks()?;
604 // TODO, everything downstream
605
606 Ok(())
607}
608
609/// Obtain the full clocks structure, calling the given closure in a critical section
610///
611/// NOTE: Clocks implements `Clone`,
612pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> {
613 critical_section::with(|cs| {
614 let c = CLOCKS.borrow(cs).as_ref()?;
615 Some(f(c))
616 })
617}