diff options
| author | James Munns <[email protected]> | 2025-11-17 14:43:06 +0100 |
|---|---|---|
| committer | James Munns <[email protected]> | 2025-11-17 14:43:24 +0100 |
| commit | 6e6ba60beb4faf17142938e1efff4b9d30e715c3 (patch) | |
| tree | e117953d6360f5f62b09a44e58bc0b461312e2d4 /src/clocks | |
| parent | 728340ba05e9a16ceb472b147737c38c144d292d (diff) | |
Docs pass and organization cleanup
Diffstat (limited to 'src/clocks')
| -rw-r--r-- | src/clocks/config.rs | 199 | ||||
| -rw-r--r-- | src/clocks/mod.rs | 981 | ||||
| -rw-r--r-- | src/clocks/periph_helpers.rs | 160 |
3 files changed, 818 insertions, 522 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 index e04f63b8e..c6606b1b6 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs | |||
| @@ -1,47 +1,335 @@ | |||
| 1 | //! Clock control helpers (no magic numbers, PAC field access only). | 1 | //! # Clock Module |
| 2 | //! Provides reusable gate abstractions for peripherals used by the examples. | 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 | |||
| 3 | use core::cell::RefCell; | 38 | use core::cell::RefCell; |
| 4 | 39 | ||
| 40 | use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; | ||
| 5 | use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; | 41 | use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; |
| 6 | use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; | 42 | use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; |
| 7 | use periph_helpers::SPConfHelper; | 43 | use periph_helpers::SPConfHelper; |
| 8 | 44 | ||
| 9 | use crate::pac; | 45 | use crate::pac; |
| 46 | pub mod config; | ||
| 10 | pub mod periph_helpers; | 47 | pub mod periph_helpers; |
| 11 | 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 | // TODO, everything downstream | ||
| 91 | |||
| 92 | critical_section::with(|cs| { | ||
| 93 | let mut clks = CLOCKS.borrow_ref_mut(cs); | ||
| 94 | assert!(clks.is_none(), "Clock setup race!"); | ||
| 95 | *clks = Some(clocks); | ||
| 96 | }); | ||
| 97 | |||
| 98 | Ok(()) | ||
| 99 | } | ||
| 100 | |||
| 101 | /// Obtain the full clocks structure, calling the given closure in a critical section. | ||
| 102 | /// | ||
| 103 | /// The given closure will be called with read-only access to the state of the system | ||
| 104 | /// clocks. This can be used to query and return the state of a given clock. | ||
| 105 | /// | ||
| 106 | /// As the caller's closure will be called in a critical section, care must be taken | ||
| 107 | /// not to block or cause any other undue delays while accessing. | ||
| 108 | /// | ||
| 109 | /// Calls to this function will not succeed until after a successful call to `init()`, | ||
| 110 | /// and will always return None. | ||
| 111 | pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> { | ||
| 112 | critical_section::with(|cs| { | ||
| 113 | let c = CLOCKS.borrow_ref(cs); | ||
| 114 | let c = c.as_ref()?; | ||
| 115 | Some(f(c)) | ||
| 116 | }) | ||
| 117 | } | ||
| 118 | |||
| 119 | // | ||
| 120 | // Structs/Enums | ||
| 121 | // | ||
| 122 | |||
| 123 | /// The `Clocks` structure contains the initialized state of the core system clocks | ||
| 124 | /// | ||
| 125 | /// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function | ||
| 126 | /// at boot time. | ||
| 127 | #[derive(Default, Debug, Clone)] | ||
| 128 | #[non_exhaustive] | ||
| 129 | pub struct Clocks { | ||
| 130 | /// The `clk_in` is a clock provided by an external oscillator | ||
| 131 | pub clk_in: Option<Clock>, | ||
| 132 | |||
| 133 | // FRO180M stuff | ||
| 134 | // | ||
| 135 | /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator | ||
| 136 | /// | ||
| 137 | /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, | ||
| 138 | /// and `fro_hf_div`. | ||
| 139 | pub fro_hf_root: Option<Clock>, | ||
| 140 | |||
| 141 | /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. | ||
| 142 | pub fro_hf: Option<Clock>, | ||
| 143 | |||
| 144 | /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. | ||
| 145 | pub clk_45m: Option<Clock>, | ||
| 146 | |||
| 147 | /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. | ||
| 148 | pub fro_hf_div: Option<Clock>, | ||
| 149 | |||
| 150 | // | ||
| 151 | // End FRO180M | ||
| 152 | |||
| 153 | // FRO12M stuff | ||
| 154 | // | ||
| 155 | /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator | ||
| 156 | /// | ||
| 157 | /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, | ||
| 158 | /// `and `fro_lf_div`. | ||
| 159 | pub fro_12m_root: Option<Clock>, | ||
| 160 | |||
| 161 | /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. | ||
| 162 | pub fro_12m: Option<Clock>, | ||
| 163 | |||
| 164 | /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` | ||
| 165 | pub clk_1m: Option<Clock>, | ||
| 166 | |||
| 167 | /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` | ||
| 168 | pub fro_lf_div: Option<Clock>, | ||
| 169 | // | ||
| 170 | // End FRO12M stuff | ||
| 171 | /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 172 | /// | ||
| 173 | /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in | ||
| 174 | /// the system domain, such as the CMP and RTC. | ||
| 175 | pub clk_16k_vsys: Option<Clock>, | ||
| 176 | |||
| 177 | /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 178 | /// | ||
| 179 | /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in | ||
| 180 | /// the VDD Core domain, such as the OSTimer or LPUarts. | ||
| 181 | pub clk_16k_vdd_core: Option<Clock>, | ||
| 182 | |||
| 183 | /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some | ||
| 184 | /// peripherals. | ||
| 185 | pub main_clk: Option<Clock>, | ||
| 186 | |||
| 187 | /// `pll1_clk` is the output of the main system PLL, `pll1`. | ||
| 188 | pub pll1_clk: Option<Clock>, | ||
| 189 | } | ||
| 190 | |||
| 191 | /// `ClockError` is the main error returned when configuring or checking clock state | ||
| 192 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 193 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 194 | #[non_exhaustive] | ||
| 195 | pub enum ClockError { | ||
| 196 | /// The system clocks were never initialized by calling [`init()`] | ||
| 197 | NeverInitialized, | ||
| 198 | /// The [`init()`] function was called more than once | ||
| 199 | AlreadyInitialized, | ||
| 200 | /// The requested configuration was not possible to fulfill, as the system clocks | ||
| 201 | /// were not configured in a compatible way | ||
| 202 | BadConfig { clock: &'static str, reason: &'static str }, | ||
| 203 | /// The requested configuration was not possible to fulfill, as the required system | ||
| 204 | /// clocks have not yet been implemented. | ||
| 205 | NotImplemented { clock: &'static str }, | ||
| 206 | /// The requested peripheral could not be configured, as the steps necessary to | ||
| 207 | /// enable it have not yet been implemented. | ||
| 208 | UnimplementedConfig, | ||
| 209 | } | ||
| 210 | |||
| 211 | /// Information regarding a system clock | ||
| 212 | #[derive(Debug, Clone)] | ||
| 213 | pub struct Clock { | ||
| 214 | /// The frequency, in Hz, of the given clock | ||
| 215 | pub frequency: u32, | ||
| 216 | /// The power state of the clock, e.g. whether it is active in deep sleep mode | ||
| 217 | /// or not. | ||
| 218 | pub power: PoweredClock, | ||
| 219 | } | ||
| 220 | |||
| 221 | /// The power state of a given clock. | ||
| 222 | /// | ||
| 223 | /// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep | ||
| 224 | /// mode will be stopped. This means that any downstream usage, e.g. by peripherals, | ||
| 225 | /// will also stop. | ||
| 226 | /// | ||
| 227 | /// In the future, we will provide an API for entering Deep Sleep, and if there are | ||
| 228 | /// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into | ||
| 229 | /// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. | ||
| 230 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 231 | pub enum PoweredClock { | ||
| 232 | /// The given clock will NOT continue running in Deep Sleep mode | ||
| 233 | NormalEnabledDeepSleepDisabled, | ||
| 234 | /// The given clock WILL continue running in Deep Sleep mode | ||
| 235 | AlwaysEnabled, | ||
| 236 | } | ||
| 237 | |||
| 238 | /// The ClockOperator is a private helper type that contains the methods used | ||
| 239 | /// during system clock initialization. | ||
| 240 | /// | ||
| 241 | /// # SAFETY | ||
| 242 | /// | ||
| 243 | /// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, | ||
| 244 | /// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. | ||
| 245 | struct ClockOperator<'a> { | ||
| 246 | /// A mutable reference to the current state of system clocks | ||
| 247 | clocks: &'a mut Clocks, | ||
| 248 | /// A reference to the requested configuration provided by the caller of [`init()`] | ||
| 249 | config: &'a ClocksConfig, | ||
| 250 | |||
| 251 | // We hold on to stolen peripherals | ||
| 252 | _mrcc0: pac::Mrcc0, | ||
| 253 | scg0: pac::Scg0, | ||
| 254 | syscon: pac::Syscon, | ||
| 255 | vbat0: pac::Vbat0, | ||
| 256 | } | ||
| 257 | |||
| 12 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | 258 | /// Trait describing an AHB clock gate that can be toggled through MRCC. |
| 13 | pub trait Gate { | 259 | pub trait Gate { |
| 14 | type MrccPeriphConfig: SPConfHelper; | 260 | type MrccPeriphConfig: SPConfHelper; |
| 15 | 261 | ||
| 16 | /// Enable the clock gate. | 262 | /// Enable the clock gate. |
| 263 | /// | ||
| 264 | /// # SAFETY | ||
| 265 | /// | ||
| 266 | /// The current peripheral must be disabled prior to calling this method | ||
| 17 | unsafe fn enable_clock(); | 267 | unsafe fn enable_clock(); |
| 18 | 268 | ||
| 19 | /// Disable the clock gate. | 269 | /// Disable the clock gate. |
| 270 | /// | ||
| 271 | /// # SAFETY | ||
| 272 | /// | ||
| 273 | /// There must be no active user of this peripheral when calling this method | ||
| 20 | unsafe fn disable_clock(); | 274 | unsafe fn disable_clock(); |
| 21 | 275 | ||
| 22 | /// Drive the peripheral into reset. | 276 | /// Drive the peripheral into reset. |
| 277 | /// | ||
| 278 | /// # SAFETY | ||
| 279 | /// | ||
| 280 | /// There must be no active user of this peripheral when calling this method | ||
| 23 | unsafe fn assert_reset(); | 281 | unsafe fn assert_reset(); |
| 24 | 282 | ||
| 25 | /// Drive the peripheral out of reset. | 283 | /// Drive the peripheral out of reset. |
| 284 | /// | ||
| 285 | /// # SAFETY | ||
| 286 | /// | ||
| 287 | /// There must be no active user of this peripheral when calling this method | ||
| 26 | unsafe fn release_reset(); | 288 | unsafe fn release_reset(); |
| 27 | 289 | ||
| 28 | /// Return whether the clock gate is currently enabled. | 290 | /// Return whether the clock gate for this peripheral is currently enabled. |
| 29 | fn is_clock_enabled() -> bool; | 291 | fn is_clock_enabled() -> bool; |
| 30 | 292 | ||
| 31 | /// . | 293 | /// Return whether the peripheral is currently held in reset. |
| 32 | fn is_reset_released() -> bool; | 294 | fn is_reset_released() -> bool; |
| 33 | } | 295 | } |
| 34 | 296 | ||
| 297 | /// This is the primary helper method HAL drivers are expected to call when creating | ||
| 298 | /// an instance of the peripheral. | ||
| 299 | /// | ||
| 300 | /// This method: | ||
| 301 | /// | ||
| 302 | /// 1. Enables the MRCC clock gate for this peripheral | ||
| 303 | /// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error | ||
| 304 | /// and re-disabling the peripheral if this fails. | ||
| 305 | /// 3. Pulses the MRCC reset line, to reset the peripheral to the default state | ||
| 306 | /// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account | ||
| 307 | /// the selected upstream clock, as well as any division specified by `cfg`. | ||
| 308 | /// | ||
| 309 | /// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method | ||
| 310 | /// may return `Ok(0)`. In the future, this might be updated to return the correct | ||
| 311 | /// "ambient" clock, e.g. the AHB/APB frequency. | ||
| 312 | /// | ||
| 313 | /// # SAFETY | ||
| 314 | /// | ||
| 315 | /// This peripheral must not yet be in use prior to calling `enable_and_reset`. | ||
| 35 | #[inline] | 316 | #[inline] |
| 36 | pub unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | 317 | pub(crate) unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { |
| 37 | let freq = enable::<G>(cfg)?; | 318 | let freq = enable::<G>(cfg).inspect_err(|_| disable::<G>())?; |
| 38 | pulse_reset::<G>(); | 319 | pulse_reset::<G>(); |
| 39 | Ok(freq) | 320 | Ok(freq) |
| 40 | } | 321 | } |
| 41 | 322 | ||
| 42 | /// Enable a clock gate for the given peripheral set. | 323 | /// Enable the clock gate for the given peripheral. |
| 324 | /// | ||
| 325 | /// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need | ||
| 326 | /// to control the duration of the pulse more directly. | ||
| 327 | /// | ||
| 328 | /// # SAFETY | ||
| 329 | /// | ||
| 330 | /// This peripheral must not yet be in use prior to calling `enable`. | ||
| 43 | #[inline] | 331 | #[inline] |
| 44 | pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | 332 | pub(crate) unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { |
| 45 | G::enable_clock(); | 333 | G::enable_clock(); |
| 46 | while !G::is_clock_enabled() {} | 334 | while !G::is_clock_enabled() {} |
| 47 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | 335 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); |
| @@ -57,239 +345,184 @@ pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockErr | |||
| 57 | }) | 345 | }) |
| 58 | } | 346 | } |
| 59 | 347 | ||
| 60 | pub unsafe fn disable<G: Gate>() { | 348 | /// Disable the clock gate for the given peripheral. |
| 349 | /// | ||
| 350 | /// # SAFETY | ||
| 351 | /// | ||
| 352 | /// This peripheral must no longer be in use prior to calling `enable`. | ||
| 353 | #[allow(dead_code)] | ||
| 354 | #[inline] | ||
| 355 | pub(crate) unsafe fn disable<G: Gate>() { | ||
| 61 | G::disable_clock(); | 356 | G::disable_clock(); |
| 62 | } | 357 | } |
| 63 | 358 | ||
| 64 | /// Check whether a gate is currently enabled. | 359 | /// Check whether a gate is currently enabled. |
| 360 | #[allow(dead_code)] | ||
| 65 | #[inline] | 361 | #[inline] |
| 66 | pub fn is_clock_enabled<G: Gate>() -> bool { | 362 | pub(crate) fn is_clock_enabled<G: Gate>() -> bool { |
| 67 | G::is_clock_enabled() | 363 | G::is_clock_enabled() |
| 68 | } | 364 | } |
| 69 | 365 | ||
| 70 | /// Release a reset line for the given peripheral set. | 366 | /// Release a reset line for the given peripheral set. |
| 367 | /// | ||
| 368 | /// Prefer [`enable_and_reset`]. | ||
| 369 | /// | ||
| 370 | /// # SAFETY | ||
| 371 | /// | ||
| 372 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 71 | #[inline] | 373 | #[inline] |
| 72 | pub unsafe fn release_reset<G: Gate>() { | 374 | pub(crate) unsafe fn release_reset<G: Gate>() { |
| 73 | G::release_reset(); | 375 | G::release_reset(); |
| 74 | } | 376 | } |
| 75 | 377 | ||
| 76 | /// Assert a reset line for the given peripheral set. | 378 | /// Assert a reset line for the given peripheral set. |
| 379 | /// | ||
| 380 | /// Prefer [`enable_and_reset`]. | ||
| 381 | /// | ||
| 382 | /// # SAFETY | ||
| 383 | /// | ||
| 384 | /// This peripheral must not yet be in use prior to calling `assert_reset`. | ||
| 77 | #[inline] | 385 | #[inline] |
| 78 | pub unsafe fn assert_reset<G: Gate>() { | 386 | pub(crate) unsafe fn assert_reset<G: Gate>() { |
| 79 | G::assert_reset(); | 387 | G::assert_reset(); |
| 80 | } | 388 | } |
| 81 | 389 | ||
| 390 | /// Check whether the peripheral is held in reset. | ||
| 82 | #[inline] | 391 | #[inline] |
| 83 | pub unsafe fn is_reset_released<G: Gate>() -> bool { | 392 | pub(crate) unsafe fn is_reset_released<G: Gate>() -> bool { |
| 84 | G::is_reset_released() | 393 | G::is_reset_released() |
| 85 | } | 394 | } |
| 86 | 395 | ||
| 87 | /// Pulse a reset line (assert then release) with a short delay. | 396 | /// Pulse a reset line (assert then release) with a short delay. |
| 397 | /// | ||
| 398 | /// Prefer [`enable_and_reset`]. | ||
| 399 | /// | ||
| 400 | /// # SAFETY | ||
| 401 | /// | ||
| 402 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 88 | #[inline] | 403 | #[inline] |
| 89 | pub unsafe fn pulse_reset<G: Gate>() { | 404 | pub(crate) unsafe fn pulse_reset<G: Gate>() { |
| 90 | G::assert_reset(); | 405 | G::assert_reset(); |
| 91 | cortex_m::asm::nop(); | 406 | cortex_m::asm::nop(); |
| 92 | cortex_m::asm::nop(); | 407 | cortex_m::asm::nop(); |
| 93 | G::release_reset(); | 408 | G::release_reset(); |
| 94 | } | 409 | } |
| 95 | 410 | ||
| 96 | macro_rules! impl_cc_gate { | 411 | // |
| 97 | ($name:ident, $reg:ident, $field:ident, $config:ty) => { | 412 | // `impl`s for structs/enums |
| 98 | impl Gate for crate::peripherals::$name { | 413 | // |
| 99 | type MrccPeriphConfig = $config; | ||
| 100 | |||
| 101 | #[inline] | ||
| 102 | unsafe fn enable_clock() { | ||
| 103 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 104 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 105 | } | ||
| 106 | |||
| 107 | #[inline] | ||
| 108 | unsafe fn disable_clock() { | ||
| 109 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 110 | mrcc.$reg().modify(|_r, w| w.$field().disabled()); | ||
| 111 | } | ||
| 112 | |||
| 113 | #[inline] | ||
| 114 | fn is_clock_enabled() -> bool { | ||
| 115 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 116 | mrcc.$reg().read().$field().is_enabled() | ||
| 117 | } | ||
| 118 | |||
| 119 | #[inline] | ||
| 120 | unsafe fn release_reset() { | ||
| 121 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 122 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 123 | } | ||
| 124 | 414 | ||
| 125 | #[inline] | 415 | /// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". |
| 126 | unsafe fn assert_reset() { | 416 | /// |
| 127 | let mrcc = unsafe { pac::Mrcc0::steal() }; | 417 | /// These methods are intended to be used by HAL peripheral implementors to ensure that their |
| 128 | mrcc.$reg().modify(|_, w| w.$field().disabled()); | 418 | /// selected clocks are active at a suitable level at time of construction. These methods |
| 129 | } | 419 | /// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. |
| 130 | 420 | impl Clocks { | |
| 131 | #[inline] | 421 | /// Ensure the `fro_lf_div` clock is active and valid at the given power state. |
| 132 | fn is_reset_released() -> bool { | 422 | pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 133 | let mrcc = unsafe { pac::Mrcc0::steal() }; | 423 | let Some(clk) = self.fro_lf_div.as_ref() else { |
| 134 | mrcc.$reg().read().$field().is_enabled() | 424 | return Err(ClockError::BadConfig { |
| 135 | } | 425 | clock: "fro_lf_div", |
| 426 | reason: "required but not active", | ||
| 427 | }); | ||
| 428 | }; | ||
| 429 | if !clk.power.meets_requirement_of(at_level) { | ||
| 430 | return Err(ClockError::BadConfig { | ||
| 431 | clock: "fro_lf_div", | ||
| 432 | reason: "not low power active", | ||
| 433 | }); | ||
| 136 | } | 434 | } |
| 137 | }; | 435 | Ok(clk.frequency) |
| 138 | } | ||
| 139 | |||
| 140 | pub struct UnimplementedConfig; | ||
| 141 | impl SPConfHelper for UnimplementedConfig { | ||
| 142 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 143 | Err(ClockError::UnimplementedConfig) | ||
| 144 | } | 436 | } |
| 145 | } | ||
| 146 | 437 | ||
| 147 | pub struct NoConfig; | 438 | /// Ensure the `fro_hf` clock is active and valid at the given power state. |
| 148 | impl SPConfHelper for NoConfig { | 439 | pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 149 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | 440 | let Some(clk) = self.fro_hf.as_ref() else { |
| 150 | Ok(0) | 441 | return Err(ClockError::BadConfig { |
| 442 | clock: "fro_hf", | ||
| 443 | reason: "required but not active", | ||
| 444 | }); | ||
| 445 | }; | ||
| 446 | if !clk.power.meets_requirement_of(at_level) { | ||
| 447 | return Err(ClockError::BadConfig { | ||
| 448 | clock: "fro_hf", | ||
| 449 | reason: "not low power active", | ||
| 450 | }); | ||
| 451 | } | ||
| 452 | Ok(clk.frequency) | ||
| 151 | } | 453 | } |
| 152 | } | ||
| 153 | 454 | ||
| 154 | pub mod gate { | 455 | /// Ensure the `fro_hf_div` clock is active and valid at the given power state. |
| 155 | use super::periph_helpers::{AdcConfig, LpuartConfig, OsTimerConfig}; | 456 | pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 156 | use super::*; | 457 | let Some(clk) = self.fro_hf_div.as_ref() else { |
| 458 | return Err(ClockError::BadConfig { | ||
| 459 | clock: "fro_hf_div", | ||
| 460 | reason: "required but not active", | ||
| 461 | }); | ||
| 462 | }; | ||
| 463 | if !clk.power.meets_requirement_of(at_level) { | ||
| 464 | return Err(ClockError::BadConfig { | ||
| 465 | clock: "fro_hf_div", | ||
| 466 | reason: "not low power active", | ||
| 467 | }); | ||
| 468 | } | ||
| 469 | Ok(clk.frequency) | ||
| 470 | } | ||
| 157 | 471 | ||
| 158 | // These peripherals have no additional upstream clocks or configuration required | 472 | /// Ensure the `clk_in` clock is active and valid at the given power state. |
| 159 | // other than enabling through the MRCC gate. | 473 | pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 160 | impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); | 474 | Err(ClockError::NotImplemented { clock: "clk_in" }) |
| 161 | impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); | 475 | } |
| 162 | impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); | ||
| 163 | impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); | ||
| 164 | 476 | ||
| 165 | impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); | 477 | /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. |
| 166 | impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); | 478 | pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 167 | impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); | 479 | // NOTE: clk_16k is always active in low power mode |
| 168 | } | 480 | Ok(self |
| 481 | .clk_16k_vsys | ||
| 482 | .as_ref() | ||
| 483 | .ok_or(ClockError::BadConfig { | ||
| 484 | clock: "clk_16k_vsys", | ||
| 485 | reason: "required but not active", | ||
| 486 | })? | ||
| 487 | .frequency) | ||
| 488 | } | ||
| 169 | 489 | ||
| 170 | // /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. | 490 | /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. |
| 171 | // pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { | 491 | pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 172 | // enable::<gate::Port2>(peripherals); | 492 | // NOTE: clk_16k is always active in low power mode |
| 173 | // enable::<gate::Lpuart2>(peripherals); | 493 | Ok(self |
| 174 | // } | 494 | .clk_16k_vdd_core |
| 175 | 495 | .as_ref() | |
| 176 | // /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. | 496 | .ok_or(ClockError::BadConfig { |
| 177 | // pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { | 497 | clock: "clk_16k_vdd_core", |
| 178 | // enable::<gate::Port3>(peripherals); | 498 | reason: "required but not active", |
| 179 | // enable::<gate::Gpio3>(peripherals); | 499 | })? |
| 180 | // } | 500 | .frequency) |
| 181 | |||
| 182 | // /// Convenience helper enabling the OSTIMER0 clock gate. | ||
| 183 | // pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { | ||
| 184 | // enable::<gate::Ostimer0>(peripherals); | ||
| 185 | // } | ||
| 186 | |||
| 187 | // pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { | ||
| 188 | // // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 189 | // let mrcc = &peripherals.mrcc0; | ||
| 190 | // mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 191 | // mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 192 | // } | ||
| 193 | |||
| 194 | // pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { | ||
| 195 | // // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) | ||
| 196 | // let sys = &peripherals.syscon; | ||
| 197 | // sys.frolfdiv().modify(|_, w| { | ||
| 198 | // // DIV defaults to 0; keep it explicit and clear HALT | ||
| 199 | // unsafe { w.div().bits(0) }.halt().run() | ||
| 200 | // }); | ||
| 201 | // } | ||
| 202 | |||
| 203 | // /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. | ||
| 204 | // /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. | ||
| 205 | // pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { | ||
| 206 | // // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. | ||
| 207 | // // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. | ||
| 208 | // // Read it anyway for future generality. | ||
| 209 | // let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; | ||
| 210 | // let base = 12_000_000u32; | ||
| 211 | // base / (div + 1) | ||
| 212 | // } | ||
| 213 | |||
| 214 | // /// Enable clock gate and release reset for OSTIMER0. | ||
| 215 | // /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). | ||
| 216 | // pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { | ||
| 217 | // let mrcc = &peripherals.mrcc0; | ||
| 218 | // mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 219 | // } | ||
| 220 | |||
| 221 | // pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { | ||
| 222 | // enable::<gate::Port1>(peripherals); | ||
| 223 | // enable::<gate::Adc1>(peripherals); | ||
| 224 | // } | ||
| 225 | |||
| 226 | // pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { | ||
| 227 | // // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 228 | // let mrcc = &peripherals.mrcc0; | ||
| 229 | // mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 230 | // mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 231 | // } | ||
| 232 | |||
| 233 | // ============================================== | ||
| 234 | |||
| 235 | /// This type represents a divider in the range 1..=256. | ||
| 236 | /// | ||
| 237 | /// At a hardware level, this is an 8-bit register from 0..=255, | ||
| 238 | /// which adds one. | ||
| 239 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 240 | pub struct Div8(pub(super) u8); | ||
| 241 | |||
| 242 | impl Div8 { | ||
| 243 | /// Store a "raw" divisor value that will divide the source by | ||
| 244 | /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source | ||
| 245 | /// by 1, and `Div8::from_raw(255)` will divide the source by | ||
| 246 | /// 256. | ||
| 247 | pub const fn from_raw(n: u8) -> Self { | ||
| 248 | Self(n) | ||
| 249 | } | 501 | } |
| 250 | 502 | ||
| 251 | /// Store a specific divisor value that will divide the source | 503 | /// Ensure the `clk_1m` clock is active and valid at the given power state. |
| 252 | /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source | 504 | pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 253 | /// by 1, and `Div8::from_divisor(256)` will divide the source | 505 | let Some(clk) = self.clk_1m.as_ref() else { |
| 254 | /// by 256. | 506 | return Err(ClockError::BadConfig { |
| 255 | /// | 507 | clock: "clk_1m", |
| 256 | /// Will return `None` if `n` is not in the range `1..=256`. | 508 | reason: "required but not active", |
| 257 | /// Consider [`Self::from_raw`] for an infallible version. | 509 | }); |
| 258 | pub const fn from_divisor(n: u16) -> Option<Self> { | ||
| 259 | let Some(n) = n.checked_sub(1) else { | ||
| 260 | return None; | ||
| 261 | }; | 510 | }; |
| 262 | if n > (u8::MAX as u16) { | 511 | if !clk.power.meets_requirement_of(at_level) { |
| 263 | return None; | 512 | return Err(ClockError::BadConfig { |
| 513 | clock: "clk_1m", | ||
| 514 | reason: "not low power active", | ||
| 515 | }); | ||
| 264 | } | 516 | } |
| 265 | Some(Self(n as u8)) | 517 | Ok(clk.frequency) |
| 266 | } | ||
| 267 | |||
| 268 | /// Convert into "raw" bits form | ||
| 269 | #[inline(always)] | ||
| 270 | pub const fn into_bits(self) -> u8 { | ||
| 271 | self.0 | ||
| 272 | } | 518 | } |
| 273 | 519 | ||
| 274 | /// Convert into "divisor" form, as a u32 for convenient frequency math | 520 | /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. |
| 275 | #[inline(always)] | 521 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 276 | pub const fn into_divisor(self) -> u32 { | 522 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) |
| 277 | self.0 as u32 + 1 | ||
| 278 | } | 523 | } |
| 279 | } | 524 | } |
| 280 | 525 | ||
| 281 | #[derive(Debug, Clone)] | ||
| 282 | pub struct Clock { | ||
| 283 | pub frequency: u32, | ||
| 284 | pub power: PoweredClock, | ||
| 285 | } | ||
| 286 | |||
| 287 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 288 | pub enum PoweredClock { | ||
| 289 | NormalEnabledDeepSleepDisabled, | ||
| 290 | AlwaysEnabled, | ||
| 291 | } | ||
| 292 | |||
| 293 | impl PoweredClock { | 526 | impl PoweredClock { |
| 294 | /// Does THIS clock meet the power requirements of the OTHER clock? | 527 | /// Does THIS clock meet the power requirements of the OTHER clock? |
| 295 | pub fn meets_requirement_of(&self, other: &Self) -> bool { | 528 | pub fn meets_requirement_of(&self, other: &Self) -> bool { |
| @@ -302,172 +535,11 @@ impl PoweredClock { | |||
| 302 | } | 535 | } |
| 303 | } | 536 | } |
| 304 | 537 | ||
| 305 | /// ```text | ||
| 306 | /// ┌─────────────────────────────────────────────────────────┐ | ||
| 307 | /// │ │ | ||
| 308 | /// │ ┌───────────┐ clk_out ┌─────────┐ │ | ||
| 309 | /// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ | ||
| 310 | /// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ | ||
| 311 | /// EXTAL ──────┼──▷│ │───────────▷│ │ │ | ||
| 312 | /// │ └───────────┘ └─────────┘ │ | ||
| 313 | /// │ │ | ||
| 314 | /// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ | ||
| 315 | /// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ | ||
| 316 | /// │ │ │ │ ├────┤ clk_45m │ | ||
| 317 | /// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ | ||
| 318 | /// │ └───────────┘ └────┘ │ | ||
| 319 | /// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ | ||
| 320 | /// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ | ||
| 321 | /// │ │ │ │ ├────┤ clk_1m │ | ||
| 322 | /// │ │ │ └─────▷│1/12│────────────────────┼──────▷ | ||
| 323 | /// │ └───────────┘ └────┘ │ | ||
| 324 | /// │ │ | ||
| 325 | /// │ ┌──────────┐ │ | ||
| 326 | /// │ │000 │ │ | ||
| 327 | /// │ clk_in │ │ │ | ||
| 328 | /// │ ───────────────▷│001 │ │ | ||
| 329 | /// │ fro_12m │ │ │ | ||
| 330 | /// │ ───────────────▷│010 │ │ | ||
| 331 | /// │ fro_hf_root │ │ │ | ||
| 332 | /// │ ───────────────▷│011 │ main_clk │ | ||
| 333 | /// │ │ │───────────────────────────┼──────▷ | ||
| 334 | /// clk_16k ──────┼─────────────────▷│100 │ │ | ||
| 335 | /// │ none │ │ │ | ||
| 336 | /// │ ───────────────▷│101 │ │ | ||
| 337 | /// │ pll1_clk │ │ │ | ||
| 338 | /// │ ───────────────▷│110 │ │ | ||
| 339 | /// │ none │ │ │ | ||
| 340 | /// │ ───────────────▷│111 │ │ | ||
| 341 | /// │ └──────────┘ │ | ||
| 342 | /// │ ▲ │ | ||
| 343 | /// │ │ │ | ||
| 344 | /// │ SCG SCS │ | ||
| 345 | /// │ SCG-Lite │ | ||
| 346 | /// └─────────────────────────────────────────────────────────┘ | ||
| 347 | /// | ||
| 348 | /// | ||
| 349 | /// clk_in ┌─────┐ | ||
| 350 | /// ───────────────▷│00 │ | ||
| 351 | /// clk_45m │ │ | ||
| 352 | /// ───────────────▷│01 │ ┌───────────┐ pll1_clk | ||
| 353 | /// none │ │─────▷│ SPLL │───────────────▷ | ||
| 354 | /// ───────────────▷│10 │ └───────────┘ | ||
| 355 | /// fro_12m │ │ | ||
| 356 | /// ───────────────▷│11 │ | ||
| 357 | /// └─────┘ | ||
| 358 | /// ``` | ||
| 359 | #[non_exhaustive] | ||
| 360 | pub struct ClocksConfig { | ||
| 361 | // FIRC, FRO180, 45/60/90/180M clock source | ||
| 362 | pub firc: Option<FircConfig>, | ||
| 363 | // NOTE: I don't think we *can* disable the SIRC? | ||
| 364 | pub sirc: SircConfig, | ||
| 365 | pub fro16k: Option<Fro16KConfig>, | ||
| 366 | } | ||
| 367 | |||
| 368 | // FIRC/FRO180M | ||
| 369 | |||
| 370 | /// ```text | ||
| 371 | /// ┌───────────┐ fro_hf_root ┌────┐ fro_hf | ||
| 372 | /// │ FRO180M ├───────┬─────▷│GATE│──────────▷ | ||
| 373 | /// │ │ │ ├────┤ clk_45m | ||
| 374 | /// │ │ └─────▷│GATE│──────────▷ | ||
| 375 | /// └───────────┘ └────┘ | ||
| 376 | /// ``` | ||
| 377 | #[non_exhaustive] | ||
| 378 | pub struct FircConfig { | ||
| 379 | pub frequency: FircFreqSel, | ||
| 380 | pub power: PoweredClock, | ||
| 381 | /// Is the "fro_hf" gated clock enabled? | ||
| 382 | pub fro_hf_enabled: bool, | ||
| 383 | /// Is the "clk_45m" gated clock enabled? | ||
| 384 | pub clk_45m_enabled: bool, | ||
| 385 | /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! | ||
| 386 | pub fro_hf_div: Option<Div8>, | ||
| 387 | } | ||
| 388 | |||
| 389 | pub enum FircFreqSel { | ||
| 390 | Mhz45, | ||
| 391 | Mhz60, | ||
| 392 | Mhz90, | ||
| 393 | Mhz180, | ||
| 394 | } | ||
| 395 | |||
| 396 | // SIRC/FRO12M | ||
| 397 | |||
| 398 | /// ```text | ||
| 399 | /// ┌───────────┐ fro_12m_root ┌────┐ fro_12m | ||
| 400 | /// │ FRO12M │────────┬─────▷│ CG │──────────▷ | ||
| 401 | /// │ │ │ ├────┤ clk_1m | ||
| 402 | /// │ │ └─────▷│1/12│──────────▷ | ||
| 403 | /// └───────────┘ └────┘ | ||
| 404 | /// ``` | ||
| 405 | #[non_exhaustive] | ||
| 406 | pub struct SircConfig { | ||
| 407 | pub power: PoweredClock, | ||
| 408 | // peripheral output, aka sirc_12mhz | ||
| 409 | pub fro_12m_enabled: bool, | ||
| 410 | /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! | ||
| 411 | pub fro_lf_div: Option<Div8>, | ||
| 412 | } | ||
| 413 | |||
| 414 | #[derive(Default, Debug, Clone)] | ||
| 415 | #[non_exhaustive] | ||
| 416 | pub struct Clocks { | ||
| 417 | pub clk_in: Option<Clock>, | ||
| 418 | |||
| 419 | // FRO180M stuff | ||
| 420 | // | ||
| 421 | pub fro_hf_root: Option<Clock>, | ||
| 422 | pub fro_hf: Option<Clock>, | ||
| 423 | pub clk_45m: Option<Clock>, | ||
| 424 | pub fro_hf_div: Option<Clock>, | ||
| 425 | // | ||
| 426 | // End FRO180M | ||
| 427 | |||
| 428 | // FRO12M stuff | ||
| 429 | pub fro_12m_root: Option<Clock>, | ||
| 430 | pub fro_12m: Option<Clock>, | ||
| 431 | pub clk_1m: Option<Clock>, | ||
| 432 | pub fro_lf_div: Option<Clock>, | ||
| 433 | // | ||
| 434 | // End FRO12M stuff | ||
| 435 | pub clk_16k_vsys: Option<Clock>, | ||
| 436 | pub clk_16k_vdd_core: Option<Clock>, | ||
| 437 | pub main_clk: Option<Clock>, | ||
| 438 | pub pll1_clk: Option<Clock>, | ||
| 439 | } | ||
| 440 | |||
| 441 | #[non_exhaustive] | ||
| 442 | pub struct Fro16KConfig { | ||
| 443 | pub vsys_domain_active: bool, | ||
| 444 | pub vdd_core_domain_active: bool, | ||
| 445 | } | ||
| 446 | |||
| 447 | static CLOCKS: critical_section::Mutex<RefCell<Option<Clocks>>> = critical_section::Mutex::new(RefCell::new(None)); | ||
| 448 | |||
| 449 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 450 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 451 | #[non_exhaustive] | ||
| 452 | pub enum ClockError { | ||
| 453 | NeverInitialized, | ||
| 454 | AlreadyInitialized, | ||
| 455 | BadConfig { clock: &'static str, reason: &'static str }, | ||
| 456 | NotImplemented { clock: &'static str }, | ||
| 457 | UnimplementedConfig, | ||
| 458 | } | ||
| 459 | |||
| 460 | struct ClockOperator<'a> { | ||
| 461 | clocks: &'a mut Clocks, | ||
| 462 | config: &'a ClocksConfig, | ||
| 463 | |||
| 464 | _mrcc0: pac::Mrcc0, | ||
| 465 | scg0: pac::Scg0, | ||
| 466 | syscon: pac::Syscon, | ||
| 467 | vbat0: pac::Vbat0, | ||
| 468 | } | ||
| 469 | |||
| 470 | impl ClockOperator<'_> { | 538 | impl ClockOperator<'_> { |
| 539 | /// Configure the FIRC/FRO180M clock family | ||
| 540 | /// | ||
| 541 | /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used | ||
| 542 | /// as the main clock used for the CPU, AHB, APB, etc. | ||
| 471 | fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { | 543 | fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { |
| 472 | const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { | 544 | const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { |
| 473 | clock: "firc", | 545 | clock: "firc", |
| @@ -484,7 +556,7 @@ impl ClockOperator<'_> { | |||
| 484 | } | 556 | } |
| 485 | let base_freq = 45_000_000; | 557 | let base_freq = 45_000_000; |
| 486 | 558 | ||
| 487 | // Is the FIRC as expected? | 559 | // Now, check if the FIRC as expected for our hardcoded value |
| 488 | let mut firc_ok = true; | 560 | let mut firc_ok = true; |
| 489 | 561 | ||
| 490 | // Is the hardware currently set to the default 45MHz? | 562 | // Is the hardware currently set to the default 45MHz? |
| @@ -604,6 +676,7 @@ impl ClockOperator<'_> { | |||
| 604 | Ok(()) | 676 | Ok(()) |
| 605 | } | 677 | } |
| 606 | 678 | ||
| 679 | /// Configure the SIRC/FRO12M clock family | ||
| 607 | fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { | 680 | fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { |
| 608 | let SircConfig { | 681 | let SircConfig { |
| 609 | power, | 682 | power, |
| @@ -694,6 +767,7 @@ impl ClockOperator<'_> { | |||
| 694 | Ok(()) | 767 | Ok(()) |
| 695 | } | 768 | } |
| 696 | 769 | ||
| 770 | /// Configure the FRO16K/clk_16k clock family | ||
| 697 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { | 771 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { |
| 698 | let Some(fro16k) = self.config.fro16k.as_ref() else { | 772 | let Some(fro16k) = self.config.fro16k.as_ref() else { |
| 699 | return Ok(()); | 773 | return Ok(()); |
| @@ -735,145 +809,74 @@ impl ClockOperator<'_> { | |||
| 735 | } | 809 | } |
| 736 | } | 810 | } |
| 737 | 811 | ||
| 738 | pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | 812 | // |
| 739 | critical_section::with(|cs| { | 813 | // Macros/macro impls |
| 740 | if CLOCKS.borrow_ref(cs).is_some() { | 814 | // |
| 741 | Err(ClockError::AlreadyInitialized) | ||
| 742 | } else { | ||
| 743 | Ok(()) | ||
| 744 | } | ||
| 745 | })?; | ||
| 746 | |||
| 747 | let mut clocks = Clocks::default(); | ||
| 748 | let mut operator = ClockOperator { | ||
| 749 | clocks: &mut clocks, | ||
| 750 | config: &settings, | ||
| 751 | |||
| 752 | _mrcc0: unsafe { pac::Mrcc0::steal() }, | ||
| 753 | scg0: unsafe { pac::Scg0::steal() }, | ||
| 754 | syscon: unsafe { pac::Syscon::steal() }, | ||
| 755 | vbat0: unsafe { pac::Vbat0::steal() }, | ||
| 756 | }; | ||
| 757 | 815 | ||
| 758 | operator.configure_firc_clocks()?; | 816 | /// This macro is used to implement the [`Gate`] trait for a given peripheral |
| 759 | operator.configure_sirc_clocks()?; | 817 | /// that is controlled by the MRCC peripheral. |
| 760 | operator.configure_fro16k_clocks()?; | 818 | macro_rules! impl_cc_gate { |
| 761 | // TODO, everything downstream | 819 | ($name:ident, $reg:ident, $field:ident, $config:ty) => { |
| 820 | impl Gate for crate::peripherals::$name { | ||
| 821 | type MrccPeriphConfig = $config; | ||
| 762 | 822 | ||
| 763 | critical_section::with(|cs| { | 823 | #[inline] |
| 764 | let mut clks = CLOCKS.borrow_ref_mut(cs); | 824 | unsafe fn enable_clock() { |
| 765 | assert!(clks.is_none(), "Clock setup race!"); | 825 | let mrcc = unsafe { pac::Mrcc0::steal() }; |
| 766 | *clks = Some(clocks); | 826 | mrcc.$reg().modify(|_, w| w.$field().enabled()); |
| 767 | }); | 827 | } |
| 768 | 828 | ||
| 769 | Ok(()) | 829 | #[inline] |
| 770 | } | 830 | unsafe fn disable_clock() { |
| 831 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 832 | mrcc.$reg().modify(|_r, w| w.$field().disabled()); | ||
| 833 | } | ||
| 771 | 834 | ||
| 772 | /// Obtain the full clocks structure, calling the given closure in a critical section | 835 | #[inline] |
| 773 | /// | 836 | fn is_clock_enabled() -> bool { |
| 774 | /// NOTE: Clocks implements `Clone`, | 837 | let mrcc = unsafe { pac::Mrcc0::steal() }; |
| 775 | pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> { | 838 | mrcc.$reg().read().$field().is_enabled() |
| 776 | critical_section::with(|cs| { | 839 | } |
| 777 | let c = CLOCKS.borrow_ref(cs); | ||
| 778 | let c = c.as_ref()?; | ||
| 779 | Some(f(c)) | ||
| 780 | }) | ||
| 781 | } | ||
| 782 | 840 | ||
| 783 | impl Clocks { | 841 | #[inline] |
| 784 | pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | 842 | unsafe fn release_reset() { |
| 785 | let Some(clk) = self.fro_lf_div.as_ref() else { | 843 | let mrcc = unsafe { pac::Mrcc0::steal() }; |
| 786 | return Err(ClockError::BadConfig { | 844 | mrcc.$reg().modify(|_, w| w.$field().enabled()); |
| 787 | clock: "fro_lf_div", | 845 | } |
| 788 | reason: "required but not active", | ||
| 789 | }); | ||
| 790 | }; | ||
| 791 | if !clk.power.meets_requirement_of(at_level) { | ||
| 792 | return Err(ClockError::BadConfig { | ||
| 793 | clock: "fro_lf_div", | ||
| 794 | reason: "not low power active", | ||
| 795 | }); | ||
| 796 | } | ||
| 797 | Ok(clk.frequency) | ||
| 798 | } | ||
| 799 | 846 | ||
| 800 | pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | 847 | #[inline] |
| 801 | let Some(clk) = self.fro_hf.as_ref() else { | 848 | unsafe fn assert_reset() { |
| 802 | return Err(ClockError::BadConfig { | 849 | let mrcc = unsafe { pac::Mrcc0::steal() }; |
| 803 | clock: "fro_hf", | 850 | mrcc.$reg().modify(|_, w| w.$field().disabled()); |
| 804 | reason: "required but not active", | 851 | } |
| 805 | }); | ||
| 806 | }; | ||
| 807 | if !clk.power.meets_requirement_of(at_level) { | ||
| 808 | return Err(ClockError::BadConfig { | ||
| 809 | clock: "fro_hf", | ||
| 810 | reason: "not low power active", | ||
| 811 | }); | ||
| 812 | } | ||
| 813 | Ok(clk.frequency) | ||
| 814 | } | ||
| 815 | 852 | ||
| 816 | pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | 853 | #[inline] |
| 817 | let Some(clk) = self.fro_hf_div.as_ref() else { | 854 | fn is_reset_released() -> bool { |
| 818 | return Err(ClockError::BadConfig { | 855 | let mrcc = unsafe { pac::Mrcc0::steal() }; |
| 819 | clock: "fro_hf_div", | 856 | mrcc.$reg().read().$field().is_enabled() |
| 820 | reason: "required but not active", | 857 | } |
| 821 | }); | ||
| 822 | }; | ||
| 823 | if !clk.power.meets_requirement_of(at_level) { | ||
| 824 | return Err(ClockError::BadConfig { | ||
| 825 | clock: "fro_hf_div", | ||
| 826 | reason: "not low power active", | ||
| 827 | }); | ||
| 828 | } | 858 | } |
| 829 | Ok(clk.frequency) | 859 | }; |
| 830 | } | 860 | } |
| 831 | |||
| 832 | pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 833 | Err(ClockError::NotImplemented { clock: "clk_in" }) | ||
| 834 | } | ||
| 835 | |||
| 836 | pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 837 | // NOTE: clk_16k is always active in low power mode | ||
| 838 | Ok(self | ||
| 839 | .clk_16k_vsys | ||
| 840 | .as_ref() | ||
| 841 | .ok_or(ClockError::BadConfig { | ||
| 842 | clock: "clk_16k_vsys", | ||
| 843 | reason: "required but not active", | ||
| 844 | })? | ||
| 845 | .frequency) | ||
| 846 | } | ||
| 847 | 861 | ||
| 848 | pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | 862 | /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, |
| 849 | // NOTE: clk_16k is always active in low power mode | 863 | /// for various low level peripherals. |
| 850 | Ok(self | 864 | pub(crate) mod gate { |
| 851 | .clk_16k_vdd_core | 865 | use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig}; |
| 852 | .as_ref() | 866 | use super::*; |
| 853 | .ok_or(ClockError::BadConfig { | ||
| 854 | clock: "clk_16k_vdd_core", | ||
| 855 | reason: "required but not active", | ||
| 856 | })? | ||
| 857 | .frequency) | ||
| 858 | } | ||
| 859 | 867 | ||
| 860 | pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | 868 | // These peripherals have no additional upstream clocks or configuration required |
| 861 | let Some(clk) = self.clk_1m.as_ref() else { | 869 | // other than enabling through the MRCC gate. Currently, these peripherals will |
| 862 | return Err(ClockError::BadConfig { | 870 | // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or |
| 863 | clock: "clk_1m", | 871 | // [`SPConfHelper::post_enable_config()`]. |
| 864 | reason: "required but not active", | 872 | impl_cc_gate!(PORT1, mrcc_glb_cc1, port1, NoConfig); |
| 865 | }); | 873 | impl_cc_gate!(PORT2, mrcc_glb_cc1, port2, NoConfig); |
| 866 | }; | 874 | impl_cc_gate!(PORT3, mrcc_glb_cc1, port3, NoConfig); |
| 867 | if !clk.power.meets_requirement_of(at_level) { | 875 | impl_cc_gate!(GPIO3, mrcc_glb_cc2, gpio3, NoConfig); |
| 868 | return Err(ClockError::BadConfig { | ||
| 869 | clock: "clk_1m", | ||
| 870 | reason: "not low power active", | ||
| 871 | }); | ||
| 872 | } | ||
| 873 | Ok(clk.frequency) | ||
| 874 | } | ||
| 875 | 876 | ||
| 876 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | 877 | // These peripherals DO have meaningful configuration, and could fail if the system |
| 877 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) | 878 | // clocks do not match their needs. |
| 878 | } | 879 | impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, ostimer0, OsTimerConfig); |
| 880 | impl_cc_gate!(LPUART2, mrcc_glb_cc0, lpuart2, LpuartConfig); | ||
| 881 | impl_cc_gate!(ADC1, mrcc_glb_cc1, adc1, AdcConfig); | ||
| 879 | } | 882 | } |
diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs index 1657bd7eb..e5b234c5b 100644 --- a/src/clocks/periph_helpers.rs +++ b/src/clocks/periph_helpers.rs | |||
| @@ -1,7 +1,43 @@ | |||
| 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 | |||
| 1 | use super::{ClockError, Clocks, PoweredClock}; | 10 | use super::{ClockError, Clocks, PoweredClock}; |
| 2 | use crate::pac; | 11 | use crate::pac; |
| 3 | 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()`. | ||
| 4 | pub trait SPConfHelper { | 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. | ||
| 5 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError>; | 41 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError>; |
| 6 | } | 42 | } |
| 7 | 43 | ||
| @@ -65,6 +101,33 @@ impl Div4 { | |||
| 65 | } | 101 | } |
| 66 | } | 102 | } |
| 67 | 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 | ||
| 68 | #[derive(Debug, Clone, Copy)] | 131 | #[derive(Debug, Clone, Copy)] |
| 69 | pub enum LpuartClockSel { | 132 | pub enum LpuartClockSel { |
| 70 | /// FRO12M/FRO_LF/SIRC clock source, passed through divider | 133 | /// FRO12M/FRO_LF/SIRC clock source, passed through divider |
| @@ -86,16 +149,26 @@ pub enum LpuartClockSel { | |||
| 86 | None, | 149 | None, |
| 87 | } | 150 | } |
| 88 | 151 | ||
| 152 | /// Which instance of the Lpuart is this? | ||
| 153 | /// | ||
| 154 | /// Should not be directly selectable by end-users. | ||
| 89 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 155 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
| 90 | pub enum LpuartInstance { | 156 | pub enum LpuartInstance { |
| 157 | /// Instance 0 | ||
| 91 | Lpuart0, | 158 | Lpuart0, |
| 159 | /// Instance 1 | ||
| 92 | Lpuart1, | 160 | Lpuart1, |
| 161 | /// Instance 2 | ||
| 93 | Lpuart2, | 162 | Lpuart2, |
| 163 | /// Instance 3 | ||
| 94 | Lpuart3, | 164 | Lpuart3, |
| 165 | /// Instance 4 | ||
| 95 | Lpuart4, | 166 | Lpuart4, |
| 167 | /// Instance 5 | ||
| 96 | Lpuart5, | 168 | Lpuart5, |
| 97 | } | 169 | } |
| 98 | 170 | ||
| 171 | /// Top level configuration for `Lpuart` instances. | ||
| 99 | pub struct LpuartConfig { | 172 | pub struct LpuartConfig { |
| 100 | /// Power state required for this peripheral | 173 | /// Power state required for this peripheral |
| 101 | pub power: PoweredClock, | 174 | pub power: PoweredClock, |
| @@ -108,39 +181,6 @@ pub struct LpuartConfig { | |||
| 108 | pub(crate) instance: LpuartInstance, | 181 | pub(crate) instance: LpuartInstance, |
| 109 | } | 182 | } |
| 110 | 183 | ||
| 111 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 112 | pub enum OstimerClockSel { | ||
| 113 | /// 16k clock, sourced from FRO16K (Vdd Core) | ||
| 114 | Clk16kVddCore, | ||
| 115 | /// 1 MHz Clock sourced from FRO12M | ||
| 116 | Clk1M, | ||
| 117 | /// Disabled | ||
| 118 | None, | ||
| 119 | } | ||
| 120 | |||
| 121 | pub struct OsTimerConfig { | ||
| 122 | pub power: PoweredClock, | ||
| 123 | pub source: OstimerClockSel, | ||
| 124 | } | ||
| 125 | |||
| 126 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 127 | pub enum AdcClockSel { | ||
| 128 | FroLfDiv, | ||
| 129 | FroHf, | ||
| 130 | ClkIn, | ||
| 131 | Clk1M, | ||
| 132 | Pll1ClkDiv, | ||
| 133 | None, | ||
| 134 | } | ||
| 135 | |||
| 136 | pub struct AdcConfig { | ||
| 137 | pub power: PoweredClock, | ||
| 138 | pub source: AdcClockSel, | ||
| 139 | pub div: Div4, | ||
| 140 | } | ||
| 141 | |||
| 142 | // impls | ||
| 143 | |||
| 144 | impl SPConfHelper for LpuartConfig { | 184 | impl SPConfHelper for LpuartConfig { |
| 145 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | 185 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { |
| 146 | // check that source is suitable | 186 | // check that source is suitable |
| @@ -215,6 +255,29 @@ impl SPConfHelper for LpuartConfig { | |||
| 215 | } | 255 | } |
| 216 | } | 256 | } |
| 217 | 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 | |||
| 218 | impl SPConfHelper for OsTimerConfig { | 281 | impl SPConfHelper for OsTimerConfig { |
| 219 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | 282 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { |
| 220 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | 283 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; |
| @@ -237,6 +300,37 @@ impl SPConfHelper for OsTimerConfig { | |||
| 237 | } | 300 | } |
| 238 | } | 301 | } |
| 239 | 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 | |||
| 240 | impl SPConfHelper for AdcConfig { | 334 | impl SPConfHelper for AdcConfig { |
| 241 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | 335 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { |
| 242 | use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; | 336 | use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; |
