Skip to main content

va416xx_hal/
clock.rs

1//! API for using the [crate::pac::Clkgen] peripheral.
2//!
3//! It also includes functionality to enable the peripheral clocks.
4//! Calling [ClockConfigurator::new] returns a builder structure which allows
5//! setting up the clock.
6//!
7//! Calling [ClockConfigurator::freeze] returns the frozen clock configuration inside the [Clocks]
8//! structure. This structure can also be used to configure other structures provided by this HAL.
9#[cfg(not(feature = "va41628"))]
10use crate::adc::ADC_MAX_CLK;
11use crate::pac;
12
13use crate::time::Hertz;
14pub use vorago_shared_hal::clock::{Clocks, HBO_FREQ};
15use vorago_shared_hal::{PeripheralSelect, enable_peripheral_clock};
16
17pub const XTAL_OSC_TSTART_MS: u32 = 15;
18
19#[derive(Debug, PartialEq, Eq)]
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21pub enum FilterClockSelect {
22    SysClk = 0,
23    Clk1 = 1,
24    Clk2 = 2,
25    Clk3 = 3,
26    Clk4 = 4,
27    Clk5 = 5,
28    Clk6 = 6,
29    Clk7 = 7,
30}
31
32/// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
33#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub enum ClockSelect {
36    // Internal Heart-Beat Osciallator. Not tightly controlled (+/-20 %). Not recommended as the regular clock!
37    Hbo = 0b00,
38    // External clock signal on XTAL_N line, 1-100 MHz
39    XtalN = 0b01,
40    // Internal Phase-Locked Loop.
41    Pll = 0b10,
42    // Crystal oscillator amplified, 4-10 MHz.
43    XtalOsc = 0b11,
44}
45
46/// This selects the input clock to the the CLKGEN peripheral in addition to the HBO clock.
47///
48/// This can either be a clock connected directly on the XTAL_N line or a chrystal on the XTAL_P
49/// line which goes through an oscillator amplifier.
50///
51/// Refer to chapter 8 (p.57) of the programmers guide for detailed information.
52#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54pub enum ReferenceClockSelect {
55    #[default]
56    None = 0b00,
57    XtalOsc = 0b01,
58    XtalN = 0b10,
59}
60
61#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "defmt", derive(defmt::Format))]
63pub enum ClockDivisorSelect {
64    #[default]
65    Div1 = 0b00,
66    Div2 = 0b01,
67    Div4 = 0b10,
68    Div8 = 0b11,
69}
70
71#[derive(Debug, Copy, Clone, PartialEq, Eq)]
72#[cfg_attr(feature = "defmt", derive(defmt::Format))]
73pub enum AdcClockDivisorSelect {
74    Div8 = 0b00,
75    Div4 = 0b01,
76    Div2 = 0b10,
77    Div1 = 0b11,
78}
79
80#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82pub struct PllConfig {
83    /// Reference clock divider.
84    pub clkr: u8,
85    /// Clock divider on feedback path
86    pub clkf: u8,
87    // Output clock divider.
88    pub clkod: u8,
89    /// Bandwidth adjustment
90    pub bwadj: u8,
91}
92
93#[inline]
94pub const fn clock_after_division(clk: Hertz, div_sel: ClockDivisorSelect) -> Hertz {
95    match div_sel {
96        ClockDivisorSelect::Div1 => clk,
97        ClockDivisorSelect::Div2 => Hertz::from_raw(clk.to_raw() / 2),
98        ClockDivisorSelect::Div4 => Hertz::from_raw(clk.to_raw() / 4),
99        ClockDivisorSelect::Div8 => Hertz::from_raw(clk.to_raw() / 8),
100    }
101}
102
103/// Wait for 500 reference clock cycles like specified in the datasheet.
104pub fn pll_setup_delay() {
105    for _ in 0..500 {
106        cortex_m::asm::nop()
107    }
108}
109
110pub trait ClkgenExt {
111    fn constrain(self) -> ClockConfigurator;
112}
113
114impl ClkgenExt for pac::Clkgen {
115    fn constrain(self) -> ClockConfigurator {
116        ClockConfigurator {
117            source_clk: None,
118            ref_clk_sel: ReferenceClockSelect::None,
119            clksel_sys: ClockSelect::Hbo,
120            clk_div_sel: ClockDivisorSelect::Div1,
121            clk_lost_detection: false,
122            pll_lock_lost_detection: false,
123            pll_cfg: None,
124            clkgen: self,
125        }
126    }
127}
128
129#[derive(Debug, PartialEq, Eq)]
130#[cfg_attr(feature = "defmt", derive(defmt::Format))]
131pub struct ClockSourceFrequencyNotSet;
132
133#[derive(Debug, PartialEq, Eq)]
134#[cfg_attr(feature = "defmt", derive(defmt::Format))]
135pub enum ClockConfigError {
136    ClkSourceFreqNotSet,
137    PllConfigNotSet,
138    PllInitError,
139    InconsistentCfg,
140}
141
142pub struct ClockConfigurator {
143    ref_clk_sel: ReferenceClockSelect,
144    clksel_sys: ClockSelect,
145    clk_div_sel: ClockDivisorSelect,
146    /// The source clock frequency which is either an external clock connected to XTAL_N, or a
147    /// crystal connected to the XTAL_OSC input.
148    source_clk: Option<Hertz>,
149    pll_cfg: Option<PllConfig>,
150    clk_lost_detection: bool,
151    /// Feature only works on revision B of the board.
152    #[cfg(feature = "revb")]
153    pll_lock_lost_detection: bool,
154    clkgen: pac::Clkgen,
155}
156
157/// Delays a given amount of milliseconds.
158///
159/// Taken from the HAL implementation. This implementation is probably not precise and it
160/// also blocks!
161pub fn hbo_clock_delay_ms(ms: u32) {
162    let wdt = unsafe { pac::WatchDog::steal() };
163    for _ in 0..ms {
164        for _ in 0..10_000 {
165            cortex_m::asm::nop();
166        }
167        wdt.wdogintclr().write(|w| unsafe { w.bits(1) });
168    }
169}
170
171impl ClockConfigurator {
172    /// Create a new clock configuration instance.
173    pub fn new(clkgen: pac::Clkgen) -> Self {
174        ClockConfigurator {
175            source_clk: None,
176            ref_clk_sel: ReferenceClockSelect::None,
177            clksel_sys: ClockSelect::Hbo,
178            clk_div_sel: ClockDivisorSelect::Div1,
179            clk_lost_detection: false,
180            pll_lock_lost_detection: false,
181            pll_cfg: None,
182            clkgen,
183        }
184    }
185
186    /// Steals a new [ClockConfigurator] instance.
187    ///
188    /// # Safety
189    ///
190    /// Circumvents HAL ownership rules.
191    pub unsafe fn steal() -> Self {
192        Self::new(unsafe { pac::Clkgen::steal() })
193    }
194
195    #[inline]
196    pub fn source_clk(mut self, src_clk: Hertz) -> Self {
197        self.source_clk = Some(src_clk);
198        self
199    }
200
201    /// This function can be used to utilize the XTAL_N clock input directly without the
202    /// oscillator.
203    ///
204    /// It sets the internal configuration to [ClockSelect::XtalN] and [ReferenceClockSelect::XtalN].
205    #[inline]
206    pub fn xtal_n_clk(mut self) -> Self {
207        self.clksel_sys = ClockSelect::XtalN;
208        self.ref_clk_sel = ReferenceClockSelect::XtalN;
209        self
210    }
211
212    #[inline]
213    pub fn xtal_n_clk_with_src_freq(mut self, src_clk: Hertz) -> Self {
214        self = self.xtal_n_clk();
215        self.source_clk(src_clk)
216    }
217
218    #[inline]
219    pub fn clksel_sys(mut self, clksel_sys: ClockSelect) -> Self {
220        self.clksel_sys = clksel_sys;
221        self
222    }
223
224    #[inline]
225    pub fn pll_cfg(mut self, pll_cfg: PllConfig) -> Self {
226        self.pll_cfg = Some(pll_cfg);
227        self
228    }
229
230    #[inline]
231    pub fn ref_clk_sel(mut self, ref_clk_sel: ReferenceClockSelect) -> Self {
232        self.ref_clk_sel = ref_clk_sel;
233        self
234    }
235
236    /// Configures all clocks and return a clock configuration structure containing the final
237    /// frozen clocks.
238    ///
239    /// Internal implementation details: This implementation is based on the HAL implementation
240    /// which performs a lot of delays. I do not know if all of those are necessary, but
241    /// I am going to be conservative here and assume that the vendor has tested though and
242    /// might have had a reason for those, so I am going to keep them. Chances are, this
243    /// process only has to be performed once, and it does not matter if it takes a few
244    /// microseconds or milliseconds longer.
245    pub fn freeze(self) -> Result<Clocks, ClockConfigError> {
246        // Sanitize configuration.
247        if self.source_clk.is_none() {
248            return Err(ClockConfigError::ClkSourceFreqNotSet);
249        }
250        if self.clksel_sys == ClockSelect::XtalOsc
251            && self.ref_clk_sel != ReferenceClockSelect::XtalOsc
252        {
253            return Err(ClockConfigError::InconsistentCfg);
254        }
255        if self.clksel_sys == ClockSelect::XtalN && self.ref_clk_sel != ReferenceClockSelect::XtalN
256        {
257            return Err(ClockConfigError::InconsistentCfg);
258        }
259        if self.clksel_sys == ClockSelect::Pll && self.pll_cfg.is_none() {
260            return Err(ClockConfigError::PllConfigNotSet);
261        }
262
263        enable_peripheral_clock(PeripheralSelect::Clkgen);
264        let mut final_sysclk = self.source_clk.unwrap();
265        // The HAL forces back the HBO clock here with a delay.. Even though this is
266        // not stricly necessary when coming from a fresh start, it could be still become relevant
267        // later if the clock lost detection mechanism require a re-configuration of the clocks.
268        // Therefore, we do it here as well.
269        self.clkgen
270            .ctrl0()
271            .modify(|_, w| unsafe { w.clksel_sys().bits(ClockSelect::Hbo as u8) });
272        pll_setup_delay();
273        self.clkgen
274            .ctrl0()
275            .modify(|_, w| unsafe { w.clk_div_sel().bits(ClockDivisorSelect::Div1 as u8) });
276
277        // Set up oscillator and PLL input clock.
278        self.clkgen
279            .ctrl0()
280            .modify(|_, w| unsafe { w.ref_clk_sel().bits(self.ref_clk_sel as u8) });
281        self.clkgen.ctrl1().modify(|_, w| {
282            w.xtal_en().clear_bit();
283            w.xtal_n_en().clear_bit();
284            w
285        });
286        match self.ref_clk_sel {
287            ReferenceClockSelect::None => pll_setup_delay(),
288            ReferenceClockSelect::XtalOsc => {
289                self.clkgen.ctrl1().modify(|_, w| w.xtal_en().set_bit());
290                hbo_clock_delay_ms(XTAL_OSC_TSTART_MS);
291            }
292            ReferenceClockSelect::XtalN => {
293                self.clkgen.ctrl1().modify(|_, w| w.xtal_n_en().set_bit());
294                pll_setup_delay()
295            }
296        }
297
298        // Set up PLL configuration.
299        match self.pll_cfg {
300            Some(cfg) => {
301                self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().clear_bit());
302                // Done in C HAL. I guess this gives the PLL some time to power down properly.
303                cortex_m::asm::nop();
304                cortex_m::asm::nop();
305                self.clkgen.ctrl0().modify(|_, w| {
306                    unsafe {
307                        w.pll_clkf().bits(cfg.clkf);
308                    }
309                    unsafe {
310                        w.pll_clkr().bits(cfg.clkr);
311                    }
312                    unsafe {
313                        w.pll_clkod().bits(cfg.clkod);
314                    }
315                    unsafe {
316                        w.pll_bwadj().bits(cfg.bwadj);
317                    }
318                    w.pll_test().clear_bit();
319                    w.pll_bypass().clear_bit();
320                    w.pll_intfb().set_bit()
321                });
322                // Taken from SystemCoreClockUpdate implementation from Vorago.
323                final_sysclk /= cfg.clkr as u32 + 1;
324                final_sysclk *= cfg.clkf as u32 + 1;
325                final_sysclk /= cfg.clkod as u32 + 1;
326
327                // Reset PLL.
328                self.clkgen.ctrl0().modify(|_, w| w.pll_reset().set_bit());
329                // The HAL does this, the datasheet specifies a delay of 5 us. I guess it does not
330                // really matter because the PLL lock detect is used later..
331                pll_setup_delay();
332                self.clkgen.ctrl0().modify(|_, w| w.pll_reset().clear_bit());
333                pll_setup_delay();
334
335                // check for lock
336                let stat = self.clkgen.stat().read();
337                if stat.fbslip().bit() || stat.rfslip().bit() {
338                    pll_setup_delay();
339                    if stat.fbslip().bit() || stat.rfslip().bit() {
340                        // This is what the HAL does. We could continue, but then we would at least
341                        // have to somehow report a partial error.. Chances are, the user does not
342                        // want to continue with a broken PLL clock.
343                        return Err(ClockConfigError::PllInitError);
344                    }
345                }
346            }
347            None => {
348                self.clkgen.ctrl0().modify(|_, w| w.pll_pwdn().set_bit());
349            }
350        }
351
352        if self.clk_lost_detection {
353            rearm_sysclk_lost_with_periph(&self.clkgen)
354        }
355        #[cfg(feature = "revb")]
356        if self.pll_lock_lost_detection {
357            rearm_pll_lock_lost_with_periph(&self.clkgen)
358        }
359
360        self.clkgen
361            .ctrl0()
362            .modify(|_, w| unsafe { w.clk_div_sel().bits(self.clk_div_sel as u8) });
363        final_sysclk = clock_after_division(final_sysclk, self.clk_div_sel);
364
365        // The HAL does this. I don't know why..
366        pll_setup_delay();
367
368        self.clkgen
369            .ctrl0()
370            .modify(|_, w| unsafe { w.clksel_sys().bits(self.clksel_sys as u8) });
371
372        Ok(Clocks::__new(
373            final_sysclk,
374            #[cfg(not(feature = "va41628"))]
375            self.cfg_adc_clk_div(final_sysclk),
376        ))
377    }
378
379    #[cfg(not(feature = "va41628"))]
380    fn cfg_adc_clk_div(&self, final_sysclk: Hertz) -> Hertz {
381        // I will just do the ADC stuff like Vorago does it.
382        // ADC clock (must be 2-12.5 MHz)
383        // NOTE: Not using divide by 1 or /2 ratio in REVA silicon because of triggering issue
384        // For this reason, keep SYSCLK above 8MHz to have the ADC /4 ratio in range)
385        if final_sysclk.to_raw() <= ADC_MAX_CLK.to_raw() * 4 {
386            self.clkgen.ctrl1().modify(|_, w| unsafe {
387                w.adc_clk_div_sel().bits(AdcClockDivisorSelect::Div4 as u8)
388            });
389            final_sysclk / 4
390        } else {
391            self.clkgen.ctrl1().modify(|_, w| unsafe {
392                w.adc_clk_div_sel().bits(AdcClockDivisorSelect::Div8 as u8)
393            });
394            final_sysclk / 8
395        }
396    }
397}
398
399pub fn rearm_sysclk_lost() {
400    rearm_sysclk_lost_with_periph(&unsafe { pac::Clkgen::steal() })
401}
402
403fn rearm_sysclk_lost_with_periph(clkgen: &pac::Clkgen) {
404    clkgen
405        .ctrl0()
406        .modify(|_, w| w.sys_clk_lost_det_en().set_bit());
407    clkgen
408        .ctrl1()
409        .write(|w| w.sys_clk_lost_det_rearm().set_bit());
410    clkgen
411        .ctrl1()
412        .write(|w| w.sys_clk_lost_det_rearm().clear_bit());
413}
414
415#[cfg(feature = "revb")]
416pub fn rearm_pll_lock_lost() {
417    rearm_pll_lock_lost_with_periph(&unsafe { pac::Clkgen::steal() })
418}
419
420fn rearm_pll_lock_lost_with_periph(clkgen: &pac::Clkgen) {
421    clkgen
422        .ctrl1()
423        .modify(|_, w| w.pll_lost_lock_det_en().set_bit());
424    clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().set_bit());
425    clkgen.ctrl1().write(|w| w.pll_lck_det_rearm().clear_bit());
426}
427
428#[cfg(test)]
429mod tests {
430
431    use super::*;
432
433    #[test]
434    fn test_basic_div() {
435        assert_eq!(
436            clock_after_division(Hertz::from_raw(10_000_000), super::ClockDivisorSelect::Div2),
437            Hertz::from_raw(5_000_000)
438        );
439    }
440}