diff options
| author | xoviat <[email protected]> | 2025-12-18 09:15:30 -0600 |
|---|---|---|
| committer | xoviat <[email protected]> | 2025-12-18 09:15:30 -0600 |
| commit | cec833c5f48ae93d1a47d5b763e613d8407d48a1 (patch) | |
| tree | 2fa25c7907ba8ef1923c4baeeb93dd88c57bce7e /embassy-mcxa/src/clocks | |
| parent | a886e97a33690cf9724dedd272d3073a577f9fa4 (diff) | |
| parent | b5b49cbcf3a991bf6d434b0870da50f3ee722612 (diff) | |
Merge branch 'main' of github.com:embassy-rs/embassy into low-power-rtc
Diffstat (limited to 'embassy-mcxa/src/clocks')
| -rw-r--r-- | embassy-mcxa/src/clocks/config.rs | 23 | ||||
| -rw-r--r-- | embassy-mcxa/src/clocks/mod.rs | 128 |
2 files changed, 149 insertions, 2 deletions
diff --git a/embassy-mcxa/src/clocks/config.rs b/embassy-mcxa/src/clocks/config.rs index 0563b8917..9f97160ff 100644 --- a/embassy-mcxa/src/clocks/config.rs +++ b/embassy-mcxa/src/clocks/config.rs | |||
| @@ -119,6 +119,28 @@ pub struct ClocksConfig { | |||
| 119 | pub sirc: SircConfig, | 119 | pub sirc: SircConfig, |
| 120 | /// FRO16K clock source | 120 | /// FRO16K clock source |
| 121 | pub fro16k: Option<Fro16KConfig>, | 121 | pub fro16k: Option<Fro16KConfig>, |
| 122 | /// SOSC, clk_in clock source | ||
| 123 | pub sosc: Option<SoscConfig>, | ||
| 124 | } | ||
| 125 | |||
| 126 | /// The mode of the external reference clock | ||
| 127 | #[derive(Copy, Clone)] | ||
| 128 | pub enum SoscMode { | ||
| 129 | /// Passive crystal oscillators | ||
| 130 | CrystalOscillator, | ||
| 131 | /// Active external reference clock | ||
| 132 | ActiveClock, | ||
| 133 | } | ||
| 134 | |||
| 135 | // SOSC/clk_in configuration | ||
| 136 | #[derive(Copy, Clone)] | ||
| 137 | pub struct SoscConfig { | ||
| 138 | /// Mode of the external reference clock | ||
| 139 | pub mode: SoscMode, | ||
| 140 | /// Specific frequency of the external reference clock | ||
| 141 | pub frequency: u32, | ||
| 142 | /// Power state of the external reference clock | ||
| 143 | pub power: PoweredClock, | ||
| 122 | } | 144 | } |
| 123 | 145 | ||
| 124 | // FIRC/FRO180M | 146 | // FIRC/FRO180M |
| @@ -199,6 +221,7 @@ impl Default for ClocksConfig { | |||
| 199 | vsys_domain_active: true, | 221 | vsys_domain_active: true, |
| 200 | vdd_core_domain_active: true, | 222 | vdd_core_domain_active: true, |
| 201 | }), | 223 | }), |
| 224 | sosc: None, | ||
| 202 | } | 225 | } |
| 203 | } | 226 | } |
| 204 | } | 227 | } |
diff --git a/embassy-mcxa/src/clocks/mod.rs b/embassy-mcxa/src/clocks/mod.rs index 037f0a656..b41a1ba46 100644 --- a/embassy-mcxa/src/clocks/mod.rs +++ b/embassy-mcxa/src/clocks/mod.rs | |||
| @@ -87,6 +87,7 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | |||
| 87 | operator.configure_firc_clocks()?; | 87 | operator.configure_firc_clocks()?; |
| 88 | operator.configure_sirc_clocks()?; | 88 | operator.configure_sirc_clocks()?; |
| 89 | operator.configure_fro16k_clocks()?; | 89 | operator.configure_fro16k_clocks()?; |
| 90 | operator.configure_sosc()?; | ||
| 90 | 91 | ||
| 91 | // For now, just use FIRC as the main/cpu clock, which should already be | 92 | // For now, just use FIRC as the main/cpu clock, which should already be |
| 92 | // the case on reset | 93 | // the case on reset |
| @@ -136,6 +137,7 @@ pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> { | |||
| 136 | #[non_exhaustive] | 137 | #[non_exhaustive] |
| 137 | pub struct Clocks { | 138 | pub struct Clocks { |
| 138 | /// The `clk_in` is a clock provided by an external oscillator | 139 | /// The `clk_in` is a clock provided by an external oscillator |
| 140 | /// AKA SOSC | ||
| 139 | pub clk_in: Option<Clock>, | 141 | pub clk_in: Option<Clock>, |
| 140 | 142 | ||
| 141 | // FRO180M stuff | 143 | // FRO180M stuff |
| @@ -485,8 +487,20 @@ impl Clocks { | |||
| 485 | } | 487 | } |
| 486 | 488 | ||
| 487 | /// Ensure the `clk_in` clock is active and valid at the given power state. | 489 | /// 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> { | 490 | pub fn ensure_clk_in_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 489 | Err(ClockError::NotImplemented { clock: "clk_in" }) | 491 | let Some(clk) = self.clk_in.as_ref() else { |
| 492 | return Err(ClockError::BadConfig { | ||
| 493 | clock: "clk_in", | ||
| 494 | reason: "required but not active", | ||
| 495 | }); | ||
| 496 | }; | ||
| 497 | if !clk.power.meets_requirement_of(at_level) { | ||
| 498 | return Err(ClockError::BadConfig { | ||
| 499 | clock: "clk_in", | ||
| 500 | reason: "not low power active", | ||
| 501 | }); | ||
| 502 | } | ||
| 503 | Ok(clk.frequency) | ||
| 490 | } | 504 | } |
| 491 | 505 | ||
| 492 | /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. | 506 | /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. |
| @@ -851,6 +865,116 @@ impl ClockOperator<'_> { | |||
| 851 | 865 | ||
| 852 | Ok(()) | 866 | Ok(()) |
| 853 | } | 867 | } |
| 868 | |||
| 869 | /// Configure the SOSC/clk_in oscillator | ||
| 870 | fn configure_sosc(&mut self) -> Result<(), ClockError> { | ||
| 871 | let Some(parts) = self.config.sosc.as_ref() else { | ||
| 872 | return Ok(()); | ||
| 873 | }; | ||
| 874 | |||
| 875 | let scg0 = unsafe { pac::Scg0::steal() }; | ||
| 876 | |||
| 877 | // TODO: Config for the LDO? For now, if we have Sosc, just enable | ||
| 878 | // using the default settings: | ||
| 879 | // LDOBYPASS: 0/not bypassed | ||
| 880 | // VOUT_SEL: 0b100: 1.1v | ||
| 881 | // LDOEN: 0/Disabled | ||
| 882 | scg0.ldocsr().modify(|_r, w| w.ldoen().enabled()); | ||
| 883 | while scg0.ldocsr().read().vout_ok().is_disabled() {} | ||
| 884 | |||
| 885 | // TODO: something something pins? This seems to work when the pins are | ||
| 886 | // not enabled, even if GPIO hasn't been initialized at all yet. | ||
| 887 | let eref = match parts.mode { | ||
| 888 | config::SoscMode::CrystalOscillator => pac::scg0::sosccfg::Erefs::Internal, | ||
| 889 | config::SoscMode::ActiveClock => pac::scg0::sosccfg::Erefs::External, | ||
| 890 | }; | ||
| 891 | let freq = parts.frequency; | ||
| 892 | |||
| 893 | // TODO: Fix PAC names here | ||
| 894 | // | ||
| 895 | // #[doc = "0: Frequency range select of 8-16 MHz."] | ||
| 896 | // Freq16to20mhz = 0, | ||
| 897 | // #[doc = "1: Frequency range select of 16-25 MHz."] | ||
| 898 | // LowFreq = 1, | ||
| 899 | // #[doc = "2: Frequency range select of 25-40 MHz."] | ||
| 900 | // MediumFreq = 2, | ||
| 901 | // #[doc = "3: Frequency range select of 40-50 MHz."] | ||
| 902 | // HighFreq = 3, | ||
| 903 | let range = match freq { | ||
| 904 | 0..8_000_000 => { | ||
| 905 | return Err(ClockError::BadConfig { | ||
| 906 | clock: "clk_in", | ||
| 907 | reason: "freq too low", | ||
| 908 | }); | ||
| 909 | } | ||
| 910 | 8_000_000..16_000_000 => pac::scg0::sosccfg::Range::Freq16to20mhz, | ||
| 911 | 16_000_000..25_000_000 => pac::scg0::sosccfg::Range::LowFreq, | ||
| 912 | 25_000_000..40_000_000 => pac::scg0::sosccfg::Range::MediumFreq, | ||
| 913 | 40_000_000..50_000_001 => pac::scg0::sosccfg::Range::HighFreq, | ||
| 914 | 50_000_001.. => { | ||
| 915 | return Err(ClockError::BadConfig { | ||
| 916 | clock: "clk_in", | ||
| 917 | reason: "freq too high", | ||
| 918 | }); | ||
| 919 | } | ||
| 920 | }; | ||
| 921 | |||
| 922 | // Set source/erefs and range | ||
| 923 | scg0.sosccfg().modify(|_r, w| { | ||
| 924 | w.erefs().variant(eref); | ||
| 925 | w.range().variant(range); | ||
| 926 | w | ||
| 927 | }); | ||
| 928 | |||
| 929 | // Disable lock | ||
| 930 | scg0.sosccsr().modify(|_r, w| w.lk().clear_bit()); | ||
| 931 | |||
| 932 | // TODO: We could enable the SOSC clock monitor. There are some things to | ||
| 933 | // figure out first: | ||
| 934 | // | ||
| 935 | // * This requires SIRC to be enabled, not sure which branch. Maybe fro12m_root? | ||
| 936 | // * If SOSC needs to work in deep sleep, AND the monitor is enabled: | ||
| 937 | // * SIRC also need needs to be low power | ||
| 938 | // * We need to decide if we need an interrupt or a reset if the monitor trips | ||
| 939 | |||
| 940 | // Apply remaining config | ||
| 941 | scg0.sosccsr().modify(|_r, w| { | ||
| 942 | // For now, just disable the monitor. See above. | ||
| 943 | w.sosccm().disabled(); | ||
| 944 | |||
| 945 | // Set deep sleep mode | ||
| 946 | match parts.power { | ||
| 947 | PoweredClock::NormalEnabledDeepSleepDisabled => { | ||
| 948 | w.soscsten().clear_bit(); | ||
| 949 | } | ||
| 950 | PoweredClock::AlwaysEnabled => { | ||
| 951 | w.soscsten().set_bit(); | ||
| 952 | } | ||
| 953 | } | ||
| 954 | |||
| 955 | // Enable SOSC | ||
| 956 | w.soscen().enabled() | ||
| 957 | }); | ||
| 958 | |||
| 959 | // Wait for SOSC to be valid, check for errors | ||
| 960 | while !scg0.sosccsr().read().soscvld().bit_is_set() {} | ||
| 961 | if scg0.sosccsr().read().soscerr().is_enabled_and_error() { | ||
| 962 | return Err(ClockError::BadConfig { | ||
| 963 | clock: "clk_in", | ||
| 964 | reason: "soscerr is set", | ||
| 965 | }); | ||
| 966 | } | ||
| 967 | |||
| 968 | // Re-lock the sosc | ||
| 969 | scg0.sosccsr().modify(|_r, w| w.lk().set_bit()); | ||
| 970 | |||
| 971 | self.clocks.clk_in = Some(Clock { | ||
| 972 | frequency: freq, | ||
| 973 | power: parts.power, | ||
| 974 | }); | ||
| 975 | |||
| 976 | Ok(()) | ||
| 977 | } | ||
| 854 | } | 978 | } |
| 855 | 979 | ||
| 856 | // | 980 | // |
