aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Munns <[email protected]>2025-11-24 17:37:57 +0100
committerGitHub <[email protected]>2025-11-24 17:37:57 +0100
commitc552cf2434b847b7a8af06e15982eb29d07ad2fa (patch)
treebff3e9c1f1413fe19823fe6114ee199ef0765b40
parentd37f74b7be5abd75c4fef1f2a020550aedecd049 (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.
-rw-r--r--examples/Cargo.toml2
-rw-r--r--examples/src/bin/clkout.rs69
-rw-r--r--src/clkout.rs169
-rw-r--r--src/clocks/mod.rs43
-rw-r--r--src/gpio.rs6
-rw-r--r--src/lib.rs5
6 files changed, 288 insertions, 6 deletions
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index d1c6a2071..4f15a6aff 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0"
6 6
7[dependencies] 7[dependencies]
8cortex-m = { version = "0.7", features = ["critical-section-single-core"] } 8cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
9cortex-m-rt = { version = "0.7" } 9cortex-m-rt = { version = "0.7", features = ["set-sp", "set-vtor"] }
10critical-section = "1.2.0" 10critical-section = "1.2.0"
11defmt = "1.0" 11defmt = "1.0"
12defmt-rtt = "1.0" 12defmt-rtt = "1.0"
diff --git a/examples/src/bin/clkout.rs b/examples/src/bin/clkout.rs
new file mode 100644
index 000000000..bfd963540
--- /dev/null
+++ b/examples/src/bin/clkout.rs
@@ -0,0 +1,69 @@
1#![no_std]
2#![no_main]
3
4use embassy_executor::Spawner;
5use embassy_mcxa::clkout::{ClockOut, ClockOutSel, Config, Div4};
6use embassy_mcxa::clocks::PoweredClock;
7use embassy_mcxa::gpio::{DriveStrength, SlewRate};
8use embassy_mcxa::{Level, Output};
9use embassy_time::Timer;
10use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
11
12/// Demonstrate CLKOUT, using Pin P4.2
13#[embassy_executor::main]
14async fn main(_spawner: Spawner) {
15 let p = hal::init(hal::config::Config::default());
16 let mut pin = p.P4_2;
17 let mut clkout = p.CLKOUT;
18
19 loop {
20 defmt::info!("Set Low...");
21 let mut output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow);
22 Timer::after_millis(500).await;
23
24 defmt::info!("Set High...");
25 output.set_high();
26 Timer::after_millis(400).await;
27
28 defmt::info!("Set Low...");
29 output.set_low();
30 Timer::after_millis(500).await;
31
32 defmt::info!("16k...");
33 // Run Clock Out with the 16K clock
34 let _clock_out = ClockOut::new(
35 clkout.reborrow(),
36 pin.reborrow(),
37 Config {
38 sel: ClockOutSel::Clk16K,
39 div: Div4::no_div(),
40 level: PoweredClock::NormalEnabledDeepSleepDisabled,
41 },
42 )
43 .unwrap();
44
45 Timer::after_millis(3000).await;
46
47 defmt::info!("Set Low...");
48 drop(_clock_out);
49
50 let _output = Output::new(pin.reborrow(), Level::Low, DriveStrength::Normal, SlewRate::Slow);
51 Timer::after_millis(500).await;
52
53 // Run Clock Out with the 12M clock, divided by 3
54 defmt::info!("4M...");
55 let _clock_out = ClockOut::new(
56 clkout.reborrow(),
57 pin.reborrow(),
58 Config {
59 sel: ClockOutSel::Fro12M,
60 div: const { Div4::from_divisor(3).unwrap() },
61 level: PoweredClock::NormalEnabledDeepSleepDisabled,
62 },
63 )
64 .unwrap();
65
66 // Let it run for 3 seconds...
67 Timer::after_millis(3000).await;
68 }
69}
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
7use core::marker::PhantomData;
8
9use embassy_hal_internal::Peri;
10
11pub use crate::clocks::periph_helpers::Div4;
12use crate::clocks::{with_clocks, ClockError, PoweredClock};
13use crate::pac::mrcc0::mrcc_clkout_clksel::Mux;
14use crate::peripherals::CLKOUT;
15
16/// A peripheral representing the CLKOUT pseudo-peripheral
17pub struct ClockOut<'a> {
18 _p: PhantomData<&'a mut CLKOUT>,
19 freq: u32,
20}
21
22/// Selected clock source to output
23pub 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
39pub 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
48impl<'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
79impl Drop for ClockOut<'_> {
80 fn drop(&mut self) {
81 disable_clkout();
82 }
83}
84
85/// Check whether the given clock selection is valid
86fn 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
104fn 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
127fn 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
143mod 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
531impl PoweredClock { 570impl 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
103impl AnyPin { 103impl 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
152embassy_hal_internal::impl_peripheral!(AnyPin); 152embassy_hal_internal::impl_peripheral!(AnyPin);
153 153
154trait SealedPin { 154pub(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;
10pub mod pins; // pin mux helpers 10pub mod pins; // pin mux helpers
11 11
12pub mod adc; 12pub mod adc;
13pub mod clkout;
13pub mod config; 14pub mod config;
14pub mod interrupt; 15pub mod interrupt;
15pub mod lpuart; 16pub 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,