aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-11-11 20:27:14 +0100
committerJames Munns <[email protected]>2025-11-11 20:27:14 +0100
commit144c659fe278959736a1d391f1582dbbfc651646 (patch)
tree6e0f950dd71be594767c7f168934c439c94b6cf6
parentf2f53306e0eeb1f36b1e9852836a3f2c4510dddf (diff)
Start implementing clocks
This starts with the FIRC/FRO180M, though leaves it mostly hardcoded.
-rw-r--r--src/clocks.rs379
1 files changed, 379 insertions, 0 deletions
diff --git a/src/clocks.rs b/src/clocks.rs
index 65a17cef6..74e0daeeb 100644
--- a/src/clocks.rs
+++ b/src/clocks.rs
@@ -1,5 +1,7 @@
1//! Clock control helpers (no magic numbers, PAC field access only). 1//! Clock control helpers (no magic numbers, PAC field access only).
2//! Provides reusable gate abstractions for peripherals used by the examples. 2//! Provides reusable gate abstractions for peripherals used by the examples.
3use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten};
4
3use crate::pac; 5use crate::pac;
4 6
5/// Trait describing an AHB clock gate that can be toggled through MRCC. 7/// Trait describing an AHB clock gate that can be toggled through MRCC.
@@ -132,3 +134,380 @@ pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) {
132 mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); 134 mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0());
133 mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); 135 mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) });
134} 136}
137
138// ==============================================
139
140/// This type represents a divider in the range 1..=256.
141///
142/// At a hardware level, this is an 8-bit register from 0..=255,
143/// which adds one.
144#[derive(Copy, Clone, Debug, PartialEq, Eq)]
145pub struct Div8(pub(super) u8);
146
147impl Div8 {
148 /// Store a "raw" divisor value that will divide the source by
149 /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source
150 /// by 1, and `Div8::from_raw(255)` will divide the source by
151 /// 256.
152 pub const fn from_raw(n: u8) -> Self {
153 Self(n)
154 }
155
156 /// Store a specific divisor value that will divide the source
157 /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source
158 /// by 1, and `Div8::from_divisor(256)` will divide the source
159 /// by 256.
160 ///
161 /// Will return `None` if `n` is not in the range `1..=256`.
162 /// Consider [`Self::from_raw`] for an infallible version.
163 pub const fn from_divisor(n: u16) -> Option<Self> {
164 let Some(n) = n.checked_sub(1) else {
165 return None;
166 };
167 if n > (u8::MAX as u16) {
168 return None;
169 }
170 Some(Self(n as u8))
171 }
172
173 /// Convert into "raw" bits form
174 #[inline(always)]
175 pub const fn into_bits(self) -> u8 {
176 self.0
177 }
178
179 /// Convert into "divisor" form, as a u32 for convenient frequency math
180 #[inline(always)]
181 pub const fn into_divisor(self) -> u32 {
182 self.0 as u32 + 1
183 }
184}
185
186#[derive(Debug, Clone)]
187pub struct Clock {
188 pub frequency: u32,
189 pub power: PoweredClock,
190}
191
192#[derive(Debug, Clone, Copy)]
193pub enum PoweredClock {
194 HighPowerOnly,
195 AlwaysEnabled,
196}
197
198/// ```text
199/// ┌─────────────────────────────────────────────────────────┐
200/// │ │
201/// │ ┌───────────┐ clk_out ┌─────────┐ │
202/// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │
203/// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷
204/// EXTAL ──────┼──▷│ │───────────▷│ │ │
205/// │ └───────────┘ └─────────┘ │
206/// │ │
207/// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │
208/// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷
209/// │ │ │ │ ├────┤ clk_45m │
210/// │ │ │ └─────▷│ CG │─────────────────────┼──────▷
211/// │ └───────────┘ └────┘ │
212/// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │
213/// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷
214/// │ │ │ │ ├────┤ clk_1m │
215/// │ │ │ └─────▷│1/12│────────────────────┼──────▷
216/// │ └───────────┘ └────┘ │
217/// │ │
218/// │ ┌──────────┐ │
219/// │ │000 │ │
220/// │ clk_in │ │ │
221/// │ ───────────────▷│001 │ │
222/// │ fro_12m │ │ │
223/// │ ───────────────▷│010 │ │
224/// │ fro_hf_root │ │ │
225/// │ ───────────────▷│011 │ main_clk │
226/// │ │ │───────────────────────────┼──────▷
227/// clk_16k ──────┼─────────────────▷│100 │ │
228/// │ none │ │ │
229/// │ ───────────────▷│101 │ │
230/// │ pll1_clk │ │ │
231/// │ ───────────────▷│110 │ │
232/// │ none │ │ │
233/// │ ───────────────▷│111 │ │
234/// │ └──────────┘ │
235/// │ ▲ │
236/// │ │ │
237/// │ SCG SCS │
238/// │ SCG-Lite │
239/// └─────────────────────────────────────────────────────────┘
240///
241///
242/// clk_in ┌─────┐
243/// ───────────────▷│00 │
244/// clk_45m │ │
245/// ───────────────▷│01 │ ┌───────────┐ pll1_clk
246/// none │ │─────▷│ SPLL │───────────────▷
247/// ───────────────▷│10 │ └───────────┘
248/// fro_12m │ │
249/// ───────────────▷│11 │
250/// └─────┘
251/// ```
252pub struct ClocksConfig {
253 // FIRC, FRO180, 45/60/90/180M clock source
254 pub firc: Option<FircConfig>,
255 pub sirc: Option<SircConfig>,
256}
257
258// FIRC/FRO180M
259
260/// ```text
261/// ┌───────────┐ fro_hf_root ┌────┐ fro_hf
262/// │ FRO180M ├───────┬─────▷│GATE│──────────▷
263/// │ │ │ ├────┤ clk_45m
264/// │ │ └─────▷│GATE│──────────▷
265/// └───────────┘ └────┘
266/// ```
267pub struct FircConfig {
268 pub frequency: FircFreqSel,
269 pub power: PoweredClock,
270 /// Is the "fro_hf" gated clock enabled?
271 pub fro_hf_enabled: bool,
272 /// Is the "clk_45m" gated clock enabled?
273 pub clk_45m_enabled: bool,
274 /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`!
275 pub fro_hf_div: Option<Div8>,
276}
277
278pub enum FircFreqSel {
279 Mhz45,
280 Mhz60,
281 Mhz90,
282 Mhz180,
283}
284
285// SIRC/FRO12M
286
287/// ```text
288/// ┌───────────┐ fro_12m_root ┌────┐ fro_12m
289/// │ FRO12M │────────┬─────▷│ CG │──────────▷
290/// │ │ │ ├────┤ clk_1m
291/// │ │ └─────▷│1/12│──────────▷
292/// └───────────┘ └────┘
293/// ```
294pub struct SircConfig {
295 pub power: PoweredClock,
296 // peripheral output, aka sirc_12mhz
297 pub fro_12m_enabled: bool,
298 /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`!
299 pub fro_lf_div: Option<Div8>,
300}
301
302#[derive(Default, Debug, Clone)]
303pub struct Clocks {
304 pub clk_in: Option<Clock>,
305
306 // FRO180M stuff
307 //
308 pub fro_hf_root: Option<Clock>,
309 pub fro_hf: Option<Clock>,
310 pub clk_45m: Option<Clock>,
311 pub fro_hf_div: Option<Clock>,
312 //
313 // End FRO180M
314
315 // FRO12M stuff
316 pub fro_12m_root: Option<Clock>,
317 pub fro_12m: Option<Clock>,
318 pub clk_1m: Option<Clock>,
319 pub fro_lf_div: Option<Clock>,
320 //
321 // End FRO12M stuff
322 pub main_clk: Option<Clock>,
323 pub pll1_clk: Option<Clock>,
324}
325
326static CLOCKS: critical_section::Mutex<Option<Clocks>> = critical_section::Mutex::new(None);
327
328pub enum ClockError {
329 AlreadyInitialized,
330 BadConfig { clock: &'static str, reason: &'static str },
331}
332
333struct ClockOperator<'a> {
334 clocks: &'a mut Clocks,
335 config: &'a ClocksConfig,
336
337 _mrcc0: pac::Mrcc0,
338 scg0: pac::Scg0,
339 syscon: pac::Syscon,
340}
341
342impl ClockOperator<'_> {
343 fn configure_firc_clocks(&mut self) -> Result<(), ClockError> {
344 const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig {
345 clock: "firc",
346 reason: "For now, FIRC must be enabled and in default state!",
347 });
348
349 // Did the user give us a FIRC config?
350 let Some(firc) = self.config.firc.as_ref() else {
351 return HARDCODED_ERR;
352 };
353 // Is the FIRC set to 45MHz (should be reset default)
354 if !matches!(firc.frequency, FircFreqSel::Mhz45) {
355 return HARDCODED_ERR;
356 }
357 let base_freq = 45_000_000;
358
359 // Is the FIRC as expected?
360 let mut firc_ok = true;
361
362 // Is the hardware currently set to the default 45MHz?
363 //
364 // NOTE: the SVD currently has the wrong(?) values for these:
365 // 45 -> 48
366 // 60 -> 64
367 // 90 -> 96
368 // 180 -> 192
369 // Probably correct-ish, but for a different trim value?
370 firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s();
371
372 // Check some values in the CSR
373 let csr = self.scg0.firccsr().read();
374 // Is it enabled?
375 firc_ok &= csr.fircen().is_enabled();
376 // Is it accurate?
377 firc_ok &= csr.fircacc().is_enabled_and_valid();
378 // Is there no error?
379 firc_ok &= csr.fircerr().is_error_not_detected();
380 // Is the FIRC the system clock?
381 firc_ok &= csr.fircsel().is_firc();
382 // Is it valid?
383 firc_ok &= csr.fircvld().is_enabled_and_valid();
384
385 // Are we happy with the current (hardcoded) state?
386 if !firc_ok {
387 return HARDCODED_ERR;
388 }
389
390 // Note that the fro_hf_root is active
391 self.clocks.fro_hf_root = Some(Clock {
392 frequency: base_freq,
393 power: firc.power,
394 });
395
396 // Okay! Now we're past that, let's enable all the downstream clocks.
397 let FircConfig {
398 frequency: _,
399 power,
400 fro_hf_enabled,
401 clk_45m_enabled,
402 fro_hf_div,
403 } = firc;
404
405 // When is the FRO enabled?
406 let pow_set = match power {
407 PoweredClock::HighPowerOnly => Fircsten::DisabledInStopModes,
408 PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes,
409 };
410
411 // Do we enable the `fro_hf` output?
412 let fro_hf_set = if *fro_hf_enabled {
413 self.clocks.fro_hf = Some(Clock {
414 frequency: base_freq,
415 power: *power,
416 });
417 FircFclkPeriphEn::Enabled
418 } else {
419 FircFclkPeriphEn::Disabled
420 };
421
422 // Do we enable the `clk_45m` output?
423 let clk_45m_set = if *clk_45m_enabled {
424 self.clocks.clk_45m = Some(Clock {
425 frequency: 45_000_000,
426 power: *power,
427 });
428 FircSclkPeriphEn::Enabled
429 } else {
430 FircSclkPeriphEn::Disabled
431 };
432
433 self.scg0.firccsr().modify(|_r, w| {
434 w.fircsten().variant(pow_set);
435 w.firc_fclk_periph_en().variant(fro_hf_set);
436 w.firc_sclk_periph_en().variant(clk_45m_set);
437 w
438 });
439
440 // Do we enable the `fro_hf_div` output?
441 if let Some(d) = fro_hf_div.as_ref() {
442 // We need `fro_hf` to be enabled
443 if !*fro_hf_enabled {
444 return Err(ClockError::BadConfig {
445 clock: "fro_hf_div",
446 reason: "fro_hf not enabled",
447 });
448 }
449
450 // Halt and reset the div
451 self.syscon.frohfdiv().write(|w| {
452 w.halt().halt();
453 w.reset().asserted();
454 w
455 });
456 // Then change the div, unhalt it, and reset it
457 self.syscon.frohfdiv().write(|w| {
458 unsafe {
459 w.div().bits(d.into_bits());
460 }
461 w.halt().run();
462 w.reset().released();
463 w
464 });
465
466 // Wait for clock to stabilize
467 while self.syscon.frohfdiv().read().unstab().is_ongoing() {}
468
469 // Store off the clock info
470 self.clocks.fro_hf_div = Some(Clock {
471 frequency: base_freq / d.into_divisor(),
472 power: *power,
473 });
474 }
475
476 Ok(())
477 }
478}
479
480pub fn init(settings: ClocksConfig) -> Result<(), ClockError> {
481 critical_section::with(|cs| {
482 if CLOCKS.borrow(cs).is_some() {
483 Err(ClockError::AlreadyInitialized)
484 } else {
485 Ok(())
486 }
487 })?;
488
489 let mut clocks = Clocks::default();
490 let mut operator = ClockOperator {
491 clocks: &mut clocks,
492 config: &settings,
493
494 _mrcc0: unsafe { pac::Mrcc0::steal() },
495 scg0: unsafe { pac::Scg0::steal() },
496 syscon: unsafe { pac::Syscon::steal() },
497 };
498
499 operator.configure_firc_clocks()?;
500 // TODO, SIRC, and everything downstream
501
502 Ok(())
503}
504
505/// Obtain the full clocks structure, calling the given closure in a critical section
506///
507/// NOTE: Clocks implements `Clone`,
508pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> {
509 critical_section::with(|cs| {
510 let c = CLOCKS.borrow(cs).as_ref()?;
511 Some(f(c))
512 })
513}