//! CLKOUT pseudo-peripheral //! //! CLKOUT is a part of the clock generation subsystem, and can be used //! either to generate arbitrary waveforms, or to debug the state of //! internal oscillators. use core::marker::PhantomData; use embassy_hal_internal::Peri; pub use crate::clocks::periph_helpers::Div4; use crate::clocks::{ClockError, PoweredClock, with_clocks}; use crate::pac::mrcc0::mrcc_clkout_clksel::Mux; use crate::peripherals::CLKOUT; /// A peripheral representing the CLKOUT pseudo-peripheral pub struct ClockOut<'a> { _p: PhantomData<&'a mut CLKOUT>, freq: u32, } /// Selected clock source to output pub enum ClockOutSel { /// 12MHz Internal Oscillator Fro12M, /// FRO180M Internal Oscillator, via divisor FroHfDiv, /// External Oscillator ClkIn, /// 16KHz oscillator Clk16K, /// Output of PLL1 Pll1Clk, /// Main System CPU clock, divided by 6 SlowClk, } /// Configuration for the ClockOut pub struct Config { /// Selected Source Clock pub sel: ClockOutSel, /// Selected division level pub div: Div4, /// Selected power level pub level: PoweredClock, } impl<'a> ClockOut<'a> { /// Create a new ClockOut pin. On success, the clock signal will begin immediately /// on the given pin. pub fn new( _peri: Peri<'a, CLKOUT>, pin: Peri<'a, impl sealed::ClockOutPin>, cfg: Config, ) -> Result { // There's no MRCC enable bit, so we check the validity of the clocks here // // TODO: Should we check that the frequency is suitably low? let (freq, mux) = check_sel(cfg.sel, cfg.level)?; // All good! Apply requested config, starting with the pin. pin.mux(); setup_clkout(mux, cfg.div); Ok(Self { _p: PhantomData, freq: freq / cfg.div.into_divisor(), }) } /// Frequency of the clkout pin #[inline] pub fn frequency(&self) -> u32 { self.freq } } impl Drop for ClockOut<'_> { fn drop(&mut self) { disable_clkout(); } } /// Check whether the given clock selection is valid fn check_sel(sel: ClockOutSel, level: PoweredClock) -> Result<(u32, Mux), ClockError> { let res = with_clocks(|c| { Ok(match sel { ClockOutSel::Fro12M => (c.ensure_fro_hf_active(&level)?, Mux::Clkroot12m), ClockOutSel::FroHfDiv => (c.ensure_fro_hf_div_active(&level)?, Mux::ClkrootFircDiv), ClockOutSel::ClkIn => (c.ensure_clk_in_active(&level)?, Mux::ClkrootSosc), ClockOutSel::Clk16K => (c.ensure_clk_16k_vdd_core_active(&level)?, Mux::Clkroot16k), ClockOutSel::Pll1Clk => (c.ensure_pll1_clk_active(&level)?, Mux::ClkrootSpll), ClockOutSel::SlowClk => (c.ensure_slow_clk_active(&level)?, Mux::ClkrootSlow), }) }); let Some(res) = res else { return Err(ClockError::NeverInitialized); }; res } /// Set up the clkout pin using the given mux and div settings fn setup_clkout(mux: Mux, div: Div4) { let mrcc = unsafe { crate::pac::Mrcc0::steal() }; mrcc.mrcc_clkout_clksel().write(|w| w.mux().variant(mux)); // Set up clkdiv mrcc.mrcc_clkout_clkdiv().write(|w| { w.halt().set_bit(); w.reset().set_bit(); unsafe { w.div().bits(div.into_bits()) }; w }); mrcc.mrcc_clkout_clkdiv().write(|w| { w.halt().clear_bit(); w.reset().clear_bit(); unsafe { w.div().bits(div.into_bits()) }; w }); while mrcc.mrcc_clkout_clkdiv().read().unstab().bit_is_set() {} } /// Stop the clkout fn disable_clkout() { // Stop the output by selecting the "none" clock // // TODO: restore the pin to hi-z or something? let mrcc = unsafe { crate::pac::Mrcc0::steal() }; mrcc.mrcc_clkout_clkdiv().write(|w| { w.reset().set_bit(); w.halt().set_bit(); unsafe { w.div().bits(0); } w }); mrcc.mrcc_clkout_clksel().write(|w| unsafe { w.bits(0b111) }); } mod sealed { use embassy_hal_internal::PeripheralType; use crate::gpio::{Pull, SealedPin}; /// Sealed marker trait for clockout pins pub trait ClockOutPin: PeripheralType { /// Set the given pin to the correct muxing state fn mux(&self); } macro_rules! impl_pin { ($pin:ident, $func:ident) => { impl ClockOutPin for crate::peripherals::$pin { fn mux(&self) { self.set_function(crate::pac::port0::pcr0::Mux::$func); self.set_pull(Pull::Disabled); } } }; } impl_pin!(P0_6, Mux12); impl_pin!(P3_6, Mux1); impl_pin!(P3_8, Mux12); impl_pin!(P4_2, Mux1); }