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