//! This also includes functionality to enable the peripheral clocks use crate::pac; use crate::time::Hertz; pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000); pub const XTAL_OSC_TSTART_MS: u32 = 15; #[derive(Copy, Clone, PartialEq)] pub enum PeripheralSelect { Spi0 = 0, Spi1 = 1, Spi2 = 2, Spi3 = 3, Uart0 = 4, Uart1 = 5, Uart2 = 6, I2c0 = 7, I2c1 = 8, I2c2 = 9, Can0 = 10, Can1 = 11, Rng = 12, Adc = 13, Dac = 14, Dma = 15, Ebi = 16, Eth = 17, Spw = 18, Clkgen = 19, IrqRouter = 20, IoConfig = 21, Utility = 22, Watchdog = 23, PortA = 24, PortB = 25, PortC = 26, PortD = 27, PortE = 28, PortF = 29, PortG = 30, } pub type PeripheralClocks = PeripheralSelect; #[derive(Debug, PartialEq, Eq)] pub enum FilterClkSel { SysClk = 0, Clk1 = 1, Clk2 = 2, Clk3 = 3, Clk4 = 4, Clk5 = 5, Clk6 = 6, Clk7 = 7, } #[inline(always)] pub fn enable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) { syscfg .peripheral_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) }); } #[inline(always)] pub fn disable_peripheral_clock(syscfg: &mut pac::Sysconfig, clock: PeripheralSelect) { syscfg .peripheral_clk_enable() .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) }); } #[inline(always)] pub fn assert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) { syscfg .peripheral_reset() .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph as u8)) }); } #[inline(always)] pub fn deassert_periph_reset(syscfg: &mut pac::Sysconfig, periph: PeripheralSelect) { syscfg .peripheral_reset() .modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph as u8)) }); } pub trait SyscfgExt { fn enable_peripheral_clock(&mut self, clock: PeripheralClocks); fn disable_peripheral_clock(&mut self, clock: PeripheralClocks); fn assert_periph_reset(&mut self, clock: PeripheralSelect); fn deassert_periph_reset(&mut self, clock: PeripheralSelect); } impl SyscfgExt for pac::Sysconfig { #[inline(always)] fn enable_peripheral_clock(&mut self, clock: PeripheralClocks) { enable_peripheral_clock(self, clock) } #[inline(always)] fn disable_peripheral_clock(&mut self, clock: PeripheralClocks) { disable_peripheral_clock(self, clock) } #[inline(always)] fn assert_periph_reset(&mut self, clock: PeripheralSelect) { assert_periph_reset(self, clock) } #[inline(always)] fn deassert_periph_reset(&mut self, clock: PeripheralSelect) { deassert_periph_reset(self, clock) } } /// Refer to chapter 8 (p.57) of the programmers guide for detailed information. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClkselSys { // Internal Heart-Beat Osciallator. Not tightly controlled (+/-20 %). Not recommended as the regular clock! Hbo = 0b00, // External clock signal on XTAL_N line, 1-100 MHz XtalN = 0b01, // Internal Phase-Locked Loop. Pll = 0b10, // Crystal oscillator amplified, 4-10 MHz. XtalOsc = 0b11, } /// This selects the input clock to the the CLKGEN peripheral in addition to the HBO clock. /// /// This can either be a clock connected directly on the XTAL_N line or a chrystal on the XTAL_P /// line which goes through an oscillator amplifier. /// /// Refer to chapter 8 (p.57) of the programmers guide for detailed information. #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RefClkSel { #[default] None = 0b00, XtalOsc = 0b01, XtalN = 0b10, } #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClkDivSel { #[default] Div1 = 0b00, Div2 = 0b01, Div4 = 0b10, Div8 = 0b11, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AdcClkDivSel { Div8 = 0b00, Div4 = 0b01, Div2 = 0b10, Div1 = 0b11, } #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PllCfg { /// Reference clock divider. pub clkr: u8, /// Clock divider on feedback path pub clkf: u8, // Output clock divider. pub clkod: u8, /// Bandwidth adjustment pub bwadj: u8, } pub fn clk_after_div(clk: Hertz, div_sel: ClkDivSel) -> Hertz { match div_sel { ClkDivSel::Div1 => clk, ClkDivSel::Div2 => clk / 2, ClkDivSel::Div4 => clk / 4, ClkDivSel::Div8 => clk / 8, } } /// Wait for 500 reference clock cycles like specified in the datasheet. pub fn pll_setup_delay() { for _ in 0..500 { cortex_m::asm::nop() } } pub trait ClkgenExt { fn constrain(self) -> ClkgenCfgr; } impl ClkgenExt for pac::Clkgen { fn constrain(self) -> ClkgenCfgr { ClkgenCfgr { source_clk: None, ref_clk_sel: RefClkSel::None, clksel_sys: ClkselSys::Hbo, clk_div_sel: ClkDivSel::Div1, clk_lost_detection: false, pll_lock_lost_detection: false, pll_cfg: None, clkgen: self, } } } pub struct ClkgenCfgr { ref_clk_sel: RefClkSel, clksel_sys: ClkselSys, clk_div_sel: ClkDivSel, /// The source clock frequency which is either an external clock connected to XTAL_N, or a /// crystal connected to the XTAL_OSC input. source_clk: Option, pll_cfg: Option, clk_lost_detection: bool, /// Feature only works on revision B of the board. #[cfg(feature = "revb")] pll_lock_lost_detection: bool, clkgen: pac::Clkgen, } #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ClkSourceFreqNotSet; #[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ClkCfgError { ClkSourceFreqNotSet, PllConfigNotSet, PllInitError, InconsistentCfg, } /// Delays a given amount of milliseconds. /// /// Taken from the HAL implementation. This implementation is probably not precise and it /// also blocks! pub fn hbo_clock_delay_ms(ms: u32) { let wdt = unsafe { pac::WatchDog::steal() }; for _ in 0..ms { for _ in 0..10_000 { cortex_m::asm::nop(); } wdt.wdogintclr().write(|w| unsafe { w.bits(1) }); } } impl ClkgenCfgr { #[inline] pub fn source_clk(mut self, src_clk: Hertz) -> Self { self.source_clk = Some(src_clk); self } /// This function can be used to utilize the XTAL_N clock input directly without the /// oscillator. /// /// It sets the internal configuration to [ClkselSys::XtalN] and [RefClkSel::XtalN]. #[inline] pub fn xtal_n_clk(mut self) -> Self { self.clksel_sys = ClkselSys::XtalN; self.ref_clk_sel = RefClkSel::XtalN; self } #[inline] pub fn xtal_n_clk_with_src_freq(mut self, src_clk: Hertz) -> Self { self = self.xtal_n_clk(); self.source_clk(src_clk) } #[inline] pub fn clksel_sys(mut self, clksel_sys: ClkselSys) -> Self { self.clksel_sys = clksel_sys; self } #[inline] pub fn ref_clk_sel(mut self, ref_clk_sel: RefClkSel) -> Self { self.ref_clk_sel = ref_clk_sel; self } /// Configures all clocks and return a clock configuration structure containing the final /// frozen clock. /// /// Internal implementation details: This implementation is based on the HAL implementation /// which performs a lot of delays. I do not know if all of those are necessary, but /// I am going to be conservative here and assume that the vendor has tested though and /// might have had a reason for those, so I am going to keep them. Chances are, this /// process only has to be performed once, and it does not matter if it takes a few /// microseconds or milliseconds longer. pub fn freeze(self, syscfg: &mut pac::Sysconfig) -> Result { // Sanitize configuration. if self.source_clk.is_none() { return Err(ClkCfgError::ClkSourceFreqNotSet); } if self.clksel_sys == ClkselSys::XtalOsc && self.ref_clk_sel != RefClkSel::XtalOsc { return Err(ClkCfgError::InconsistentCfg); } if self.clksel_sys == ClkselSys::XtalN && self.ref_clk_sel != RefClkSel::XtalN { return Err(ClkCfgError::InconsistentCfg); } if self.clksel_sys == ClkselSys::Pll && self.pll_cfg.is_none() { return Err(ClkCfgError::PllConfigNotSet); } syscfg.enable_peripheral_clock(PeripheralSelect::Clkgen); let mut final_sysclk = self.source_clk.unwrap(); // The HAL forces back the HBO clock here with a delay.. Even though this is // not stricly necessary when coming from a fresh start, it could be still become relevant // later if the clock lost detection mechanism require a re-configuration of the clocks. // Therefore, we do it here as well. self.clkgen .ctrl0() .modify(|_, w| unsafe { w.clksel_sys().bits(ClkselSys::Hbo as u8) }); pll_setup_delay(); self.clkgen .ctrl0() .modify(|_, w| unsafe { w.clk_div_sel().bits(ClkDivSel::Div1 as u8) }); // Set up oscillator and PLL input clock. self.clkgen .ctrl0() .modify(|_, w| unsafe { w.ref_clk_sel().bits(self.ref_clk_sel as u8) }); self.clkgen.ctrl1().modify(|_, w| { w.xtal_en().clear_bit(); w.xtal_n_en().clear_bit(); w }); match self.ref_clk_sel { RefClkSel::None => pll_setup_delay(), RefClkSel::XtalOsc => { self.clkgen.ctrl1().modify(|_, w| w.xtal_en().set_bit()); hbo_clock_delay_ms(XTAL_OSC_TSTART_MS); } RefClkSel::XtalN => { self.clkgen.ctrl1().modify(|_, w| w.xtal_n_en().set_bit()); pll_setup_delay() } } // Set up PLL configuration. match self.pll_cfg { Some(cfg) => { self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().clear_bit()); // Done in C HAL. I guess this gives the PLL some time to power down properly. cortex_m::asm::nop(); cortex_m::asm::nop(); self.clkgen.ctrl0().modify(|_, w| { unsafe { w.pll_clkf().bits(cfg.clkf); } unsafe { w.pll_clkr().bits(cfg.clkr); } unsafe { w.pll_clkod().bits(cfg.clkod); } unsafe { w.pll_bwadj().bits(cfg.bwadj); } w.pll_test().clear_bit(); w.pll_bypass().clear_bit(); w.pll_intfb().set_bit() }); // Taken from SystemCoreClockUpdate implementation from Vorago. final_sysclk /= cfg.clkr as u32 + 1; final_sysclk *= cfg.clkf as u32 + 1; final_sysclk /= cfg.clkod as u32 + 1; // Reset PLL. self.clkgen.ctrl0().modify(|_, w| w.pll_reset().set_bit()); // The HAL does this, the datasheet specifies a delay of 5 us. I guess it does not // really matter because the PLL lock detect is used later.. pll_setup_delay(); self.clkgen.ctrl0().modify(|_, w| w.pll_reset().clear_bit()); pll_setup_delay(); // check for lock let stat = self.clkgen.stat().read(); if stat.fbslip().bit() || stat.rfslip().bit() { pll_setup_delay(); if stat.fbslip().bit() || stat.rfslip().bit() { // This is what the HAL does. We could continue, but then we would at least // have to somehow report a partial error.. Chances are, the user does not // want to continue with a broken PLL clock. return Err(ClkCfgError::PllInitError); } } } None => self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit()), } if self.clk_lost_detection { rearm_sysclk_lost_with_periph(&self.clkgen) } #[cfg(feature = "revb")] if self.pll_lock_lost_detection { rearm_pll_lock_lost_with_periph(&self.clkgen) } self.clkgen .ctrl0() .modify(|_, w| unsafe { w.clk_div_sel().bits(self.clk_div_sel as u8) }); final_sysclk = clk_after_div(final_sysclk, self.clk_div_sel); // The HAL does this. I don't know why.. pll_setup_delay(); self.clkgen .ctrl0() .modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) }); // I will just do the ADC stuff like Vorago does it. // ADC clock (must be 2-12.5 MHz) // NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue // For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range) if final_sysclk.raw() <= 50_000_000 { self.clkgen .ctrl1() .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div4 as u8) }); } else { self.clkgen .ctrl1() .modify(|_, w| unsafe { w.adc_clk_div_sel().bits(AdcClkDivSel::Div8 as u8) }); } Ok(Clocks { sysclk: final_sysclk, apb1: final_sysclk / 2, apb2: final_sysclk / 4, }) } } /// Frozen clock frequencies /// /// The existence of this value indicates that the clock configuration can no longer be changed #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Clocks { sysclk: Hertz, apb1: Hertz, apb2: Hertz, } impl Clocks { /// Returns the frequency of the HBO clock pub fn hbo(&self) -> Hertz { HBO_FREQ } /// Returns the frequency of the APB0 which is equal to the system clock. pub fn apb0(&self) -> Hertz { self.sysclk() } /// Returns system clock divied by 2. pub fn apb1(&self) -> Hertz { self.apb1 } /// Returns system clock divied by 4. pub fn apb2(&self) -> Hertz { self.apb2 } /// Returns the system (core) frequency pub fn sysclk(&self) -> Hertz { self.sysclk } } pub fn rearm_sysclk_lost() { rearm_sysclk_lost_with_periph(&unsafe { pac::Clkgen::steal() }) } fn rearm_sysclk_lost_with_periph(clkgen: &pac::Clkgen) { clkgen .ctrl0() .modify(|_, w| w.sys_clk_lost_det_en().set_bit()); clkgen .ctrl1() .write(|w| w.sys_clk_lost_det_rearm().set_bit()); clkgen .ctrl1() .write(|w| w.sys_clk_lost_det_rearm().clear_bit()); } #[cfg(feature = "revb")] pub fn rearm_pll_lock_lost() { rearm_pll_lock_lost_with_periph(&unsafe { pac::Clkgen::steal() }) } fn rearm_pll_lock_lost_with_periph(clkgen: &pac::Clkgen) { clkgen .ctrl1() .modify(|_, w| w.pll_lost_lock_det_en().set_bit()); clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().set_bit()); clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().clear_bit()); } #[cfg(test)] mod tests { use super::*; #[test] fn test_basic_div() { assert_eq!( clk_after_div(Hertz::from_raw(10_000_000), super::ClkDivSel::Div2), Hertz::from_raw(5_000_000) ); } }