sifli_hal/rcc/
clock_configure.rs

1use core::sync::atomic::{compiler_fence, Ordering};
2
3use crate::pac::{HPSYS_RCC, HPSYS_AON, HPSYS_CFG, PMUC};
4use crate::time::Hertz;
5
6use super::{ClkSysSel, ClkPeriSel, UsbSel, TickSel};
7
8/// Represents a configuration value that can either be updated with a new value
9/// or kept unchanged from its previous state.
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum ConfigOption<T> {
12    /// Update the configuration with a new value
13    Update(T),
14    /// Keep the existing configuration value unchanged
15    Keep,
16}
17
18impl<T> ConfigOption<T> {
19    /// Creates a new ConfigOption that will update to the given value
20    pub fn new(value: T) -> Self {
21        ConfigOption::Update(value)
22    }
23
24    /// Creates a new ConfigOption that will keep the existing value
25    pub fn keep() -> Self {
26        ConfigOption::Keep
27    }
28
29    /// Returns true if this ConfigOption is set to update with a new value
30    pub fn is_update(&self) -> bool {
31        matches!(self, ConfigOption::Update(_))
32    }
33
34    /// Returns true if this ConfigOption is set to keep the existing value
35    pub fn is_keep(&self) -> bool {
36        matches!(self, ConfigOption::Keep)
37    }
38
39    /// Applies this ConfigOption to an existing value, either updating it or keeping it unchanged
40    pub fn apply(self, current: T) -> T {
41        match self {
42            ConfigOption::Update(new_value) => new_value,
43            ConfigOption::Keep => current,
44        }
45    }
46}
47
48pub struct Config {
49    /// Enable the 48MHz external crystal oscillator
50    pub hxt48_enable: ConfigOption<bool>,
51    /// Enable the 48MHz internal RC oscillator
52    pub hrc48_enable: ConfigOption<bool>,
53    /// Configuration for DLL1
54    pub dll1: ConfigOption<DllConfig>,
55    /// Configuration for DLL2 
56    /// Note: Bootloader typically configures this to 288MHz for PSRAM and external flash
57    pub dll2: ConfigOption<DllConfig>,
58    /// Select the clock source for system clock (clk_sys)
59    pub clk_sys_sel: ConfigOption<ClkSysSel>,
60    /// HCLK divider: HCLK = CLK_SYS / hclk_div
61    /// Valid range: 0 to 255
62    pub hclk_div: ConfigOption<u8>,
63    /// PCLK1 divider: PCLK1 = HCLK / 2^pclk1_div
64    /// Valid range: 0 to 7
65    pub pclk1_div: ConfigOption<u8>,
66    /// PCLK2 divider: PCLK2 = HCLK / 2^pclk2_div
67    /// Valid range: 0 to 7
68    pub pclk2_div: ConfigOption<u8>,
69    
70    /// USB clock configuration
71    pub usb: ConfigOption<UsbConfig>,
72    /// Tick clock configuration
73    pub tick: ConfigOption<TickConfig>,
74    /// Select the clock source for peripheral clock
75    pub clk_peri_sel: ConfigOption<ClkPeriSel>,
76}
77
78pub struct DllConfig {
79    /// Enable/disable the DLL
80    pub enable: bool,
81    /// DLL multiplication factor
82    /// Output frequency = (stg + 1) × 24MHz
83    /// Valid range: 0 to 15
84    pub stg: u8,
85    /// Enable output frequency division by 2
86    pub div2: bool,
87}
88
89pub struct UsbConfig {
90    /// Select the clock source for USB
91    pub sel: UsbSel,
92    /// USB clock divider: USB_CLK = CLK_SYS / div
93    /// Valid range: 1 to 7
94    pub div: u8,
95}
96
97pub struct TickConfig {
98    /// Select the clock source for system tick
99    pub sel: TickSel,
100    /// System tick divider
101    /// Valid range: 0 to 63
102    pub div: u8,
103}
104
105impl Default for Config {
106    fn default() -> Self {
107        Self {
108            hxt48_enable: ConfigOption::new(true),
109            hrc48_enable: ConfigOption::new(false),
110            dll1: ConfigOption::new(DllConfig { enable: true, stg: 5, div2: false }),
111            dll2: ConfigOption::keep(),
112            clk_sys_sel: ConfigOption::new(ClkSysSel::Dll1),
113            hclk_div: ConfigOption::new(0),
114            pclk1_div: ConfigOption::new(0),
115            pclk2_div: ConfigOption::new(0),
116            usb: ConfigOption::new(UsbConfig { sel: UsbSel::ClkSys, div: 0 }),
117            tick: ConfigOption::new(TickConfig { sel: TickSel::ClkRtc, div: 0 }),
118            clk_peri_sel: ConfigOption::new(ClkPeriSel::Hxt48),
119        }
120    }
121}
122
123impl Config {
124    pub fn new_keep() -> Self {
125        Self {
126            hxt48_enable: ConfigOption::keep(),
127            hrc48_enable: ConfigOption::keep(),
128            dll1: ConfigOption::keep(),
129            dll2: ConfigOption::keep(),
130            clk_sys_sel: ConfigOption::keep(),
131            hclk_div: ConfigOption::keep(),
132            pclk1_div: ConfigOption::keep(),
133            pclk2_div: ConfigOption::keep(),
134            usb: ConfigOption::keep(),
135            tick: ConfigOption::keep(),
136            clk_peri_sel: ConfigOption::keep(),
137        }
138    }
139
140    /// Apply the RCC clock configuration to the hardware registers
141    /// 
142    /// Safety
143    /// This function is typically called by sifli_hal::init() (configured 
144    /// in sifli_hal::Config.rcc), but can also be called independently as 
145    /// long as it does not interfere with the clocks of already initialized 
146    /// peripherals.
147    /// In the Bootloader, FLASH and PSRAM have already been initialized. 
148    /// You must ensure that their clocks are not broken.
149    /// If configuring the clock after calling sifli_hal::init(), make sure 
150    /// not to break the clock of Timer used as the time driver.
151    pub unsafe fn apply(&self) {
152        // Configure oscillators
153        if let ConfigOption::Update(enable) = self.hxt48_enable {
154            HPSYS_AON.acr().modify(|w| w.set_hxt48_req(enable));
155            while HPSYS_AON.acr().read().hxt48_rdy() != enable {}
156        }
157
158        if let ConfigOption::Update(enable) = self.hrc48_enable {
159            HPSYS_AON.acr().modify(|w| w.set_hrc48_req(enable));
160            while HPSYS_AON.acr().read().hrc48_rdy() != enable {}
161        }
162
163        if let ConfigOption::Update(div) = self.pclk1_div {
164            HPSYS_RCC.cfgr().modify(|w| w.set_pdiv1(div));
165        }
166        if let ConfigOption::Update(div) = self.pclk2_div {
167            HPSYS_RCC.cfgr().modify(|w| w.set_pdiv2(div));
168        }
169
170        // Configure USB clock
171        if let ConfigOption::Update(usb_cfg) = &self.usb {
172            HPSYS_RCC.csr().modify(|w| w.set_sel_usbc(usb_cfg.sel));
173            HPSYS_RCC.usbcr().modify(|w| w.set_div(usb_cfg.div));
174        }
175
176        // Configure tick clock
177        if let ConfigOption::Update(tick_cfg) = &self.tick {
178            HPSYS_RCC.csr().modify(|w| w.set_sel_tick(tick_cfg.sel));
179            HPSYS_RCC.cfgr().modify(|w| w.set_tickdiv(tick_cfg.div));
180        }
181
182        // Configure peripheral clock
183        if let ConfigOption::Update(sel) = self.clk_peri_sel {
184            HPSYS_RCC.csr().modify(|w| w.set_sel_peri(sel));
185        }
186
187        // Configure system clock
188        if self.hclk_is_update() {
189            let hclk_freq = self.get_final_hclk_freq().unwrap();
190            let current_hclk_freq = super::get_hclk_freq().unwrap();
191
192            let config_hclk_fn = || self.config_hclk();
193            crate::pmu::dvfs::config_hcpu_dvfs(current_hclk_freq, hclk_freq, config_hclk_fn); 
194            if ConfigOption::Update(ClkSysSel::Dll1) != self.clk_sys_sel {
195                self.config_dll1();
196            }
197        } else {
198            self.config_dll1();
199        }
200
201        // Configure DLL2, Must be done after configuring DVFS
202        self.config_dll2();
203    }
204
205    fn config_hclk(&self) {
206        // Configure system clock selection last
207        if let ConfigOption::Update(sel) = self.clk_sys_sel {
208            match sel {
209                ClkSysSel::Hrc48 => if !self.get_final_hrc48_enable() {
210                    panic!("clk_sys_sel is Hrc48, but hrc48 is disabled")
211                },
212                ClkSysSel::Hxt48 => if !self.get_final_hxt48_enable() {
213                    panic!("clk_sys_sel is Hxt48, but hxt48 is disabled")
214                },
215                ClkSysSel::Dbl96 => todo!(),
216                ClkSysSel::Dll1 => if !self.get_final_dll1_enable() {
217                    panic!("clk_sys_sel is dll1, but dll1 is disabled")
218                } else {
219                    self.config_dll1();
220                }
221            }
222            HPSYS_RCC.csr().modify(|w| w.set_sel_sys(sel));
223        }
224
225        // Configure clock selectors and dividers
226        if let ConfigOption::Update(div) = self.hclk_div {
227            HPSYS_RCC.cfgr().modify(|w| w.set_hdiv(div));
228        }
229    }
230
231    fn config_dll1(&self) {
232        if let ConfigOption::Update(dll1) = &self.dll1 {
233            let old_clk_sys_sel = super::get_clk_sys_source();
234            if old_clk_sys_sel == ClkSysSel::Dll1 {
235                if !dll1.enable {
236                    panic!("Disabling DLL1 while it is the current clk_sys source is not allowed");
237                }
238                // If switching away from DLL1, switch to HRC48 first
239                if super::get_hxt48_freq().is_some() {
240                    HPSYS_RCC.csr().modify(|w| w.set_sel_sys(ClkSysSel::Hxt48));
241                } else if super::get_hrc48_freq().is_some() {
242                    HPSYS_RCC.csr().modify(|w| w.set_sel_sys(ClkSysSel::Hrc48));
243                } else {
244                    panic!();
245                }
246            }
247            if dll1.enable {
248                rcc_assert!(max::DLL.contains(
249                    &self.get_final_dll1_freq().unwrap()
250                ));
251                
252                PMUC.hxt_cr1().modify(|w| w.set_buf_dll_en(true));
253
254                HPSYS_CFG.cau2_cr().modify(|w| {
255                    if !w.hpbg_en() { // SDK does this check, but it's not clear why
256                        w.set_hpbg_en(true);
257                    }
258                    if !w.hpbg_vddpsw_en() {
259                        w.set_hpbg_vddpsw_en(true);
260                    }
261                });
262                HPSYS_RCC.dllcr(0).modify(|w| w.set_en(false));
263                compiler_fence(Ordering::SeqCst);
264                // Enable DLL1
265                HPSYS_RCC.dllcr(0).modify(|w| {
266                    w.set_en(true);
267                    w.set_stg(dll1.stg);
268                    w.set_out_div2_en(dll1.div2);
269                });
270                // SDK: wait for DLL ready, 5us at least
271                crate::cortex_m_blocking_delay_us(10);
272                while !HPSYS_RCC.dllcr(0).read().ready() {}
273
274                if old_clk_sys_sel == ClkSysSel::Dll1 {
275                    HPSYS_RCC.csr().modify(|w| w.set_sel_sys(ClkSysSel::Dll1));
276                }
277            } else {
278                // Disable DLL1
279                HPSYS_RCC.dllcr(0).modify(|w| w.set_en(false));
280            }
281        }
282    }
283
284    fn config_dll2(&self) {
285        // Configure DLL2
286        if let ConfigOption::Update(_dll2) = &self.dll2 {
287            todo!("MPI uses DLL2, so we cannot change it simply");
288            // if dll2.enable {
289            //     rcc_assert!(max::DLL.contains(
290            //         &self.get_final_dll2_freq().unwrap()
291            //     ));
292
293            //     let dvfs_mode = crate::pmu::dvfs::HpsysDvfsMode::from_hertz(self.get_final_hclk_freq().unwrap()).unwrap();
294            //     let dll2_limit = dvfs_mode.get_dll2_limit();
295            //     let dll2_freq = self.get_final_dll2_freq().unwrap();
296            //     rcc_assert!(dll2_limit >= dll2_freq, 
297            //         "DLL2 frequency({}) exceeds DVFS mode {:?} limit: {}",dll2_freq.0, dvfs_mode, dll2_limit.0
298            //     );
299
300            //     PMUC.hxt_cr1().modify(|w| w.set_buf_dll_en(true));
301
302            //     HPSYS_CFG.cau2_cr().modify(|w| {
303            //         if !w.hpbg_en() { // SDK does this check, but it's not clear why
304            //             w.set_hpbg_en(true);
305            //         }
306            //         if !w.hpbg_vddpsw_en() {
307            //             w.set_hpbg_vddpsw_en(true);
308            //         }
309            //     });
310
311            //     HPSYS_RCC.dllcr(1).modify(|w| w.set_en(false));
312            //     compiler_fence(Ordering::SeqCst);
313            //     // Enable DLL2
314            //     HPSYS_RCC.dllcr(1).modify(|w| {
315            //         w.set_stg(dll2.stg);
316            //         w.set_in_div2_en(true);
317            //         w.set_out_div2_en(dll2.div2);
318            //         w.set_en(true);
319            //     });
320            //     // SDK: wait for DLL ready, 5us at least
321            //     crate::cortex_m_blocking_delay_us(10);
322            //     while !HPSYS_RCC.dllcr(1).read().ready() {}
323            // } else {
324            //     // Disable DLL1
325            //     HPSYS_RCC.dllcr(1).modify(|w| w.set_en(false));
326            // }
327        }
328    }
329
330    fn get_final_dll1_freq(&self) -> Option<Hertz> {
331        if let ConfigOption::Update(dll1) = &self.dll1 {
332            if dll1.enable {
333                Some(Hertz((dll1.stg + 1) as u32 * 24_000_000 / (dll1.div2 as u32 + 1)))
334            } else {
335                None
336            }
337        } else {
338            super::get_clk_dll1_freq()
339        }
340    }
341
342    // fn get_final_dll2_freq(&self) -> Option<Hertz> {
343    //     if let ConfigOption::Update(dll2) = &self.dll2 {
344    //         if dll2.enable {
345    //             Some(Hertz((dll2.stg + 1) as u32 * 24_000_000 / (dll2.div2 as u32 + 1)))
346    //         } else {
347    //             None
348    //         }
349    //     } else {
350    //         super::get_clk_dll2_freq()
351    //     }
352    // }
353
354    fn get_final_clk_sys_freq(&self) -> Option<Hertz> {
355        match self.clk_sys_sel {
356            ConfigOption::Update(ClkSysSel::Hxt48) => Some(Hertz(48_000_000)),
357            ConfigOption::Update(ClkSysSel::Hrc48) => Some(Hertz(48_000_000)),
358            ConfigOption::Update(ClkSysSel::Dll1) => self.get_final_dll1_freq(),
359            ConfigOption::Update(ClkSysSel::Dbl96) => todo!(),
360            ConfigOption::Keep => super::get_clk_sys_freq(),
361        }
362    }
363
364    fn get_final_hclk_freq(&self) -> Option<Hertz> {
365        if self.hclk_is_update() {
366            let hclk_div = match self.hclk_div {
367                ConfigOption::Update(div) => div,
368                ConfigOption::Keep => super::get_hclk_div()
369            };
370            let clk_sys = self.get_final_clk_sys_freq()?;
371            Some(clk_sys / hclk_div as u32)
372            
373        } else {
374            super::get_hclk_freq()
375        }
376    }
377
378    fn hclk_is_update(&self) -> bool {
379        if self.hclk_div.is_update() || self.clk_sys_sel.is_update() {
380            return true
381        }
382
383        match super::get_clk_sys_source() {
384            ClkSysSel::Hrc48 => false,
385            ClkSysSel::Hxt48 => false,
386            ClkSysSel::Dbl96 => todo!(),
387            ClkSysSel::Dll1 => self.dll1.is_update()
388        }
389    }
390
391    fn get_final_dll1_enable(&self) -> bool {
392        if let ConfigOption::Update(dll1) = &self.dll1 {
393            dll1.enable
394        } else {
395            super::get_clk_dll1_freq().is_some()
396        }
397    }
398
399    fn get_final_hxt48_enable(&self) -> bool {
400        if let ConfigOption::Update(enable) = self.hxt48_enable {
401            enable
402        } else {
403            super::get_hxt48_freq().is_some()
404        }
405    }
406
407    fn get_final_hrc48_enable(&self) -> bool {
408        if let ConfigOption::Update(enable) = self.hrc48_enable {
409            enable
410        } else {
411            super::get_hrc48_freq().is_some()
412        }
413    }
414}
415
416#[cfg(feature = "sf32lb52x")]
417mod max {
418    use core::ops::RangeInclusive;
419    use crate::time::Hertz;
420
421    pub(crate) const DLL: RangeInclusive<Hertz> = Hertz(24_000_000)..=Hertz(384_000_000);
422}