diff options
| author | 1-rafael-1 <[email protected]> | 2025-04-26 21:54:40 +0200 |
|---|---|---|
| committer | 1-rafael-1 <[email protected]> | 2025-04-26 21:54:40 +0200 |
| commit | 4ce3bdb3703e5120c7936b5e3762744ae4461e75 (patch) | |
| tree | 6c7f85a2f77470863f11795865fb630cc97970ff | |
| parent | 572e788b2e878436bde527ad66cf561775cebc66 (diff) | |
Add core voltage scaling options and PLL parameter finder for RP2040
| -rw-r--r-- | embassy-rp/src/clocks.rs | 242 |
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)] | ||
| 77 | pub 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")] | ||
| 97 | impl 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] |
| 74 | pub struct ClockConfig { | 116 | pub 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")] | ||
| 547 | fn 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 |
| 409 | pub(crate) unsafe fn init(config: ClockConfig) { | 609 | pub(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 { |
