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