diff options
| author | James Munns <[email protected]> | 2025-11-18 14:19:09 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-18 14:19:09 +0100 |
| commit | 5b1149a52dbec9e3bdd10dc341dc0751ab4798a6 (patch) | |
| tree | 3ff7098471cf660a54a707464a0e2feb2080b09e /src/clocks | |
| parent | 62e297c130ac26afe4d7d5752bb79709bd370e39 (diff) | |
| parent | c8942aec2478ff077b55da0e86801f8a6a88a7de (diff) | |
Merge pull request #11 from jamesmunns/james/impl-clocks
Implement initial `clock` driver.
This PR introduces an initial two-phase clock driver system:
1. The first stage is responsible for initializing the core/system clocks at the time of `embassy_mcxa::init()`
2. The second stage is done on creation of peripherals
This work is limited to currently used clocks and peripherals, but has room for expansion for later peripherals. This model is based on the preliminary refactoring performed for the `embassy-imxrt` crate.
Diffstat (limited to 'src/clocks')
| -rw-r--r-- | src/clocks/config.rs | 199 | ||||
| -rw-r--r-- | src/clocks/mod.rs | 887 | ||||
| -rw-r--r-- | src/clocks/periph_helpers.rs | 393 |
3 files changed, 1479 insertions, 0 deletions
diff --git a/src/clocks/config.rs b/src/clocks/config.rs new file mode 100644 index 000000000..a517afcca --- /dev/null +++ b/src/clocks/config.rs | |||
| @@ -0,0 +1,199 @@ | |||
| 1 | //! Clock Configuration | ||
| 2 | //! | ||
| 3 | //! This module holds configuration types used for the system clocks. For | ||
| 4 | //! configuration of individual peripherals, see [`super::periph_helpers`]. | ||
| 5 | |||
| 6 | use super::PoweredClock; | ||
| 7 | |||
| 8 | /// This type represents a divider in the range 1..=256. | ||
| 9 | /// | ||
| 10 | /// At a hardware level, this is an 8-bit register from 0..=255, | ||
| 11 | /// which adds one. | ||
| 12 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 13 | pub struct Div8(pub(super) u8); | ||
| 14 | |||
| 15 | impl Div8 { | ||
| 16 | /// Store a "raw" divisor value that will divide the source by | ||
| 17 | /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source | ||
| 18 | /// by 1, and `Div8::from_raw(255)` will divide the source by | ||
| 19 | /// 256. | ||
| 20 | pub const fn from_raw(n: u8) -> Self { | ||
| 21 | Self(n) | ||
| 22 | } | ||
| 23 | |||
| 24 | /// Store a specific divisor value that will divide the source | ||
| 25 | /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source | ||
| 26 | /// by 1, and `Div8::from_divisor(256)` will divide the source | ||
| 27 | /// by 256. | ||
| 28 | /// | ||
| 29 | /// Will return `None` if `n` is not in the range `1..=256`. | ||
| 30 | /// Consider [`Self::from_raw`] for an infallible version. | ||
| 31 | pub const fn from_divisor(n: u16) -> Option<Self> { | ||
| 32 | let Some(n) = n.checked_sub(1) else { | ||
| 33 | return None; | ||
| 34 | }; | ||
| 35 | if n > (u8::MAX as u16) { | ||
| 36 | return None; | ||
| 37 | } | ||
| 38 | Some(Self(n as u8)) | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Convert into "raw" bits form | ||
| 42 | #[inline(always)] | ||
| 43 | pub const fn into_bits(self) -> u8 { | ||
| 44 | self.0 | ||
| 45 | } | ||
| 46 | |||
| 47 | /// Convert into "divisor" form, as a u32 for convenient frequency math | ||
| 48 | #[inline(always)] | ||
| 49 | pub const fn into_divisor(self) -> u32 { | ||
| 50 | self.0 as u32 + 1 | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// ```text | ||
| 55 | /// ┌─────────────────────────────────────────────────────────┐ | ||
| 56 | /// │ │ | ||
| 57 | /// │ ┌───────────┐ clk_out ┌─────────┐ │ | ||
| 58 | /// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ | ||
| 59 | /// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ | ||
| 60 | /// EXTAL ──────┼──▷│ │───────────▷│ │ │ | ||
| 61 | /// │ └───────────┘ └─────────┘ │ | ||
| 62 | /// │ │ | ||
| 63 | /// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ | ||
| 64 | /// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ | ||
| 65 | /// │ │ │ │ ├────┤ clk_45m │ | ||
| 66 | /// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ | ||
| 67 | /// │ └───────────┘ └────┘ │ | ||
| 68 | /// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ | ||
| 69 | /// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ | ||
| 70 | /// │ │ │ │ ├────┤ clk_1m │ | ||
| 71 | /// │ │ │ └─────▷│1/12│────────────────────┼──────▷ | ||
| 72 | /// │ └───────────┘ └────┘ │ | ||
| 73 | /// │ │ | ||
| 74 | /// │ ┌──────────┐ │ | ||
| 75 | /// │ │000 │ │ | ||
| 76 | /// │ clk_in │ │ │ | ||
| 77 | /// │ ───────────────▷│001 │ │ | ||
| 78 | /// │ fro_12m │ │ │ | ||
| 79 | /// │ ───────────────▷│010 │ │ | ||
| 80 | /// │ fro_hf_root │ │ │ | ||
| 81 | /// │ ───────────────▷│011 │ main_clk │ | ||
| 82 | /// │ │ │───────────────────────────┼──────▷ | ||
| 83 | /// clk_16k ──────┼─────────────────▷│100 │ │ | ||
| 84 | /// │ none │ │ │ | ||
| 85 | /// │ ───────────────▷│101 │ │ | ||
| 86 | /// │ pll1_clk │ │ │ | ||
| 87 | /// │ ───────────────▷│110 │ │ | ||
| 88 | /// │ none │ │ │ | ||
| 89 | /// │ ───────────────▷│111 │ │ | ||
| 90 | /// │ └──────────┘ │ | ||
| 91 | /// │ ▲ │ | ||
| 92 | /// │ │ │ | ||
| 93 | /// │ SCG SCS │ | ||
| 94 | /// │ SCG-Lite │ | ||
| 95 | /// └─────────────────────────────────────────────────────────┘ | ||
| 96 | /// | ||
| 97 | /// | ||
| 98 | /// clk_in ┌─────┐ | ||
| 99 | /// ───────────────▷│00 │ | ||
| 100 | /// clk_45m │ │ | ||
| 101 | /// ───────────────▷│01 │ ┌───────────┐ pll1_clk | ||
| 102 | /// none │ │─────▷│ SPLL │───────────────▷ | ||
| 103 | /// ───────────────▷│10 │ └───────────┘ | ||
| 104 | /// fro_12m │ │ | ||
| 105 | /// ───────────────▷│11 │ | ||
| 106 | /// └─────┘ | ||
| 107 | /// ``` | ||
| 108 | #[non_exhaustive] | ||
| 109 | pub struct ClocksConfig { | ||
| 110 | /// FIRC, FRO180, 45/60/90/180M clock source | ||
| 111 | pub firc: Option<FircConfig>, | ||
| 112 | /// SIRC, FRO12M, clk_12m clock source | ||
| 113 | // NOTE: I don't think we *can* disable the SIRC? | ||
| 114 | pub sirc: SircConfig, | ||
| 115 | /// FRO16K clock source | ||
| 116 | pub fro16k: Option<Fro16KConfig>, | ||
| 117 | } | ||
| 118 | |||
| 119 | // FIRC/FRO180M | ||
| 120 | |||
| 121 | /// ```text | ||
| 122 | /// ┌───────────┐ fro_hf_root ┌────┐ fro_hf | ||
| 123 | /// │ FRO180M ├───────┬─────▷│GATE│──────────▷ | ||
| 124 | /// │ │ │ ├────┤ clk_45m | ||
| 125 | /// │ │ └─────▷│GATE│──────────▷ | ||
| 126 | /// └───────────┘ └────┘ | ||
| 127 | /// ``` | ||
| 128 | #[non_exhaustive] | ||
| 129 | pub struct FircConfig { | ||
| 130 | /// Selected clock frequency | ||
| 131 | pub frequency: FircFreqSel, | ||
| 132 | /// Selected power state of the clock | ||
| 133 | pub power: PoweredClock, | ||
| 134 | /// Is the "fro_hf" gated clock enabled? | ||
| 135 | pub fro_hf_enabled: bool, | ||
| 136 | /// Is the "clk_45m" gated clock enabled? | ||
| 137 | pub clk_45m_enabled: bool, | ||
| 138 | /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! | ||
| 139 | pub fro_hf_div: Option<Div8>, | ||
| 140 | } | ||
| 141 | |||
| 142 | /// Selected FIRC frequency | ||
| 143 | pub enum FircFreqSel { | ||
| 144 | /// 45MHz Output | ||
| 145 | Mhz45, | ||
| 146 | /// 60MHz Output | ||
| 147 | Mhz60, | ||
| 148 | /// 90MHz Output | ||
| 149 | Mhz90, | ||
| 150 | /// 180MHz Output | ||
| 151 | Mhz180, | ||
| 152 | } | ||
| 153 | |||
| 154 | // SIRC/FRO12M | ||
| 155 | |||
| 156 | /// ```text | ||
| 157 | /// ┌───────────┐ fro_12m_root ┌────┐ fro_12m | ||
| 158 | /// │ FRO12M │────────┬─────▷│ CG │──────────▷ | ||
| 159 | /// │ │ │ ├────┤ clk_1m | ||
| 160 | /// │ │ └─────▷│1/12│──────────▷ | ||
| 161 | /// └───────────┘ └────┘ | ||
| 162 | /// ``` | ||
| 163 | #[non_exhaustive] | ||
| 164 | pub struct SircConfig { | ||
| 165 | pub power: PoweredClock, | ||
| 166 | // peripheral output, aka sirc_12mhz | ||
| 167 | pub fro_12m_enabled: bool, | ||
| 168 | /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! | ||
| 169 | pub fro_lf_div: Option<Div8>, | ||
| 170 | } | ||
| 171 | |||
| 172 | #[non_exhaustive] | ||
| 173 | pub struct Fro16KConfig { | ||
| 174 | pub vsys_domain_active: bool, | ||
| 175 | pub vdd_core_domain_active: bool, | ||
| 176 | } | ||
| 177 | |||
| 178 | impl Default for ClocksConfig { | ||
| 179 | fn default() -> Self { | ||
| 180 | Self { | ||
| 181 | firc: Some(FircConfig { | ||
| 182 | frequency: FircFreqSel::Mhz45, | ||
| 183 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 184 | fro_hf_enabled: true, | ||
| 185 | clk_45m_enabled: true, | ||
| 186 | fro_hf_div: None, | ||
| 187 | }), | ||
| 188 | sirc: SircConfig { | ||
| 189 | power: PoweredClock::AlwaysEnabled, | ||
| 190 | fro_12m_enabled: true, | ||
| 191 | fro_lf_div: None, | ||
| 192 | }, | ||
| 193 | fro16k: Some(Fro16KConfig { | ||
| 194 | vsys_domain_active: true, | ||
| 195 | vdd_core_domain_active: true, | ||
| 196 | }), | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs new file mode 100644 index 000000000..e02840592 --- /dev/null +++ b/src/clocks/mod.rs | |||
| @@ -0,0 +1,887 @@ | |||
| 1 | //! # Clock Module | ||
| 2 | //! | ||
| 3 | //! For the MCX-A, we separate clock and peripheral control into two main stages: | ||
| 4 | //! | ||
| 5 | //! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the | ||
| 6 | //! core system clocks, including external and internal oscillators. This | ||
| 7 | //! configuration is then largely static for the duration of the program. | ||
| 8 | //! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver | ||
| 9 | //! is responsible for two main things: | ||
| 10 | //! * Ensuring that any required "upstream" core system clocks necessary for | ||
| 11 | //! clocking the peripheral is active and configured to a reasonable value | ||
| 12 | //! * Enabling the clock gates for that peripheral, and resetting the peripheral | ||
| 13 | //! | ||
| 14 | //! From a user perspective, only step 1 is visible. Step 2 is automatically handled | ||
| 15 | //! by HAL drivers, using interfaces defined in this module. | ||
| 16 | //! | ||
| 17 | //! It is also possible to *view* the state of the clock configuration after [`init()`] | ||
| 18 | //! has been called, using the [`with_clocks()`] function, which provides a view of the | ||
| 19 | //! [`Clocks`] structure. | ||
| 20 | //! | ||
| 21 | //! ## For HAL driver implementors | ||
| 22 | //! | ||
| 23 | //! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or | ||
| 24 | //! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This | ||
| 25 | //! selection, as well as often any pre-scaler division from that source clock, is made | ||
| 26 | //! through MRCC registers. | ||
| 27 | //! | ||
| 28 | //! Any peripheral that is controlled through the MRCC register can automatically implement | ||
| 29 | //! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need | ||
| 30 | //! to define the configuration surface and steps necessary to fully configure that peripheral | ||
| 31 | //! from a clocks perspective by: | ||
| 32 | //! | ||
| 33 | //! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects | ||
| 34 | //! or divisions available to the HAL driver | ||
| 35 | //! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the | ||
| 36 | //! necessary input clocks are reasonable | ||
| 37 | |||
| 38 | use core::cell::RefCell; | ||
| 39 | |||
| 40 | use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; | ||
| 41 | use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; | ||
| 42 | use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; | ||
| 43 | use periph_helpers::SPConfHelper; | ||
| 44 | |||
| 45 | use crate::pac; | ||
| 46 | pub mod config; | ||
| 47 | pub mod periph_helpers; | ||
| 48 | |||
| 49 | // | ||
| 50 | // Statics/Consts | ||
| 51 | // | ||
| 52 | |||
| 53 | /// The state of system core clocks. | ||
| 54 | /// | ||
| 55 | /// Initialized by [`init()`], and then unchanged for the remainder of the program. | ||
| 56 | static CLOCKS: critical_section::Mutex<RefCell<Option<Clocks>>> = critical_section::Mutex::new(RefCell::new(None)); | ||
| 57 | |||
| 58 | // | ||
| 59 | // Free functions | ||
| 60 | // | ||
| 61 | |||
| 62 | /// Initialize the core system clocks with the given [`ClocksConfig`]. | ||
| 63 | /// | ||
| 64 | /// This function should be called EXACTLY once at start-up, usually via a | ||
| 65 | /// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will | ||
| 66 | /// return an error. | ||
| 67 | pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | ||
| 68 | critical_section::with(|cs| { | ||
| 69 | if CLOCKS.borrow_ref(cs).is_some() { | ||
| 70 | Err(ClockError::AlreadyInitialized) | ||
| 71 | } else { | ||
| 72 | Ok(()) | ||
| 73 | } | ||
| 74 | })?; | ||
| 75 | |||
| 76 | let mut clocks = Clocks::default(); | ||
| 77 | let mut operator = ClockOperator { | ||
| 78 | clocks: &mut clocks, | ||
| 79 | config: &settings, | ||
| 80 | |||
| 81 | _mrcc0: unsafe { pac::Mrcc0::steal() }, | ||
| 82 | scg0: unsafe { pac::Scg0::steal() }, | ||
| 83 | syscon: unsafe { pac::Syscon::steal() }, | ||
| 84 | vbat0: unsafe { pac::Vbat0::steal() }, | ||
| 85 | }; | ||
| 86 | |||
| 87 | operator.configure_firc_clocks()?; | ||
| 88 | operator.configure_sirc_clocks()?; | ||
| 89 | operator.configure_fro16k_clocks()?; | ||
| 90 | |||
| 91 | // For now, just use FIRC as the main/cpu clock, which should already be | ||
| 92 | // the case on reset | ||
| 93 | assert!(operator.scg0.rccr().read().scs().is_firc()); | ||
| 94 | assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); | ||
| 95 | operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap()); | ||
| 96 | |||
| 97 | critical_section::with(|cs| { | ||
| 98 | let mut clks = CLOCKS.borrow_ref_mut(cs); | ||
| 99 | assert!(clks.is_none(), "Clock setup race!"); | ||
| 100 | *clks = Some(clocks); | ||
| 101 | }); | ||
| 102 | |||
| 103 | Ok(()) | ||
| 104 | } | ||
| 105 | |||
| 106 | /// Obtain the full clocks structure, calling the given closure in a critical section. | ||
| 107 | /// | ||
| 108 | /// The given closure will be called with read-only access to the state of the system | ||
| 109 | /// clocks. This can be used to query and return the state of a given clock. | ||
| 110 | /// | ||
| 111 | /// As the caller's closure will be called in a critical section, care must be taken | ||
| 112 | /// not to block or cause any other undue delays while accessing. | ||
| 113 | /// | ||
| 114 | /// Calls to this function will not succeed until after a successful call to `init()`, | ||
| 115 | /// and will always return None. | ||
| 116 | pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> { | ||
| 117 | critical_section::with(|cs| { | ||
| 118 | let c = CLOCKS.borrow_ref(cs); | ||
| 119 | let c = c.as_ref()?; | ||
| 120 | Some(f(c)) | ||
| 121 | }) | ||
| 122 | } | ||
| 123 | |||
| 124 | // | ||
| 125 | // Structs/Enums | ||
| 126 | // | ||
| 127 | |||
| 128 | /// The `Clocks` structure contains the initialized state of the core system clocks | ||
| 129 | /// | ||
| 130 | /// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function | ||
| 131 | /// at boot time. | ||
| 132 | #[derive(Default, Debug, Clone)] | ||
| 133 | #[non_exhaustive] | ||
| 134 | pub struct Clocks { | ||
| 135 | /// The `clk_in` is a clock provided by an external oscillator | ||
| 136 | pub clk_in: Option<Clock>, | ||
| 137 | |||
| 138 | // FRO180M stuff | ||
| 139 | // | ||
| 140 | /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator | ||
| 141 | /// | ||
| 142 | /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, | ||
| 143 | /// and `fro_hf_div`. | ||
| 144 | pub fro_hf_root: Option<Clock>, | ||
| 145 | |||
| 146 | /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. | ||
| 147 | pub fro_hf: Option<Clock>, | ||
| 148 | |||
| 149 | /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. | ||
| 150 | pub clk_45m: Option<Clock>, | ||
| 151 | |||
| 152 | /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. | ||
| 153 | pub fro_hf_div: Option<Clock>, | ||
| 154 | |||
| 155 | // | ||
| 156 | // End FRO180M | ||
| 157 | |||
| 158 | // FRO12M stuff | ||
| 159 | // | ||
| 160 | /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator | ||
| 161 | /// | ||
| 162 | /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, | ||
| 163 | /// `and `fro_lf_div`. | ||
| 164 | pub fro_12m_root: Option<Clock>, | ||
| 165 | |||
| 166 | /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. | ||
| 167 | pub fro_12m: Option<Clock>, | ||
| 168 | |||
| 169 | /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` | ||
| 170 | pub clk_1m: Option<Clock>, | ||
| 171 | |||
| 172 | /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` | ||
| 173 | pub fro_lf_div: Option<Clock>, | ||
| 174 | // | ||
| 175 | // End FRO12M stuff | ||
| 176 | /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 177 | /// | ||
| 178 | /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in | ||
| 179 | /// the system domain, such as the CMP and RTC. | ||
| 180 | pub clk_16k_vsys: Option<Clock>, | ||
| 181 | |||
| 182 | /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 183 | /// | ||
| 184 | /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in | ||
| 185 | /// the VDD Core domain, such as the OSTimer or LPUarts. | ||
| 186 | pub clk_16k_vdd_core: Option<Clock>, | ||
| 187 | |||
| 188 | /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some | ||
| 189 | /// peripherals. | ||
| 190 | pub main_clk: Option<Clock>, | ||
| 191 | |||
| 192 | /// `pll1_clk` is the output of the main system PLL, `pll1`. | ||
| 193 | pub pll1_clk: Option<Clock>, | ||
| 194 | } | ||
| 195 | |||
| 196 | /// `ClockError` is the main error returned when configuring or checking clock state | ||
| 197 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 198 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 199 | #[non_exhaustive] | ||
| 200 | pub enum ClockError { | ||
| 201 | /// The system clocks were never initialized by calling [`init()`] | ||
| 202 | NeverInitialized, | ||
| 203 | /// The [`init()`] function was called more than once | ||
| 204 | AlreadyInitialized, | ||
| 205 | /// The requested configuration was not possible to fulfill, as the system clocks | ||
| 206 | /// were not configured in a compatible way | ||
| 207 | BadConfig { clock: &'static str, reason: &'static str }, | ||
| 208 | /// The requested configuration was not possible to fulfill, as the required system | ||
| 209 | /// clocks have not yet been implemented. | ||
| 210 | NotImplemented { clock: &'static str }, | ||
| 211 | /// The requested peripheral could not be configured, as the steps necessary to | ||
| 212 | /// enable it have not yet been implemented. | ||
| 213 | UnimplementedConfig, | ||
| 214 | } | ||
| 215 | |||
| 216 | /// Information regarding a system clock | ||
| 217 | #[derive(Debug, Clone)] | ||
| 218 | pub struct Clock { | ||
| 219 | /// The frequency, in Hz, of the given clock | ||
| 220 | pub frequency: u32, | ||
| 221 | /// The power state of the clock, e.g. whether it is active in deep sleep mode | ||
| 222 | /// or not. | ||
| 223 | pub power: PoweredClock, | ||
| 224 | } | ||
| 225 | |||
| 226 | /// The power state of a given clock. | ||
| 227 | /// | ||
| 228 | /// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep | ||
| 229 | /// mode will be stopped. This means that any downstream usage, e.g. by peripherals, | ||
| 230 | /// will also stop. | ||
| 231 | /// | ||
| 232 | /// In the future, we will provide an API for entering Deep Sleep, and if there are | ||
| 233 | /// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into | ||
| 234 | /// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. | ||
| 235 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 236 | pub enum PoweredClock { | ||
| 237 | /// The given clock will NOT continue running in Deep Sleep mode | ||
| 238 | NormalEnabledDeepSleepDisabled, | ||
| 239 | /// The given clock WILL continue running in Deep Sleep mode | ||
| 240 | AlwaysEnabled, | ||
| 241 | } | ||
| 242 | |||
| 243 | /// The ClockOperator is a private helper type that contains the methods used | ||
| 244 | /// during system clock initialization. | ||
| 245 | /// | ||
| 246 | /// # SAFETY | ||
| 247 | /// | ||
| 248 | /// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, | ||
| 249 | /// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. | ||
| 250 | struct ClockOperator<'a> { | ||
| 251 | /// A mutable reference to the current state of system clocks | ||
| 252 | clocks: &'a mut Clocks, | ||
| 253 | /// A reference to the requested configuration provided by the caller of [`init()`] | ||
| 254 | config: &'a ClocksConfig, | ||
| 255 | |||
| 256 | // We hold on to stolen peripherals | ||
| 257 | _mrcc0: pac::Mrcc0, | ||
| 258 | scg0: pac::Scg0, | ||
| 259 | syscon: pac::Syscon, | ||
| 260 | vbat0: pac::Vbat0, | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | ||
| 264 | pub trait Gate { | ||
| 265 | type MrccPeriphConfig: SPConfHelper; | ||
| 266 | |||
| 267 | /// Enable the clock gate. | ||
| 268 | /// | ||
| 269 | /// # SAFETY | ||
| 270 | /// | ||
| 271 | /// The current peripheral must be disabled prior to calling this method | ||
| 272 | unsafe fn enable_clock(); | ||
| 273 | |||
| 274 | /// Disable the clock gate. | ||
| 275 | /// | ||
| 276 | /// # SAFETY | ||
| 277 | /// | ||
| 278 | /// There must be no active user of this peripheral when calling this method | ||
| 279 | unsafe fn disable_clock(); | ||
| 280 | |||
| 281 | /// Drive the peripheral into reset. | ||
| 282 | /// | ||
| 283 | /// # SAFETY | ||
| 284 | /// | ||
| 285 | /// There must be no active user of this peripheral when calling this method | ||
| 286 | unsafe fn assert_reset(); | ||
| 287 | |||
| 288 | /// Drive the peripheral out of reset. | ||
| 289 | /// | ||
| 290 | /// # SAFETY | ||
| 291 | /// | ||
| 292 | /// There must be no active user of this peripheral when calling this method | ||
| 293 | unsafe fn release_reset(); | ||
| 294 | |||
| 295 | /// Return whether the clock gate for this peripheral is currently enabled. | ||
| 296 | fn is_clock_enabled() -> bool; | ||
| 297 | |||
| 298 | /// Return whether the peripheral is currently held in reset. | ||
| 299 | fn is_reset_released() -> bool; | ||
| 300 | } | ||
| 301 | |||
| 302 | /// This is the primary helper method HAL drivers are expected to call when creating | ||
| 303 | /// an instance of the peripheral. | ||
| 304 | /// | ||
| 305 | /// This method: | ||
| 306 | /// | ||
| 307 | /// 1. Enables the MRCC clock gate for this peripheral | ||
| 308 | /// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error | ||
| 309 | /// and re-disabling the peripheral if this fails. | ||
| 310 | /// 3. Pulses the MRCC reset line, to reset the peripheral to the default state | ||
| 311 | /// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account | ||
| 312 | /// the selected upstream clock, as well as any division specified by `cfg`. | ||
| 313 | /// | ||
| 314 | /// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method | ||
| 315 | /// may return `Ok(0)`. In the future, this might be updated to return the correct | ||
| 316 | /// "ambient" clock, e.g. the AHB/APB frequency. | ||
| 317 | /// | ||
| 318 | /// # SAFETY | ||
| 319 | /// | ||
| 320 | /// This peripheral must not yet be in use prior to calling `enable_and_reset`. | ||
| 321 | #[inline] | ||
| 322 | pub unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | ||
| 323 | let freq = enable::<G>(cfg).inspect_err(|_| disable::<G>())?; | ||
| 324 | pulse_reset::<G>(); | ||
| 325 | Ok(freq) | ||
| 326 | } | ||
| 327 | |||
| 328 | /// Enable the clock gate for the given peripheral. | ||
| 329 | /// | ||
| 330 | /// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need | ||
| 331 | /// to control the duration of the pulse more directly. | ||
| 332 | /// | ||
| 333 | /// # SAFETY | ||
| 334 | /// | ||
| 335 | /// This peripheral must not yet be in use prior to calling `enable`. | ||
| 336 | #[inline] | ||
| 337 | pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | ||
| 338 | G::enable_clock(); | ||
| 339 | while !G::is_clock_enabled() {} | ||
| 340 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | ||
| 341 | |||
| 342 | let freq = critical_section::with(|cs| { | ||
| 343 | let clocks = CLOCKS.borrow_ref(cs); | ||
| 344 | let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; | ||
| 345 | cfg.post_enable_config(clocks) | ||
| 346 | }); | ||
| 347 | |||
| 348 | freq.inspect_err(|_e| { | ||
| 349 | G::disable_clock(); | ||
| 350 | }) | ||
| 351 | } | ||
| 352 | |||
| 353 | /// Disable the clock gate for the given peripheral. | ||
| 354 | /// | ||
| 355 | /// # SAFETY | ||
| 356 | /// | ||
| 357 | /// This peripheral must no longer be in use prior to calling `enable`. | ||
| 358 | #[allow(dead_code)] | ||
| 359 | #[inline] | ||
| 360 | pub unsafe fn disable<G: Gate>() { | ||
| 361 | G::disable_clock(); | ||
| 362 | } | ||
| 363 | |||
| 364 | /// Check whether a gate is currently enabled. | ||
| 365 | #[allow(dead_code)] | ||
| 366 | #[inline] | ||
| 367 | pub fn is_clock_enabled<G: Gate>() -> bool { | ||
| 368 | G::is_clock_enabled() | ||
| 369 | } | ||
| 370 | |||
| 371 | /// Release a reset line for the given peripheral set. | ||
| 372 | /// | ||
| 373 | /// Prefer [`enable_and_reset`]. | ||
| 374 | /// | ||
| 375 | /// # SAFETY | ||
| 376 | /// | ||
| 377 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 378 | #[inline] | ||
| 379 | pub unsafe fn release_reset<G: Gate>() { | ||
| 380 | G::release_reset(); | ||
| 381 | } | ||
| 382 | |||
| 383 | /// Assert a reset line for the given peripheral set. | ||
| 384 | /// | ||
| 385 | /// Prefer [`enable_and_reset`]. | ||
| 386 | /// | ||
| 387 | /// # SAFETY | ||
| 388 | /// | ||
| 389 | /// This peripheral must not yet be in use prior to calling `assert_reset`. | ||
| 390 | #[inline] | ||
| 391 | pub unsafe fn assert_reset<G: Gate>() { | ||
| 392 | G::assert_reset(); | ||
| 393 | } | ||
| 394 | |||
| 395 | /// Check whether the peripheral is held in reset. | ||
| 396 | #[inline] | ||
| 397 | pub unsafe fn is_reset_released<G: Gate>() -> bool { | ||
| 398 | G::is_reset_released() | ||
| 399 | } | ||
| 400 | |||
| 401 | /// Pulse a reset line (assert then release) with a short delay. | ||
| 402 | /// | ||
| 403 | /// Prefer [`enable_and_reset`]. | ||
| 404 | /// | ||
| 405 | /// # SAFETY | ||
| 406 | /// | ||
| 407 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 408 | #[inline] | ||
| 409 | pub unsafe fn pulse_reset<G: Gate>() { | ||
| 410 | G::assert_reset(); | ||
| 411 | cortex_m::asm::nop(); | ||
| 412 | cortex_m::asm::nop(); | ||
| 413 | G::release_reset(); | ||
| 414 | } | ||
| 415 | |||
| 416 | // | ||
| 417 | // `impl`s for structs/enums | ||
| 418 | // | ||
| 419 | |||
| 420 | /// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". | ||
| 421 | /// | ||
| 422 | /// These methods are intended to be used by HAL peripheral implementors to ensure that their | ||
| 423 | /// selected clocks are active at a suitable level at time of construction. These methods | ||
| 424 | /// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. | ||
| 425 | impl Clocks { | ||
| 426 | /// Ensure the `fro_lf_div` clock is active and valid at the given power state. | ||
| 427 | pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 428 | let Some(clk) = self.fro_lf_div.as_ref() else { | ||
| 429 | return Err(ClockError::BadConfig { | ||
| 430 | clock: "fro_lf_div", | ||
| 431 | reason: "required but not active", | ||
| 432 | }); | ||
| 433 | }; | ||
| 434 | if !clk.power.meets_requirement_of(at_level) { | ||
| 435 | return Err(ClockError::BadConfig { | ||
| 436 | clock: "fro_lf_div", | ||
| 437 | reason: "not low power active", | ||
| 438 | }); | ||
| 439 | } | ||
| 440 | Ok(clk.frequency) | ||
| 441 | } | ||
| 442 | |||
| 443 | /// Ensure the `fro_hf` clock is active and valid at the given power state. | ||
| 444 | pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 445 | let Some(clk) = self.fro_hf.as_ref() else { | ||
| 446 | return Err(ClockError::BadConfig { | ||
| 447 | clock: "fro_hf", | ||
| 448 | reason: "required but not active", | ||
| 449 | }); | ||
| 450 | }; | ||
| 451 | if !clk.power.meets_requirement_of(at_level) { | ||
| 452 | return Err(ClockError::BadConfig { | ||
| 453 | clock: "fro_hf", | ||
| 454 | reason: "not low power active", | ||
| 455 | }); | ||
| 456 | } | ||
| 457 | Ok(clk.frequency) | ||
| 458 | } | ||
| 459 | |||
| 460 | /// Ensure the `fro_hf_div` clock is active and valid at the given power state. | ||
| 461 | pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 462 | let Some(clk) = self.fro_hf_div.as_ref() else { | ||
| 463 | return Err(ClockError::BadConfig { | ||
| 464 | clock: "fro_hf_div", | ||
| 465 | reason: "required but not active", | ||
| 466 | }); | ||
| 467 | }; | ||
| 468 | if !clk.power.meets_requirement_of(at_level) { | ||
| 469 | return Err(ClockError::BadConfig { | ||
| 470 | clock: "fro_hf_div", | ||
| 471 | reason: "not low power active", | ||
| 472 | }); | ||
| 473 | } | ||
| 474 | Ok(clk.frequency) | ||
| 475 | } | ||
| 476 | |||
| 477 | /// Ensure the `clk_in` clock is active and valid at the given power state. | ||
| 478 | pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 479 | Err(ClockError::NotImplemented { clock: "clk_in" }) | ||
| 480 | } | ||
| 481 | |||
| 482 | /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. | ||
| 483 | pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 484 | // NOTE: clk_16k is always active in low power mode | ||
| 485 | Ok(self | ||
| 486 | .clk_16k_vsys | ||
| 487 | .as_ref() | ||
| 488 | .ok_or(ClockError::BadConfig { | ||
| 489 | clock: "clk_16k_vsys", | ||
| 490 | reason: "required but not active", | ||
| 491 | })? | ||
| 492 | .frequency) | ||
| 493 | } | ||
| 494 | |||
| 495 | /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. | ||
| 496 | pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 497 | // NOTE: clk_16k is always active in low power mode | ||
| 498 | Ok(self | ||
| 499 | .clk_16k_vdd_core | ||
| 500 | .as_ref() | ||
| 501 | .ok_or(ClockError::BadConfig { | ||
| 502 | clock: "clk_16k_vdd_core", | ||
| 503 | reason: "required but not active", | ||
| 504 | })? | ||
| 505 | .frequency) | ||
| 506 | } | ||
| 507 | |||
| 508 | /// Ensure the `clk_1m` clock is active and valid at the given power state. | ||
| 509 | pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 510 | let Some(clk) = self.clk_1m.as_ref() else { | ||
| 511 | return Err(ClockError::BadConfig { | ||
| 512 | clock: "clk_1m", | ||
| 513 | reason: "required but not active", | ||
| 514 | }); | ||
| 515 | }; | ||
| 516 | if !clk.power.meets_requirement_of(at_level) { | ||
| 517 | return Err(ClockError::BadConfig { | ||
| 518 | clock: "clk_1m", | ||
| 519 | reason: "not low power active", | ||
| 520 | }); | ||
| 521 | } | ||
| 522 | Ok(clk.frequency) | ||
| 523 | } | ||
| 524 | |||
| 525 | /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. | ||
| 526 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 527 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | impl PoweredClock { | ||
| 532 | /// Does THIS clock meet the power requirements of the OTHER clock? | ||
| 533 | pub fn meets_requirement_of(&self, other: &Self) -> bool { | ||
| 534 | match (self, other) { | ||
| 535 | (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, | ||
| 536 | (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, | ||
| 537 | (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, | ||
| 538 | (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, | ||
| 539 | } | ||
| 540 | } | ||
| 541 | } | ||
| 542 | |||
| 543 | impl ClockOperator<'_> { | ||
| 544 | /// Configure the FIRC/FRO180M clock family | ||
| 545 | /// | ||
| 546 | /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used | ||
| 547 | /// as the main clock used for the CPU, AHB, APB, etc. | ||
| 548 | fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { | ||
| 549 | const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { | ||
| 550 | clock: "firc", | ||
| 551 | reason: "For now, FIRC must be enabled and in default state!", | ||
| 552 | }); | ||
| 553 | |||
| 554 | // Did the user give us a FIRC config? | ||
| 555 | let Some(firc) = self.config.firc.as_ref() else { | ||
| 556 | return HARDCODED_ERR; | ||
| 557 | }; | ||
| 558 | // Is the FIRC set to 45MHz (should be reset default) | ||
| 559 | if !matches!(firc.frequency, FircFreqSel::Mhz45) { | ||
| 560 | return HARDCODED_ERR; | ||
| 561 | } | ||
| 562 | let base_freq = 45_000_000; | ||
| 563 | |||
| 564 | // Now, check if the FIRC as expected for our hardcoded value | ||
| 565 | let mut firc_ok = true; | ||
| 566 | |||
| 567 | // Is the hardware currently set to the default 45MHz? | ||
| 568 | // | ||
| 569 | // NOTE: the SVD currently has the wrong(?) values for these: | ||
| 570 | // 45 -> 48 | ||
| 571 | // 60 -> 64 | ||
| 572 | // 90 -> 96 | ||
| 573 | // 180 -> 192 | ||
| 574 | // Probably correct-ish, but for a different trim value? | ||
| 575 | firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); | ||
| 576 | |||
| 577 | // Check some values in the CSR | ||
| 578 | let csr = self.scg0.firccsr().read(); | ||
| 579 | // Is it enabled? | ||
| 580 | firc_ok &= csr.fircen().is_enabled(); | ||
| 581 | // Is it accurate? | ||
| 582 | firc_ok &= csr.fircacc().is_enabled_and_valid(); | ||
| 583 | // Is there no error? | ||
| 584 | firc_ok &= csr.fircerr().is_error_not_detected(); | ||
| 585 | // Is the FIRC the system clock? | ||
| 586 | firc_ok &= csr.fircsel().is_firc(); | ||
| 587 | // Is it valid? | ||
| 588 | firc_ok &= csr.fircvld().is_enabled_and_valid(); | ||
| 589 | |||
| 590 | // Are we happy with the current (hardcoded) state? | ||
| 591 | if !firc_ok { | ||
| 592 | return HARDCODED_ERR; | ||
| 593 | } | ||
| 594 | |||
| 595 | // Note that the fro_hf_root is active | ||
| 596 | self.clocks.fro_hf_root = Some(Clock { | ||
| 597 | frequency: base_freq, | ||
| 598 | power: firc.power, | ||
| 599 | }); | ||
| 600 | |||
| 601 | // Okay! Now we're past that, let's enable all the downstream clocks. | ||
| 602 | let FircConfig { | ||
| 603 | frequency: _, | ||
| 604 | power, | ||
| 605 | fro_hf_enabled, | ||
| 606 | clk_45m_enabled, | ||
| 607 | fro_hf_div, | ||
| 608 | } = firc; | ||
| 609 | |||
| 610 | // When is the FRO enabled? | ||
| 611 | let pow_set = match power { | ||
| 612 | PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, | ||
| 613 | PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, | ||
| 614 | }; | ||
| 615 | |||
| 616 | // Do we enable the `fro_hf` output? | ||
| 617 | let fro_hf_set = if *fro_hf_enabled { | ||
| 618 | self.clocks.fro_hf = Some(Clock { | ||
| 619 | frequency: base_freq, | ||
| 620 | power: *power, | ||
| 621 | }); | ||
| 622 | FircFclkPeriphEn::Enabled | ||
| 623 | } else { | ||
| 624 | FircFclkPeriphEn::Disabled | ||
| 625 | }; | ||
| 626 | |||
| 627 | // Do we enable the `clk_45m` output? | ||
| 628 | let clk_45m_set = if *clk_45m_enabled { | ||
| 629 | self.clocks.clk_45m = Some(Clock { | ||
| 630 | frequency: 45_000_000, | ||
| 631 | power: *power, | ||
| 632 | }); | ||
| 633 | FircSclkPeriphEn::Enabled | ||
| 634 | } else { | ||
| 635 | FircSclkPeriphEn::Disabled | ||
| 636 | }; | ||
| 637 | |||
| 638 | self.scg0.firccsr().modify(|_r, w| { | ||
| 639 | w.fircsten().variant(pow_set); | ||
| 640 | w.firc_fclk_periph_en().variant(fro_hf_set); | ||
| 641 | w.firc_sclk_periph_en().variant(clk_45m_set); | ||
| 642 | w | ||
| 643 | }); | ||
| 644 | |||
| 645 | // Do we enable the `fro_hf_div` output? | ||
| 646 | if let Some(d) = fro_hf_div.as_ref() { | ||
| 647 | // We need `fro_hf` to be enabled | ||
| 648 | if !*fro_hf_enabled { | ||
| 649 | return Err(ClockError::BadConfig { | ||
| 650 | clock: "fro_hf_div", | ||
| 651 | reason: "fro_hf not enabled", | ||
| 652 | }); | ||
| 653 | } | ||
| 654 | |||
| 655 | // Halt and reset the div | ||
| 656 | self.syscon.frohfdiv().write(|w| { | ||
| 657 | w.halt().halt(); | ||
| 658 | w.reset().asserted(); | ||
| 659 | w | ||
| 660 | }); | ||
| 661 | // Then change the div, unhalt it, and reset it | ||
| 662 | self.syscon.frohfdiv().write(|w| { | ||
| 663 | unsafe { | ||
| 664 | w.div().bits(d.into_bits()); | ||
| 665 | } | ||
| 666 | w.halt().run(); | ||
| 667 | w.reset().released(); | ||
| 668 | w | ||
| 669 | }); | ||
| 670 | |||
| 671 | // Wait for clock to stabilize | ||
| 672 | while self.syscon.frohfdiv().read().unstab().is_ongoing() {} | ||
| 673 | |||
| 674 | // Store off the clock info | ||
| 675 | self.clocks.fro_hf_div = Some(Clock { | ||
| 676 | frequency: base_freq / d.into_divisor(), | ||
| 677 | power: *power, | ||
| 678 | }); | ||
| 679 | } | ||
| 680 | |||
| 681 | Ok(()) | ||
| 682 | } | ||
| 683 | |||
| 684 | /// Configure the SIRC/FRO12M clock family | ||
| 685 | fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { | ||
| 686 | let SircConfig { | ||
| 687 | power, | ||
| 688 | fro_12m_enabled, | ||
| 689 | fro_lf_div, | ||
| 690 | } = &self.config.sirc; | ||
| 691 | let base_freq = 12_000_000; | ||
| 692 | |||
| 693 | // Allow writes | ||
| 694 | self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); | ||
| 695 | self.clocks.fro_12m_root = Some(Clock { | ||
| 696 | frequency: base_freq, | ||
| 697 | power: *power, | ||
| 698 | }); | ||
| 699 | |||
| 700 | let deep = match power { | ||
| 701 | PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, | ||
| 702 | PoweredClock::AlwaysEnabled => Sircsten::Enabled, | ||
| 703 | }; | ||
| 704 | let pclk = if *fro_12m_enabled { | ||
| 705 | self.clocks.fro_12m = Some(Clock { | ||
| 706 | frequency: base_freq, | ||
| 707 | power: *power, | ||
| 708 | }); | ||
| 709 | self.clocks.clk_1m = Some(Clock { | ||
| 710 | frequency: base_freq / 12, | ||
| 711 | power: *power, | ||
| 712 | }); | ||
| 713 | SircClkPeriphEn::Enabled | ||
| 714 | } else { | ||
| 715 | SircClkPeriphEn::Disabled | ||
| 716 | }; | ||
| 717 | |||
| 718 | // Set sleep/peripheral usage | ||
| 719 | self.scg0.sirccsr().modify(|_r, w| { | ||
| 720 | w.sircsten().variant(deep); | ||
| 721 | w.sirc_clk_periph_en().variant(pclk); | ||
| 722 | w | ||
| 723 | }); | ||
| 724 | |||
| 725 | while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} | ||
| 726 | if self.scg0.sirccsr().read().sircerr().is_error_detected() { | ||
| 727 | return Err(ClockError::BadConfig { | ||
| 728 | clock: "sirc", | ||
| 729 | reason: "error set", | ||
| 730 | }); | ||
| 731 | } | ||
| 732 | |||
| 733 | // reset lock | ||
| 734 | self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); | ||
| 735 | |||
| 736 | // Do we enable the `fro_lf_div` output? | ||
| 737 | if let Some(d) = fro_lf_div.as_ref() { | ||
| 738 | // We need `fro_lf` to be enabled | ||
| 739 | if !*fro_12m_enabled { | ||
| 740 | return Err(ClockError::BadConfig { | ||
| 741 | clock: "fro_lf_div", | ||
| 742 | reason: "fro_12m not enabled", | ||
| 743 | }); | ||
| 744 | } | ||
| 745 | |||
| 746 | // Halt and reset the div | ||
| 747 | self.syscon.frolfdiv().write(|w| { | ||
| 748 | w.halt().halt(); | ||
| 749 | w.reset().asserted(); | ||
| 750 | w | ||
| 751 | }); | ||
| 752 | // Then change the div, unhalt it, and reset it | ||
| 753 | self.syscon.frolfdiv().write(|w| { | ||
| 754 | unsafe { | ||
| 755 | w.div().bits(d.into_bits()); | ||
| 756 | } | ||
| 757 | w.halt().run(); | ||
| 758 | w.reset().released(); | ||
| 759 | w | ||
| 760 | }); | ||
| 761 | |||
| 762 | // Wait for clock to stabilize | ||
| 763 | while self.syscon.frolfdiv().read().unstab().is_ongoing() {} | ||
| 764 | |||
| 765 | // Store off the clock info | ||
| 766 | self.clocks.fro_lf_div = Some(Clock { | ||
| 767 | frequency: base_freq / d.into_divisor(), | ||
| 768 | power: *power, | ||
| 769 | }); | ||
| 770 | } | ||
| 771 | |||
| 772 | Ok(()) | ||
| 773 | } | ||
| 774 | |||
| 775 | /// Configure the FRO16K/clk_16k clock family | ||
| 776 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { | ||
| 777 | let Some(fro16k) = self.config.fro16k.as_ref() else { | ||
| 778 | return Ok(()); | ||
| 779 | }; | ||
| 780 | // Enable FRO16K oscillator | ||
| 781 | self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); | ||
| 782 | |||
| 783 | // Lock the control register | ||
| 784 | self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); | ||
| 785 | |||
| 786 | let Fro16KConfig { | ||
| 787 | vsys_domain_active, | ||
| 788 | vdd_core_domain_active, | ||
| 789 | } = fro16k; | ||
| 790 | |||
| 791 | // Enable clock outputs to both VSYS and VDD_CORE domains | ||
| 792 | // Bit 0: clk_16k0 to VSYS domain | ||
| 793 | // Bit 1: clk_16k1 to VDD_CORE domain | ||
| 794 | // | ||
| 795 | // TODO: Define sub-fields for this register with a PAC patch? | ||
| 796 | let mut bits = 0; | ||
| 797 | if *vsys_domain_active { | ||
| 798 | bits |= 0b01; | ||
| 799 | self.clocks.clk_16k_vsys = Some(Clock { | ||
| 800 | frequency: 16_384, | ||
| 801 | power: PoweredClock::AlwaysEnabled, | ||
| 802 | }); | ||
| 803 | } | ||
| 804 | if *vdd_core_domain_active { | ||
| 805 | bits |= 0b10; | ||
| 806 | self.clocks.clk_16k_vdd_core = Some(Clock { | ||
| 807 | frequency: 16_384, | ||
| 808 | power: PoweredClock::AlwaysEnabled, | ||
| 809 | }); | ||
| 810 | } | ||
| 811 | self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); | ||
| 812 | |||
| 813 | Ok(()) | ||
| 814 | } | ||
| 815 | } | ||
| 816 | |||
| 817 | // | ||
| 818 | // Macros/macro impls | ||
| 819 | // | ||
| 820 | |||
| 821 | /// This macro is used to implement the [`Gate`] trait for a given peripheral | ||
| 822 | /// that is controlled by the MRCC peripheral. | ||
| 823 | macro_rules! impl_cc_gate { | ||
| 824 | ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { | ||
| 825 | impl Gate for crate::peripherals::$name { | ||
| 826 | type MrccPeriphConfig = $config; | ||
| 827 | |||
| 828 | #[inline] | ||
| 829 | unsafe fn enable_clock() { | ||
| 830 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 831 | mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); | ||
| 832 | } | ||
| 833 | |||
| 834 | #[inline] | ||
| 835 | unsafe fn disable_clock() { | ||
| 836 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 837 | mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); | ||
| 838 | } | ||
| 839 | |||
| 840 | #[inline] | ||
| 841 | fn is_clock_enabled() -> bool { | ||
| 842 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 843 | mrcc.$clk_reg().read().$field().is_enabled() | ||
| 844 | } | ||
| 845 | |||
| 846 | #[inline] | ||
| 847 | unsafe fn release_reset() { | ||
| 848 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 849 | mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); | ||
| 850 | } | ||
| 851 | |||
| 852 | #[inline] | ||
| 853 | unsafe fn assert_reset() { | ||
| 854 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 855 | mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); | ||
| 856 | } | ||
| 857 | |||
| 858 | #[inline] | ||
| 859 | fn is_reset_released() -> bool { | ||
| 860 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 861 | mrcc.$rst_reg().read().$field().is_enabled() | ||
| 862 | } | ||
| 863 | } | ||
| 864 | }; | ||
| 865 | } | ||
| 866 | |||
| 867 | /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, | ||
| 868 | /// for various low level peripherals. | ||
| 869 | pub(crate) mod gate { | ||
| 870 | use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig}; | ||
| 871 | use super::*; | ||
| 872 | |||
| 873 | // These peripherals have no additional upstream clocks or configuration required | ||
| 874 | // other than enabling through the MRCC gate. Currently, these peripherals will | ||
| 875 | // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or | ||
| 876 | // [`SPConfHelper::post_enable_config()`]. | ||
| 877 | impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); | ||
| 878 | impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); | ||
| 879 | impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); | ||
| 880 | impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); | ||
| 881 | |||
| 882 | // These peripherals DO have meaningful configuration, and could fail if the system | ||
| 883 | // clocks do not match their needs. | ||
| 884 | impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); | ||
| 885 | impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); | ||
| 886 | impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); | ||
| 887 | } | ||
diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs new file mode 100644 index 000000000..e5b234c5b --- /dev/null +++ b/src/clocks/periph_helpers.rs | |||
| @@ -0,0 +1,393 @@ | |||
| 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 | |||
| 10 | use super::{ClockError, Clocks, PoweredClock}; | ||
| 11 | use 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()`. | ||
| 24 | pub 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 | // config types | ||
| 45 | |||
| 46 | /// This type represents a divider in the range 1..=16. | ||
| 47 | /// | ||
| 48 | /// At a hardware level, this is an 8-bit register from 0..=15, | ||
| 49 | /// which adds one. | ||
| 50 | /// | ||
| 51 | /// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain | ||
| 52 | /// seems to use 4 bit dividers! | ||
| 53 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 54 | pub struct Div4(pub(super) u8); | ||
| 55 | |||
| 56 | impl Div4 { | ||
| 57 | /// Divide by one, or no division | ||
| 58 | pub const fn no_div() -> Self { | ||
| 59 | Self(0) | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Store a "raw" divisor value that will divide the source by | ||
| 63 | /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source | ||
| 64 | /// by 1, and `Div4::from_raw(15)` will divide the source by | ||
| 65 | /// 16. | ||
| 66 | pub const fn from_raw(n: u8) -> Option<Self> { | ||
| 67 | if n > 0b1111 { | ||
| 68 | None | ||
| 69 | } else { | ||
| 70 | Some(Self(n)) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Store a specific divisor value that will divide the source | ||
| 75 | /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source | ||
| 76 | /// by 1, and `Div4::from_divisor(16)` will divide the source | ||
| 77 | /// by 16. | ||
| 78 | /// | ||
| 79 | /// Will return `None` if `n` is not in the range `1..=16`. | ||
| 80 | /// Consider [`Self::from_raw`] for an infallible version. | ||
| 81 | pub const fn from_divisor(n: u8) -> Option<Self> { | ||
| 82 | let Some(n) = n.checked_sub(1) else { | ||
| 83 | return None; | ||
| 84 | }; | ||
| 85 | if n > 0b1111 { | ||
| 86 | return None; | ||
| 87 | } | ||
| 88 | Some(Self(n)) | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Convert into "raw" bits form | ||
| 92 | #[inline(always)] | ||
| 93 | pub const fn into_bits(self) -> u8 { | ||
| 94 | self.0 | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Convert into "divisor" form, as a u32 for convenient frequency math | ||
| 98 | #[inline(always)] | ||
| 99 | pub const fn into_divisor(self) -> u32 { | ||
| 100 | self.0 as u32 + 1 | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | /// A basic type that always returns an error when `post_enable_config` is called. | ||
| 105 | /// | ||
| 106 | /// Should only be used as a placeholder. | ||
| 107 | pub struct UnimplementedConfig; | ||
| 108 | |||
| 109 | impl SPConfHelper for UnimplementedConfig { | ||
| 110 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 111 | Err(ClockError::UnimplementedConfig) | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /// A basic type that always returns `Ok(0)` when `post_enable_config` is called. | ||
| 116 | /// | ||
| 117 | /// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` | ||
| 118 | /// peripherals, which have no selectable/configurable source clock. | ||
| 119 | pub struct NoConfig; | ||
| 120 | impl SPConfHelper for NoConfig { | ||
| 121 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 122 | Ok(0) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | // | ||
| 127 | // LPUart | ||
| 128 | // | ||
| 129 | |||
| 130 | /// Selectable clocks for Lpuart peripherals | ||
| 131 | #[derive(Debug, Clone, Copy)] | ||
| 132 | pub enum LpuartClockSel { | ||
| 133 | /// FRO12M/FRO_LF/SIRC clock source, passed through divider | ||
| 134 | /// "fro_lf_div" | ||
| 135 | FroLfDiv, | ||
| 136 | /// FRO180M/FRO_HF/FIRC clock source, passed through divider | ||
| 137 | /// "fro_hf_div" | ||
| 138 | FroHfDiv, | ||
| 139 | /// SOSC/XTAL/EXTAL clock source | ||
| 140 | ClkIn, | ||
| 141 | /// FRO16K/clk_16k source | ||
| 142 | Clk16K, | ||
| 143 | /// clk_1m/FRO_LF divided by 12 | ||
| 144 | Clk1M, | ||
| 145 | /// Output of PLL1, passed through clock divider, | ||
| 146 | /// "pll1_clk_div", maybe "pll1_lf_div"? | ||
| 147 | Pll1ClkDiv, | ||
| 148 | /// Disabled | ||
| 149 | None, | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Which instance of the Lpuart is this? | ||
| 153 | /// | ||
| 154 | /// Should not be directly selectable by end-users. | ||
| 155 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 156 | pub enum LpuartInstance { | ||
| 157 | /// Instance 0 | ||
| 158 | Lpuart0, | ||
| 159 | /// Instance 1 | ||
| 160 | Lpuart1, | ||
| 161 | /// Instance 2 | ||
| 162 | Lpuart2, | ||
| 163 | /// Instance 3 | ||
| 164 | Lpuart3, | ||
| 165 | /// Instance 4 | ||
| 166 | Lpuart4, | ||
| 167 | /// Instance 5 | ||
| 168 | Lpuart5, | ||
| 169 | } | ||
| 170 | |||
| 171 | /// Top level configuration for `Lpuart` instances. | ||
| 172 | pub struct LpuartConfig { | ||
| 173 | /// Power state required for this peripheral | ||
| 174 | pub power: PoweredClock, | ||
| 175 | /// Clock source | ||
| 176 | pub source: LpuartClockSel, | ||
| 177 | /// Clock divisor | ||
| 178 | pub div: Div4, | ||
| 179 | /// Which instance is this? | ||
| 180 | // NOTE: should not be user settable | ||
| 181 | pub(crate) instance: LpuartInstance, | ||
| 182 | } | ||
| 183 | |||
| 184 | impl SPConfHelper for LpuartConfig { | ||
| 185 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 186 | // check that source is suitable | ||
| 187 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 188 | use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; | ||
| 189 | |||
| 190 | let (clkdiv, clksel) = match self.instance { | ||
| 191 | LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), | ||
| 192 | LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), | ||
| 193 | LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), | ||
| 194 | LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), | ||
| 195 | LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), | ||
| 196 | LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), | ||
| 197 | }; | ||
| 198 | |||
| 199 | let (freq, variant) = match self.source { | ||
| 200 | LpuartClockSel::FroLfDiv => { | ||
| 201 | let freq = clocks.ensure_fro_lf_div_active(&self.power)?; | ||
| 202 | (freq, Mux::ClkrootFunc0) | ||
| 203 | } | ||
| 204 | LpuartClockSel::FroHfDiv => { | ||
| 205 | let freq = clocks.ensure_fro_hf_div_active(&self.power)?; | ||
| 206 | (freq, Mux::ClkrootFunc2) | ||
| 207 | } | ||
| 208 | LpuartClockSel::ClkIn => { | ||
| 209 | let freq = clocks.ensure_clk_in_active(&self.power)?; | ||
| 210 | (freq, Mux::ClkrootFunc3) | ||
| 211 | } | ||
| 212 | LpuartClockSel::Clk16K => { | ||
| 213 | let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; | ||
| 214 | (freq, Mux::ClkrootFunc4) | ||
| 215 | } | ||
| 216 | LpuartClockSel::Clk1M => { | ||
| 217 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 218 | (freq, Mux::ClkrootFunc5) | ||
| 219 | } | ||
| 220 | LpuartClockSel::Pll1ClkDiv => { | ||
| 221 | let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; | ||
| 222 | (freq, Mux::ClkrootFunc6) | ||
| 223 | } | ||
| 224 | LpuartClockSel::None => unsafe { | ||
| 225 | // no ClkrootFunc7, just write manually for now | ||
| 226 | clksel.write(|w| w.bits(0b111)); | ||
| 227 | clkdiv.modify(|_r, w| { | ||
| 228 | w.reset().on(); | ||
| 229 | w.halt().on(); | ||
| 230 | w | ||
| 231 | }); | ||
| 232 | return Ok(0); | ||
| 233 | }, | ||
| 234 | }; | ||
| 235 | |||
| 236 | // set clksel | ||
| 237 | clksel.modify(|_r, w| w.mux().variant(variant)); | ||
| 238 | |||
| 239 | // Set up clkdiv | ||
| 240 | clkdiv.modify(|_r, w| { | ||
| 241 | w.halt().on(); | ||
| 242 | w.reset().on(); | ||
| 243 | w | ||
| 244 | }); | ||
| 245 | clkdiv.modify(|_r, w| { | ||
| 246 | w.halt().off(); | ||
| 247 | w.reset().off(); | ||
| 248 | unsafe { w.div().bits(self.div.into_bits()) }; | ||
| 249 | w | ||
| 250 | }); | ||
| 251 | |||
| 252 | while clkdiv.read().unstab().is_on() {} | ||
| 253 | |||
| 254 | Ok(freq / self.div.into_divisor()) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | // | ||
| 259 | // OSTimer | ||
| 260 | // | ||
| 261 | |||
| 262 | /// Selectable clocks for the OSTimer peripheral | ||
| 263 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 264 | pub enum OstimerClockSel { | ||
| 265 | /// 16k clock, sourced from FRO16K (Vdd Core) | ||
| 266 | Clk16kVddCore, | ||
| 267 | /// 1 MHz Clock sourced from FRO12M | ||
| 268 | Clk1M, | ||
| 269 | /// Disabled | ||
| 270 | None, | ||
| 271 | } | ||
| 272 | |||
| 273 | /// Top level configuration for the `OSTimer` peripheral | ||
| 274 | pub struct OsTimerConfig { | ||
| 275 | /// Power state required for this peripheral | ||
| 276 | pub power: PoweredClock, | ||
| 277 | /// Selected clock source for this peripheral | ||
| 278 | pub source: OstimerClockSel, | ||
| 279 | } | ||
| 280 | |||
| 281 | impl SPConfHelper for OsTimerConfig { | ||
| 282 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 283 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 284 | Ok(match self.source { | ||
| 285 | OstimerClockSel::Clk16kVddCore => { | ||
| 286 | let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; | ||
| 287 | mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); | ||
| 288 | freq | ||
| 289 | } | ||
| 290 | OstimerClockSel::Clk1M => { | ||
| 291 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 292 | mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 293 | freq | ||
| 294 | } | ||
| 295 | OstimerClockSel::None => { | ||
| 296 | mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); | ||
| 297 | 0 | ||
| 298 | } | ||
| 299 | }) | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | // | ||
| 304 | // Adc | ||
| 305 | // | ||
| 306 | |||
| 307 | /// Selectable clocks for the ADC peripheral | ||
| 308 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 309 | pub enum AdcClockSel { | ||
| 310 | /// Divided `fro_lf`/`clk_12m`/FRO12M source | ||
| 311 | FroLfDiv, | ||
| 312 | /// Gated `fro_hf`/`FRO180M` source | ||
| 313 | FroHf, | ||
| 314 | /// External Clock Source | ||
| 315 | ClkIn, | ||
| 316 | /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` | ||
| 317 | Clk1M, | ||
| 318 | /// Internal PLL output, with configurable divisor | ||
| 319 | Pll1ClkDiv, | ||
| 320 | /// No clock/disabled | ||
| 321 | None, | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Top level configuration for the ADC peripheral | ||
| 325 | pub struct AdcConfig { | ||
| 326 | /// Power state required for this peripheral | ||
| 327 | pub power: PoweredClock, | ||
| 328 | /// Selected clock-source for this peripheral | ||
| 329 | pub source: AdcClockSel, | ||
| 330 | /// Pre-divisor, applied to the upstream clock output | ||
| 331 | pub div: Div4, | ||
| 332 | } | ||
| 333 | |||
| 334 | impl SPConfHelper for AdcConfig { | ||
| 335 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 336 | use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; | ||
| 337 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 338 | let (freq, variant) = match self.source { | ||
| 339 | AdcClockSel::FroLfDiv => { | ||
| 340 | let freq = clocks.ensure_fro_lf_div_active(&self.power)?; | ||
| 341 | (freq, Mux::ClkrootFunc0) | ||
| 342 | } | ||
| 343 | AdcClockSel::FroHf => { | ||
| 344 | let freq = clocks.ensure_fro_hf_active(&self.power)?; | ||
| 345 | (freq, Mux::ClkrootFunc1) | ||
| 346 | } | ||
| 347 | AdcClockSel::ClkIn => { | ||
| 348 | let freq = clocks.ensure_clk_in_active(&self.power)?; | ||
| 349 | (freq, Mux::ClkrootFunc3) | ||
| 350 | } | ||
| 351 | AdcClockSel::Clk1M => { | ||
| 352 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 353 | (freq, Mux::ClkrootFunc5) | ||
| 354 | } | ||
| 355 | AdcClockSel::Pll1ClkDiv => { | ||
| 356 | let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; | ||
| 357 | (freq, Mux::ClkrootFunc6) | ||
| 358 | } | ||
| 359 | AdcClockSel::None => { | ||
| 360 | mrcc0.mrcc_adc_clksel().write(|w| unsafe { | ||
| 361 | // no ClkrootFunc7, just write manually for now | ||
| 362 | w.mux().bits(0b111) | ||
| 363 | }); | ||
| 364 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 365 | w.reset().on(); | ||
| 366 | w.halt().on(); | ||
| 367 | w | ||
| 368 | }); | ||
| 369 | return Ok(0); | ||
| 370 | } | ||
| 371 | }; | ||
| 372 | |||
| 373 | // set clksel | ||
| 374 | mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant)); | ||
| 375 | |||
| 376 | // Set up clkdiv | ||
| 377 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 378 | w.halt().on(); | ||
| 379 | w.reset().on(); | ||
| 380 | w | ||
| 381 | }); | ||
| 382 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 383 | w.halt().off(); | ||
| 384 | w.reset().off(); | ||
| 385 | unsafe { w.div().bits(self.div.into_bits()) }; | ||
| 386 | w | ||
| 387 | }); | ||
| 388 | |||
| 389 | while mrcc0.mrcc_adc_clkdiv().read().unstab().is_on() {} | ||
| 390 | |||
| 391 | Ok(freq / self.div.into_divisor()) | ||
| 392 | } | ||
| 393 | } | ||
