diff options
| -rw-r--r-- | embassy-rp/src/clocks.rs | 222 |
1 files changed, 214 insertions, 8 deletions
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 86c172879..005564b8b 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs | |||
| @@ -391,7 +391,7 @@ impl ClockConfig { | |||
| 391 | /// | 391 | /// |
| 392 | /// # Example | 392 | /// # Example |
| 393 | /// | 393 | /// |
| 394 | /// ``` | 394 | /// ```rust,ignore |
| 395 | /// // Overclock to 200MHz | 395 | /// // Overclock to 200MHz |
| 396 | /// let config = ClockConfig::at_sys_frequency_mhz(200); | 396 | /// let config = ClockConfig::at_sys_frequency_mhz(200); |
| 397 | /// ``` | 397 | /// ``` |
| @@ -424,7 +424,7 @@ impl ClockConfig { | |||
| 424 | /// | 424 | /// |
| 425 | /// # Example | 425 | /// # Example |
| 426 | /// | 426 | /// |
| 427 | /// ``` | 427 | /// ```rust,ignore |
| 428 | /// // Use a non-standard 16MHz crystal to achieve 250MHz | 428 | /// // Use a non-standard 16MHz crystal to achieve 250MHz |
| 429 | /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); | 429 | /// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000); |
| 430 | /// ``` | 430 | /// ``` |
| @@ -875,7 +875,7 @@ pub struct RtcClkConfig { | |||
| 875 | /// | 875 | /// |
| 876 | /// # Example | 876 | /// # Example |
| 877 | /// | 877 | /// |
| 878 | /// ``` | 878 | /// ```rust,ignore |
| 879 | /// // Find parameters for 133MHz system clock from 12MHz crystal | 879 | /// // Find parameters for 133MHz system clock from 12MHz crystal |
| 880 | /// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); | 880 | /// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); |
| 881 | /// ``` | 881 | /// ``` |
| @@ -885,7 +885,7 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { | |||
| 885 | const PLL_SYS_REFDIV: u8 = 1; | 885 | const PLL_SYS_REFDIV: u8 = 1; |
| 886 | 886 | ||
| 887 | // Calculate reference frequency | 887 | // Calculate reference frequency |
| 888 | let reference_freq = input_hz / PLL_SYS_REFDIV as u32; | 888 | let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64; |
| 889 | 889 | ||
| 890 | // Start from highest fbdiv for better stability (like SDK does) | 890 | // Start from highest fbdiv for better stability (like SDK does) |
| 891 | for fbdiv in (16..=320).rev() { | 891 | for fbdiv in (16..=320).rev() { |
| @@ -900,10 +900,10 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { | |||
| 900 | // (more conservative/stable approach) | 900 | // (more conservative/stable approach) |
| 901 | for post_div1 in (1..=7).rev() { | 901 | for post_div1 in (1..=7).rev() { |
| 902 | for post_div2 in (1..=post_div1).rev() { | 902 | for post_div2 in (1..=post_div1).rev() { |
| 903 | let out_freq = vco_freq / (post_div1 * post_div2) as u32; | 903 | let out_freq = vco_freq / (post_div1 * post_div2); |
| 904 | 904 | ||
| 905 | // Check if we get the exact target frequency without remainder | 905 | // Check if we get the exact target frequency without remainder |
| 906 | if out_freq == target_hz && (vco_freq % (post_div1 * post_div2) as u32 == 0) { | 906 | if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) { |
| 907 | return Some(PllConfig { | 907 | return Some(PllConfig { |
| 908 | refdiv: PLL_SYS_REFDIV, | 908 | refdiv: PLL_SYS_REFDIV, |
| 909 | fbdiv: fbdiv as u16, | 909 | fbdiv: fbdiv as u16, |
| @@ -928,7 +928,7 @@ fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> { | |||
| 928 | 928 | ||
| 929 | for post_div1 in (1..=7).rev() { | 929 | for post_div1 in (1..=7).rev() { |
| 930 | for post_div2 in (1..=post_div1).rev() { | 930 | for post_div2 in (1..=post_div1).rev() { |
| 931 | let out_freq = vco_freq / (post_div1 * post_div2) as u32; | 931 | let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32; |
| 932 | let diff = if out_freq > target_hz { | 932 | let diff = if out_freq > target_hz { |
| 933 | out_freq - target_hz | 933 | out_freq - target_hz |
| 934 | } else { | 934 | } else { |
| @@ -1018,7 +1018,10 @@ pub(crate) unsafe fn init(config: ClockConfig) { | |||
| 1018 | cortex_m::asm::delay(delay_cycles); | 1018 | cortex_m::asm::delay(delay_cycles); |
| 1019 | 1019 | ||
| 1020 | // Only now set the BOD level after voltage has stabilized | 1020 | // Only now set the BOD level after voltage has stabilized |
| 1021 | vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod())); | 1021 | vreg.bod().write(|w| { |
| 1022 | w.set_vsel(voltage.recommended_bod()); | ||
| 1023 | w.set_en(true); // Enable brownout detection | ||
| 1024 | }); | ||
| 1022 | } | 1025 | } |
| 1023 | } | 1026 | } |
| 1024 | 1027 | ||
| @@ -1875,3 +1878,206 @@ pub fn dormant_sleep() { | |||
| 1875 | } | 1878 | } |
| 1876 | } | 1879 | } |
| 1877 | } | 1880 | } |
| 1881 | |||
| 1882 | #[cfg(test)] | ||
| 1883 | mod tests { | ||
| 1884 | use super::*; | ||
| 1885 | |||
| 1886 | #[cfg(feature = "rp2040")] | ||
| 1887 | #[test] | ||
| 1888 | fn test_voltage_scale_bod_values() { | ||
| 1889 | // Test that each voltage level maps to the correct BOD threshold (approx. 90% of VDD) | ||
| 1890 | // This verifies our BOD settings match our documentation | ||
| 1891 | { | ||
| 1892 | assert_eq!(VoltageScale::V0_85.recommended_bod(), 0b0111); // ~0.774V (91% of 0.85V) | ||
| 1893 | assert_eq!(VoltageScale::V0_90.recommended_bod(), 0b1000); // ~0.817V (91% of 0.90V) | ||
| 1894 | assert_eq!(VoltageScale::V0_95.recommended_bod(), 0b1001); // ~0.860V (91% of 0.95V) | ||
| 1895 | assert_eq!(VoltageScale::V1_00.recommended_bod(), 0b1010); // ~0.903V (90% of 1.00V) | ||
| 1896 | assert_eq!(VoltageScale::V1_05.recommended_bod(), 0b1011); // ~0.946V (90% of 1.05V) | ||
| 1897 | assert_eq!(VoltageScale::V1_10.recommended_bod(), 0b1100); // ~0.989V (90% of 1.10V) | ||
| 1898 | assert_eq!(VoltageScale::V1_15.recommended_bod(), 0b1101); // ~1.032V (90% of 1.15V) | ||
| 1899 | assert_eq!(VoltageScale::V1_20.recommended_bod(), 0b1110); // ~1.075V (90% of 1.20V) | ||
| 1900 | assert_eq!(VoltageScale::V1_25.recommended_bod(), 0b1111); // ~1.118V (89% of 1.25V) | ||
| 1901 | assert_eq!(VoltageScale::V1_30.recommended_bod(), 0b1111); // ~1.118V (86% of 1.30V) - using max available | ||
| 1902 | } | ||
| 1903 | } | ||
| 1904 | |||
| 1905 | #[cfg(feature = "rp2040")] | ||
| 1906 | #[test] | ||
| 1907 | fn test_find_pll_params() { | ||
| 1908 | #[cfg(feature = "rp2040")] | ||
| 1909 | { | ||
| 1910 | // Test standard 125 MHz configuration with 12 MHz crystal | ||
| 1911 | let params = find_pll_params(12_000_000, 125_000_000).unwrap(); | ||
| 1912 | assert_eq!(params.refdiv, 1); | ||
| 1913 | assert_eq!(params.fbdiv, 125); | ||
| 1914 | |||
| 1915 | // Test USB PLL configuration for 48MHz | ||
| 1916 | // The algorithm may find different valid parameters than the SDK defaults | ||
| 1917 | // We'll check that it generates a valid configuration that produces 48MHz | ||
| 1918 | let params = find_pll_params(12_000_000, 48_000_000).unwrap(); | ||
| 1919 | assert_eq!(params.refdiv, 1); | ||
| 1920 | |||
| 1921 | // Calculate the actual output frequency | ||
| 1922 | let ref_freq = 12_000_000 / params.refdiv as u32; | ||
| 1923 | let vco_freq = ref_freq as u64 * params.fbdiv as u64; | ||
| 1924 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1925 | |||
| 1926 | // Verify the output frequency is correct | ||
| 1927 | assert_eq!(output_freq, 48_000_000); | ||
| 1928 | |||
| 1929 | // Verify VCO frequency is in valid range | ||
| 1930 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | ||
| 1931 | |||
| 1932 | // Test overclocked configuration for 200 MHz | ||
| 1933 | let params = find_pll_params(12_000_000, 200_000_000).unwrap(); | ||
| 1934 | assert_eq!(params.refdiv, 1); | ||
| 1935 | let vco_freq = 12_000_000 as u64 * params.fbdiv as u64; | ||
| 1936 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1937 | assert_eq!(output_freq, 200_000_000); | ||
| 1938 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range | ||
| 1939 | |||
| 1940 | // Test non-standard crystal with 16 MHz | ||
| 1941 | let params = find_pll_params(16_000_000, 125_000_000).unwrap(); | ||
| 1942 | let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; | ||
| 1943 | let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; | ||
| 1944 | |||
| 1945 | // With a 16 MHz crystal, we might not get exactly 125 MHz | ||
| 1946 | // Check that it's close enough (within 0.2% margin) | ||
| 1947 | let freq_diff = if output_freq > 125_000_000 { | ||
| 1948 | output_freq - 125_000_000 | ||
| 1949 | } else { | ||
| 1950 | 125_000_000 - output_freq | ||
| 1951 | }; | ||
| 1952 | let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0; | ||
| 1953 | assert!( | ||
| 1954 | error_percentage < 0.2, | ||
| 1955 | "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%", | ||
| 1956 | output_freq, | ||
| 1957 | error_percentage | ||
| 1958 | ); | ||
| 1959 | |||
| 1960 | assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); | ||
| 1961 | } | ||
| 1962 | } | ||
| 1963 | |||
| 1964 | #[cfg(feature = "rp2040")] | ||
| 1965 | #[test] | ||
| 1966 | fn test_pll_config_validation() { | ||
| 1967 | // Test PLL configuration validation logic | ||
| 1968 | let valid_config = PllConfig { | ||
| 1969 | refdiv: 1, | ||
| 1970 | fbdiv: 125, | ||
| 1971 | post_div1: 6, | ||
| 1972 | post_div2: 2, | ||
| 1973 | }; | ||
| 1974 | |||
| 1975 | // Valid configuration should pass validation | ||
| 1976 | assert!(valid_config.is_valid(12_000_000)); | ||
| 1977 | |||
| 1978 | // Test fbdiv constraints | ||
| 1979 | let mut invalid_config = valid_config; | ||
| 1980 | invalid_config.fbdiv = 15; // Below minimum of 16 | ||
| 1981 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1982 | |||
| 1983 | invalid_config.fbdiv = 321; // Above maximum of 320 | ||
| 1984 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1985 | |||
| 1986 | // Test post_div constraints | ||
| 1987 | invalid_config = valid_config; | ||
| 1988 | invalid_config.post_div1 = 0; // Below minimum of 1 | ||
| 1989 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1990 | |||
| 1991 | invalid_config = valid_config; | ||
| 1992 | invalid_config.post_div1 = 8; // Above maximum of 7 | ||
| 1993 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 1994 | |||
| 1995 | // Test post_div2 must be <= post_div1 | ||
| 1996 | invalid_config = valid_config; | ||
| 1997 | invalid_config.post_div2 = 7; | ||
| 1998 | invalid_config.post_div1 = 3; | ||
| 1999 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 2000 | |||
| 2001 | // Test reference frequency constraints | ||
| 2002 | invalid_config = valid_config; | ||
| 2003 | assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz | ||
| 2004 | assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz | ||
| 2005 | |||
| 2006 | // Test VCO frequency constraints | ||
| 2007 | invalid_config = valid_config; | ||
| 2008 | invalid_config.fbdiv = 16; | ||
| 2009 | assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz | ||
| 2010 | |||
| 2011 | // Test VCO frequency constraints - too high | ||
| 2012 | invalid_config = valid_config; | ||
| 2013 | invalid_config.fbdiv = 200; | ||
| 2014 | invalid_config.refdiv = 1; | ||
| 2015 | // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz | ||
| 2016 | assert!(!invalid_config.is_valid(12_000_000)); | ||
| 2017 | |||
| 2018 | // Test a valid high VCO configuration | ||
| 2019 | invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit | ||
| 2020 | assert!(invalid_config.is_valid(12_000_000)); | ||
| 2021 | } | ||
| 2022 | |||
| 2023 | #[cfg(feature = "rp2040")] | ||
| 2024 | #[test] | ||
| 2025 | fn test_manual_pll_helper() { | ||
| 2026 | { | ||
| 2027 | // Test the new manual_pll helper method | ||
| 2028 | let config = ClockConfig::manual_pll( | ||
| 2029 | 12_000_000, | ||
| 2030 | PllConfig { | ||
| 2031 | refdiv: 1, | ||
| 2032 | fbdiv: 100, | ||
| 2033 | post_div1: 3, | ||
| 2034 | post_div2: 2, | ||
| 2035 | }, | ||
| 2036 | Some(VoltageScale::V1_15), | ||
| 2037 | ); | ||
| 2038 | |||
| 2039 | // Check voltage scale was set correctly | ||
| 2040 | assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); | ||
| 2041 | |||
| 2042 | // Check PLL config was set correctly | ||
| 2043 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); | ||
| 2044 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100); | ||
| 2045 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3); | ||
| 2046 | assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2); | ||
| 2047 | |||
| 2048 | // Check we get the expected frequency | ||
| 2049 | assert_eq!( | ||
| 2050 | config | ||
| 2051 | .xosc | ||
| 2052 | .as_ref() | ||
| 2053 | .unwrap() | ||
| 2054 | .sys_pll | ||
| 2055 | .as_ref() | ||
| 2056 | .unwrap() | ||
| 2057 | .output_frequency(12_000_000), | ||
| 2058 | 200_000_000 | ||
| 2059 | ); | ||
| 2060 | } | ||
| 2061 | } | ||
| 2062 | |||
| 2063 | #[cfg(feature = "rp2040")] | ||
| 2064 | #[test] | ||
| 2065 | fn test_auto_voltage_scaling() { | ||
| 2066 | { | ||
| 2067 | // Test automatic voltage scaling based on frequency | ||
| 2068 | // Under 133 MHz should use default voltage (None) | ||
| 2069 | let config = ClockConfig::at_sys_frequency_mhz(125); | ||
| 2070 | assert_eq!(config.voltage_scale, None); | ||
| 2071 | |||
| 2072 | // 133-200 MHz should use V1_15 | ||
| 2073 | let config = ClockConfig::at_sys_frequency_mhz(150); | ||
| 2074 | assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); | ||
| 2075 | let config = ClockConfig::at_sys_frequency_mhz(200); | ||
| 2076 | assert_eq!(config.voltage_scale, Some(VoltageScale::V1_15)); | ||
| 2077 | |||
| 2078 | // Above 200 MHz should use V1_20 | ||
| 2079 | let config = ClockConfig::at_sys_frequency_mhz(250); | ||
| 2080 | assert_eq!(config.voltage_scale, Some(VoltageScale::V1_20)); | ||
| 2081 | } | ||
| 2082 | } | ||
| 2083 | } | ||
