kea_hal/clocks/ics.rs
1//! ## Internal Clock Source Peripheral
2//!
3//! The ICS peripheral provides clock sources for the MCU. This controls the
4//! low-power oscillator and a 1024x multiplying frequency locked loop (FLL).
5//! This peripheral is documented in Ch. 20 of KEA64 Sub-family reference
6//! manual.
7//!
8//! ### Operational Modes
9//!
10//! The ICS has 7 operation modes. These modes determine the source of the
11//! clock, if the FLL is bypassed, and if the FLL is turned off for power
12//! savings.
13//!
14//! #### FEI - FLL Engaged, Internal Reference Clock Mode
15//!
16//! This is the default mode of operation (on reset). This multiplies the asdf
17//! 31.25kHz IRC by 1024 (in the FLL) to result in a 16MHz system clock. In
18//! order to reach the 40MHz maximum clock speed the sctrim must be
19//! stuffed with 0x1FF to yield (approximately) 39.0625kHz IRC clock, which
20//! in turn gives a 40MHz system clock in this mode. See `Ics::sctrim`
21//! documentation for me detail.
22//!
23//! #### FEE - FLL Engaged, External Reference mode
24//!
25//! This mode is used with external resonators or a clock source. Configuration
26//! of the external source is handled in the OSC module. The OSC module must
27//! output this signal in the range of 31.25 - 39.0625kHz. Like the FEI mode this
28//! will yield a system clock of 32 - 40MHz.
29//!
30//! #### FBI - FLL Bypassed, Interal Reference Clock Mode
31//!
32//! This mode bypasses the FLL (but leaves it on, for reasons?) and runs the
33//! IRC straight into the output. This would set the system clock between
34//! 31.25-39.0625kHz This mode would be used to transition from FBILP mode to FEI
35//! mode, in order to allow the FLL output to stabilize (maximum accuracy)
36//! before the switch.
37//!
38//! #### FBILP - FLL Disabled, Internal Reference Clock Mode
39//!
40//! This mode is just like FBI, but the FLL is turned off to save power. The
41//! FLL needs time to stabilized after restarting. If the FLL needs to be used
42//! again, for best accuracy ICS should be switched to FBI mode and held there
43//! until stabilization, then an alternative timing mode can be used.
44//!
45//!
46//! #### FBE - FLL Bypassed, External Reference Mode
47//!
48//! This mode is like FBI, except the external reference clock as provided by
49//! the OSC module is used instead of the IRC. This mode would be used with a
50//! high frequency crystal or clock. This mode would be used to transition from
51//! FBELP mode to FEE mode, in order to allow the FLL output to stabilize
52//! (maximum accuracy) before the switch.
53//!
54//! #### FBELP - FLL Disabled, External Reference Mode
55//!
56//! This mode is to FBE mode as FBILP is to FBI mode. And they said the SAT/ACT
57//! was a useless test...
58//!
59//! #### STOP - FLL Disabled? No clock output
60//!
61//! This mode is entered whenever the MCU enters the STOP state. ICSIRCLK could
62//! be active if enabled (IRCLKEN set) and allowed to work in stop mode
63//! (IREFSTEN set).
64//!
65//! ### ICSFFCLK - Fixed Frequency Clock
66//!
67//! Only available in the FBE and FBELP modes. ICSFFCLK < ICSOUT/4. Provides
68//! the input of the FLL as an output clock source. Passes through RDIV. IREFS
69//! must be set to select the IRC.
70//!
71//! ## Definitions
72//! - Core clock - ICSOUTCLK / BDIV - clock for the ARM core (HCLK). 40MHz max
73//! - Platform Clock - ICSOUTCLK / BDIV - clocks crossbar switch and NVIC
74//! (FCLK). 40MHz max
75//! - System clock - ICSOUTCLK / BDIV - bus master clock. 40MHz max
76//! - Bus Clock - System clock / BUSDIV - bus slave and peripheral clock. 20MHz
77//! Max
78//! - Flash clock - derived from Bus Clock. 20MHz max
79//! - Debug clock - derived from platform clock. 20MHz max
80//! - SWD clock - CMSIS-DAP clock, external, driven by debugger. 20MHz max
81//! - ICSIRCLK - ICS output of the 32kHZ-ish internal reference clock. Can be
82//! source for RTC or WDOG modules. 31.25-39.0625kHz
83//! - ICSOUTCLK - output of ICS module's output MUX, drives core, platform,
84//! system clocks and more
85//! - ICSFLLCLK - 1024 * input clock
86//! - ICSFFCLK - input to FLL made available to FTM modules
87//! - OSCCLK - aka OSC_OUT - output of external oscillator (OSC) module.
88//! DC-40MHz (bypass), 31.25-39.0625kHz (resonator), 4-20MHz (crystal)
89//! - OSCERCLK - same as OSCCLK, made available to RTC, WDOG, and ADC modules
90//! - LPOCLK - fixed 1kHz output clock, available to RTC or WDOG modules.
91
92use crate::pac::{ICS, OSC};
93use core::marker::PhantomData;
94
95/// Custom Error Types
96pub enum Error {
97 /// Value was not valid for the function it was passed into.
98 InvalidValue,
99
100 /// Pin has already been returned. Can't give it again
101 NoPin,
102}
103
104// Common state-types
105
106/// Clock feature is disabled
107pub struct Stopped;
108
109/// Clock feature is Enabled, but is Disabled on entry to Stop Mode.
110pub struct Running;
111
112/// Clock feature is always Enabled, even in Stop mode.
113pub struct Unstoppable;
114
115/// Grabs ownership of ICS from the PAC.
116pub trait ICSExt {
117 /// ICS struct
118 type Ics;
119 /// grab the Peripheral from PAC;
120 fn split(self) -> Self::Ics;
121}
122
123/// HAL struct for the Internal clock system.
124pub struct Ics {
125 /// The state of the source of the system clock
126 ///
127 /// The ICS defaults to the FEI mode of operation. In this mode, ICSOUT,
128 /// which is the system clock, is
129 /// 16MHz = IRC * FLL * BDIV = 31.25kHz * 1024 / 2; at default values.
130 pub system_clock: SystemClock<IntRefClock, FllEnabled, LpDisabled>,
131
132 /// The state of ICSIRCLK output
133 pub irc_out: IrcOut<DefaultIrcOut>,
134
135 /// The state of the frequency-locked loop monitor
136 pub lock_status: LockStatus<Poll>,
137}
138
139impl ICSExt for ICS {
140 type Ics = Ics;
141 fn split(self) -> Ics {
142 Ics {
143 system_clock: SystemClock {
144 _source: PhantomData,
145 _fll: PhantomData,
146 _low_power: PhantomData,
147 },
148 irc_out: IrcOut { _mode: PhantomData },
149 lock_status: LockStatus { _mode: PhantomData },
150 }
151 }
152}
153
154impl Ics {
155 /// Read the IRC's trim value.
156 ///
157 /// This SCTRIM is used to tweak the frequency of the Internal
158 /// Reference Clock. This factory trimmed value is loaded from
159 /// nonvolatile memory on boot to set the IRC to be 31.25kHZ (yielding
160 /// a 16MHz system clock in FEI mode). Note that this interface
161 /// includes the SCFTRIM bit, which contains the LSB of the value used
162 /// by the ICS module.
163 pub fn sctrim(&self) -> u16 {
164 unsafe {
165 let ics = &(*ICS::ptr());
166 ((ics.c3.read().bits() as u16) << 1) + ics.c4.read().scftrim().bit() as u16
167 }
168 }
169
170 /// Set the IRC's trim value.
171 ///
172 /// This SCTRIM is used to tweak the frequency of the Internal
173 /// Reference Clock. This factory trimmed value is loaded from
174 /// nonvolatile memory on boot to set the IRC to be 31.25kHZ (yielding
175 /// a 16MHz system clock in FEI mode). Note that this interface
176 /// includes the SCFTRIM bit, which contains the LSB of the value used
177 /// by the ICS module.
178 ///
179 /// Write 0x1FF to max out the system clock frequency to (close to)
180 /// 40MHz.
181 ///
182 /// If system_clock configured in FEI mode this should probably wait for
183 /// the FLL to stabilize (LOCK)
184 pub fn set_sctrim(&self, value: u16) -> Result<(), Error> {
185 if value > 0x1FF {
186 return Err(Error::InvalidValue);
187 }
188 unsafe {
189 let ics = &(*ICS::ptr());
190 ics.c3.write(|w| w.bits((value >> 1) as u8));
191 ics.c4.modify(|_, w| w.scftrim().bit((value & 1) == 1));
192 }
193 Ok(())
194 }
195
196 /// Returns which reference clock is currently active.
197 ///
198 /// Indicates the current clock mode, either Internal or External Reference
199 /// Clock 0 = ExtRefClock, 1 = IntRefClock.
200 ///
201 /// This should probably return an Enum value, or the sub-types used in the
202 /// SystemClock struct. For now this just returns the field value until
203 /// this can be rethought.
204 // this should probably also get a better name.
205 // Is this even needed since we know this from the current type of
206 // SystemClock's Source sub-type? The into_whatever methods should be
207 // waiting for this to switch before returning.
208 pub fn internal_reference(&self) -> bool {
209 unsafe { (*ICS::ptr()).s.read().irefst().bit() }
210 }
211
212 /// Returns which clock mode is currently active.
213 ///
214 /// 0 = FllEnabled
215 /// 1 = IntRefClock,FllBypassed (FBI mode)
216 /// 2 = ExtRefClock,FllBypassed (FBE mode)
217 /// 3 = Reserved, not used.
218 ///
219 /// This should probably return an Enum value, or the sub-types used in the
220 /// SystemClock struct. For nwo this just returns the field value until
221 /// this can be rethough
222 // is this even needed? see `internal_reference`
223 pub fn clock_mode(&self) -> u8 {
224 unsafe { (*ICS::ptr()).s.read().clkst().bits() }
225 }
226}
227
228/// struct that represents the state of the System Clock output ICSOUT
229pub struct SystemClock<Source, FLL, LowPower> {
230 _source: PhantomData<Source>,
231 _fll: PhantomData<FLL>,
232 _low_power: PhantomData<LowPower>,
233}
234
235// needed for Copy
236impl<Source, FLL, LowPower> Clone for SystemClock<Source, FLL, LowPower> {
237 fn clone(&self) -> SystemClock<Source, FLL, LowPower> {
238 SystemClock {
239 _source: PhantomData,
240 _fll: PhantomData,
241 _low_power: PhantomData,
242 }
243 }
244}
245
246// Need to implement Copy so we can "move" the SystemClock type around. This is
247// needed to be able to redefine the peripheral.
248impl<Source, FLL, LowPower> Copy for SystemClock<Source, FLL, LowPower> {}
249
250/// Internal Reference Clock source
251pub struct IntRefClock;
252
253/// External Reference Clock source
254pub struct ExtRefClock;
255
256/// Frequency-Locked Loop (1024x mutliplier) Used
257pub struct FllEnabled;
258
259/// FLL bypassed
260///
261/// CLKS mux needs to be set to select the appropriate Internal/External Source
262pub struct FllBypassed;
263
264/// Low Power Mode Enabled
265pub struct LpEnabled;
266
267/// Low Power Mode Disabled
268pub struct LpDisabled;
269
270/// In FBILP mode
271impl SystemClock<IntRefClock, FllBypassed, LpEnabled> {
272 /// Can only transition to FBI mode
273 pub fn into_fbi(self) -> SystemClock<IntRefClock, FllBypassed, LpDisabled> {
274 unsafe {
275 // Disable Low Power (turn on FLL, leave disconnected)
276 (*ICS::ptr()).c2.modify(|_, w| w.lp()._0());
277 }
278 SystemClock {
279 _source: PhantomData,
280 _fll: PhantomData,
281 _low_power: PhantomData,
282 }
283 }
284}
285
286/// In FBELP mode
287impl SystemClock<ExtRefClock, FllBypassed, LpEnabled> {
288 /// Can only transition to FBE mode
289 pub fn into_fbe(self) -> SystemClock<ExtRefClock, FllBypassed, LpDisabled> {
290 unsafe {
291 // Disable Low Power (turn on FLL, leave disconnected)
292 &(*ICS::ptr()).c2.modify(|_, w| w.lp()._0());
293 }
294 SystemClock {
295 _source: PhantomData,
296 _fll: PhantomData,
297 _low_power: PhantomData,
298 }
299 }
300}
301
302impl<Source, Fll> SystemClock<Source, Fll, LpDisabled> {
303 /// Transition from any non-lowpower mode
304 pub fn into_fei(self) -> SystemClock<IntRefClock, FllEnabled, LpDisabled> {
305 unsafe {
306 let ics = &(*ICS::ptr());
307 // may be faster to check if IREFST is set first. @TODO verify
308 // switch IREFS to use IRC
309 ics.c1.modify(|_, w| w.irefs()._1());
310
311 // wait for IREFST to indicate mux is set to IRC
312 while !ics.s.read().irefst().is_1() {
313 cortex_m::asm::nop(); // may not be needed? @TODO verify
314 }
315
316 // Change CLKS to select FLL output
317 ics.c1.modify(|_, w| w.clks()._00());
318 }
319 SystemClock {
320 _source: PhantomData,
321 _fll: PhantomData,
322 _low_power: PhantomData,
323 }
324 }
325
326 /// Transition from any non-lowpower mode
327 ///
328 /// Technically, fbi mode isn't active until CLKST shows it. It's up to the
329 /// caller to decide if they want to wait
330 pub fn into_fbi(self) -> SystemClock<IntRefClock, FllBypassed, LpDisabled> {
331 unsafe {
332 &(*ICS::ptr()).c1.modify(|_, w| w.clks()._01());
333 }
334 SystemClock {
335 _source: PhantomData,
336 _fll: PhantomData,
337 _low_power: PhantomData,
338 }
339 }
340
341 /// Transition from any non-lowpower mode.
342 pub fn into_fee(self) -> SystemClock<ExtRefClock, FllEnabled, LpDisabled> {
343 unsafe {
344 let ics = &(*ICS::ptr());
345
346 // Switch IREFS to use External ref
347 ics.c1.modify(|_, w| w.irefs()._0());
348
349 // Wait for mux to switch
350 while !ics.s.read().irefst().is_0() {
351 cortex_m::asm::nop();
352 }
353
354 // Set CLKS to select FLL output
355 ics.c1.modify(|_, w| w.clks()._00());
356 }
357 SystemClock {
358 _source: PhantomData,
359 _fll: PhantomData,
360 _low_power: PhantomData,
361 }
362 }
363 /// Transition from any non-lowpower mode
364 ///
365 /// Technically, fbe mode isn't active until CLKST shows it. It's up to the
366 /// caller to decide if they want to wait
367 pub fn into_fbe(self) -> SystemClock<ExtRefClock, FllBypassed, LpDisabled> {
368 unsafe {
369 &(*ICS::ptr()).c1.modify(|_, w| w.clks()._10());
370 }
371 SystemClock {
372 _source: PhantomData,
373 _fll: PhantomData,
374 _low_power: PhantomData,
375 }
376 }
377}
378
379/// In FBE or FBI mode, depending on Source
380impl<Source> SystemClock<Source, FllBypassed, LpDisabled> {
381 /// Transition to FBELP or FBILP mode, whichever is the low power version
382 /// of the current mode.
383 ///
384 /// If in FBE mode, calling this method will transition to FBELP.
385 /// Likewise for FBI and FBILP.
386 pub fn into_low_power(self) -> SystemClock<Source, FllBypassed, LpEnabled> {
387 unsafe {
388 &(*ICS::ptr()).c2.modify(|_, w| w.lp()._1());
389 }
390 SystemClock {
391 _source: PhantomData,
392 _fll: PhantomData,
393 _low_power: PhantomData,
394 }
395 }
396}
397
398/// Any Internal mode (cannot be set in FEE, FBE, or FBELP mode)
399impl<Fll, LowPower> SystemClock<IntRefClock, Fll, LowPower> {
400 /// Set the RDIV value
401 ///
402 /// RDIV divides the output of the external reference clock by powers of 2.
403 /// RDIV_OUT = OSC_OUT / (2 ** n + 1)
404 /// The value at reset is 0 (RDIV_OUT = OSC_OUT)
405 ///
406 /// Note that per RDIV definition (pg272 in ICS_C1 of KEA64 ref man) this
407 /// cannot be changed while the SystemClock is in FEE or FBE mode. To use
408 /// set this first, then transition to FBE or FEE mode.
409 pub fn rdiv(&self, div: u8) -> Result<(), Error> {
410 if div > 0b111 {
411 return Err(Error::InvalidValue);
412 }
413
414 // values 0b110 & 0b111 are undefined / reserved. When osc is in high
415 // range mode.
416 if unsafe { (*OSC::ptr()).cr.read().range().is_1() } && (div >= 0b110) {
417 return Err(Error::InvalidValue);
418 }
419
420 unsafe {
421 &(*ICS::ptr()).c1.modify(|_, w| w.rdiv().bits(div));
422 }
423 Ok(())
424 }
425}
426
427/// Any External mode (cannot be set in FEI, FBI, or FBILP)
428impl<Fll, LowPower> SystemClock<ExtRefClock, Fll, LowPower> {
429 /// Enable/Disable Clock Monitor.
430 ///
431 /// When enabled, the MCU will reset if it loses the external clock signal
432 pub fn clock_monitor(&self, enable: bool) {
433 unsafe {
434 (*ICS::ptr()).c4.modify(|_, w| w.cme().bit(enable));
435 }
436 }
437}
438
439/// For any state of SystemClock
440impl<Source, Fll, LowPower> SystemClock<Source, Fll, LowPower> {
441 /// Set the BDIV value
442 ///
443 /// BDIV divides the output of the ICS (in any mode) by powers of 2.
444 /// ICSOUT = CLKSoutput / (2 ** n + 1)
445 /// The value at reset is 1 (ICSOUT = CLKSoutput / 2)
446 ///
447 /// Note that by default the bus clock and system clock run at the same
448 /// multiplier, but the max bus clock is 20MHz. SIM_BUSDIV must be set
449 /// appropriately to ensure this limit is not exceeded before BDIV is set
450 /// to 1.
451 // Once SIM is implemented, add checks to prevent overclocking the bus.
452 pub fn set_bdiv(self, div: u8) -> Result<(), Error> {
453 if div > 0b111 {
454 return Err(Error::InvalidValue);
455 }
456
457 unsafe {
458 &(*ICS::ptr()).c2.modify(|_, w| w.bdiv().bits(div));
459 }
460 Ok(())
461 }
462}
463
464/// State of ICSIRCLK (output of the ICS module's Internal Reference CLocK)
465pub struct IrcOut<MODE> {
466 _mode: PhantomData<MODE>,
467}
468
469/// ICSIRCLK defaults to Disabled. Uses Stopped/Running/Unstoppable types
470pub type DefaultIrcOut = Stopped;
471
472impl IrcOut<Stopped> {
473 /// Enable the Internal Reference clock output on ICSIRCLK
474 pub fn into_running(self) -> IrcOut<Running> {
475 unsafe {
476 (*ICS::ptr()).c1.modify(|_, w| w.irclken()._1());
477 }
478 IrcOut { _mode: PhantomData }
479 }
480}
481
482impl IrcOut<Running> {
483 /// Disable the Internal Reference Clock output on ICSIRCLK
484 pub fn into_stopped(self) -> IrcOut<Stopped> {
485 unsafe {
486 (*ICS::ptr()).c1.modify(|_, w| w.irclken()._0());
487 }
488 IrcOut { _mode: PhantomData }
489 }
490
491 /// Allow Internal reference clock and ICSIRCLK to continue running during
492 /// Stop mode.
493 ///
494 /// This allows other peripherals such as the RTC and Watchdog to continue
495 /// using this clock. This mode is also recommended if the systemclock is
496 /// using FEI, FBI, of FBILP and needs to restart quickly after Stop mode.
497 pub fn into_unstoppable(self) -> IrcOut<Unstoppable> {
498 unsafe {
499 (*ICS::ptr()).c1.modify(|_, w| w.irefsten()._1());
500 }
501 IrcOut { _mode: PhantomData }
502 }
503}
504
505impl IrcOut<Unstoppable> {
506 /// make stoppable, but keep ICSIRCLK running.
507 pub fn into_running(self) -> IrcOut<Running> {
508 unsafe {
509 (*ICS::ptr()).c1.modify(|_, w| w.irefsten()._0());
510 }
511 IrcOut { _mode: PhantomData }
512 }
513
514 /// Make stoppable and disable ICSIRCLK.
515 pub fn into_stopped(self) -> IrcOut<Stopped> {
516 let temp = self.into_running();
517 temp.into_stopped()
518 }
519}
520
521/// Monitor the frequency-locked loop for loss of lock
522///
523/// This may make more sense to be a method of SystemClock, so that it can
524/// only be used while the FLL is active. The default mode is polled (Poll).
525pub struct LockStatus<MODE> {
526 _mode: PhantomData<MODE>,
527}
528
529/// FLL monitor must be polled to determine if lock has been lost.
530pub struct Poll;
531
532/// FLL monitor generates an interrupt when lock is lossed
533pub struct Interrupt;
534
535/// Any state of lock status
536impl<MODE> LockStatus<MODE> {
537 /// Is the FLL currently locked?
538 pub fn locked(&self) -> bool {
539 unsafe { (*ICS::ptr()).s.read().lock().bit() }
540 }
541
542 /// Has the FLL lost its lock since the last time it was acknowledged
543 pub fn lock_lost(&self) -> bool {
544 let ics = unsafe { &(*ICS::ptr()) };
545 let ret_val: bool = ics.s.read().lols().bit();
546
547 // Acknowledge the loss of lock (if happened)
548 if ret_val {
549 // write 1 to clear
550 ics.s.modify(|_, w| w.lols()._1());
551 }
552
553 ret_val
554 }
555}
556
557impl LockStatus<Poll> {
558 /// Trigger an Interrupt on loss of lock
559 ///
560 /// Polling is still functional in the Interrupt state, if needed to verify
561 /// no loss occurred or current state.
562 pub fn into_interrupt(self) -> LockStatus<Interrupt> {
563 unsafe {
564 (*ICS::ptr()).c4.modify(|_, w| w.lolie()._1());
565 }
566 LockStatus { _mode: PhantomData }
567 }
568}
569
570impl LockStatus<Interrupt> {
571 /// Disable interrupt on loss of lock.
572 pub fn into_poll(self) -> LockStatus<Poll> {
573 unsafe {
574 (*ICS::ptr()).c4.modify(|_, w| w.lolie()._0());
575 }
576 LockStatus { _mode: PhantomData }
577 }
578}