aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author1-rafael-1 <[email protected]>2025-04-26 21:54:40 +0200
committer1-rafael-1 <[email protected]>2025-04-26 21:54:40 +0200
commit4ce3bdb3703e5120c7936b5e3762744ae4461e75 (patch)
tree6c7f85a2f77470863f11795865fb630cc97970ff
parent572e788b2e878436bde527ad66cf561775cebc66 (diff)
Add core voltage scaling options and PLL parameter finder for RP2040
-rw-r--r--embassy-rp/src/clocks.rs242
1 files changed, 228 insertions, 14 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 67aa5e540..ba7b139a6 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -69,6 +69,48 @@ pub enum PeriClkSrc {
69 // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , 69 // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
70} 70}
71 71
72/// Core voltage scaling options for RP2040.
73/// See RP2040 Datasheet, Table 18.
74#[cfg(feature = "rp2040")]
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76#[repr(u8)]
77pub enum VoltageScale {
78 /// 0.85V
79 V0_85 = 0b1000,
80 /// 0.90V
81 V0_90 = 0b1001,
82 /// 0.95V
83 V0_95 = 0b1010,
84 /// 1.00V
85 V1_00 = 0b1011,
86 /// 1.05V
87 V1_05 = 0b1100,
88 /// 1.10V (Default)
89 V1_10 = 0b1101,
90 /// 1.15V
91 V1_15 = 0b1110,
92 /// 1.20V
93 V1_20 = 0b1111,
94}
95
96#[cfg(feature = "rp2040")]
97impl VoltageScale {
98 /// Get the recommended Brown-Out Detection (BOD) setting for this voltage.
99 /// See RP2040 Datasheet, Table 19.
100 fn recommended_bod(self) -> u8 {
101 match self {
102 VoltageScale::V0_85 => 0b1000, // BOD recommends VSEL + 1
103 VoltageScale::V0_90 => 0b1001,
104 VoltageScale::V0_95 => 0b1010,
105 VoltageScale::V1_00 => 0b1011,
106 VoltageScale::V1_05 => 0b1100,
107 VoltageScale::V1_10 => 0b1101, // Default
108 VoltageScale::V1_15 => 0b1110,
109 VoltageScale::V1_20 => 0b1111,
110 }
111 }
112}
113
72/// CLock configuration. 114/// CLock configuration.
73#[non_exhaustive] 115#[non_exhaustive]
74pub struct ClockConfig { 116pub struct ClockConfig {
@@ -89,6 +131,9 @@ pub struct ClockConfig {
89 /// RTC clock configuration. 131 /// RTC clock configuration.
90 #[cfg(feature = "rp2040")] 132 #[cfg(feature = "rp2040")]
91 pub rtc_clk: Option<RtcClkConfig>, 133 pub rtc_clk: Option<RtcClkConfig>,
134 /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None.
135 #[cfg(feature = "rp2040")]
136 pub voltage_scale: Option<VoltageScale>,
92 // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, 137 // gpin0: Option<(u32, Gpin<'static, AnyPin>)>,
93 // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, 138 // gpin1: Option<(u32, Gpin<'static, AnyPin>)>,
94} 139}
@@ -152,6 +197,93 @@ impl ClockConfig {
152 div_frac: 0, 197 div_frac: 0,
153 phase: 0, 198 phase: 0,
154 }), 199 }),
200 #[cfg(feature = "rp2040")]
201 voltage_scale: None, // Use hardware default (1.10V)
202 // gpin0: None,
203 // gpin1: None,
204 }
205 }
206
207 /// Clock configuration derived from external crystal, targeting a specific SYS clock frequency for RP2040.
208 ///
209 /// This function calculates the required PLL settings and core voltage based on the target frequency.
210 ///
211 /// # Arguments
212 ///
213 /// * `crystal_hz`: The frequency of the external crystal (e.g., 12_000_000 for 12MHz).
214 /// * `sys_freq_hz`: The target system clock frequency.
215 ///
216 /// # Panics
217 ///
218 /// Panics if the requested frequency is impossible to achieve with the given crystal,
219 /// or if the required voltage for the frequency is not supported or known.
220 ///
221 /// # Safety Notes (RP2040)
222 ///
223 /// * Frequencies > 133MHz require increased core voltage.
224 /// * This function automatically selects `VoltageScale::V1_15` for frequencies > 133MHz and <= 200MHz.
225 /// * Frequencies > 200MHz might require `VoltageScale::V1_20` or higher and are considered overclocking beyond datasheet recommendations.
226 /// This function will select `VoltageScale::V1_20` for frequencies > 200MHz, use with caution.
227 /// * Ensure your hardware supports the selected voltage and frequency.
228 #[cfg(feature = "rp2040")]
229 pub fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self {
230 // Determine required voltage based on target frequency
231 let voltage_scale = match sys_freq_hz {
232 0..=133_000_000 => VoltageScale::V1_10, // Default voltage is sufficient
233 133_000_001..=200_000_000 => VoltageScale::V1_15, // Requires 1.15V
234 _ => VoltageScale::V1_20, // Frequencies > 200MHz require at least 1.20V (Overclocking)
235 };
236
237 // Find suitable PLL parameters
238 let pll_params = find_pll_params(crystal_hz, sys_freq_hz)
239 .expect("Could not find valid PLL parameters for the requested frequency");
240
241 Self {
242 rosc: Some(RoscConfig {
243 hz: 6_500_000,
244 range: RoscRange::Medium,
245 drive_strength: [0; 8],
246 div: 16,
247 }),
248 xosc: Some(XoscConfig {
249 hz: crystal_hz,
250 sys_pll: Some(pll_params),
251 // Keep USB PLL at 48MHz for compatibility
252 usb_pll: Some(PllConfig {
253 refdiv: 1,
254 fbdiv: 120,
255 post_div1: 6,
256 post_div2: 5,
257 }),
258 delay_multiplier: 128,
259 }),
260 ref_clk: RefClkConfig {
261 src: RefClkSrc::Xosc,
262 div: 1,
263 },
264 sys_clk: SysClkConfig {
265 src: SysClkSrc::PllSys,
266 div_int: 1,
267 div_frac: 0,
268 },
269 peri_clk_src: Some(PeriClkSrc::Sys),
270 usb_clk: Some(UsbClkConfig {
271 src: UsbClkSrc::PllUsb,
272 div: 1,
273 phase: 0,
274 }),
275 adc_clk: Some(AdcClkConfig {
276 src: AdcClkSrc::PllUsb,
277 div: 1,
278 phase: 0,
279 }),
280 rtc_clk: Some(RtcClkConfig {
281 src: RtcClkSrc::PllUsb,
282 div_int: 1024,
283 div_frac: 0,
284 phase: 0,
285 }),
286 voltage_scale: Some(voltage_scale),
155 // gpin0: None, 287 // gpin0: None,
156 // gpin1: None, 288 // gpin1: None,
157 } 289 }
@@ -192,8 +324,10 @@ impl ClockConfig {
192 div_frac: 171, 324 div_frac: 171,
193 phase: 0, 325 phase: 0,
194 }), 326 }),
195 // gpin0: None, 327 #[cfg(feature = "rp2040")]
196 // gpin1: None, 328 voltage_scale: None, // Use hardware default (1.10V)
329 // gpin0: None,
330 // gpin1: None,
197 } 331 }
198 } 332 }
199 333
@@ -405,6 +539,72 @@ pub struct RtcClkConfig {
405 pub phase: u8, 539 pub phase: u8,
406} 540}
407 541
542/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency
543/// based on the input frequency.
544///
545/// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL
546#[cfg(feature = "rp2040")]
547fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> {
548 // Constraints from datasheet:
549 // REFDIV: 1..=63
550 // FBDIV: 16..=320
551 // POSTDIV1: 1..=7
552 // POSTDIV2: 1..=7 (must be <= POSTDIV1)
553 // VCO frequency (input_hz / refdiv * fbdiv): 400MHz ..= 1600MHz
554
555 for refdiv in 1..=63 {
556 let ref_clk = input_hz / refdiv;
557 // Reference clock must be >= 5MHz (implied by VCO min / FBDIV max)
558 if ref_clk < 5_000_000 {
559 continue;
560 }
561
562 for fbdiv in (16..=320).rev() {
563 // Iterate high fbdiv first for better VCO stability
564 let vco_freq = ref_clk * fbdiv;
565 if !(400_000_000..=1_600_000_000).contains(&vco_freq) {
566 continue;
567 }
568
569 // We want vco_freq / (post_div1 * post_div2) = target_hz
570 // So, post_div1 * post_div2 = vco_freq / target_hz
571 let target_post_div_product = vco_freq as f64 / target_hz as f64;
572 if target_post_div_product < 1.0 || target_post_div_product > 49.0 {
573 // 7*7 = 49
574 continue;
575 }
576 // Manual rounding: floor(x + 0.5)
577 let target_post_div_product_int = (target_post_div_product + 0.5) as u32;
578 if target_post_div_product_int == 0 {
579 continue;
580 }
581
582 // Check if the rounded product gives the target frequency
583 if vco_freq / target_post_div_product_int != target_hz {
584 continue;
585 }
586
587 for post_div1 in (1..=7).rev() {
588 // Iterate high post_div1 first
589 if target_post_div_product_int % post_div1 == 0 {
590 let post_div2 = target_post_div_product_int / post_div1;
591 if (1..=7).contains(&post_div2) && post_div2 <= post_div1 {
592 // Found a valid combination
593 return Some(PllConfig {
594 refdiv: refdiv as u8, // Cast u32 to u8 (safe: 1..=63)
595 fbdiv: fbdiv as u16, // Cast u32 to u16 (safe: 16..=320)
596 post_div1: post_div1 as u8,
597 post_div2: post_div2 as u8,
598 });
599 }
600 }
601 }
602 }
603 }
604
605 None // No valid parameters found
606}
607
408/// safety: must be called exactly once at bootup 608/// safety: must be called exactly once at bootup
409pub(crate) unsafe fn init(config: ClockConfig) { 609pub(crate) unsafe fn init(config: ClockConfig) {
410 // Reset everything except: 610 // Reset everything except:
@@ -447,23 +647,14 @@ pub(crate) unsafe fn init(config: ClockConfig) {
447 reset::reset(peris); 647 reset::reset(peris);
448 reset::unreset_wait(peris); 648 reset::unreset_wait(peris);
449 649
450 // let gpin0_freq = config.gpin0.map_or(0, |p| { 650 // Configure ROSC first if present
451 // core::mem::forget(p.1);
452 // p.0
453 // });
454 // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed);
455 // let gpin1_freq = config.gpin1.map_or(0, |p| {
456 // core::mem::forget(p.1);
457 // p.0
458 // });
459 // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed);
460
461 let rosc_freq = match config.rosc { 651 let rosc_freq = match config.rosc {
462 Some(config) => configure_rosc(config), 652 Some(config) => configure_rosc(config),
463 None => 0, 653 None => 0,
464 }; 654 };
465 CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); 655 CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed);
466 656
657 // Configure XOSC and PLLs if present
467 let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { 658 let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc {
468 Some(config) => { 659 Some(config) => {
469 // start XOSC 660 // start XOSC
@@ -488,6 +679,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
488 CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); 679 CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed);
489 CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); 680 CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed);
490 681
682 // Configure REF clock source and divider
491 let (ref_src, ref_aux, clk_ref_freq) = { 683 let (ref_src, ref_aux, clk_ref_freq) = {
492 use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; 684 use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src};
493 let div = config.ref_clk.div as u32; 685 let div = config.ref_clk.div as u32;
@@ -514,7 +706,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
514 w.set_int(config.ref_clk.div); 706 w.set_int(config.ref_clk.div);
515 }); 707 });
516 708
517 // Configure tick generation on the 2040. 709 // Configure tick generation using REF clock
518 #[cfg(feature = "rp2040")] 710 #[cfg(feature = "rp2040")]
519 pac::WATCHDOG.tick().write(|w| { 711 pac::WATCHDOG.tick().write(|w| {
520 w.set_cycles((clk_ref_freq / 1_000_000) as u16); 712 w.set_cycles((clk_ref_freq / 1_000_000) as u16);
@@ -532,6 +724,28 @@ pub(crate) unsafe fn init(config: ClockConfig) {
532 pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); 724 pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true));
533 } 725 }
534 726
727 // Set Core Voltage (RP2040 only) BEFORE switching SYS clock to high speed PLL
728 #[cfg(feature = "rp2040")]
729 if let Some(voltage) = config.voltage_scale {
730 let vreg = pac::VREG_AND_CHIP_RESET;
731 let current_vsel = vreg.vreg().read().vsel();
732 let target_vsel = voltage as u8;
733
734 if target_vsel != current_vsel {
735 // Set voltage and recommended BOD level
736 vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod()));
737 vreg.vreg().write(|w| w.set_vsel(target_vsel));
738
739 // Wait 10us for regulator to settle. Delay calculation uses REF clock
740 // as it's guaranteed to be stable here, before SYS potentially switches.
741 // 10 us = 1/100_000 s. cycles = freq * time.
742 let delay_cycles = clk_ref_freq / 100_000;
743 // delay(N) waits N+1 cycles.
744 cortex_m::asm::delay(delay_cycles.saturating_sub(1));
745 }
746 }
747
748 // Configure SYS clock source and divider
535 let (sys_src, sys_aux, clk_sys_freq) = { 749 let (sys_src, sys_aux, clk_sys_freq) = {
536 use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; 750 use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src};
537 let (src, aux, freq) = match config.sys_clk.src { 751 let (src, aux, freq) = match config.sys_clk.src {