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}