diff options
| author | James Munns <[email protected]> | 2025-11-24 17:37:57 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-11-24 17:37:57 +0100 |
| commit | c552cf2434b847b7a8af06e15982eb29d07ad2fa (patch) | |
| tree | bff3e9c1f1413fe19823fe6114ee199ef0765b40 /src | |
| parent | d37f74b7be5abd75c4fef1f2a020550aedecd049 (diff) | |
Introduce clkout peripheral (#36)
Introduces a ClockOut peripheral, allowing for ensuring that internal clocks are operating correctly.
Also fixes some incorrect PLL/Div4 usage.
Diffstat (limited to 'src')
| -rw-r--r-- | src/clkout.rs | 169 | ||||
| -rw-r--r-- | src/clocks/mod.rs | 43 | ||||
| -rw-r--r-- | src/gpio.rs | 6 | ||||
| -rw-r--r-- | src/lib.rs | 5 |
4 files changed, 218 insertions, 5 deletions
diff --git a/src/clkout.rs b/src/clkout.rs new file mode 100644 index 000000000..88c731df1 --- /dev/null +++ b/src/clkout.rs | |||
| @@ -0,0 +1,169 @@ | |||
| 1 | //! CLKOUT pseudo-peripheral | ||
| 2 | //! | ||
| 3 | //! CLKOUT is a part of the clock generation subsystem, and can be used | ||
| 4 | //! either to generate arbitrary waveforms, or to debug the state of | ||
| 5 | //! internal oscillators. | ||
| 6 | |||
| 7 | use core::marker::PhantomData; | ||
| 8 | |||
| 9 | use embassy_hal_internal::Peri; | ||
| 10 | |||
| 11 | pub use crate::clocks::periph_helpers::Div4; | ||
| 12 | use crate::clocks::{with_clocks, ClockError, PoweredClock}; | ||
| 13 | use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; | ||
| 14 | use crate::peripherals::CLKOUT; | ||
| 15 | |||
| 16 | /// A peripheral representing the CLKOUT pseudo-peripheral | ||
| 17 | pub struct ClockOut<'a> { | ||
| 18 | _p: PhantomData<&'a mut CLKOUT>, | ||
| 19 | freq: u32, | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Selected clock source to output | ||
| 23 | pub enum ClockOutSel { | ||
| 24 | /// 12MHz Internal Oscillator | ||
| 25 | Fro12M, | ||
| 26 | /// FRO180M Internal Oscillator, via divisor | ||
| 27 | FroHfDiv, | ||
| 28 | /// External Oscillator | ||
| 29 | ClkIn, | ||
| 30 | /// 16KHz oscillator | ||
| 31 | Clk16K, | ||
| 32 | /// Output of PLL1 | ||
| 33 | Pll1Clk, | ||
| 34 | /// Main System CPU clock, divided by 6 | ||
| 35 | SlowClk, | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Configuration for the ClockOut | ||
| 39 | pub struct Config { | ||
| 40 | /// Selected Source Clock | ||
| 41 | pub sel: ClockOutSel, | ||
| 42 | /// Selected division level | ||
| 43 | pub div: Div4, | ||
| 44 | /// Selected power level | ||
| 45 | pub level: PoweredClock, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl<'a> ClockOut<'a> { | ||
| 49 | /// Create a new ClockOut pin. On success, the clock signal will begin immediately | ||
| 50 | /// on the given pin. | ||
| 51 | pub fn new( | ||
| 52 | _peri: Peri<'a, CLKOUT>, | ||
| 53 | pin: Peri<'a, impl sealed::ClockOutPin>, | ||
| 54 | cfg: Config, | ||
| 55 | ) -> Result<Self, ClockError> { | ||
| 56 | // There's no MRCC enable bit, so we check the validity of the clocks here | ||
| 57 | // | ||
| 58 | // TODO: Should we check that the frequency is suitably low? | ||
| 59 | let (freq, mux) = check_sel(cfg.sel, cfg.level)?; | ||
| 60 | |||
| 61 | // All good! Apply requested config, starting with the pin. | ||
| 62 | pin.mux(); | ||
| 63 | |||
| 64 | setup_clkout(mux, cfg.div); | ||
| 65 | |||
| 66 | Ok(Self { | ||
| 67 | _p: PhantomData, | ||
| 68 | freq: freq / cfg.div.into_divisor(), | ||
| 69 | }) | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Frequency of the clkout pin | ||
| 73 | #[inline] | ||
| 74 | pub fn frequency(&self) -> u32 { | ||
| 75 | self.freq | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | impl Drop for ClockOut<'_> { | ||
| 80 | fn drop(&mut self) { | ||
| 81 | disable_clkout(); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Check whether the given clock selection is valid | ||
| 86 | fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { | ||
| 87 | let res = with_clocks(|c| { | ||
| 88 | Ok(match sel { | ||
| 89 | ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), | ||
| 90 | ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), | ||
| 91 | ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), | ||
| 92 | ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), | ||
| 93 | ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), | ||
| 94 | ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), | ||
| 95 | }) | ||
| 96 | }); | ||
| 97 | let Some(res) = res else { | ||
| 98 | return Err(ClockError::NeverInitialized); | ||
| 99 | }; | ||
| 100 | res | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Set up the clkout pin using the given mux and div settings | ||
| 104 | fn setup_clkout(mux: Mux, div: Div4) { | ||
| 105 | let mrcc = unsafe { crate::pac::Mrcc0::steal() }; | ||
| 106 | |||
| 107 | mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); | ||
| 108 | |||
| 109 | // Set up clkdiv | ||
| 110 | mrcc.mrcc_clkout_clkdiv().write(|w| { | ||
| 111 | w.halt().set_bit(); | ||
| 112 | w.reset().set_bit(); | ||
| 113 | unsafe { w.div().bits(div.into_bits()) }; | ||
| 114 | w | ||
| 115 | }); | ||
| 116 | mrcc.mrcc_clkout_clkdiv().write(|w| { | ||
| 117 | w.halt().clear_bit(); | ||
| 118 | w.reset().clear_bit(); | ||
| 119 | unsafe { w.div().bits(div.into_bits()) }; | ||
| 120 | w | ||
| 121 | }); | ||
| 122 | |||
| 123 | while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} | ||
| 124 | } | ||
| 125 | |||
| 126 | /// Stop the clkout | ||
| 127 | fn disable_clkout() { | ||
| 128 | // Stop the output by selecting the "none" clock | ||
| 129 | // | ||
| 130 | // TODO: restore the pin to hi-z or something? | ||
| 131 | let mrcc = unsafe { crate::pac::Mrcc0::steal() }; | ||
| 132 | mrcc.mrcc_clkout_clkdiv().write(|w| { | ||
| 133 | w.reset().set_bit(); | ||
| 134 | w.halt().set_bit(); | ||
| 135 | unsafe { | ||
| 136 | w.div().bits(0); | ||
| 137 | } | ||
| 138 | w | ||
| 139 | }); | ||
| 140 | mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); | ||
| 141 | } | ||
| 142 | |||
| 143 | mod sealed { | ||
| 144 | use embassy_hal_internal::PeripheralType; | ||
| 145 | |||
| 146 | use crate::gpio::{Pull, SealedPin}; | ||
| 147 | |||
| 148 | /// Sealed marker trait for clockout pins | ||
| 149 | pub trait ClockOutPin: PeripheralType { | ||
| 150 | /// Set the given pin to the correct muxing state | ||
| 151 | fn mux(&self); | ||
| 152 | } | ||
| 153 | |||
| 154 | macro_rules! impl_pin { | ||
| 155 | ($pin:ident, $func:ident) => { | ||
| 156 | impl ClockOutPin for crate::peripherals::$pin { | ||
| 157 | fn mux(&self) { | ||
| 158 | self.set_function(crate::pac::port0::pcr0::Mux::$func); | ||
| 159 | self.set_pull(Pull::Disabled); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | } | ||
| 164 | |||
| 165 | impl_pin!(P0_6, Mux12); | ||
| 166 | impl_pin!(P3_6, Mux1); | ||
| 167 | impl_pin!(P3_8, Mux12); | ||
| 168 | impl_pin!(P4_2, Mux1); | ||
| 169 | } | ||
diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 0b4535dc4..948355079 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs | |||
| @@ -91,8 +91,11 @@ pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | |||
| 91 | // For now, just use FIRC as the main/cpu clock, which should already be | 91 | // For now, just use FIRC as the main/cpu clock, which should already be |
| 92 | // the case on reset | 92 | // the case on reset |
| 93 | assert!(operator.scg0.rccr().read().scs().is_firc()); | 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. | ||
| 94 | assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); | 97 | assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); |
| 95 | operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap()); | 98 | operator.clocks.cpu_system_clk = Some(input); |
| 96 | 99 | ||
| 97 | critical_section::with(|cs| { | 100 | critical_section::with(|cs| { |
| 98 | let mut clks = CLOCKS.borrow_ref_mut(cs); | 101 | let mut clks = CLOCKS.borrow_ref_mut(cs); |
| @@ -189,6 +192,9 @@ pub struct Clocks { | |||
| 189 | /// peripherals. | 192 | /// peripherals. |
| 190 | pub main_clk: Option<Clock>, | 193 | pub main_clk: Option<Clock>, |
| 191 | 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 | |||
| 192 | /// `pll1_clk` is the output of the main system PLL, `pll1`. | 198 | /// `pll1_clk` is the output of the main system PLL, `pll1`. |
| 193 | pub pll1_clk: Option<Clock>, | 199 | pub pll1_clk: Option<Clock>, |
| 194 | } | 200 | } |
| @@ -522,10 +528,43 @@ impl Clocks { | |||
| 522 | Ok(clk.frequency) | 528 | Ok(clk.frequency) |
| 523 | } | 529 | } |
| 524 | 530 | ||
| 531 | /// Ensure the `pll1_clk` clock is active and valid at the given power state. | ||
| 532 | pub fn ensure_pll1_clk_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 533 | Err(ClockError::NotImplemented { clock: "pll1_clk" }) | ||
| 534 | } | ||
| 535 | |||
| 525 | /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. | 536 | /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. |
| 526 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | 537 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { |
| 527 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) | 538 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) |
| 528 | } | 539 | } |
| 540 | |||
| 541 | /// Ensure the `CPU_CLK` or `SYSTEM_CLK` is active | ||
| 542 | pub fn ensure_cpu_system_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 543 | let Some(clk) = self.cpu_system_clk.as_ref() else { | ||
| 544 | return Err(ClockError::BadConfig { | ||
| 545 | clock: "cpu_system_clk", | ||
| 546 | reason: "required but not active", | ||
| 547 | }); | ||
| 548 | }; | ||
| 549 | // Can the main_clk ever be active in deep sleep? I think it is gated? | ||
| 550 | match at_level { | ||
| 551 | PoweredClock::NormalEnabledDeepSleepDisabled => {} | ||
| 552 | PoweredClock::AlwaysEnabled => { | ||
| 553 | return Err(ClockError::BadConfig { | ||
| 554 | clock: "main_clk", | ||
| 555 | reason: "not low power active", | ||
| 556 | }) | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | Ok(clk.frequency) | ||
| 561 | } | ||
| 562 | |||
| 563 | pub fn ensure_slow_clk_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 564 | let freq = self.ensure_cpu_system_clk_active(at_level)?; | ||
| 565 | |||
| 566 | Ok(freq / 6) | ||
| 567 | } | ||
| 529 | } | 568 | } |
| 530 | 569 | ||
| 531 | impl PoweredClock { | 570 | impl PoweredClock { |
| @@ -749,7 +788,7 @@ impl ClockOperator<'_> { | |||
| 749 | w | 788 | w |
| 750 | }); | 789 | }); |
| 751 | // Then unhalt it, and reset it | 790 | // Then unhalt it, and reset it |
| 752 | self.syscon.frolfdiv().write(|w| { | 791 | self.syscon.frolfdiv().modify(|_r, w| { |
| 753 | w.halt().run(); | 792 | w.halt().run(); |
| 754 | w.reset().released(); | 793 | w.reset().released(); |
| 755 | w | 794 | w |
diff --git a/src/gpio.rs b/src/gpio.rs index 7dba7ab4f..4f79aff93 100644 --- a/src/gpio.rs +++ b/src/gpio.rs | |||
| @@ -102,7 +102,7 @@ pub struct AnyPin { | |||
| 102 | 102 | ||
| 103 | impl AnyPin { | 103 | impl AnyPin { |
| 104 | /// Create an `AnyPin` from raw components. | 104 | /// Create an `AnyPin` from raw components. |
| 105 | pub fn new( | 105 | fn new( |
| 106 | port: usize, | 106 | port: usize, |
| 107 | pin: usize, | 107 | pin: usize, |
| 108 | gpio: &'static crate::pac::gpio0::RegisterBlock, | 108 | gpio: &'static crate::pac::gpio0::RegisterBlock, |
| @@ -151,7 +151,7 @@ impl AnyPin { | |||
| 151 | 151 | ||
| 152 | embassy_hal_internal::impl_peripheral!(AnyPin); | 152 | embassy_hal_internal::impl_peripheral!(AnyPin); |
| 153 | 153 | ||
| 154 | trait SealedPin { | 154 | pub(crate) trait SealedPin { |
| 155 | fn pin_port(&self) -> usize; | 155 | fn pin_port(&self) -> usize; |
| 156 | 156 | ||
| 157 | fn port(&self) -> usize { | 157 | fn port(&self) -> usize { |
| @@ -297,7 +297,7 @@ macro_rules! impl_pin { | |||
| 297 | } | 297 | } |
| 298 | } | 298 | } |
| 299 | 299 | ||
| 300 | impl crate::peripherals::$peri { | 300 | impl crate::peripherals::$peri { |
| 301 | /// Convenience helper to obtain a type-erased handle to this pin. | 301 | /// Convenience helper to obtain a type-erased handle to this pin. |
| 302 | pub fn degrade(&self) -> AnyPin { | 302 | pub fn degrade(&self) -> AnyPin { |
| 303 | AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) | 303 | AnyPin::new(self.port(), self.pin(), self.gpio(), self.port_reg(), self.pcr_reg()) |
diff --git a/src/lib.rs b/src/lib.rs index 175642f75..6f3a63c94 100644 --- a/src/lib.rs +++ b/src/lib.rs | |||
| @@ -10,6 +10,7 @@ pub mod gpio; | |||
| 10 | pub mod pins; // pin mux helpers | 10 | pub mod pins; // pin mux helpers |
| 11 | 11 | ||
| 12 | pub mod adc; | 12 | pub mod adc; |
| 13 | pub mod clkout; | ||
| 13 | pub mod config; | 14 | pub mod config; |
| 14 | pub mod interrupt; | 15 | pub mod interrupt; |
| 15 | pub mod lpuart; | 16 | pub mod lpuart; |
| @@ -33,6 +34,10 @@ embassy_hal_internal::peripherals!( | |||
| 33 | CDOG0, | 34 | CDOG0, |
| 34 | CDOG1, | 35 | CDOG1, |
| 35 | 36 | ||
| 37 | // CLKOUT is not specifically a peripheral (it's part of SYSCON), | ||
| 38 | // but we still want it to be a singleton. | ||
| 39 | CLKOUT, | ||
| 40 | |||
| 36 | CMC, | 41 | CMC, |
| 37 | CMP0, | 42 | CMP0, |
| 38 | CMP1, | 43 | CMP1, |
