diff options
| author | 1-rafael-1 <[email protected]> | 2025-04-28 22:54:15 +0200 |
|---|---|---|
| committer | 1-rafael-1 <[email protected]> | 2025-04-28 22:54:15 +0200 |
| commit | 3a6dc910ffc66d4a30b89f299432b383271a719f (patch) | |
| tree | b053571d597b2030e1069397a93de05cf9939661 /embassy-rp/src | |
| parent | b0594d16f238f803a0192810833ae2b0c3941ec3 (diff) | |
first working draft
Diffstat (limited to 'embassy-rp/src')
| -rw-r--r-- | embassy-rp/src/clocks.rs | 658 |
1 files changed, 489 insertions, 169 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index ba7b139a6..b74e90f5e 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs | |||
| @@ -70,43 +70,51 @@ pub enum PeriClkSrc { | |||
| 70 | } | 70 | } |
| 71 | 71 | ||
| 72 | /// Core voltage scaling options for RP2040. | 72 | /// Core voltage scaling options for RP2040. |
| 73 | /// See RP2040 Datasheet, Table 18. | 73 | /// See RP2040 Datasheet, Table 189, VREG Register. |
| 74 | #[cfg(feature = "rp2040")] | 74 | #[cfg(feature = "rp2040")] |
| 75 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | 75 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 76 | #[repr(u8)] | 76 | #[repr(u8)] |
| 77 | pub enum VoltageScale { | 77 | pub enum VoltageScale { |
| 78 | /// 0.85V | 78 | /// 0.85V |
| 79 | V0_85 = 0b1000, | 79 | V0_85 = 0b0110, |
| 80 | /// 0.90V | 80 | /// 0.90V |
| 81 | V0_90 = 0b1001, | 81 | V0_90 = 0b0111, |
| 82 | /// 0.95V | 82 | /// 0.95V |
| 83 | V0_95 = 0b1010, | 83 | V0_95 = 0b1000, |
| 84 | /// 1.00V | 84 | /// 1.00V |
| 85 | V1_00 = 0b1011, | 85 | V1_00 = 0b1001, |
| 86 | /// 1.05V | 86 | /// 1.05V |
| 87 | V1_05 = 0b1100, | 87 | V1_05 = 0b1010, |
| 88 | /// 1.10V (Default) | 88 | /// 1.10V (Default) |
| 89 | V1_10 = 0b1101, | 89 | V1_10 = 0b1011, |
| 90 | /// 1.15V | 90 | /// 1.15V |
| 91 | V1_15 = 0b1110, | 91 | V1_15 = 0b1100, |
| 92 | /// 1.20V | 92 | /// 1.20V |
| 93 | V1_20 = 0b1111, | 93 | V1_20 = 0b1101, |
| 94 | /// 1.25V | ||
| 95 | V1_25 = 0b1110, | ||
| 96 | /// 1.30V | ||
| 97 | V1_30 = 0b1111, | ||
| 94 | } | 98 | } |
| 95 | 99 | ||
| 96 | #[cfg(feature = "rp2040")] | 100 | #[cfg(feature = "rp2040")] |
| 97 | impl VoltageScale { | 101 | impl VoltageScale { |
| 98 | /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. | 102 | /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. |
| 99 | /// See RP2040 Datasheet, Table 19. | 103 | /// Sets the BOD threshold to approximately 90% of the core voltage. |
| 104 | /// See RP2040 Datasheet, Table 190, BOD Register | ||
| 100 | fn recommended_bod(self) -> u8 { | 105 | fn recommended_bod(self) -> u8 { |
| 101 | match self { | 106 | match self { |
| 102 | VoltageScale::V0_85 => 0b1000, // BOD recommends VSEL + 1 | 107 | // ~90% of voltage setting based on Table 190 values |
| 103 | VoltageScale::V0_90 => 0b1001, | 108 | VoltageScale::V0_85 => 0b0111, // 0.774V (~91% of 0.85V) |
| 104 | VoltageScale::V0_95 => 0b1010, | 109 | VoltageScale::V0_90 => 0b1000, // 0.817V (~91% of 0.90V) |
| 105 | VoltageScale::V1_00 => 0b1011, | 110 | VoltageScale::V0_95 => 0b1001, // 0.860V (~91% of 0.95V) |
| 106 | VoltageScale::V1_05 => 0b1100, | 111 | VoltageScale::V1_00 => 0b1010, // 0.903V (~90% of 1.00V) |
| 107 | VoltageScale::V1_10 => 0b1101, // Default | 112 | VoltageScale::V1_05 => 0b1011, // 0.946V (~90% of 1.05V) |
| 108 | VoltageScale::V1_15 => 0b1110, | 113 | VoltageScale::V1_10 => 0b1100, // 0.989V (~90% of 1.10V) |
| 109 | VoltageScale::V1_20 => 0b1111, | 114 | VoltageScale::V1_15 => 0b1101, // 1.032V (~90% of 1.15V) |
| 115 | VoltageScale::V1_20 => 0b1110, // 1.075V (~90% of 1.20V) | ||
| 116 | VoltageScale::V1_25 => 0b1111, // 1.118V (~89% of 1.25V) | ||
| 117 | VoltageScale::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold | ||
| 110 | } | 118 | } |
| 111 | } | 119 | } |
| 112 | } | 120 | } |
| @@ -134,6 +142,9 @@ pub struct ClockConfig { | |||
| 134 | /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None. | 142 | /// Core voltage scaling (RP2040 only). Defaults to 1.10V if None. |
| 135 | #[cfg(feature = "rp2040")] | 143 | #[cfg(feature = "rp2040")] |
| 136 | pub voltage_scale: Option<VoltageScale>, | 144 | pub voltage_scale: Option<VoltageScale>, |
| 145 | /// Voltage stabilization delay in microseconds. | ||
| 146 | #[cfg(feature = "rp2040")] | ||
| 147 | pub voltage_stabilization_delay_us: Option<u32>, | ||
| 137 | // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, | 148 | // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, |
| 138 | // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, | 149 | // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, |
| 139 | } | 150 | } |
| @@ -199,8 +210,10 @@ impl ClockConfig { | |||
| 199 | }), | 210 | }), |
| 200 | #[cfg(feature = "rp2040")] | 211 | #[cfg(feature = "rp2040")] |
| 201 | voltage_scale: None, // Use hardware default (1.10V) | 212 | voltage_scale: None, // Use hardware default (1.10V) |
| 202 | // gpin0: None, | 213 | #[cfg(feature = "rp2040")] |
| 203 | // gpin1: None, | 214 | voltage_stabilization_delay_us: None, |
| 215 | // gpin0: None, | ||
| 216 | // gpin1: None, | ||
| 204 | } | 217 | } |
| 205 | } | 218 | } |
| 206 | 219 | ||
| @@ -235,8 +248,16 @@ impl ClockConfig { | |||
| 235 | }; | 248 | }; |
| 236 | 249 | ||
| 237 | // Find suitable PLL parameters | 250 | // Find suitable PLL parameters |
| 238 | let pll_params = find_pll_params(crystal_hz, sys_freq_hz) | 251 | let pll_params = match find_pll_params(crystal_hz, sys_freq_hz) { |
| 239 | .expect("Could not find valid PLL parameters for the requested frequency"); | 252 | Some(params) => params, |
| 253 | None => { | ||
| 254 | // If we can't find valid parameters for the requested frequency, | ||
| 255 | // fall back to safe defaults (125 MHz for RP2040) | ||
| 256 | let safe_freq = 125_000_000; // Safe default frequency | ||
| 257 | find_pll_params(crystal_hz, safe_freq) | ||
| 258 | .expect("Could not find valid PLL parameters even for safe default frequency") | ||
| 259 | } | ||
| 260 | }; | ||
| 240 | 261 | ||
| 241 | Self { | 262 | Self { |
| 242 | rosc: Some(RoscConfig { | 263 | rosc: Some(RoscConfig { |
| @@ -284,6 +305,8 @@ impl ClockConfig { | |||
| 284 | phase: 0, | 305 | phase: 0, |
| 285 | }), | 306 | }), |
| 286 | voltage_scale: Some(voltage_scale), | 307 | voltage_scale: Some(voltage_scale), |
| 308 | #[cfg(feature = "rp2040")] | ||
| 309 | voltage_stabilization_delay_us: None, | ||
| 287 | // gpin0: None, | 310 | // gpin0: None, |
| 288 | // gpin1: None, | 311 | // gpin1: None, |
| 289 | } | 312 | } |
| @@ -326,11 +349,128 @@ impl ClockConfig { | |||
| 326 | }), | 349 | }), |
| 327 | #[cfg(feature = "rp2040")] | 350 | #[cfg(feature = "rp2040")] |
| 328 | voltage_scale: None, // Use hardware default (1.10V) | 351 | voltage_scale: None, // Use hardware default (1.10V) |
| 329 | // gpin0: None, | 352 | #[cfg(feature = "rp2040")] |
| 330 | // gpin1: None, | 353 | voltage_stabilization_delay_us: None, |
| 354 | // gpin0: None, | ||
| 355 | // gpin1: None, | ||
| 331 | } | 356 | } |
| 332 | } | 357 | } |
| 333 | 358 | ||
| 359 | /// Configure the system clock to a specific frequency in MHz. | ||
| 360 | /// | ||
| 361 | /// This is a more user-friendly way to configure the system clock, similar to | ||
| 362 | /// the Pico SDK's approach. It automatically handles voltage scaling based on the | ||
| 363 | /// requested frequency and uses the standard 12MHz crystal found on most RP2040 boards. | ||
| 364 | /// | ||
| 365 | /// # Arguments | ||
| 366 | /// | ||
| 367 | /// * `sys_freq_mhz` - The target system clock frequency in MHz | ||
| 368 | /// | ||
| 369 | /// # Safety Notes | ||
| 370 | /// | ||
| 371 | /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. | ||
| 372 | /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. | ||
| 373 | /// | ||
| 374 | /// # Example | ||
| 375 | /// | ||
| 376 | /// ``` | ||
| 377 | /// // Configure for standard 125MHz clock | ||
| 378 | /// let config = ClockConfig::with_speed_mhz(125); | ||
| 379 | /// | ||
| 380 | /// // Overclock to 200MHz (requires higher voltage) | ||
| 381 | /// let config = ClockConfig::with_speed_mhz(200); | ||
| 382 | /// ``` | ||
| 383 | #[cfg(feature = "rp2040")] | ||
| 384 | pub fn with_speed_mhz(sys_freq_mhz: u32) -> Self { | ||
| 385 | // For 125MHz, use exactly the same config as the default to avoid any differences | ||
| 386 | if sys_freq_mhz == 125 { | ||
| 387 | return Self::crystal(12_000_000); | ||
| 388 | } | ||
| 389 | |||
| 390 | // For other frequencies, provide appropriate voltage scaling and PLL configuration | ||
| 391 | // Standard crystal on Raspberry Pi Pico boards is 12MHz | ||
| 392 | const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; | ||
| 393 | |||
| 394 | let sys_freq_hz = sys_freq_mhz * 1_000_000; | ||
| 395 | let mut config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz); | ||
| 396 | |||
| 397 | // For frequencies above 200MHz, ensure we're using the highest voltage | ||
| 398 | if sys_freq_mhz > 200 { | ||
| 399 | config.voltage_scale = Some(VoltageScale::V1_20); | ||
| 400 | } | ||
| 401 | |||
| 402 | config | ||
| 403 | } | ||
| 404 | |||
| 405 | /// Configure the system clock to a specific frequency in Hz, using a custom crystal frequency. | ||
| 406 | /// | ||
| 407 | /// This more flexible version allows specifying both the crystal frequency and target | ||
| 408 | /// system frequency for boards with non-standard crystals. | ||
| 409 | /// | ||
| 410 | /// # Arguments | ||
| 411 | /// | ||
| 412 | /// * `crystal_hz` - The frequency of the external crystal in Hz | ||
| 413 | /// * `sys_freq_hz` - The target system clock frequency in Hz | ||
| 414 | /// | ||
| 415 | /// # Safety Notes | ||
| 416 | /// | ||
| 417 | /// * Frequencies > 133MHz require increased core voltage and are considered overclocking. | ||
| 418 | /// * Frequencies > 250MHz are extreme overclocking and may not be stable on all chips. | ||
| 419 | /// | ||
| 420 | /// # Example | ||
| 421 | /// | ||
| 422 | /// ``` | ||
| 423 | /// // Use a non-standard 16MHz crystal to achieve 250MHz | ||
| 424 | /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); | ||
| 425 | /// ``` | ||
| 426 | #[cfg(feature = "rp2040")] | ||
| 427 | pub fn with_custom_crystal(crystal_hz: u32, sys_freq_hz: u32) -> Self { | ||
| 428 | Self::crystal_freq(crystal_hz, sys_freq_hz) | ||
| 429 | } | ||
| 430 | |||
| 431 | #[cfg(feature = "rp2040")] | ||
| 432 | pub fn with_speed_mhz_test_voltage(sys_freq_mhz: u32, voltage: Option<VoltageScale>) -> Self { | ||
| 433 | // Create a config with the requested frequency | ||
| 434 | let sys_freq_hz = sys_freq_mhz * 1_000_000; | ||
| 435 | let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); | ||
| 436 | |||
| 437 | // Override the voltage setting | ||
| 438 | config.voltage_scale = voltage; | ||
| 439 | |||
| 440 | // For debugging | ||
| 441 | // println!("Debug: Setting freq to {}MHz with voltage {:?}", sys_freq_mhz, voltage); | ||
| 442 | |||
| 443 | config | ||
| 444 | } | ||
| 445 | |||
| 446 | /// Similar to `with_speed_mhz_test_voltage` but with an extended voltage stabilization delay. | ||
| 447 | /// | ||
| 448 | /// This function is useful for testing voltage stability issues where the default delay | ||
| 449 | /// may not be sufficient for the voltage regulator to fully stabilize. | ||
| 450 | /// | ||
| 451 | /// # Arguments | ||
| 452 | /// | ||
| 453 | /// * `sys_freq_mhz` - The target system clock frequency in MHz | ||
| 454 | /// * `voltage` - The desired voltage scale setting | ||
| 455 | /// * `stabilization_delay_us` - Voltage stabilization delay in microseconds (default: 500μs) | ||
| 456 | #[cfg(feature = "rp2040")] | ||
| 457 | pub fn with_speed_mhz_test_voltage_extended_delay( | ||
| 458 | sys_freq_mhz: u32, | ||
| 459 | voltage: Option<VoltageScale>, | ||
| 460 | stabilization_delay_us: Option<u32>, | ||
| 461 | ) -> Self { | ||
| 462 | // Create a config with the requested frequency | ||
| 463 | let sys_freq_hz = sys_freq_mhz * 1_000_000; | ||
| 464 | let mut config = Self::crystal_freq(12_000_000, sys_freq_hz); | ||
| 465 | |||
| 466 | // Override the voltage setting | ||
| 467 | config.voltage_scale = voltage; | ||
| 468 | |||
| 469 | // Add a custom voltage stabilization delay | ||
| 470 | config.voltage_stabilization_delay_us = stabilization_delay_us; | ||
| 471 | |||
| 472 | config | ||
| 473 | } | ||
| 334 | // pub fn bind_gpin<P: GpinPin>(&mut self, gpin: Gpin<'static, P>, hz: u32) { | 474 | // pub fn bind_gpin<P: GpinPin>(&mut self, gpin: Gpin<'static, P>, hz: u32) { |
| 335 | // match P::NR { | 475 | // match P::NR { |
| 336 | // 0 => self.gpin0 = Some((hz, gpin.into())), | 476 | // 0 => self.gpin0 = Some((hz, gpin.into())), |
| @@ -385,6 +525,7 @@ pub struct XoscConfig { | |||
| 385 | } | 525 | } |
| 386 | 526 | ||
| 387 | /// PLL configuration. | 527 | /// PLL configuration. |
| 528 | #[derive(Clone, Copy, Debug)] | ||
| 388 | pub struct PllConfig { | 529 | pub struct PllConfig { |
| 389 | /// Reference divisor. | 530 | /// Reference divisor. |
| 390 | pub refdiv: u8, | 531 | pub refdiv: u8, |
| @@ -542,67 +683,124 @@ pub struct RtcClkConfig { | |||
| 542 | /// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency | 683 | /// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency |
| 543 | /// based on the input frequency. | 684 | /// based on the input frequency. |
| 544 | /// | 685 | /// |
| 686 | /// Similar to the Pico SDK's parameter selection approach, prioritizing stability. | ||
| 545 | /// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL | 687 | /// See RP2040 Datasheet section 2.16.3. Reference Clock (ref) and 2.18.3. PLL |
| 546 | #[cfg(feature = "rp2040")] | 688 | #[cfg(feature = "rp2040")] |
| 547 | fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { | 689 | fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { |
| 690 | // Fixed reference divider for system PLL | ||
| 691 | const PLL_SYS_REFDIV: u8 = 1; | ||
| 692 | |||
| 548 | // Constraints from datasheet: | 693 | // Constraints from datasheet: |
| 549 | // REFDIV: 1..=63 | 694 | // REFDIV: 1..=63 |
| 550 | // FBDIV: 16..=320 | 695 | // FBDIV: 16..=320 |
| 551 | // POSTDIV1: 1..=7 | 696 | // POSTDIV1: 1..=7 |
| 552 | // POSTDIV2: 1..=7 (must be <= POSTDIV1) | 697 | // POSTDIV2: 1..=7 (must be <= POSTDIV1) |
| 553 | // VCO frequency (input_hz / refdiv * fbdiv): 400MHz ..= 1600MHz | 698 | // VCO frequency (input_hz / refdiv * fbdiv): 750MHz ..= 1800MHz |
| 554 | 699 | ||
| 555 | for refdiv in 1..=63 { | 700 | // Calculate reference frequency |
| 556 | let ref_clk = input_hz / refdiv; | 701 | let reference_freq = input_hz / PLL_SYS_REFDIV as u32; |
| 557 | // Reference clock must be >= 5MHz (implied by VCO min / FBDIV max) | 702 | |
| 558 | if ref_clk < 5_000_000 { | 703 | // Start from highest fbdiv for better stability (like SDK does) |
| 704 | for fbdiv in (16..=320).rev() { | ||
| 705 | let vco_freq = reference_freq * fbdiv; | ||
| 706 | |||
| 707 | // Check VCO frequency is within valid range | ||
| 708 | if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { | ||
| 559 | continue; | 709 | continue; |
| 560 | } | 710 | } |
| 561 | 711 | ||
| 562 | for fbdiv in (16..=320).rev() { | 712 | // Try all possible postdiv combinations starting from larger values |
| 563 | // Iterate high fbdiv first for better VCO stability | 713 | // (more conservative/stable approach) |
| 564 | let vco_freq = ref_clk * fbdiv; | 714 | for post_div1 in (1..=7).rev() { |
| 565 | if !(400_000_000..=1_600_000_000).contains(&vco_freq) { | 715 | for post_div2 in (1..=post_div1).rev() { |
| 566 | continue; | 716 | let out_freq = vco_freq / (post_div1 * post_div2) as u32; |
| 717 | |||
| 718 | // Check if we get the exact target frequency without remainder | ||
| 719 | if out_freq == target_hz && (vco_freq % (post_div1 * post_div2) as u32 == 0) { | ||
| 720 | return Some(PllConfig { | ||
| 721 | refdiv: PLL_SYS_REFDIV, | ||
| 722 | fbdiv: fbdiv as u16, | ||
| 723 | post_div1: post_div1 as u8, | ||
| 724 | post_div2: post_div2 as u8, | ||
| 725 | }); | ||
| 726 | } | ||
| 567 | } | 727 | } |
| 728 | } | ||
| 729 | } | ||
| 568 | 730 | ||
| 569 | // We want vco_freq / (post_div1 * post_div2) = target_hz | 731 | // If we couldn't find an exact match, find the closest match |
| 570 | // So, post_div1 * post_div2 = vco_freq / target_hz | 732 | let mut best_config = None; |
| 571 | let target_post_div_product = vco_freq as f64 / target_hz as f64; | 733 | let mut min_diff = u32::MAX; |
| 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 | 734 | ||
| 582 | // Check if the rounded product gives the target frequency | 735 | for fbdiv in (16..=320).rev() { |
| 583 | if vco_freq / target_post_div_product_int != target_hz { | 736 | let vco_freq = reference_freq * fbdiv; |
| 584 | continue; | ||
| 585 | } | ||
| 586 | 737 | ||
| 587 | for post_div1 in (1..=7).rev() { | 738 | if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { |
| 588 | // Iterate high post_div1 first | 739 | continue; |
| 589 | if target_post_div_product_int % post_div1 == 0 { | 740 | } |
| 590 | let post_div2 = target_post_div_product_int / post_div1; | 741 | |
| 591 | if (1..=7).contains(&post_div2) && post_div2 <= post_div1 { | 742 | for post_div1 in (1..=7).rev() { |
| 592 | // Found a valid combination | 743 | for post_div2 in (1..=post_div1).rev() { |
| 593 | return Some(PllConfig { | 744 | let out_freq = vco_freq / (post_div1 * post_div2) as u32; |
| 594 | refdiv: refdiv as u8, // Cast u32 to u8 (safe: 1..=63) | 745 | let diff = if out_freq > target_hz { |
| 595 | fbdiv: fbdiv as u16, // Cast u32 to u16 (safe: 16..=320) | 746 | out_freq - target_hz |
| 596 | post_div1: post_div1 as u8, | 747 | } else { |
| 597 | post_div2: post_div2 as u8, | 748 | target_hz - out_freq |
| 598 | }); | 749 | }; |
| 599 | } | 750 | |
| 751 | // If this is closer to the target, save it | ||
| 752 | if diff < min_diff { | ||
| 753 | min_diff = diff; | ||
| 754 | best_config = Some(PllConfig { | ||
| 755 | refdiv: PLL_SYS_REFDIV, | ||
| 756 | fbdiv: fbdiv as u16, | ||
| 757 | post_div1: post_div1 as u8, | ||
| 758 | post_div2: post_div2 as u8, | ||
| 759 | }); | ||
| 600 | } | 760 | } |
| 601 | } | 761 | } |
| 602 | } | 762 | } |
| 603 | } | 763 | } |
| 604 | 764 | ||
| 605 | None // No valid parameters found | 765 | // Return the closest match if we found one |
| 766 | best_config | ||
| 767 | } | ||
| 768 | |||
| 769 | #[cfg(feature = "rp2040")] | ||
| 770 | pub fn compare_pll_params() { | ||
| 771 | // Parameters from default configuration | ||
| 772 | let default_params = PllConfig { | ||
| 773 | refdiv: 1, | ||
| 774 | fbdiv: 125, | ||
| 775 | post_div1: 6, | ||
| 776 | post_div2: 2, | ||
| 777 | }; | ||
| 778 | |||
| 779 | // Calculate parameters using our find_pll_params function | ||
| 780 | let crystal_hz = 12_000_000; | ||
| 781 | let target_hz = 125_000_000; | ||
| 782 | let calculated_params = find_pll_params(crystal_hz, target_hz).expect("Failed to find PLL parameters"); | ||
| 783 | |||
| 784 | // Check if they're identical | ||
| 785 | let params_match = default_params.refdiv == calculated_params.refdiv | ||
| 786 | && default_params.fbdiv == calculated_params.fbdiv | ||
| 787 | && default_params.post_div1 == calculated_params.post_div1 | ||
| 788 | && default_params.post_div2 == calculated_params.post_div2; | ||
| 789 | |||
| 790 | // Here we'd normally print results, but without a console we'll just | ||
| 791 | // use this for debugging in our IDE | ||
| 792 | let _default_output_freq = crystal_hz / default_params.refdiv as u32 * default_params.fbdiv as u32 | ||
| 793 | / (default_params.post_div1 * default_params.post_div2) as u32; | ||
| 794 | |||
| 795 | let _calculated_output_freq = crystal_hz / calculated_params.refdiv as u32 * calculated_params.fbdiv as u32 | ||
| 796 | / (calculated_params.post_div1 * calculated_params.post_div2) as u32; | ||
| 797 | |||
| 798 | // Parameters: default vs calculated | ||
| 799 | // refdiv: 1 vs {calculated_params.refdiv} | ||
| 800 | // fbdiv: 125 vs {calculated_params.fbdiv} | ||
| 801 | // post_div1: 6 vs {calculated_params.post_div1} | ||
| 802 | // post_div2: 2 vs {calculated_params.post_div2} | ||
| 803 | // params_match: {params_match} | ||
| 606 | } | 804 | } |
| 607 | 805 | ||
| 608 | /// safety: must be called exactly once at bootup | 806 | /// safety: must be called exactly once at bootup |
| @@ -640,12 +838,42 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 640 | #[cfg(feature = "_rp235x")] | 838 | #[cfg(feature = "_rp235x")] |
| 641 | while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} | 839 | while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} |
| 642 | 840 | ||
| 643 | // Reset the PLLs | 841 | // Set Core Voltage (RP2040 only) BEFORE doing anything with PLLs |
| 644 | let mut peris = reset::Peripherals(0); | 842 | // This is critical for overclocking - must be done before PLL setup |
| 645 | peris.set_pll_sys(true); | 843 | #[cfg(feature = "rp2040")] |
| 646 | peris.set_pll_usb(true); | 844 | if let Some(voltage) = config.voltage_scale { |
| 647 | reset::reset(peris); | 845 | let vreg = pac::VREG_AND_CHIP_RESET; |
| 648 | reset::unreset_wait(peris); | 846 | let current_vsel = vreg.vreg().read().vsel(); |
| 847 | let target_vsel = voltage as u8; | ||
| 848 | |||
| 849 | if target_vsel != current_vsel { | ||
| 850 | // IMPORTANT: Use modify() instead of write() to preserve the HIZ and EN bits | ||
| 851 | // This is critical - otherwise we might disable the regulator when changing voltage | ||
| 852 | vreg.vreg().modify(|w| w.set_vsel(target_vsel)); | ||
| 853 | |||
| 854 | // For higher voltage settings (overclocking), we need a longer stabilization time | ||
| 855 | // Default to 1000 µs (1ms) like the SDK, but allow user override | ||
| 856 | let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { | ||
| 857 | match voltage { | ||
| 858 | VoltageScale::V1_15 => 1000, // 1ms for 1.15V (matches SDK default) | ||
| 859 | VoltageScale::V1_20 | VoltageScale::V1_25 | VoltageScale::V1_30 => 2000, // 2ms for higher voltages | ||
| 860 | _ => 500, // 500 µs for standard voltages | ||
| 861 | } | ||
| 862 | }); | ||
| 863 | |||
| 864 | // We need a clock that's guaranteed to be running at this point | ||
| 865 | // Use ROSC which should be configured by now | ||
| 866 | let rosc_freq_rough = 6_000_000; // Rough ROSC frequency estimate | ||
| 867 | let cycles_per_us = rosc_freq_rough / 1_000_000; | ||
| 868 | let delay_cycles = settling_time_us * cycles_per_us; | ||
| 869 | |||
| 870 | // Wait for voltage to stabilize | ||
| 871 | cortex_m::asm::delay(delay_cycles); | ||
| 872 | |||
| 873 | // Only NOW set the BOD level after voltage has stabilized | ||
| 874 | vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); | ||
| 875 | } | ||
| 876 | } | ||
| 649 | 877 | ||
| 650 | // Configure ROSC first if present | 878 | // Configure ROSC first if present |
| 651 | let rosc_freq = match config.rosc { | 879 | let rosc_freq = match config.rosc { |
| @@ -654,59 +882,91 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 654 | }; | 882 | }; |
| 655 | CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); | 883 | CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); |
| 656 | 884 | ||
| 657 | // Configure XOSC and PLLs if present | 885 | // Configure XOSC - we'll need this for our temporary stable clock |
| 658 | let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { | 886 | let xosc_freq = match &config.xosc { |
| 659 | Some(config) => { | 887 | Some(config) => { |
| 660 | // start XOSC | ||
| 661 | // datasheet mentions support for clock inputs into XIN, but doesn't go into | ||
| 662 | // how this is achieved. pico-sdk doesn't support this at all. | ||
| 663 | start_xosc(config.hz, config.delay_multiplier); | 888 | start_xosc(config.hz, config.delay_multiplier); |
| 664 | 889 | config.hz | |
| 665 | let pll_sys_freq = match config.sys_pll { | ||
| 666 | Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), | ||
| 667 | None => 0, | ||
| 668 | }; | ||
| 669 | let pll_usb_freq = match config.usb_pll { | ||
| 670 | Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), | ||
| 671 | None => 0, | ||
| 672 | }; | ||
| 673 | |||
| 674 | (config.hz, pll_sys_freq, pll_usb_freq) | ||
| 675 | } | 890 | } |
| 676 | None => (0, 0, 0), | 891 | None => 0, |
| 677 | }; | 892 | }; |
| 678 | CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); | 893 | CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); |
| 679 | CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); | 894 | |
| 680 | CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); | 895 | // SETUP TEMPORARY STABLE CLOCKS FIRST |
| 681 | 896 | // Configure USB PLL for our stable temporary clock | |
| 682 | // Configure REF clock source and divider | 897 | // This follows the SDK's approach of using USB PLL as a stable intermediate clock |
| 683 | let (ref_src, ref_aux, clk_ref_freq) = { | 898 | let usb_pll_freq = match &config.xosc { |
| 684 | use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; | 899 | Some(config) => match &config.usb_pll { |
| 685 | let div = config.ref_clk.div as u32; | 900 | Some(usb_pll_config) => { |
| 686 | assert!(div >= 1 && div <= 4); | 901 | // Reset USB PLL |
| 687 | match config.ref_clk.src { | 902 | let mut peris = reset::Peripherals(0); |
| 688 | RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), | 903 | peris.set_pll_usb(true); |
| 689 | RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), | 904 | reset::reset(peris); |
| 690 | RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), | 905 | reset::unreset_wait(peris); |
| 691 | // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), | 906 | |
| 692 | // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), | 907 | // Configure the USB PLL - this should give us 48MHz |
| 693 | } | 908 | let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *usb_pll_config); |
| 909 | CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed); | ||
| 910 | usb_pll_freq | ||
| 911 | } | ||
| 912 | None => 0, | ||
| 913 | }, | ||
| 914 | None => 0, | ||
| 694 | }; | 915 | }; |
| 695 | assert!(clk_ref_freq != 0); | 916 | |
| 696 | CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); | 917 | // Configure REF clock to use XOSC |
| 697 | c.clk_ref_ctrl().write(|w| { | 918 | c.clk_ref_ctrl().write(|w| { |
| 698 | w.set_src(ref_src); | 919 | w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); |
| 699 | w.set_auxsrc(ref_aux); | ||
| 700 | }); | 920 | }); |
| 701 | #[cfg(feature = "rp2040")] | 921 | #[cfg(feature = "rp2040")] |
| 702 | while c.clk_ref_selected().read() != (1 << ref_src as u32) {} | 922 | while c.clk_ref_selected().read() != (1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} |
| 703 | #[cfg(feature = "_rp235x")] | 923 | #[cfg(feature = "_rp235x")] |
| 704 | while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} | 924 | while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {} |
| 705 | c.clk_ref_div().write(|w| { | 925 | |
| 706 | w.set_int(config.ref_clk.div); | 926 | // First switch the system clock to a stable source (USB PLL at 48MHz) |
| 927 | // This follows the Pico SDK's approach to ensure stability during reconfiguration | ||
| 928 | c.clk_sys_ctrl().write(|w| { | ||
| 929 | w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_USB); | ||
| 930 | w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); | ||
| 707 | }); | 931 | }); |
| 708 | 932 | ||
| 933 | #[cfg(feature = "rp2040")] | ||
| 934 | while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) {} | ||
| 935 | #[cfg(feature = "_rp235x")] | ||
| 936 | while c.clk_sys_selected().read() | ||
| 937 | != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) | ||
| 938 | {} | ||
| 939 | |||
| 940 | // Short delay after switching to USB PLL to ensure stability | ||
| 941 | cortex_m::asm::delay(100); | ||
| 942 | |||
| 943 | // NOW CONFIGURE THE SYSTEM PLL (safely, since we're running from the USB PLL) | ||
| 944 | let pll_sys_freq = match &config.xosc { | ||
| 945 | Some(config) => match &config.sys_pll { | ||
| 946 | Some(sys_pll_config) => { | ||
| 947 | // Reset SYS PLL | ||
| 948 | let mut peris = reset::Peripherals(0); | ||
| 949 | peris.set_pll_sys(true); | ||
| 950 | reset::reset(peris); | ||
| 951 | reset::unreset_wait(peris); | ||
| 952 | |||
| 953 | // Configure the SYS PLL | ||
| 954 | let pll_sys_freq = configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config); | ||
| 955 | |||
| 956 | // Ensure PLL is locked and stable | ||
| 957 | cortex_m::asm::delay(100); | ||
| 958 | |||
| 959 | CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); | ||
| 960 | pll_sys_freq | ||
| 961 | } | ||
| 962 | None => 0, | ||
| 963 | }, | ||
| 964 | None => 0, | ||
| 965 | }; | ||
| 966 | |||
| 709 | // Configure tick generation using REF clock | 967 | // Configure tick generation using REF clock |
| 968 | let clk_ref_freq = xosc_freq; // REF clock is now XOSC | ||
| 969 | CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); | ||
| 710 | #[cfg(feature = "rp2040")] | 970 | #[cfg(feature = "rp2040")] |
| 711 | pac::WATCHDOG.tick().write(|w| { | 971 | pac::WATCHDOG.tick().write(|w| { |
| 712 | w.set_cycles((clk_ref_freq / 1_000_000) as u16); | 972 | w.set_cycles((clk_ref_freq / 1_000_000) as u16); |
| @@ -724,34 +984,14 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 724 | pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); | 984 | pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); |
| 725 | } | 985 | } |
| 726 | 986 | ||
| 727 | // Set Core Voltage (RP2040 only) BEFORE switching SYS clock to high speed PLL | 987 | // NOW SWITCH THE SYSTEM CLOCK TO THE CONFIGURED SOURCE |
| 728 | #[cfg(feature = "rp2040")] | 988 | // The SYS PLL is now stable and we can safely switch to it |
| 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 | ||
| 749 | let (sys_src, sys_aux, clk_sys_freq) = { | 989 | let (sys_src, sys_aux, clk_sys_freq) = { |
| 750 | use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; | 990 | use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; |
| 751 | let (src, aux, freq) = match config.sys_clk.src { | 991 | let (src, aux, freq) = match config.sys_clk.src { |
| 752 | SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), | 992 | SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), |
| 753 | SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), | 993 | SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), |
| 754 | SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), | 994 | SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, usb_pll_freq), |
| 755 | SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), | 995 | SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), |
| 756 | SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), | 996 | SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), |
| 757 | // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), | 997 | // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), |
| @@ -762,28 +1002,48 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 762 | }; | 1002 | }; |
| 763 | assert!(clk_sys_freq != 0); | 1003 | assert!(clk_sys_freq != 0); |
| 764 | CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); | 1004 | CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); |
| 765 | if sys_src != ClkSysCtrlSrc::CLK_REF { | 1005 | |
| 766 | c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); | 1006 | // Set the divider before changing the source if it's increasing |
| 767 | #[cfg(feature = "rp2040")] | 1007 | if config.sys_clk.div_int > 1 || config.sys_clk.div_frac > 0 { |
| 768 | while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} | 1008 | c.clk_sys_div().write(|w| { |
| 769 | #[cfg(feature = "_rp235x")] | 1009 | w.set_int(config.sys_clk.div_int); |
| 770 | while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} | 1010 | w.set_frac(config.sys_clk.div_frac); |
| 1011 | }); | ||
| 1012 | } | ||
| 1013 | |||
| 1014 | // Configure aux source first if needed | ||
| 1015 | if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { | ||
| 1016 | c.clk_sys_ctrl().modify(|w| { | ||
| 1017 | w.set_auxsrc(sys_aux); | ||
| 1018 | }); | ||
| 771 | } | 1019 | } |
| 1020 | |||
| 1021 | // Now set the source | ||
| 772 | c.clk_sys_ctrl().write(|w| { | 1022 | c.clk_sys_ctrl().write(|w| { |
| 773 | w.set_auxsrc(sys_aux); | 1023 | if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX { |
| 1024 | w.set_auxsrc(sys_aux); | ||
| 1025 | } | ||
| 774 | w.set_src(sys_src); | 1026 | w.set_src(sys_src); |
| 775 | }); | 1027 | }); |
| 776 | 1028 | ||
| 1029 | // Wait for the clock to be selected | ||
| 777 | #[cfg(feature = "rp2040")] | 1030 | #[cfg(feature = "rp2040")] |
| 778 | while c.clk_sys_selected().read() != (1 << sys_src as u32) {} | 1031 | while c.clk_sys_selected().read() != (1 << sys_src as u32) {} |
| 779 | #[cfg(feature = "_rp235x")] | 1032 | #[cfg(feature = "_rp235x")] |
| 780 | while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} | 1033 | while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} |
| 781 | 1034 | ||
| 782 | c.clk_sys_div().write(|w| { | 1035 | // Short delay after final clock switch to ensure stability |
| 783 | w.set_int(config.sys_clk.div_int); | 1036 | cortex_m::asm::delay(100); |
| 784 | w.set_frac(config.sys_clk.div_frac); | ||
| 785 | }); | ||
| 786 | 1037 | ||
| 1038 | // Set the divider after changing the source if it's decreasing | ||
| 1039 | if config.sys_clk.div_int == 1 && config.sys_clk.div_frac == 0 { | ||
| 1040 | c.clk_sys_div().write(|w| { | ||
| 1041 | w.set_int(config.sys_clk.div_int); | ||
| 1042 | w.set_frac(config.sys_clk.div_frac); | ||
| 1043 | }); | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | // CONFIGURE PERIPHERAL CLOCK | ||
| 787 | let mut peris = reset::ALL_PERIPHERALS; | 1047 | let mut peris = reset::ALL_PERIPHERALS; |
| 788 | 1048 | ||
| 789 | if let Some(src) = config.peri_clk_src { | 1049 | if let Some(src) = config.peri_clk_src { |
| @@ -794,7 +1054,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 794 | let peri_freq = match src { | 1054 | let peri_freq = match src { |
| 795 | PeriClkSrc::Sys => clk_sys_freq, | 1055 | PeriClkSrc::Sys => clk_sys_freq, |
| 796 | PeriClkSrc::PllSys => pll_sys_freq, | 1056 | PeriClkSrc::PllSys => pll_sys_freq, |
| 797 | PeriClkSrc::PllUsb => pll_usb_freq, | 1057 | PeriClkSrc::PllUsb => usb_pll_freq, |
| 798 | PeriClkSrc::Rosc => rosc_freq, | 1058 | PeriClkSrc::Rosc => rosc_freq, |
| 799 | PeriClkSrc::Xosc => xosc_freq, | 1059 | PeriClkSrc::Xosc => xosc_freq, |
| 800 | // PeriClkSrc::Gpin0 => gpin0_freq, | 1060 | // PeriClkSrc::Gpin0 => gpin0_freq, |
| @@ -810,6 +1070,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 810 | CLOCKS.peri.store(0, Ordering::Relaxed); | 1070 | CLOCKS.peri.store(0, Ordering::Relaxed); |
| 811 | } | 1071 | } |
| 812 | 1072 | ||
| 1073 | // CONFIGURE USB CLOCK | ||
| 813 | if let Some(conf) = config.usb_clk { | 1074 | if let Some(conf) = config.usb_clk { |
| 814 | c.clk_usb_div().write(|w| w.set_int(conf.div)); | 1075 | c.clk_usb_div().write(|w| w.set_int(conf.div)); |
| 815 | c.clk_usb_ctrl().write(|w| { | 1076 | c.clk_usb_ctrl().write(|w| { |
| @@ -818,7 +1079,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 818 | w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); | 1079 | w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); |
| 819 | }); | 1080 | }); |
| 820 | let usb_freq = match conf.src { | 1081 | let usb_freq = match conf.src { |
| 821 | UsbClkSrc::PllUsb => pll_usb_freq, | 1082 | UsbClkSrc::PllUsb => usb_pll_freq, |
| 822 | UsbClkSrc::PllSys => pll_sys_freq, | 1083 | UsbClkSrc::PllSys => pll_sys_freq, |
| 823 | UsbClkSrc::Rosc => rosc_freq, | 1084 | UsbClkSrc::Rosc => rosc_freq, |
| 824 | UsbClkSrc::Xosc => xosc_freq, | 1085 | UsbClkSrc::Xosc => xosc_freq, |
| @@ -833,6 +1094,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 833 | CLOCKS.usb.store(0, Ordering::Relaxed); | 1094 | CLOCKS.usb.store(0, Ordering::Relaxed); |
| 834 | } | 1095 | } |
| 835 | 1096 | ||
| 1097 | // CONFIGURE ADC CLOCK | ||
| 836 | if let Some(conf) = config.adc_clk { | 1098 | if let Some(conf) = config.adc_clk { |
| 837 | c.clk_adc_div().write(|w| w.set_int(conf.div)); | 1099 | c.clk_adc_div().write(|w| w.set_int(conf.div)); |
| 838 | c.clk_adc_ctrl().write(|w| { | 1100 | c.clk_adc_ctrl().write(|w| { |
| @@ -841,7 +1103,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 841 | w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); | 1103 | w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); |
| 842 | }); | 1104 | }); |
| 843 | let adc_in_freq = match conf.src { | 1105 | let adc_in_freq = match conf.src { |
| 844 | AdcClkSrc::PllUsb => pll_usb_freq, | 1106 | AdcClkSrc::PllUsb => usb_pll_freq, |
| 845 | AdcClkSrc::PllSys => pll_sys_freq, | 1107 | AdcClkSrc::PllSys => pll_sys_freq, |
| 846 | AdcClkSrc::Rosc => rosc_freq, | 1108 | AdcClkSrc::Rosc => rosc_freq, |
| 847 | AdcClkSrc::Xosc => xosc_freq, | 1109 | AdcClkSrc::Xosc => xosc_freq, |
| @@ -856,7 +1118,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 856 | CLOCKS.adc.store(0, Ordering::Relaxed); | 1118 | CLOCKS.adc.store(0, Ordering::Relaxed); |
| 857 | } | 1119 | } |
| 858 | 1120 | ||
| 859 | // rp2040 specific clocks | 1121 | // CONFIGURE RTC CLOCK |
| 860 | #[cfg(feature = "rp2040")] | 1122 | #[cfg(feature = "rp2040")] |
| 861 | if let Some(conf) = config.rtc_clk { | 1123 | if let Some(conf) = config.rtc_clk { |
| 862 | c.clk_rtc_div().write(|w| { | 1124 | c.clk_rtc_div().write(|w| { |
| @@ -869,7 +1131,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 869 | w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); | 1131 | w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); |
| 870 | }); | 1132 | }); |
| 871 | let rtc_in_freq = match conf.src { | 1133 | let rtc_in_freq = match conf.src { |
| 872 | RtcClkSrc::PllUsb => pll_usb_freq, | 1134 | RtcClkSrc::PllUsb => usb_pll_freq, |
| 873 | RtcClkSrc::PllSys => pll_sys_freq, | 1135 | RtcClkSrc::PllSys => pll_sys_freq, |
| 874 | RtcClkSrc::Rosc => rosc_freq, | 1136 | RtcClkSrc::Rosc => rosc_freq, |
| 875 | RtcClkSrc::Xosc => xosc_freq, | 1137 | RtcClkSrc::Xosc => xosc_freq, |
| @@ -999,43 +1261,101 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { | |||
| 999 | 1261 | ||
| 1000 | #[inline(always)] | 1262 | #[inline(always)] |
| 1001 | fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { | 1263 | fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { |
| 1264 | // Calculate reference frequency | ||
| 1002 | let ref_freq = input_freq / config.refdiv as u32; | 1265 | let ref_freq = input_freq / config.refdiv as u32; |
| 1003 | assert!(config.fbdiv >= 16 && config.fbdiv <= 320); | 1266 | |
| 1004 | assert!(config.post_div1 >= 1 && config.post_div1 <= 7); | 1267 | // Validate PLL parameters |
| 1005 | assert!(config.post_div2 >= 1 && config.post_div2 <= 7); | 1268 | assert!( |
| 1006 | assert!(config.refdiv >= 1 && config.refdiv <= 63); | 1269 | config.fbdiv >= 16 && config.fbdiv <= 320, |
| 1007 | assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); | 1270 | "fbdiv must be between 16 and 320" |
| 1271 | ); | ||
| 1272 | assert!( | ||
| 1273 | config.post_div1 >= 1 && config.post_div1 <= 7, | ||
| 1274 | "post_div1 must be between 1 and 7" | ||
| 1275 | ); | ||
| 1276 | assert!( | ||
| 1277 | config.post_div2 >= 1 && config.post_div2 <= 7, | ||
| 1278 | "post_div2 must be between 1 and 7" | ||
| 1279 | ); | ||
| 1280 | assert!(config.post_div2 <= config.post_div1, "post_div2 must be <= post_div1"); | ||
| 1281 | assert!( | ||
| 1282 | config.refdiv >= 1 && config.refdiv <= 63, | ||
| 1283 | "refdiv must be between 1 and 63" | ||
| 1284 | ); | ||
| 1285 | assert!( | ||
| 1286 | ref_freq >= 5_000_000 && ref_freq <= 800_000_000, | ||
| 1287 | "ref_freq must be between 5MHz and 800MHz" | ||
| 1288 | ); | ||
| 1289 | |||
| 1290 | // Calculate VCO frequency | ||
| 1008 | let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); | 1291 | let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); |
| 1009 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | 1292 | assert!( |
| 1293 | vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000, | ||
| 1294 | "VCO frequency must be between 750MHz and 1800MHz" | ||
| 1295 | ); | ||
| 1296 | |||
| 1297 | // We follow the SDK's approach to PLL configuration which is: | ||
| 1298 | // 1. Power down PLL | ||
| 1299 | // 2. Configure the reference divider | ||
| 1300 | // 3. Configure the feedback divider | ||
| 1301 | // 4. Power up PLL and VCO | ||
| 1302 | // 5. Wait for PLL to lock | ||
| 1303 | // 6. Configure post-dividers | ||
| 1304 | // 7. Enable post-divider output | ||
| 1305 | |||
| 1306 | // 1. Power down PLL before configuration | ||
| 1307 | p.pwr().write(|w| { | ||
| 1308 | w.set_pd(true); // Power down the PLL | ||
| 1309 | w.set_vcopd(true); // Power down the VCO | ||
| 1310 | w.set_postdivpd(true); // Power down the post divider | ||
| 1311 | w.set_dsmpd(true); // Disable fractional mode | ||
| 1312 | *w | ||
| 1313 | }); | ||
| 1314 | |||
| 1315 | // Short delay after powering down | ||
| 1316 | cortex_m::asm::delay(10); | ||
| 1010 | 1317 | ||
| 1011 | // Load VCO-related dividers before starting VCO | 1318 | // 2. Configure reference divider first |
| 1012 | p.cs().write(|w| w.set_refdiv(config.refdiv as _)); | 1319 | p.cs().write(|w| w.set_refdiv(config.refdiv as _)); |
| 1320 | |||
| 1321 | // 3. Configure feedback divider | ||
| 1013 | p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); | 1322 | p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); |
| 1014 | 1323 | ||
| 1015 | // Turn on PLL | 1324 | // 4. Power up PLL and VCO, but keep post divider powered down during initial lock |
| 1016 | let pwr = p.pwr().write(|w| { | 1325 | p.pwr().write(|w| { |
| 1017 | w.set_dsmpd(true); // "nothing is achieved by setting this low" | 1326 | w.set_pd(false); // Power up the PLL |
| 1018 | w.set_pd(false); | 1327 | w.set_vcopd(false); // Power up the VCO |
| 1019 | w.set_vcopd(false); | 1328 | w.set_postdivpd(true); // Keep post divider powered down during initial lock |
| 1020 | w.set_postdivpd(true); | 1329 | w.set_dsmpd(true); // Disable fractional mode (simpler configuration) |
| 1021 | *w | 1330 | *w |
| 1022 | }); | 1331 | }); |
| 1023 | 1332 | ||
| 1024 | // Wait for PLL to lock | 1333 | // 5. Wait for PLL to lock with a timeout |
| 1025 | while !p.cs().read().lock() {} | 1334 | let mut timeout = 1_000_000; // Reasonable timeout value |
| 1335 | while !p.cs().read().lock() { | ||
| 1336 | timeout -= 1; | ||
| 1337 | if timeout == 0 { | ||
| 1338 | // PLL failed to lock, return 0 to indicate failure | ||
| 1339 | return 0; | ||
| 1340 | } | ||
| 1341 | } | ||
| 1026 | 1342 | ||
| 1027 | // Set post-dividers | 1343 | // 6. Configure post dividers after PLL is locked |
| 1028 | p.prim().write(|w| { | 1344 | p.prim().write(|w| { |
| 1029 | w.set_postdiv1(config.post_div1); | 1345 | w.set_postdiv1(config.post_div1); |
| 1030 | w.set_postdiv2(config.post_div2); | 1346 | w.set_postdiv2(config.post_div2); |
| 1031 | }); | 1347 | }); |
| 1032 | 1348 | ||
| 1033 | // Turn on post divider | 1349 | // 7. Enable the post divider output |
| 1034 | p.pwr().write(|w| { | 1350 | p.pwr().modify(|w| { |
| 1035 | *w = pwr; | 1351 | w.set_postdivpd(false); // Power up post divider |
| 1036 | w.set_postdivpd(false); | 1352 | *w |
| 1037 | }); | 1353 | }); |
| 1038 | 1354 | ||
| 1355 | // Final delay to ensure everything is stable | ||
| 1356 | cortex_m::asm::delay(100); | ||
| 1357 | |||
| 1358 | // Calculate and return actual output frequency | ||
| 1039 | vco_freq / ((config.post_div1 * config.post_div2) as u32) | 1359 | vco_freq / ((config.post_div1 * config.post_div2) as u32) |
| 1040 | } | 1360 | } |
| 1041 | 1361 | ||
