e310x_hal/
clock.rs

1//! Clock configuration
2use crate::time::Hertz;
3use e310x::{Aonclk as AONCLK, Prci as PRCI, CLINT};
4use riscv::interrupt;
5use riscv::register::mcycle;
6
7const PLLREF_MIN: u32 = 6_000_000;
8const PLLREF_MAX: u32 = 48_000_000;
9const REFR_MIN: u32 = 6_000_000;
10const REFR_MAX: u32 = 12_000_000;
11const VCO_MIN: u32 = 384_000_000;
12const VCO_MAX: u32 = 768_000_000;
13const PLLOUT_MIN: u32 = 48_000_000;
14const PLLOUT_MAX: u32 = 384_000_000;
15const DIVOUT_MIN: u32 = 375_000;
16const DIVOUT_MAX: u32 = 384_000_000;
17
18/// PrciExt trait extends `PRCI` peripheral.
19pub trait PrciExt {
20    /// Constrains the `PRCI` peripheral so it plays nicely with the other
21    /// abstractions.
22    fn constrain(self) -> CoreClk;
23}
24
25/// AonExt trait extends `AONCLK` peripheral.
26pub trait AonExt {
27    /// Constrains the `AON` peripheral so it plays nicely with the other
28    /// abstractions.
29    fn constrain(self) -> AonClk;
30}
31
32impl PrciExt for PRCI {
33    fn constrain(self) -> CoreClk {
34        CoreClk {
35            hfxosc: None,
36            coreclk: Hertz(13_800_000), // Default after reset
37        }
38    }
39}
40
41impl AonExt for AONCLK {
42    fn constrain(self) -> AonClk {
43        AonClk { lfaltclk: None }
44    }
45}
46
47/// Constrainted `PRCI` peripheral
48pub struct CoreClk {
49    hfxosc: Option<Hertz>,
50    coreclk: Hertz,
51}
52
53impl CoreClk {
54    /// Uses `HFXOSC` (external oscillator) instead of `HFROSC` (internal ring oscillator) as the clock source.
55    pub fn use_external<F: Into<Hertz>>(mut self, freq: F) -> Self {
56        let hz: Hertz = freq.into();
57        assert!(hz.0 < 20_000_000);
58
59        self.hfxosc = Some(hz);
60        self
61    }
62
63    /// Sets the desired frequency for the `coreclk` clock
64    pub fn coreclk<F: Into<Hertz>>(mut self, freq: F) -> Self {
65        self.coreclk = freq.into();
66        self
67    }
68
69    /// Freezes high-frequency clock configuration, making it effective
70    pub(crate) fn freeze(self) -> Hertz {
71        // Assume `psdclkbypass_n` is not used
72
73        // Temporarily switch to the internal oscillator
74        let prci = unsafe { PRCI::steal() };
75        let hfrosc_freq = self.configure_hfrosc();
76        // Switch to HFROSC, bypass PLL
77        prci.pllcfg()
78            .modify(|_, w| w.sel().bit(false).bypass().bit(true));
79
80        if let Some(freq) = self.hfxosc {
81            self.configure_with_external(freq)
82        } else {
83            self.configure_with_internal(hfrosc_freq)
84        }
85    }
86
87    /// Configures clock generation system with external oscillator
88    fn configure_with_external(self, source_freq: Hertz) -> Hertz {
89        let prci = unsafe { PRCI::steal() };
90
91        // Enable HFXOSC
92        prci.hfxosccfg().write(|w| w.enable().bit(true));
93
94        // Wait for HFXOSC to stabilize
95        while !prci.hfxosccfg().read().ready().bit_is_set() {}
96
97        // Select HFXOSC as pllref
98        prci.pllcfg().modify(|_, w| w.refsel().bit(true));
99
100        let freq;
101        if source_freq.0 == self.coreclk.0 {
102            // Use external oscillator with bypassed PLL
103            freq = source_freq;
104
105            // Bypass PLL
106            prci.pllcfg().modify(|_, w| w.bypass().bit(true));
107
108            // Bypass divider
109            prci.plloutdiv().write(|w| w.divby1().bit(true));
110        } else {
111            // Use external oscillator with PLL
112
113            // Configure PLL and divider
114            freq = self.configure_pll(source_freq, self.coreclk);
115        }
116
117        // Switch to PLL
118        prci.pllcfg().modify(|_, w| w.sel().bit(true));
119
120        // Disable HFROSC to save power
121        prci.hfrosccfg().write(|w| w.enable().bit(false));
122
123        freq
124    }
125
126    /// Configures clock generation system with internal oscillator
127    fn configure_with_internal(self, hfrosc_freq: Hertz) -> Hertz {
128        let prci = unsafe { PRCI::steal() };
129
130        let freq;
131        if hfrosc_freq.0 == self.coreclk.0 {
132            // Use internal oscillator with bypassed PLL
133            freq = hfrosc_freq;
134
135            // Switch to HFROSC, bypass PLL to save power
136            prci.pllcfg()
137                .modify(|_, w| w.sel().bit(false).bypass().bit(true));
138
139            //
140            prci.pllcfg().modify(|_, w| w.bypass().bit(true));
141        } else {
142            // Use internal oscillator with PLL
143
144            // Configure PLL and divider
145            freq = self.configure_pll(hfrosc_freq, self.coreclk);
146
147            // Switch to PLL
148            prci.pllcfg().modify(|_, w| w.sel().bit(true));
149        }
150
151        // Disable HFXOSC to save power
152        prci.hfxosccfg().write(|w| w.enable().bit(false));
153
154        freq
155    }
156
157    /// Configures internal high-frequency oscillator (`HFROSC`)
158    fn configure_hfrosc(&self) -> Hertz {
159        let prci = unsafe { PRCI::steal() };
160
161        // TODO: use trim value from OTP
162
163        // Configure HFROSC to 13.8 MHz
164        prci.hfrosccfg()
165            .write(|w| unsafe { w.div().bits(4).trim().bits(16).enable().bit(true) });
166
167        // Wait for HFROSC to stabilize
168        while !prci.hfrosccfg().read().ready().bit_is_set() {}
169
170        Hertz(13_800_000)
171    }
172
173    /// Configures PLL and PLL Output Divider
174    /// The resulting frequency may differ by 0-2% from the requested
175    fn configure_pll(&self, pllref_freq: Hertz, divout_freq: Hertz) -> Hertz {
176        let pllref_freq = pllref_freq.0;
177        assert!((PLLREF_MIN..=PLLREF_MAX).contains(&pllref_freq));
178
179        let divout_freq = divout_freq.0;
180        assert!((DIVOUT_MIN..=DIVOUT_MAX).contains(&divout_freq));
181
182        // Calculate PLL Output Divider settings
183        let divider_div;
184        let divider_bypass;
185
186        let d = PLLOUT_MAX / divout_freq;
187        if d > 1 {
188            divider_bypass = false;
189
190            if d > 128 {
191                divider_div = (128 / 2) - 1;
192            } else {
193                divider_div = (d / 2) - 1;
194            }
195        } else {
196            divider_div = 0;
197            divider_bypass = true;
198        }
199
200        // Calculate pllout frequency
201        let d = if divider_bypass {
202            1
203        } else {
204            2 * (divider_div + 1)
205        };
206        let pllout_freq = divout_freq * d;
207        assert!((PLLOUT_MIN..=PLLOUT_MAX).contains(&pllout_freq));
208
209        // Calculate PLL R ratio
210        let r = match pllref_freq {
211            24_000_000..=48_000_000 => 4,
212            18_000_000..=23_999_999 => 3,
213            12_000_000..=17_999_999 => 2,
214            6_000_000..=11_999_999 => 1,
215            _ => unreachable!(),
216        };
217
218        // Calculate refr frequency
219        let refr_freq = pllref_freq / r;
220        assert!((REFR_MIN..=REFR_MAX).contains(&refr_freq));
221
222        // Calculate PLL Q ratio
223        let q = match pllout_freq {
224            192_000_000..=384_000_000 => 2,
225            96_000_000..=191_999_999 => 4,
226            48_000_000..=95_999_999 => 8,
227            _ => unreachable!(),
228        };
229
230        // Calculate the desired vco frequency
231        let target_vco_freq: u32 = pllout_freq * q;
232        assert!((VCO_MIN..=VCO_MAX).contains(&target_vco_freq));
233
234        // Calculate PLL F ratio
235        let f = target_vco_freq / refr_freq;
236        assert!(f <= 128);
237
238        // Choose the best F ratio
239        let f_lo = (f / 2) * 2; // F must be a multiple of 2
240        let vco_lo = refr_freq * f_lo;
241        let f_hi = f_lo + 2;
242        let vco_hi = refr_freq * f_hi;
243        let (f, vco_freq) = if (f_hi <= 128 && vco_hi <= VCO_MAX)
244            && (target_vco_freq as i32 - vco_hi as i32).abs()
245                < (target_vco_freq as i32 - vco_lo as i32).abs()
246        {
247            (f_hi, vco_hi)
248        } else {
249            (f_lo, vco_lo)
250        };
251        assert!((VCO_MIN..=VCO_MAX).contains(&vco_freq));
252
253        // Calculate actual pllout frequency
254        let pllout_freq = vco_freq / q;
255        assert!((PLLOUT_MIN..=PLLOUT_MAX).contains(&pllout_freq));
256
257        // Calculate actual divout frequency
258        let divout_freq = pllout_freq / d;
259        assert!((DIVOUT_MIN..=DIVOUT_MAX).contains(&divout_freq));
260
261        // Calculate bit-values
262        let r: u8 = (r - 1) as u8;
263        let f: u8 = (f / 2 - 1) as u8;
264        let q: u8 = match q {
265            2 => 0b01,
266            4 => 0b10,
267            8 => 0b11,
268            _ => unreachable!(),
269        };
270
271        // Configure PLL
272        let prci = unsafe { PRCI::steal() };
273        prci.pllcfg().modify(|_, w| unsafe {
274            w.pllr()
275                .bits(r)
276                .pllf()
277                .bits(f)
278                .pllq()
279                .bits(q)
280                .bypass()
281                .bit(false)
282        });
283
284        // Configure PLL Output Divider
285        prci.plloutdiv()
286            .write(|w| unsafe { w.div().bits(divider_div as u8).divby1().bit(divider_bypass) });
287
288        // Wait for PLL Lock
289        // Note that the Lock signal can be glitchy.
290        // Need to wait 100 us
291        // RTC is running at 32kHz.
292        // So wait 4 ticks of RTC.
293        let mtime = CLINT::mtimer().mtime;
294        let time = mtime.read() + 4;
295        while mtime.read() < time {}
296        // Now it is safe to check for PLL Lock
297        while !prci.pllcfg().read().lock().bit_is_set() {}
298
299        Hertz(divout_freq)
300    }
301}
302
303/// Constrained `AONCLK` peripheral
304pub struct AonClk {
305    lfaltclk: Option<Hertz>,
306}
307
308impl AonClk {
309    /// Uses `LFALTCLK` (external low-frequency clock) instead of `LFROSC` (internal ring oscillator) as the clock source.
310    pub fn use_external<F: Into<Hertz>>(mut self, freq: F) -> Self {
311        let hz: Hertz = freq.into();
312        assert!(hz.0 < 500_000);
313
314        self.lfaltclk = Some(hz);
315        self
316    }
317
318    /// Freezes low-frequency clock configuration, making it effective
319    pub(crate) fn freeze(self) -> Hertz {
320        let aonclk = unsafe { AONCLK::steal() };
321
322        if let Some(freq) = self.lfaltclk {
323            // Use external oscillator.
324
325            // Disable unused LFROSC to save power.
326            aonclk.lfrosccfg().write(|w| w.enable().bit(false));
327
328            freq
329        } else {
330            // Use internal oscillator.
331
332            let trim = 16;
333            let div = 4; // LFROSC/5
334
335            // Configure LFROSC
336            aonclk.lfrosccfg().write(|w| unsafe {
337                w.trim().bits(trim);
338                w.div().bits(div);
339                w.enable().bit(true)
340            });
341
342            // Wait for LFROSC to stabilize
343            while !aonclk.lfrosccfg().read().ready().bit_is_set() {}
344
345            Hertz(32_768) // It's not so accurate: ≈30 kHz according to the datasheet
346        }
347    }
348}
349
350/// Frozen clock frequencies
351///
352/// The existence of this value indicates that the clock configuration can no
353/// longer be changed.
354#[derive(Clone, Copy)]
355pub struct Clocks {
356    coreclk: Hertz,
357    lfclk: Hertz,
358}
359
360impl Clocks {
361    /// Freezes the coreclk and aonclk frequencies.
362    pub fn freeze(coreclk: CoreClk, aonclk: AonClk) -> Self {
363        let coreclk = coreclk.freeze();
364        let lfclk = aonclk.freeze();
365        Clocks { coreclk, lfclk }
366    }
367
368    /// Returns the frozen coreclk frequency
369    pub fn coreclk(&self) -> Hertz {
370        self.coreclk
371    }
372
373    /// Returns the frozen tlclk frequency
374    pub fn tlclk(&self) -> Hertz {
375        // For the FE310-G000, the TileLink bus clock (tlclk) is fixed to be
376        // the same as the processor core clock (coreclk)
377        self.coreclk
378    }
379
380    /// Returns the frozen lfclk frequency
381    pub fn lfclk(&self) -> Hertz {
382        self.lfclk
383    }
384
385    /// Measure the coreclk frequency by counting the number of aonclk ticks.
386    fn _measure_coreclk(&self, min_ticks: u64) -> Hertz {
387        let mtime = CLINT::mtimer().mtime;
388        interrupt::free(|| {
389            // Don't start measuring until we see an mtime tick
390            while mtime.read() == mtime.read() {}
391
392            let start_cycle = mcycle::read64();
393            let start_time = mtime.read();
394
395            // Wait for min_ticks to pass
396            while start_time + min_ticks > mtime.read() {}
397
398            let end_cycle = mcycle::read64();
399            let end_time = mtime.read();
400
401            let delta_cycle: u64 = end_cycle - start_cycle;
402            let delta_time: u64 = end_time - start_time;
403
404            let res = (delta_cycle / delta_time) * 32768
405                + ((delta_cycle % delta_time) * 32768) / delta_time;
406            // u32 can represent 4GHz way above the expected measurement value
407            Hertz(res as u32)
408        })
409    }
410
411    /// Measure the coreclk frequency by counting the number of aonclk ticks.
412    pub fn measure_coreclk(&self) -> Hertz {
413        // warm up I$
414        self._measure_coreclk(1);
415        // measure for real
416        self._measure_coreclk(10)
417    }
418}