525 lines
16 KiB
Rust
525 lines
16 KiB
Rust
//! 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<Hertz>,
|
|
pll_cfg: Option<PllCfg>,
|
|
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<Clocks, ClkCfgError> {
|
|
// 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)
|
|
);
|
|
}
|
|
}
|