aboutsummaryrefslogtreecommitdiff
path: root/embassy-rp
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2025-05-09 19:34:43 +0200
committerGitHub <[email protected]>2025-05-09 19:34:43 +0200
commit11364077a7bb6d14bd37567d17ddb21249409ec7 (patch)
treec1d3fb45984b5ae17a684a32f6d45aff63b5592d /embassy-rp
parent2a27aa828cdc310b17c379183bdfaba6585ad933 (diff)
parent4621c8aa7a1ee1b55f2f0bf80fc48eddf76af320 (diff)
Merge pull request #4150 from 1-rafael-1/rp2040-overclocking
RP: rp2040 overclocking
Diffstat (limited to 'embassy-rp')
-rw-r--r--embassy-rp/src/clocks.rs821
-rw-r--r--embassy-rp/src/pio_programs/clock_divider.rs25
-rw-r--r--embassy-rp/src/pio_programs/hd44780.rs11
-rw-r--r--embassy-rp/src/pio_programs/mod.rs1
-rw-r--r--embassy-rp/src/pio_programs/rotary_encoder.rs8
-rw-r--r--embassy-rp/src/pio_programs/stepper.rs17
6 files changed, 839 insertions, 44 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 67aa5e540..6694aab66 100644
--- a/embassy-rp/src/clocks.rs
+++ b/embassy-rp/src/clocks.rs
@@ -1,4 +1,67 @@
1//! Clock configuration for the RP2040 1//! # Clock configuration for the RP2040 and RP235x microcontrollers.
2//!
3//! # Clock Configuration API
4//!
5//! This module provides both high-level convenience functions and low-level manual
6//! configuration options for the RP2040 clock system.
7//!
8//! ## High-Level Convenience Functions
9//!
10//! For most users, these functions provide an easy way to configure clocks:
11//!
12//! - `ClockConfig::system_freq(125_000_000)` - Set system clock to a specific frequency with automatic voltage scaling
13//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock
14//!
15//! ## Manual Configuration
16//!
17//! For advanced users who need precise control:
18//!
19//! ```rust,ignore
20//! // Start with default configuration and customize it
21//! let mut config = ClockConfig::default();
22//!
23//! // Set custom PLL parameters
24//! config.xosc = Some(XoscConfig {
25//! hz: 12_000_000,
26//! sys_pll: Some(PllConfig {
27//! refdiv: 1,
28//! fbdiv: 200,
29//! post_div1: 6,
30//! post_div2: 2,
31//! }),
32//! // ... other fields
33//! });
34//!
35//! // Set voltage for overclocking
36//! config.core_voltage = CoreVoltage::V1_15;
37//! ```
38//!
39//! ## Examples
40//!
41//! ### Standard 125MHz configuration
42//! ```rust,ignore
43//! let config = ClockConfig::crystal(12_000_000);
44//! ```
45//!
46//! Or using the default configuration:
47//! ```rust,ignore
48//! let config = ClockConfig::default();
49//! ```
50//!
51//! ### Overclock to 200MHz
52//! ```rust,ignore
53//! let config = ClockConfig::system_freq(200_000_000);
54//! ```
55//!
56//! ### Manual configuration for advanced scenarios
57//! ```rust,ignore
58//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, CoreVoltage};
59//!
60//! // Start with defaults and customize
61//! let mut config = ClockConfig::default();
62//! config.core_voltage = CoreVoltage::V1_15;
63//! // Set other parameters as needed...
64//! ```
2 65
3#[cfg(feature = "rp2040")] 66#[cfg(feature = "rp2040")]
4use core::arch::asm; 67use core::arch::asm;
@@ -18,6 +81,7 @@ use crate::{pac, reset, Peri};
18// gpin is not usually safe to use during the boot init() call, so it won't 81// gpin is not usually safe to use during the boot init() call, so it won't
19// be very useful until we have runtime clock reconfiguration. once this 82// be very useful until we have runtime clock reconfiguration. once this
20// happens we can resurrect the commented-out gpin bits. 83// happens we can resurrect the commented-out gpin bits.
84
21struct Clocks { 85struct Clocks {
22 xosc: AtomicU32, 86 xosc: AtomicU32,
23 sys: AtomicU32, 87 sys: AtomicU32,
@@ -26,6 +90,7 @@ struct Clocks {
26 pll_usb: AtomicU32, 90 pll_usb: AtomicU32,
27 usb: AtomicU32, 91 usb: AtomicU32,
28 adc: AtomicU32, 92 adc: AtomicU32,
93 // See above re gpin handling being commented out
29 // gpin0: AtomicU32, 94 // gpin0: AtomicU32,
30 // gpin1: AtomicU32, 95 // gpin1: AtomicU32,
31 rosc: AtomicU32, 96 rosc: AtomicU32,
@@ -42,6 +107,7 @@ static CLOCKS: Clocks = Clocks {
42 pll_usb: AtomicU32::new(0), 107 pll_usb: AtomicU32::new(0),
43 usb: AtomicU32::new(0), 108 usb: AtomicU32::new(0),
44 adc: AtomicU32::new(0), 109 adc: AtomicU32::new(0),
110 // See above re gpin handling being commented out
45 // gpin0: AtomicU32::new(0), 111 // gpin0: AtomicU32::new(0),
46 // gpin1: AtomicU32::new(0), 112 // gpin1: AtomicU32::new(0),
47 rosc: AtomicU32::new(0), 113 rosc: AtomicU32::new(0),
@@ -65,10 +131,64 @@ pub enum PeriClkSrc {
65 Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, 131 Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _,
66 /// XOSC. 132 /// XOSC.
67 Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, 133 Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _,
134 // See above re gpin handling being commented out
68 // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , 135 // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
69 // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , 136 // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
70} 137}
71 138
139/// Core voltage regulator settings for RP2040.
140///
141/// The RP2040 voltage regulator can be configured for different output voltages.
142/// Higher voltages allow for higher clock frequencies but increase power consumption and heat.
143#[cfg(feature = "rp2040")]
144#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145#[repr(u8)]
146pub enum CoreVoltage {
147 /// 0.80V - Suitable for lower frequencies
148 V0_80 = 0b0000,
149 /// 0.85V
150 V0_85 = 0b0110,
151 /// 0.90V
152 V0_90 = 0b0111,
153 /// 0.95V
154 V0_95 = 0b1000,
155 /// 1.00V
156 V1_00 = 0b1001,
157 /// 1.05V
158 V1_05 = 0b1010,
159 /// 1.10V - Default voltage level
160 V1_10 = 0b1011,
161 /// 1.15V - Required for overclocking to 133-200MHz
162 V1_15 = 0b1100,
163 /// 1.20V
164 V1_20 = 0b1101,
165 /// 1.25V
166 V1_25 = 0b1110,
167 /// 1.30V
168 V1_30 = 0b1111,
169}
170
171#[cfg(feature = "rp2040")]
172impl CoreVoltage {
173 /// Get the recommended Brown-Out Detection (BOD) setting for this voltage.
174 /// Sets the BOD threshold to approximately 80% of the core voltage.
175 fn recommended_bod(self) -> u8 {
176 match self {
177 CoreVoltage::V0_80 => 0b0100, // 0.645V (~81% of 0.80V)
178 CoreVoltage::V0_85 => 0b0101, // 0.688V (~81% of 0.85V)
179 CoreVoltage::V0_90 => 0b0110, // 0.731V (~81% of 0.90V)
180 CoreVoltage::V0_95 => 0b0111, // 0.774V (~81% of 0.95V)
181 CoreVoltage::V1_00 => 0b1000, // 0.817V (~82% of 1.00V)
182 CoreVoltage::V1_05 => 0b1000, // 0.817V (~78% of 1.05V)
183 CoreVoltage::V1_10 => 0b1001, // 0.860V (~78% of 1.10V)
184 CoreVoltage::V1_15 => 0b1010, // 0.903V (~79% of 1.15V)
185 CoreVoltage::V1_20 => 0b1011, // 0.946V (~79% of 1.20V)
186 CoreVoltage::V1_25 => 0b1100, // 0.989V (~79% of 1.25V)
187 CoreVoltage::V1_30 => 0b1101, // 1.032V (~79% of 1.30V)
188 }
189 }
190}
191
72/// CLock configuration. 192/// CLock configuration.
73#[non_exhaustive] 193#[non_exhaustive]
74pub struct ClockConfig { 194pub struct ClockConfig {
@@ -89,12 +209,63 @@ pub struct ClockConfig {
89 /// RTC clock configuration. 209 /// RTC clock configuration.
90 #[cfg(feature = "rp2040")] 210 #[cfg(feature = "rp2040")]
91 pub rtc_clk: Option<RtcClkConfig>, 211 pub rtc_clk: Option<RtcClkConfig>,
212 /// Core voltage scaling (RP2040 only). Defaults to 1.10V.
213 #[cfg(feature = "rp2040")]
214 pub core_voltage: CoreVoltage,
215 /// Voltage stabilization delay in microseconds.
216 /// If not set, defaults will be used based on voltage level.
217 #[cfg(feature = "rp2040")]
218 pub voltage_stabilization_delay_us: Option<u32>,
219 // See above re gpin handling being commented out
92 // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, 220 // gpin0: Option<(u32, Gpin<'static, AnyPin>)>,
93 // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, 221 // gpin1: Option<(u32, Gpin<'static, AnyPin>)>,
94} 222}
95 223
224impl Default for ClockConfig {
225 /// Creates a minimal default configuration with safe values.
226 ///
227 /// This configuration uses the ring oscillator (ROSC) as the clock source
228 /// and sets minimal defaults that guarantee a working system. It's intended
229 /// as a starting point for manual configuration.
230 ///
231 /// Most users should use one of the more specific configuration functions:
232 /// - `ClockConfig::crystal()` - Standard configuration with external crystal
233 /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator
234 /// - `ClockConfig::system_freq()` - Configuration for a specific system frequency
235 fn default() -> Self {
236 Self {
237 rosc: None,
238 xosc: None,
239 ref_clk: RefClkConfig {
240 src: RefClkSrc::Rosc,
241 div: 1,
242 },
243 sys_clk: SysClkConfig {
244 src: SysClkSrc::Rosc,
245 div_int: 1,
246 div_frac: 0,
247 },
248 peri_clk_src: None,
249 usb_clk: None,
250 adc_clk: None,
251 #[cfg(feature = "rp2040")]
252 rtc_clk: None,
253 #[cfg(feature = "rp2040")]
254 core_voltage: CoreVoltage::V1_10,
255 #[cfg(feature = "rp2040")]
256 voltage_stabilization_delay_us: None,
257 // See above re gpin handling being commented out
258 // gpin0: None,
259 // gpin1: None,
260 }
261 }
262}
263
96impl ClockConfig { 264impl ClockConfig {
97 /// Clock configuration derived from external crystal. 265 /// Clock configuration derived from external crystal.
266 ///
267 /// This uses default settings for most parameters, suitable for typical use cases.
268 /// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly.
98 pub fn crystal(crystal_hz: u32) -> Self { 269 pub fn crystal(crystal_hz: u32) -> Self {
99 Self { 270 Self {
100 rosc: Some(RoscConfig { 271 rosc: Some(RoscConfig {
@@ -152,6 +323,11 @@ impl ClockConfig {
152 div_frac: 0, 323 div_frac: 0,
153 phase: 0, 324 phase: 0,
154 }), 325 }),
326 #[cfg(feature = "rp2040")]
327 core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V)
328 #[cfg(feature = "rp2040")]
329 voltage_stabilization_delay_us: None,
330 // See above re gpin handling being commented out
155 // gpin0: None, 331 // gpin0: None,
156 // gpin1: None, 332 // gpin1: None,
157 } 333 }
@@ -192,20 +368,157 @@ impl ClockConfig {
192 div_frac: 171, 368 div_frac: 171,
193 phase: 0, 369 phase: 0,
194 }), 370 }),
371 #[cfg(feature = "rp2040")]
372 core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V)
373 #[cfg(feature = "rp2040")]
374 voltage_stabilization_delay_us: None,
375 // See above re gpin handling being commented out
195 // gpin0: None, 376 // gpin0: None,
196 // gpin1: None, 377 // gpin1: None,
197 } 378 }
198 } 379 }
199 380
200 // pub fn bind_gpin<P: GpinPin>(&mut self, gpin: Gpin<'static, P>, hz: u32) { 381 /// Configure clocks derived from an external crystal with specific system frequency.
201 // match P::NR { 382 ///
202 // 0 => self.gpin0 = Some((hz, gpin.into())), 383 /// This function calculates optimal PLL parameters to achieve the requested system
203 // 1 => self.gpin1 = Some((hz, gpin.into())), 384 /// frequency. This only works for the usual 12MHz crystal. In case a different crystal is used,
204 // _ => unreachable!(), 385 /// You will have to set the PLL parameters manually.
205 // } 386 ///
206 // // pin is now provisionally bound. if the config is applied it must be forgotten, 387 /// # Arguments
207 // // or Gpin::drop will deconfigure the clock input. 388 ///
208 // } 389 /// * `sys_freq_hz` - The desired system clock frequency in Hz
390 ///
391 /// # Returns
392 ///
393 /// A ClockConfig configured to achieve the requested system frequency using the
394 /// the usual 12Mhz crystal, or panic if no valid parameters can be found.
395 ///
396 /// # Note on core voltage:
397 /// To date the only officially documented core voltages (see Datasheet section 2.15.3.1. Instances) are:
398 /// - Up to 133MHz: V1_10 (default)
399 /// - Above 133MHz: V1_15, but in the context of the datasheet covering reaching up to 200Mhz
400 /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here.
401 /// In case You want to go below 133MHz or above 200MHz and want a different voltage, You will have to set that manually and with caution.
402 #[cfg(feature = "rp2040")]
403 pub fn system_freq(hz: u32) -> Self {
404 // Start with the standard configuration from crystal()
405 const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000;
406 let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ);
407
408 // No need to modify anything if target frequency is already 125MHz
409 // (which is what crystal() configures by default)
410 if hz == 125_000_000 {
411 return config;
412 }
413
414 // Find optimal PLL parameters for the requested frequency
415 let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, hz)
416 .unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock"));
417
418 // Replace the sys_pll configuration with our custom parameters
419 if let Some(xosc) = &mut config.xosc {
420 xosc.sys_pll = Some(sys_pll_params);
421 }
422
423 // Set the voltage scale based on the target frequency
424 // Higher frequencies require higher voltage
425 #[cfg(feature = "rp2040")]
426 {
427 config.core_voltage = match hz {
428 freq if freq > 133_000_000 => CoreVoltage::V1_15,
429 _ => CoreVoltage::V1_10, // Use default voltage (V1_10)
430 };
431 }
432
433 config
434 }
435
436 /// Configure with manual PLL settings for full control over system clock
437 ///
438 /// This method provides a simple way to configure the system with custom PLL parameters
439 /// without needing to understand the full nested configuration structure.
440 ///
441 /// # Arguments
442 ///
443 /// * `xosc_hz` - The frequency of the external crystal in Hz
444 /// * `pll_config` - The PLL configuration parameters to achieve desired frequency
445 /// * `core_voltage` - Voltage scaling for overclocking (required for >133MHz)
446 ///
447 /// # Returns
448 ///
449 /// A ClockConfig configured with the specified PLL parameters
450 ///
451 /// # Example
452 ///
453 /// ```rust,ignore
454 /// // Configure for 200MHz operation
455 /// let config = Config::default();
456 /// config.clocks = ClockConfig::manual_pll(
457 /// 12_000_000,
458 /// PllConfig {
459 /// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz)
460 /// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO)
461 /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz)
462 /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz)
463 /// },
464 /// CoreVoltage::V1_15
465 /// );
466 /// ```
467 #[cfg(feature = "rp2040")]
468 pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self {
469 // Validate PLL parameters
470 assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters");
471
472 let mut config = Self::default();
473
474 config.xosc = Some(XoscConfig {
475 hz: xosc_hz,
476 sys_pll: Some(pll_config),
477 usb_pll: Some(PllConfig {
478 refdiv: 1,
479 fbdiv: 120,
480 post_div1: 6,
481 post_div2: 5,
482 }),
483 delay_multiplier: 128,
484 });
485
486 config.ref_clk = RefClkConfig {
487 src: RefClkSrc::Xosc,
488 div: 1,
489 };
490
491 config.sys_clk = SysClkConfig {
492 src: SysClkSrc::PllSys,
493 div_int: 1,
494 div_frac: 0,
495 };
496
497 config.core_voltage = core_voltage;
498 config.peri_clk_src = Some(PeriClkSrc::Sys);
499
500 // Set reasonable defaults for other clocks
501 config.usb_clk = Some(UsbClkConfig {
502 src: UsbClkSrc::PllUsb,
503 div: 1,
504 phase: 0,
505 });
506
507 config.adc_clk = Some(AdcClkConfig {
508 src: AdcClkSrc::PllUsb,
509 div: 1,
510 phase: 0,
511 });
512
513 config.rtc_clk = Some(RtcClkConfig {
514 src: RtcClkSrc::PllUsb,
515 div_int: 1024,
516 div_frac: 0,
517 phase: 0,
518 });
519
520 config
521 }
209} 522}
210 523
211/// ROSC freq range. 524/// ROSC freq range.
@@ -251,6 +564,7 @@ pub struct XoscConfig {
251} 564}
252 565
253/// PLL configuration. 566/// PLL configuration.
567#[derive(Clone, Copy, Debug)]
254pub struct PllConfig { 568pub struct PllConfig {
255 /// Reference divisor. 569 /// Reference divisor.
256 pub refdiv: u8, 570 pub refdiv: u8,
@@ -262,6 +576,50 @@ pub struct PllConfig {
262 pub post_div2: u8, 576 pub post_div2: u8,
263} 577}
264 578
579impl PllConfig {
580 /// Calculate the output frequency for this PLL configuration
581 /// given an input frequency.
582 pub fn output_frequency(&self, input_hz: u32) -> u32 {
583 let ref_freq = input_hz / self.refdiv as u32;
584 let vco_freq = ref_freq * self.fbdiv as u32;
585 vco_freq / ((self.post_div1 * self.post_div2) as u32)
586 }
587
588 /// Check if this PLL configuration is valid for the given input frequency.
589 pub fn is_valid(&self, input_hz: u32) -> bool {
590 // Check divisor constraints
591 if self.refdiv < 1 || self.refdiv > 63 {
592 return false;
593 }
594 if self.fbdiv < 16 || self.fbdiv > 320 {
595 return false;
596 }
597 if self.post_div1 < 1 || self.post_div1 > 7 {
598 return false;
599 }
600 if self.post_div2 < 1 || self.post_div2 > 7 {
601 return false;
602 }
603 if self.post_div2 > self.post_div1 {
604 return false;
605 }
606
607 // Calculate reference frequency
608 let ref_freq = input_hz / self.refdiv as u32;
609
610 // Check reference frequency range
611 if ref_freq < 5_000_000 || ref_freq > 800_000_000 {
612 return false;
613 }
614
615 // Calculate VCO frequency
616 let vco_freq = ref_freq * self.fbdiv as u32;
617
618 // Check VCO frequency range
619 vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000
620 }
621}
622
265/// Reference clock config. 623/// Reference clock config.
266pub struct RefClkConfig { 624pub struct RefClkConfig {
267 /// Reference clock source. 625 /// Reference clock source.
@@ -280,6 +638,7 @@ pub enum RefClkSrc {
280 Rosc, 638 Rosc,
281 /// PLL USB. 639 /// PLL USB.
282 PllUsb, 640 PllUsb,
641 // See above re gpin handling being commented out
283 // Gpin0, 642 // Gpin0,
284 // Gpin1, 643 // Gpin1,
285} 644}
@@ -298,6 +657,7 @@ pub enum SysClkSrc {
298 Rosc, 657 Rosc,
299 /// XOSC. 658 /// XOSC.
300 Xosc, 659 Xosc,
660 // See above re gpin handling being commented out
301 // Gpin0, 661 // Gpin0,
302 // Gpin1, 662 // Gpin1,
303} 663}
@@ -333,6 +693,7 @@ pub enum UsbClkSrc {
333 Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, 693 Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _,
334 /// XOSC. 694 /// XOSC.
335 Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, 695 Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _,
696 // See above re gpin handling being commented out
336 // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , 697 // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
337 // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , 698 // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
338} 699}
@@ -360,6 +721,7 @@ pub enum AdcClkSrc {
360 Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, 721 Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
361 /// XOSC. 722 /// XOSC.
362 Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, 723 Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _,
724 // See above re gpin handling being commented out
363 // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , 725 // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
364 // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , 726 // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
365} 727}
@@ -388,6 +750,7 @@ pub enum RtcClkSrc {
388 Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, 750 Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
389 /// XOSC. 751 /// XOSC.
390 Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, 752 Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _,
753 // See above re gpin handling being commented out
391 // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , 754 // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
392 // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , 755 // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
393} 756}
@@ -405,6 +768,103 @@ pub struct RtcClkConfig {
405 pub phase: u8, 768 pub phase: u8,
406} 769}
407 770
771/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency
772/// based on the input frequency.
773///
774/// This function searches for the best PLL configuration to achieve the requested target frequency
775/// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability
776/// over exact frequency matching by using larger divisors where possible.
777///
778/// # Parameters
779///
780/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz for th most common one used on rp2040 boards)
781/// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation)
782///
783/// # Returns
784///
785/// * `Some(PllConfig)` if valid parameters were found
786/// * `None` if no valid parameters could be found for the requested combination
787///
788/// # Example
789///
790/// ```rust,ignore
791/// // Find parameters for 133MHz system clock from 12MHz crystal
792/// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap();
793/// ```
794#[cfg(feature = "rp2040")]
795fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> {
796 // Fixed reference divider for system PLL
797 const PLL_SYS_REFDIV: u8 = 1;
798
799 // Calculate reference frequency
800 let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64;
801
802 // Start from highest fbdiv for better stability (like SDK does)
803 for fbdiv in (16..=320).rev() {
804 let vco_freq = reference_freq * fbdiv;
805
806 // Check VCO frequency is within valid range
807 if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 {
808 continue;
809 }
810
811 // Try all possible postdiv combinations starting from larger values
812 // (more conservative/stable approach)
813 for post_div1 in (1..=7).rev() {
814 for post_div2 in (1..=post_div1).rev() {
815 let out_freq = vco_freq / (post_div1 * post_div2);
816
817 // Check if we get the exact target frequency without remainder
818 if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) {
819 return Some(PllConfig {
820 refdiv: PLL_SYS_REFDIV,
821 fbdiv: fbdiv as u16,
822 post_div1: post_div1 as u8,
823 post_div2: post_div2 as u8,
824 });
825 }
826 }
827 }
828 }
829
830 // If we couldn't find an exact match, find the closest match
831 let mut best_config = None;
832 let mut min_diff = u32::MAX;
833
834 for fbdiv in (16..=320).rev() {
835 let vco_freq = reference_freq * fbdiv;
836
837 if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 {
838 continue;
839 }
840
841 for post_div1 in (1..=7).rev() {
842 for post_div2 in (1..=post_div1).rev() {
843 let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32;
844 let diff = if out_freq > target_hz {
845 out_freq - target_hz
846 } else {
847 target_hz - out_freq
848 };
849
850 // If this is closer to the target, save it
851 if diff < min_diff {
852 min_diff = diff;
853 best_config = Some(PllConfig {
854 refdiv: PLL_SYS_REFDIV,
855 fbdiv: fbdiv as u16,
856 post_div1: post_div1 as u8,
857 post_div2: post_div2 as u8,
858 });
859 }
860 }
861 }
862 }
863
864 // Return the closest match if we found one
865 best_config
866}
867
408/// safety: must be called exactly once at bootup 868/// safety: must be called exactly once at bootup
409pub(crate) unsafe fn init(config: ClockConfig) { 869pub(crate) unsafe fn init(config: ClockConfig) {
410 // Reset everything except: 870 // Reset everything except:
@@ -447,6 +907,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
447 reset::reset(peris); 907 reset::reset(peris);
448 reset::unreset_wait(peris); 908 reset::unreset_wait(peris);
449 909
910 // See above re gpin handling being commented out
450 // let gpin0_freq = config.gpin0.map_or(0, |p| { 911 // let gpin0_freq = config.gpin0.map_or(0, |p| {
451 // core::mem::forget(p.1); 912 // core::mem::forget(p.1);
452 // p.0 913 // p.0
@@ -464,19 +925,60 @@ pub(crate) unsafe fn init(config: ClockConfig) {
464 }; 925 };
465 CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); 926 CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed);
466 927
928 // Set Core Voltage (RP2040 only), if we have config for it and we're not using the default
929 #[cfg(feature = "rp2040")]
930 {
931 let voltage = config.core_voltage;
932 let vreg = pac::VREG_AND_CHIP_RESET;
933 let current_vsel = vreg.vreg().read().vsel();
934 let target_vsel = voltage as u8;
935
936 // If the target voltage is different from the current one, we need to change it
937 if target_vsel != current_vsel {
938 // Use modify() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage
939 vreg.vreg().modify(|w| w.set_vsel(target_vsel));
940
941 // Wait for the voltage to stabilize. Use the provided delay or default based on voltage
942 let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| {
943 match voltage {
944 CoreVoltage::V1_15 => 1000, // 1ms for 1.15V
945 CoreVoltage::V1_20 | CoreVoltage::V1_25 | CoreVoltage::V1_30 => 2000, // 2ms for higher voltages
946 _ => 0, // no delay for all others
947 }
948 });
949
950 if settling_time_us != 0 {
951 // Delay in microseconds, using the ROSC frequency to calculate cycles
952 let cycles_per_us = rosc_freq / 1_000_000;
953 let delay_cycles = settling_time_us * cycles_per_us;
954 cortex_m::asm::delay(delay_cycles);
955 }
956
957 // Only now set the BOD level. At this point the voltage is considered stable.
958 vreg.bod().write(|w| {
959 w.set_vsel(voltage.recommended_bod());
960 w.set_en(true); // Enable brownout detection
961 });
962 }
963 }
964
467 let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { 965 let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc {
468 Some(config) => { 966 Some(config) => {
469 // start XOSC 967 // start XOSC
470 // datasheet mentions support for clock inputs into XIN, but doesn't go into
471 // how this is achieved. pico-sdk doesn't support this at all.
472 start_xosc(config.hz, config.delay_multiplier); 968 start_xosc(config.hz, config.delay_multiplier);
473 969
474 let pll_sys_freq = match config.sys_pll { 970 let pll_sys_freq = match config.sys_pll {
475 Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), 971 Some(sys_pll_config) => match configure_pll(pac::PLL_SYS, config.hz, sys_pll_config) {
972 Ok(freq) => freq,
973 Err(e) => panic!("Failed to configure PLL_SYS: {}", e),
974 },
476 None => 0, 975 None => 0,
477 }; 976 };
478 let pll_usb_freq = match config.usb_pll { 977 let pll_usb_freq = match config.usb_pll {
479 Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), 978 Some(usb_pll_config) => match configure_pll(pac::PLL_USB, config.hz, usb_pll_config) {
979 Ok(freq) => freq,
980 Err(e) => panic!("Failed to configure PLL_USB: {}", e),
981 },
480 None => 0, 982 None => 0,
481 }; 983 };
482 984
@@ -484,6 +986,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
484 } 986 }
485 None => (0, 0, 0), 987 None => (0, 0, 0),
486 }; 988 };
989
487 CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); 990 CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed);
488 CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); 991 CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed);
489 CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); 992 CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed);
@@ -496,6 +999,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
496 RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), 999 RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div),
497 RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), 1000 RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div),
498 RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), 1001 RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div),
1002 // See above re gpin handling being commented out
499 // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), 1003 // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div),
500 // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), 1004 // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div),
501 } 1005 }
@@ -540,6 +1044,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
540 SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), 1044 SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq),
541 SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), 1045 SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq),
542 SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), 1046 SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq),
1047 // See above re gpin handling being commented out
543 // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), 1048 // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq),
544 // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), 1049 // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq),
545 }; 1050 };
@@ -583,6 +1088,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
583 PeriClkSrc::PllUsb => pll_usb_freq, 1088 PeriClkSrc::PllUsb => pll_usb_freq,
584 PeriClkSrc::Rosc => rosc_freq, 1089 PeriClkSrc::Rosc => rosc_freq,
585 PeriClkSrc::Xosc => xosc_freq, 1090 PeriClkSrc::Xosc => xosc_freq,
1091 // See above re gpin handling being commented out
586 // PeriClkSrc::Gpin0 => gpin0_freq, 1092 // PeriClkSrc::Gpin0 => gpin0_freq,
587 // PeriClkSrc::Gpin1 => gpin1_freq, 1093 // PeriClkSrc::Gpin1 => gpin1_freq,
588 }; 1094 };
@@ -608,6 +1114,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
608 UsbClkSrc::PllSys => pll_sys_freq, 1114 UsbClkSrc::PllSys => pll_sys_freq,
609 UsbClkSrc::Rosc => rosc_freq, 1115 UsbClkSrc::Rosc => rosc_freq,
610 UsbClkSrc::Xosc => xosc_freq, 1116 UsbClkSrc::Xosc => xosc_freq,
1117 // See above re gpin handling being commented out
611 // UsbClkSrc::Gpin0 => gpin0_freq, 1118 // UsbClkSrc::Gpin0 => gpin0_freq,
612 // UsbClkSrc::Gpin1 => gpin1_freq, 1119 // UsbClkSrc::Gpin1 => gpin1_freq,
613 }; 1120 };
@@ -631,6 +1138,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
631 AdcClkSrc::PllSys => pll_sys_freq, 1138 AdcClkSrc::PllSys => pll_sys_freq,
632 AdcClkSrc::Rosc => rosc_freq, 1139 AdcClkSrc::Rosc => rosc_freq,
633 AdcClkSrc::Xosc => xosc_freq, 1140 AdcClkSrc::Xosc => xosc_freq,
1141 // See above re gpin handling being commented out
634 // AdcClkSrc::Gpin0 => gpin0_freq, 1142 // AdcClkSrc::Gpin0 => gpin0_freq,
635 // AdcClkSrc::Gpin1 => gpin1_freq, 1143 // AdcClkSrc::Gpin1 => gpin1_freq,
636 }; 1144 };
@@ -659,6 +1167,7 @@ pub(crate) unsafe fn init(config: ClockConfig) {
659 RtcClkSrc::PllSys => pll_sys_freq, 1167 RtcClkSrc::PllSys => pll_sys_freq,
660 RtcClkSrc::Rosc => rosc_freq, 1168 RtcClkSrc::Rosc => rosc_freq,
661 RtcClkSrc::Xosc => xosc_freq, 1169 RtcClkSrc::Xosc => xosc_freq,
1170 // See above re gpin handling being commented out
662 // RtcClkSrc::Gpin0 => gpin0_freq, 1171 // RtcClkSrc::Gpin0 => gpin0_freq,
663 // RtcClkSrc::Gpin1 => gpin1_freq, 1172 // RtcClkSrc::Gpin1 => gpin1_freq,
664 }; 1173 };
@@ -725,6 +1234,7 @@ pub fn xosc_freq() -> u32 {
725 CLOCKS.xosc.load(Ordering::Relaxed) 1234 CLOCKS.xosc.load(Ordering::Relaxed)
726} 1235}
727 1236
1237// See above re gpin handling being commented out
728// pub fn gpin0_freq() -> u32 { 1238// pub fn gpin0_freq() -> u32 {
729// CLOCKS.gpin0.load(Ordering::Relaxed) 1239// CLOCKS.gpin0.load(Ordering::Relaxed)
730// } 1240// }
@@ -783,46 +1293,100 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) {
783 while !pac::XOSC.status().read().stable() {} 1293 while !pac::XOSC.status().read().stable() {}
784} 1294}
785 1295
1296/// PLL (Phase-Locked Loop) configuration
786#[inline(always)] 1297#[inline(always)]
787fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { 1298fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result<u32, &'static str> {
1299 // Calculate reference frequency
788 let ref_freq = input_freq / config.refdiv as u32; 1300 let ref_freq = input_freq / config.refdiv as u32;
1301
1302 // Validate PLL parameters
1303 // Feedback divider (FBDIV) must be between 16 and 320
789 assert!(config.fbdiv >= 16 && config.fbdiv <= 320); 1304 assert!(config.fbdiv >= 16 && config.fbdiv <= 320);
1305
1306 // Post divider 1 (POSTDIV1) must be between 1 and 7
790 assert!(config.post_div1 >= 1 && config.post_div1 <= 7); 1307 assert!(config.post_div1 >= 1 && config.post_div1 <= 7);
1308
1309 // Post divider 2 (POSTDIV2) must be between 1 and 7
791 assert!(config.post_div2 >= 1 && config.post_div2 <= 7); 1310 assert!(config.post_div2 >= 1 && config.post_div2 <= 7);
1311
1312 // Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1)
1313 assert!(config.post_div2 <= config.post_div1);
1314
1315 // Reference divider (REFDIV) must be between 1 and 63
792 assert!(config.refdiv >= 1 && config.refdiv <= 63); 1316 assert!(config.refdiv >= 1 && config.refdiv <= 63);
1317
1318 // Reference frequency (REF_FREQ) must be between 5MHz and 800MHz
793 assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); 1319 assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000);
1320
1321 // Calculate VCO frequency
794 let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); 1322 let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32);
1323
1324 // VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz
795 assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); 1325 assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000);
796 1326
797 // Load VCO-related dividers before starting VCO 1327 // We follow the SDK's approach to PLL configuration which is:
1328 // 1. Power down PLL
1329 // 2. Configure the reference divider
1330 // 3. Configure the feedback divider
1331 // 4. Power up PLL and VCO
1332 // 5. Wait for PLL to lock
1333 // 6. Configure post-dividers
1334 // 7. Enable post-divider output
1335
1336 // 1. Power down PLL before configuration
1337 p.pwr().write(|w| {
1338 w.set_pd(true); // Power down the PLL
1339 w.set_vcopd(true); // Power down the VCO
1340 w.set_postdivpd(true); // Power down the post divider
1341 w.set_dsmpd(true); // Disable fractional mode
1342 *w
1343 });
1344
1345 // Short delay after powering down
1346 cortex_m::asm::delay(10);
1347
1348 // 2. Configure reference divider first
798 p.cs().write(|w| w.set_refdiv(config.refdiv as _)); 1349 p.cs().write(|w| w.set_refdiv(config.refdiv as _));
1350
1351 // 3. Configure feedback divider
799 p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); 1352 p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv));
800 1353
801 // Turn on PLL 1354 // 4. Power up PLL and VCO, but keep post divider powered down during initial lock
802 let pwr = p.pwr().write(|w| { 1355 p.pwr().write(|w| {
803 w.set_dsmpd(true); // "nothing is achieved by setting this low" 1356 w.set_pd(false); // Power up the PLL
804 w.set_pd(false); 1357 w.set_vcopd(false); // Power up the VCO
805 w.set_vcopd(false); 1358 w.set_postdivpd(true); // Keep post divider powered down during initial lock
806 w.set_postdivpd(true); 1359 w.set_dsmpd(true); // Disable fractional mode (simpler configuration)
807 *w 1360 *w
808 }); 1361 });
809 1362
810 // Wait for PLL to lock 1363 // 5. Wait for PLL to lock with a timeout
811 while !p.cs().read().lock() {} 1364 let mut timeout = 1_000_000;
1365 while !p.cs().read().lock() {
1366 timeout -= 1;
1367 if timeout == 0 {
1368 // PLL failed to lock, return 0 to indicate failure
1369 return Err("PLL failed to lock");
1370 }
1371 }
812 1372
813 // Set post-dividers 1373 // 6. Configure post dividers after PLL is locked
814 p.prim().write(|w| { 1374 p.prim().write(|w| {
815 w.set_postdiv1(config.post_div1); 1375 w.set_postdiv1(config.post_div1);
816 w.set_postdiv2(config.post_div2); 1376 w.set_postdiv2(config.post_div2);
817 }); 1377 });
818 1378
819 // Turn on post divider 1379 // 7. Enable the post divider output
820 p.pwr().write(|w| { 1380 p.pwr().modify(|w| {
821 *w = pwr; 1381 w.set_postdivpd(false); // Power up post divider
822 w.set_postdivpd(false); 1382 *w
823 }); 1383 });
824 1384
825 vco_freq / ((config.post_div1 * config.post_div2) as u32) 1385 // Final delay to ensure everything is stable
1386 cortex_m::asm::delay(100);
1387
1388 // Calculate and return actual output frequency
1389 Ok(vco_freq / ((config.post_div1 * config.post_div2) as u32))
826} 1390}
827 1391
828/// General purpose input clock pin. 1392/// General purpose input clock pin.
@@ -906,6 +1470,7 @@ impl_gpoutpin!(PIN_25, 3);
906pub enum GpoutSrc { 1470pub enum GpoutSrc {
907 /// Sys PLL. 1471 /// Sys PLL.
908 PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, 1472 PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _,
1473 // See above re gpin handling being commented out
909 // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , 1474 // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
910 // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , 1475 // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
911 /// USB PLL. 1476 /// USB PLL.
@@ -1001,6 +1566,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
1001 1566
1002 let base = match src { 1567 let base = match src {
1003 ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), 1568 ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(),
1569 // See above re gpin handling being commented out
1004 // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), 1570 // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(),
1005 // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), 1571 // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(),
1006 ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), 1572 ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(),
@@ -1009,7 +1575,6 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
1009 ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), 1575 ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(),
1010 ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), 1576 ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(),
1011 ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), 1577 ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(),
1012 //ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _,
1013 ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), 1578 ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(),
1014 _ => unreachable!(), 1579 _ => unreachable!(),
1015 }; 1580 };
@@ -1069,6 +1634,7 @@ impl rand_core::RngCore for RoscRng {
1069 dest.fill_with(Self::next_u8) 1634 dest.fill_with(Self::next_u8)
1070 } 1635 }
1071} 1636}
1637
1072/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks 1638/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks
1073/// and can only be exited through resets, dormant-wake GPIO interrupts, 1639/// and can only be exited through resets, dormant-wake GPIO interrupts,
1074/// and RTC interrupts. If RTC is clocked from an internal clock source 1640/// and RTC interrupts. If RTC is clocked from an internal clock source
@@ -1197,3 +1763,196 @@ pub fn dormant_sleep() {
1197 } 1763 }
1198 } 1764 }
1199} 1765}
1766
1767#[cfg(test)]
1768mod tests {
1769 use super::*;
1770
1771 #[cfg(feature = "rp2040")]
1772 #[test]
1773 fn test_find_pll_params() {
1774 #[cfg(feature = "rp2040")]
1775 {
1776 // Test standard 125 MHz configuration with 12 MHz crystal
1777 let params = find_pll_params(12_000_000, 125_000_000).unwrap();
1778 assert_eq!(params.refdiv, 1);
1779 assert_eq!(params.fbdiv, 125);
1780
1781 // Test USB PLL configuration for 48MHz
1782 // The algorithm may find different valid parameters than the SDK defaults
1783 // We'll check that it generates a valid configuration that produces 48MHz
1784 let params = find_pll_params(12_000_000, 48_000_000).unwrap();
1785 assert_eq!(params.refdiv, 1);
1786
1787 // Calculate the actual output frequency
1788 let ref_freq = 12_000_000 / params.refdiv as u32;
1789 let vco_freq = ref_freq as u64 * params.fbdiv as u64;
1790 let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32;
1791
1792 // Verify the output frequency is correct
1793 assert_eq!(output_freq, 48_000_000);
1794
1795 // Verify VCO frequency is in valid range
1796 assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000);
1797
1798 // Test overclocked configuration for 200 MHz
1799 let params = find_pll_params(12_000_000, 200_000_000).unwrap();
1800 assert_eq!(params.refdiv, 1);
1801 let vco_freq = 12_000_000 as u64 * params.fbdiv as u64;
1802 let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32;
1803 assert_eq!(output_freq, 200_000_000);
1804 assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range
1805
1806 // Test non-standard crystal with 16 MHz
1807 let params = find_pll_params(16_000_000, 125_000_000).unwrap();
1808 let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64;
1809 let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32;
1810
1811 // Test non-standard crystal with 15 MHz
1812 let params = find_pll_params(15_000_000, 125_000_000).unwrap();
1813 let vco_freq = (15_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64;
1814 let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32;
1815
1816 // With a 15 MHz crystal, we might not get exactly 125 MHz
1817 // Check that it's close enough (within 0.2% margin)
1818 let freq_diff = if output_freq > 125_000_000 {
1819 output_freq - 125_000_000
1820 } else {
1821 125_000_000 - output_freq
1822 };
1823 let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0;
1824 assert!(
1825 error_percentage < 0.2,
1826 "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%",
1827 output_freq,
1828 error_percentage
1829 );
1830
1831 assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000);
1832 }
1833 }
1834
1835 #[cfg(feature = "rp2040")]
1836 #[test]
1837 fn test_pll_config_validation() {
1838 // Test PLL configuration validation logic
1839 let valid_config = PllConfig {
1840 refdiv: 1,
1841 fbdiv: 125,
1842 post_div1: 6,
1843 post_div2: 2,
1844 };
1845
1846 // Valid configuration should pass validation
1847 assert!(valid_config.is_valid(12_000_000));
1848
1849 // Test fbdiv constraints
1850 let mut invalid_config = valid_config;
1851 invalid_config.fbdiv = 15; // Below minimum of 16
1852 assert!(!invalid_config.is_valid(12_000_000));
1853
1854 invalid_config.fbdiv = 321; // Above maximum of 320
1855 assert!(!invalid_config.is_valid(12_000_000));
1856
1857 // Test post_div constraints
1858 invalid_config = valid_config;
1859 invalid_config.post_div1 = 0; // Below minimum of 1
1860 assert!(!invalid_config.is_valid(12_000_000));
1861
1862 invalid_config = valid_config;
1863 invalid_config.post_div1 = 8; // Above maximum of 7
1864 assert!(!invalid_config.is_valid(12_000_000));
1865
1866 // Test post_div2 must be <= post_div1
1867 invalid_config = valid_config;
1868 invalid_config.post_div2 = 7;
1869 invalid_config.post_div1 = 3;
1870 assert!(!invalid_config.is_valid(12_000_000));
1871
1872 // Test reference frequency constraints
1873 invalid_config = valid_config;
1874 assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz
1875 assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz
1876
1877 // Test VCO frequency constraints
1878 invalid_config = valid_config;
1879 invalid_config.fbdiv = 16;
1880 assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz
1881
1882 // Test VCO frequency constraints - too high
1883 invalid_config = valid_config;
1884 invalid_config.fbdiv = 200;
1885 invalid_config.refdiv = 1;
1886 // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz
1887 assert!(!invalid_config.is_valid(12_000_000));
1888
1889 // Test a valid high VCO configuration
1890 invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit
1891 assert!(invalid_config.is_valid(12_000_000));
1892 }
1893
1894 #[cfg(feature = "rp2040")]
1895 #[test]
1896 fn test_manual_pll_helper() {
1897 {
1898 // Test the new manual_pll helper method
1899 let config = ClockConfig::manual_pll(
1900 12_000_000,
1901 PllConfig {
1902 refdiv: 1,
1903 fbdiv: 100,
1904 post_div1: 3,
1905 post_div2: 2,
1906 },
1907 CoreVoltage::V1_15,
1908 );
1909
1910 // Check voltage scale was set correctly
1911 assert_eq!(config.core_voltage, CoreVoltage::V1_15);
1912
1913 // Check PLL config was set correctly
1914 assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1);
1915 assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100);
1916 assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3);
1917 assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2);
1918
1919 // Check we get the expected frequency
1920 assert_eq!(
1921 config
1922 .xosc
1923 .as_ref()
1924 .unwrap()
1925 .sys_pll
1926 .as_ref()
1927 .unwrap()
1928 .output_frequency(12_000_000),
1929 200_000_000
1930 );
1931 }
1932 }
1933
1934 #[cfg(feature = "rp2040")]
1935 #[test]
1936 fn test_auto_voltage_scaling() {
1937 {
1938 // Test automatic voltage scaling based on frequency
1939 // Under 133 MHz should use default voltage (V1_10)
1940 let config = ClockConfig::system_freq(125_000_000);
1941 assert_eq!(config.core_voltage, CoreVoltage::V1_10);
1942
1943 // 133-200 MHz should use V1_15
1944 let config = ClockConfig::system_freq(150_000_000);
1945 assert_eq!(config.core_voltage, CoreVoltage::V1_15);
1946 let config = ClockConfig::system_freq(200_000_000);
1947 assert_eq!(config.core_voltage, CoreVoltage::V1_15);
1948
1949 // Above 200 MHz should use V1_25
1950 let config = ClockConfig::system_freq(250_000_000);
1951 assert_eq!(config.core_voltage, CoreVoltage::V1_15);
1952
1953 // Below 125 MHz should use V1_10
1954 let config = ClockConfig::system_freq(100_000_000);
1955 assert_eq!(config.core_voltage, CoreVoltage::V1_10);
1956 }
1957 }
1958}
diff --git a/embassy-rp/src/pio_programs/clock_divider.rs b/embassy-rp/src/pio_programs/clock_divider.rs
new file mode 100644
index 000000000..02e353f53
--- /dev/null
+++ b/embassy-rp/src/pio_programs/clock_divider.rs
@@ -0,0 +1,25 @@
1//! Helper functions for calculating PIO clock dividers
2
3use fixed::traits::ToFixed;
4use fixed::types::extra::U8;
5
6use crate::clocks::clk_sys_freq;
7
8/// Calculate a PIO clock divider value based on the desired target frequency.
9///
10/// # Arguments
11///
12/// * `target_hz` - The desired PIO clock frequency in Hz
13///
14/// # Returns
15///
16/// A fixed-point divider value suitable for use in a PIO state machine configuration
17#[inline]
18pub fn calculate_pio_clock_divider(target_hz: u32) -> fixed::FixedU32<U8> {
19 // Requires a non-zero frequency
20 assert!(target_hz > 0, "PIO clock frequency cannot be zero");
21
22 // Calculate the divider
23 let divider = (clk_sys_freq() + target_hz / 2) / target_hz;
24 divider.to_fixed()
25}
diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs
index 5846a8027..546c85a89 100644
--- a/embassy-rp/src/pio_programs/hd44780.rs
+++ b/embassy-rp/src/pio_programs/hd44780.rs
@@ -5,6 +5,7 @@ use crate::pio::{
5 Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, 5 Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
6 StateMachine, 6 StateMachine,
7}; 7};
8use crate::pio_programs::clock_divider::calculate_pio_clock_divider;
8use crate::Peri; 9use crate::Peri;
9 10
10/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>) 11/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
@@ -134,7 +135,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
134 135
135 let mut cfg = Config::default(); 136 let mut cfg = Config::default();
136 cfg.use_program(&word_prg.prg, &[&e]); 137 cfg.use_program(&word_prg.prg, &[&e]);
137 cfg.clock_divider = 125u8.into(); 138
139 // Target 1 MHz PIO clock (each cycle is 1µs)
140 cfg.clock_divider = calculate_pio_clock_divider(1_000_000);
141
138 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); 142 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
139 cfg.shift_out = ShiftConfig { 143 cfg.shift_out = ShiftConfig {
140 auto_fill: true, 144 auto_fill: true,
@@ -160,7 +164,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
160 164
161 let mut cfg = Config::default(); 165 let mut cfg = Config::default();
162 cfg.use_program(&seq_prg.prg, &[&e]); 166 cfg.use_program(&seq_prg.prg, &[&e]);
163 cfg.clock_divider = 8u8.into(); // ~64ns/insn 167
168 // Target ~15.6 MHz PIO clock (~64ns/insn)
169 cfg.clock_divider = calculate_pio_clock_divider(15_600_000);
170
164 cfg.set_jmp_pin(&db7); 171 cfg.set_jmp_pin(&db7);
165 cfg.set_set_pins(&[&rs, &rw]); 172 cfg.set_set_pins(&[&rs, &rw]);
166 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); 173 cfg.set_out_pins(&[&db4, &db5, &db6, &db7]);
diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs
index 74537825b..8eac328b3 100644
--- a/embassy-rp/src/pio_programs/mod.rs
+++ b/embassy-rp/src/pio_programs/mod.rs
@@ -1,5 +1,6 @@
1//! Pre-built pio programs for common interfaces 1//! Pre-built pio programs for common interfaces
2 2
3pub mod clock_divider;
3pub mod hd44780; 4pub mod hd44780;
4pub mod i2s; 5pub mod i2s;
5pub mod onewire; 6pub mod onewire;
diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs
index e520da8a3..70b3795e9 100644
--- a/embassy-rp/src/pio_programs/rotary_encoder.rs
+++ b/embassy-rp/src/pio_programs/rotary_encoder.rs
@@ -1,11 +1,10 @@
1//! PIO backed quadrature encoder 1//! PIO backed quadrature encoder
2 2
3use fixed::traits::ToFixed;
4
5use crate::gpio::Pull; 3use crate::gpio::Pull;
6use crate::pio::{ 4use crate::pio::{
7 Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, 5 Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
8}; 6};
7use crate::pio_programs::clock_divider::calculate_pio_clock_divider;
9use crate::Peri; 8use crate::Peri;
10 9
11/// This struct represents an Encoder program loaded into pio instruction memory. 10/// This struct represents an Encoder program loaded into pio instruction memory.
@@ -48,7 +47,10 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
48 cfg.set_in_pins(&[&pin_a, &pin_b]); 47 cfg.set_in_pins(&[&pin_a, &pin_b]);
49 cfg.fifo_join = FifoJoin::RxOnly; 48 cfg.fifo_join = FifoJoin::RxOnly;
50 cfg.shift_in.direction = ShiftDirection::Left; 49 cfg.shift_in.direction = ShiftDirection::Left;
51 cfg.clock_divider = 10_000.to_fixed(); 50
51 // Target 12.5 KHz PIO clock
52 cfg.clock_divider = calculate_pio_clock_divider(12_500);
53
52 cfg.use_program(&program.prg, &[]); 54 cfg.use_program(&program.prg, &[]);
53 sm.set_config(&cfg); 55 sm.set_config(&cfg);
54 sm.set_enable(true); 56 sm.set_enable(true);
diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs
index 495191659..0e9a8daf9 100644
--- a/embassy-rp/src/pio_programs/stepper.rs
+++ b/embassy-rp/src/pio_programs/stepper.rs
@@ -2,11 +2,8 @@
2 2
3use core::mem::{self, MaybeUninit}; 3use core::mem::{self, MaybeUninit};
4 4
5use fixed::traits::ToFixed;
6use fixed::types::extra::U8;
7use fixed::FixedU32;
8
9use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; 5use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
6use crate::pio_programs::clock_divider::calculate_pio_clock_divider;
10use crate::Peri; 7use crate::Peri;
11 8
12/// This struct represents a Stepper driver program loaded into pio instruction memory. 9/// This struct represents a Stepper driver program loaded into pio instruction memory.
@@ -64,7 +61,9 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
64 sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); 61 sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
65 let mut cfg = Config::default(); 62 let mut cfg = Config::default();
66 cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); 63 cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
67 cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); 64
65 cfg.clock_divider = calculate_pio_clock_divider(100 * 136);
66
68 cfg.use_program(&program.prg, &[]); 67 cfg.use_program(&program.prg, &[]);
69 sm.set_config(&cfg); 68 sm.set_config(&cfg);
70 sm.set_enable(true); 69 sm.set_enable(true);
@@ -73,9 +72,11 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
73 72
74 /// Set pulse frequency 73 /// Set pulse frequency
75 pub fn set_frequency(&mut self, freq: u32) { 74 pub fn set_frequency(&mut self, freq: u32) {
76 let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed(); 75 let clock_divider = calculate_pio_clock_divider(freq * 136);
77 assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); 76 let divider_f32 = clock_divider.to_num::<f32>();
78 assert!(clock_divider >= 1, "clkdiv must be >= 1"); 77 assert!(divider_f32 <= 65536.0, "clkdiv must be <= 65536");
78 assert!(divider_f32 >= 1.0, "clkdiv must be >= 1");
79
79 self.sm.set_clock_divider(clock_divider); 80 self.sm.set_clock_divider(clock_divider);
80 self.sm.clkdiv_restart(); 81 self.sm.clkdiv_restart();
81 } 82 }