1878 lines
61 KiB
Rust
1878 lines
61 KiB
Rust
//! # Clock configuration for the RP2040 and RP235x microcontrollers.
|
|
//!
|
|
//! # Clock Configuration API
|
|
//!
|
|
//! This module provides both high-level convenience functions and low-level manual
|
|
//! configuration options for the RP2040 clock system.
|
|
//!
|
|
//! ## High-Level Convenience Functions
|
|
//!
|
|
//! For most users, these functions provide an easy way to configure clocks:
|
|
//!
|
|
//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock
|
|
//! - `ClockConfig::at_sys_frequency_mhz(200)` - Set system clock to a specific frequency with automatic voltage scaling
|
|
//! - `ClockConfig::with_external_crystal(16_000_000)` - Configure with a non-standard crystal frequency
|
|
//!
|
|
//! ## Manual Configuration
|
|
//!
|
|
//! For advanced users who need precise control:
|
|
//!
|
|
//! ```rust,ignore
|
|
//! // Start with default configuration and customize it
|
|
//! let mut config = ClockConfig::default();
|
|
//!
|
|
//! // Set custom PLL parameters
|
|
//! config.xosc = Some(XoscConfig {
|
|
//! hz: 12_000_000,
|
|
//! sys_pll: Some(PllConfig {
|
|
//! refdiv: 1,
|
|
//! fbdiv: 200,
|
|
//! post_div1: 6,
|
|
//! post_div2: 2,
|
|
//! }),
|
|
//! // ... other fields
|
|
//! });
|
|
//!
|
|
//! // Set voltage for overclocking
|
|
//! config.voltage_scale = Some(VoltageScale::V1_15);
|
|
//! ```
|
|
//!
|
|
//! ## Voltage Scaling for Overclocking (RP2040 only)
|
|
//!
|
|
//! When overclocking beyond 133MHz, higher core voltages are needed:
|
|
//!
|
|
//! - Up to 133MHz: `VoltageScale::V1_10` (default)
|
|
//! - 133-200MHz: `VoltageScale::V1_15`
|
|
//! - Above 200MHz: `VoltageScale::V1_20` or higher
|
|
//!
|
|
//! The `at_sys_frequency_mhz()` function automatically sets appropriate voltages.
|
|
//!
|
|
//! ## Examples
|
|
//!
|
|
//! ### Standard 125MHz configuration
|
|
//! ```rust,ignore
|
|
//! let config = ClockConfig::crystal(12_000_000);
|
|
//! ```
|
|
//!
|
|
//! Or using the default configuration:
|
|
//! ```rust,ignore
|
|
//! let config = ClockConfig::default();
|
|
//! ```
|
|
//!
|
|
//! ### Overclock to 200MHz
|
|
//! ```rust,ignore
|
|
//! let config = ClockConfig::at_sys_frequency_mhz(200);
|
|
//! ```
|
|
//!
|
|
//! ### Manual configuration for advanced scenarios
|
|
//! ```rust,ignore
|
|
//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, VoltageScale};
|
|
//!
|
|
//! // Start with defaults and customize
|
|
//! let mut config = ClockConfig::default();
|
|
//! config.voltage_scale = Some(VoltageScale::V1_15);
|
|
//! // Set other parameters as needed...
|
|
//! ```
|
|
|
|
#[cfg(feature = "rp2040")]
|
|
use core::arch::asm;
|
|
use core::marker::PhantomData;
|
|
#[cfg(feature = "rp2040")]
|
|
use core::sync::atomic::AtomicU16;
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
use pac::clocks::vals::*;
|
|
|
|
use crate::gpio::{AnyPin, SealedPin};
|
|
#[cfg(feature = "rp2040")]
|
|
use crate::pac::common::{Reg, RW};
|
|
use crate::{pac, reset, Peri};
|
|
|
|
// NOTE: all gpin handling is commented out for future reference.
|
|
// gpin is not usually safe to use during the boot init() call, so it won't
|
|
// be very useful until we have runtime clock reconfiguration. once this
|
|
// happens we can resurrect the commented-out gpin bits.
|
|
|
|
struct Clocks {
|
|
xosc: AtomicU32,
|
|
sys: AtomicU32,
|
|
reference: AtomicU32,
|
|
pll_sys: AtomicU32,
|
|
pll_usb: AtomicU32,
|
|
usb: AtomicU32,
|
|
adc: AtomicU32,
|
|
// gpin0: AtomicU32,
|
|
// gpin1: AtomicU32,
|
|
rosc: AtomicU32,
|
|
peri: AtomicU32,
|
|
#[cfg(feature = "rp2040")]
|
|
rtc: AtomicU16,
|
|
}
|
|
|
|
static CLOCKS: Clocks = Clocks {
|
|
xosc: AtomicU32::new(0),
|
|
sys: AtomicU32::new(0),
|
|
reference: AtomicU32::new(0),
|
|
pll_sys: AtomicU32::new(0),
|
|
pll_usb: AtomicU32::new(0),
|
|
usb: AtomicU32::new(0),
|
|
adc: AtomicU32::new(0),
|
|
// gpin0: AtomicU32::new(0),
|
|
// gpin1: AtomicU32::new(0),
|
|
rosc: AtomicU32::new(0),
|
|
peri: AtomicU32::new(0),
|
|
#[cfg(feature = "rp2040")]
|
|
rtc: AtomicU16::new(0),
|
|
};
|
|
|
|
/// Peripheral clock sources.
|
|
#[repr(u8)]
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum PeriClkSrc {
|
|
/// SYS.
|
|
Sys = ClkPeriCtrlAuxsrc::CLK_SYS as _,
|
|
/// PLL SYS.
|
|
PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS as _,
|
|
/// PLL USB.
|
|
PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
|
/// ROSC.
|
|
Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _,
|
|
/// XOSC.
|
|
Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _,
|
|
// Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
|
|
// Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
|
|
}
|
|
|
|
/// Core voltage scaling options for RP2040.
|
|
///
|
|
/// The RP2040 voltage regulator can be configured for different output voltages.
|
|
/// Higher voltages allow for higher clock frequencies but increase power consumption and heat.
|
|
#[cfg(feature = "rp2040")]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
pub enum VoltageScale {
|
|
/// 0.85V
|
|
V0_85 = 0b0110,
|
|
/// 0.90V
|
|
V0_90 = 0b0111,
|
|
/// 0.95V
|
|
V0_95 = 0b1000,
|
|
/// 1.00V
|
|
V1_00 = 0b1001,
|
|
/// 1.05V
|
|
V1_05 = 0b1010,
|
|
/// 1.10V
|
|
V1_10 = 0b1011,
|
|
/// 1.15V
|
|
V1_15 = 0b1100,
|
|
/// 1.20V
|
|
V1_20 = 0b1101,
|
|
/// 1.25V
|
|
V1_25 = 0b1110,
|
|
/// 1.30V
|
|
V1_30 = 0b1111,
|
|
}
|
|
|
|
#[cfg(feature = "rp2040")]
|
|
impl VoltageScale {
|
|
/// Get the recommended Brown-Out Detection (BOD) setting for this voltage.
|
|
/// Sets the BOD threshold to approximately 90% of the core voltage.
|
|
fn recommended_bod(self) -> u8 {
|
|
match self {
|
|
VoltageScale::V0_85 => 0b0111, // 0.774V (~91% of 0.85V)
|
|
VoltageScale::V0_90 => 0b1000, // 0.817V (~91% of 0.90V)
|
|
VoltageScale::V0_95 => 0b1001, // 0.860V (~91% of 0.95V)
|
|
VoltageScale::V1_00 => 0b1010, // 0.903V (~90% of 1.00V)
|
|
VoltageScale::V1_05 => 0b1011, // 0.946V (~90% of 1.05V)
|
|
VoltageScale::V1_10 => 0b1100, // 0.989V (~90% of 1.10V)
|
|
VoltageScale::V1_15 => 0b1101, // 1.032V (~90% of 1.15V)
|
|
VoltageScale::V1_20 => 0b1110, // 1.075V (~90% of 1.20V)
|
|
VoltageScale::V1_25 => 0b1111, // 1.118V (~89% of 1.25V)
|
|
VoltageScale::V1_30 => 0b1111, // 1.118V (~86% of 1.30V) - using max available threshold
|
|
}
|
|
}
|
|
}
|
|
|
|
/// CLock configuration.
|
|
#[non_exhaustive]
|
|
pub struct ClockConfig {
|
|
/// Ring oscillator configuration.
|
|
pub rosc: Option<RoscConfig>,
|
|
/// External oscillator configuration.
|
|
pub xosc: Option<XoscConfig>,
|
|
/// Reference clock configuration.
|
|
pub ref_clk: RefClkConfig,
|
|
/// System clock configuration.
|
|
pub sys_clk: SysClkConfig,
|
|
/// Peripheral clock source configuration.
|
|
pub peri_clk_src: Option<PeriClkSrc>,
|
|
/// USB clock configuration.
|
|
pub usb_clk: Option<UsbClkConfig>,
|
|
/// ADC clock configuration.
|
|
pub adc_clk: Option<AdcClkConfig>,
|
|
/// RTC clock configuration.
|
|
#[cfg(feature = "rp2040")]
|
|
pub rtc_clk: Option<RtcClkConfig>,
|
|
/// Core voltage scaling (RP2040 only). Defaults to 1.10V if None.
|
|
#[cfg(feature = "rp2040")]
|
|
pub voltage_scale: Option<VoltageScale>,
|
|
/// Voltage stabilization delay in microseconds.
|
|
/// If not set, defaults will be used based on voltage level.
|
|
#[cfg(feature = "rp2040")]
|
|
pub voltage_stabilization_delay_us: Option<u32>,
|
|
// gpin0: Option<(u32, Gpin<'static, AnyPin>)>,
|
|
// gpin1: Option<(u32, Gpin<'static, AnyPin>)>,
|
|
}
|
|
|
|
impl Default for ClockConfig {
|
|
/// Creates a minimal default configuration with safe values.
|
|
///
|
|
/// This configuration uses the ring oscillator (ROSC) as the clock source
|
|
/// and sets minimal defaults that guarantee a working system. It's intended
|
|
/// as a starting point for manual configuration.
|
|
///
|
|
/// Most users should use one of the more specific configuration functions:
|
|
/// - `ClockConfig::crystal()` - Standard configuration with external crystal
|
|
/// - `ClockConfig::rosc()` - Configuration using only the internal oscillator
|
|
/// - `ClockConfig::at_sys_frequency_mhz()` - Configuration for a specific system frequency
|
|
fn default() -> Self {
|
|
Self {
|
|
rosc: None,
|
|
xosc: None,
|
|
ref_clk: RefClkConfig {
|
|
src: RefClkSrc::Rosc,
|
|
div: 1,
|
|
},
|
|
sys_clk: SysClkConfig {
|
|
src: SysClkSrc::Rosc,
|
|
div_int: 1,
|
|
div_frac: 0,
|
|
},
|
|
peri_clk_src: None,
|
|
usb_clk: None,
|
|
adc_clk: None,
|
|
#[cfg(feature = "rp2040")]
|
|
rtc_clk: None,
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_scale: None,
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_stabilization_delay_us: None,
|
|
// gpin0: None,
|
|
// gpin1: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ClockConfig {
|
|
/// Clock configuration derived from external crystal.
|
|
///
|
|
/// This uses default settings for most parameters, suitable for typical use cases.
|
|
/// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly.
|
|
pub fn crystal(crystal_hz: u32) -> Self {
|
|
Self {
|
|
rosc: Some(RoscConfig {
|
|
hz: 6_500_000,
|
|
range: RoscRange::Medium,
|
|
drive_strength: [0; 8],
|
|
div: 16,
|
|
}),
|
|
xosc: Some(XoscConfig {
|
|
hz: crystal_hz,
|
|
sys_pll: Some(PllConfig {
|
|
refdiv: 1,
|
|
fbdiv: 125,
|
|
#[cfg(feature = "rp2040")]
|
|
post_div1: 6,
|
|
#[cfg(feature = "_rp235x")]
|
|
post_div1: 5,
|
|
post_div2: 2,
|
|
}),
|
|
usb_pll: Some(PllConfig {
|
|
refdiv: 1,
|
|
fbdiv: 120,
|
|
post_div1: 6,
|
|
post_div2: 5,
|
|
}),
|
|
delay_multiplier: 128,
|
|
}),
|
|
ref_clk: RefClkConfig {
|
|
src: RefClkSrc::Xosc,
|
|
div: 1,
|
|
},
|
|
sys_clk: SysClkConfig {
|
|
src: SysClkSrc::PllSys,
|
|
div_int: 1,
|
|
div_frac: 0,
|
|
},
|
|
peri_clk_src: Some(PeriClkSrc::Sys),
|
|
// CLK USB = PLL USB (48MHz) / 1 = 48MHz
|
|
usb_clk: Some(UsbClkConfig {
|
|
src: UsbClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
}),
|
|
// CLK ADC = PLL USB (48MHZ) / 1 = 48MHz
|
|
adc_clk: Some(AdcClkConfig {
|
|
src: AdcClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
}),
|
|
// CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz
|
|
#[cfg(feature = "rp2040")]
|
|
rtc_clk: Some(RtcClkConfig {
|
|
src: RtcClkSrc::PllUsb,
|
|
div_int: 1024,
|
|
div_frac: 0,
|
|
phase: 0,
|
|
}),
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_scale: None, // Use hardware default (1.10V)
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_stabilization_delay_us: None,
|
|
// gpin0: None,
|
|
// gpin1: None,
|
|
}
|
|
}
|
|
|
|
/// Clock configuration from internal oscillator.
|
|
pub fn rosc() -> Self {
|
|
Self {
|
|
rosc: Some(RoscConfig {
|
|
hz: 140_000_000,
|
|
range: RoscRange::High,
|
|
drive_strength: [0; 8],
|
|
div: 1,
|
|
}),
|
|
xosc: None,
|
|
ref_clk: RefClkConfig {
|
|
src: RefClkSrc::Rosc,
|
|
div: 1,
|
|
},
|
|
sys_clk: SysClkConfig {
|
|
src: SysClkSrc::Rosc,
|
|
div_int: 1,
|
|
div_frac: 0,
|
|
},
|
|
peri_clk_src: Some(PeriClkSrc::Rosc),
|
|
usb_clk: None,
|
|
// CLK ADC = ROSC (140MHz) / 3 ≅ 48MHz
|
|
adc_clk: Some(AdcClkConfig {
|
|
src: AdcClkSrc::Rosc,
|
|
div: 3,
|
|
phase: 0,
|
|
}),
|
|
// CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz
|
|
#[cfg(feature = "rp2040")]
|
|
rtc_clk: Some(RtcClkConfig {
|
|
src: RtcClkSrc::Rosc,
|
|
div_int: 2986,
|
|
div_frac: 171,
|
|
phase: 0,
|
|
}),
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_scale: None, // Use hardware default (1.10V)
|
|
#[cfg(feature = "rp2040")]
|
|
voltage_stabilization_delay_us: None,
|
|
// gpin0: None,
|
|
// gpin1: None,
|
|
}
|
|
}
|
|
|
|
/// Configure the system clock to a specific frequency in MHz.
|
|
///
|
|
/// This is a user-friendly way to configure the system clock, similar to
|
|
/// the Pico SDK's approach. It automatically handles voltage scaling based on the
|
|
/// requested frequency and uses the standard 12MHz crystal found on most RP2040 boards.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `sys_freq_mhz` - The target system clock frequency in MHz
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// // Overclock to 200MHz
|
|
/// let config = ClockConfig::at_sys_frequency_mhz(200);
|
|
/// ```
|
|
#[cfg(feature = "rp2040")]
|
|
pub fn at_sys_frequency_mhz(sys_freq_mhz: u32) -> Self {
|
|
// For 125MHz, use exactly the same config as the default to avoid any differences
|
|
if sys_freq_mhz == 125 {
|
|
return Self::crystal(12_000_000);
|
|
}
|
|
|
|
// For other frequencies, provide appropriate voltage scaling and PLL configuration
|
|
// Standard crystal on Raspberry Pi Pico boards is 12MHz
|
|
const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000;
|
|
|
|
let sys_freq_hz = sys_freq_mhz * 1_000_000;
|
|
let config = Self::crystal_freq(DEFAULT_CRYSTAL_HZ, sys_freq_hz);
|
|
|
|
config
|
|
}
|
|
|
|
/// Configure the system clock to a specific frequency in Hz, using a custom crystal frequency.
|
|
///
|
|
/// This more flexible version allows specifying both the crystal frequency and target
|
|
/// system frequency for boards with non-standard crystals.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `crystal_hz` - The frequency of the external crystal in Hz
|
|
/// * `sys_freq_hz` - The target system clock frequency in Hz
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// // Use a non-standard 16MHz crystal to achieve 250MHz
|
|
/// let config = ClockConfig::with_custom_crystal(16_000_000, 250_000_000);
|
|
/// ```
|
|
#[cfg(feature = "rp2040")]
|
|
pub fn with_custom_crystal(crystal_hz: u32, sys_freq_hz: u32) -> Self {
|
|
Self::crystal_freq(crystal_hz, sys_freq_hz)
|
|
}
|
|
|
|
/// Configure clocks derived from an external crystal with specific system frequency.
|
|
///
|
|
/// This function calculates optimal PLL parameters to achieve the requested system
|
|
/// frequency from the given crystal frequency. It's used internally by higher-level
|
|
/// configuration functions.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `crystal_hz` - The frequency of the external crystal in Hz
|
|
/// * `sys_freq_hz` - The desired system clock frequency in Hz
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A ClockConfig configured to achieve the requested system frequency using the
|
|
/// specified crystal, or panic if no valid parameters can be found.
|
|
#[cfg(feature = "rp2040")]
|
|
fn crystal_freq(crystal_hz: u32, sys_freq_hz: u32) -> Self {
|
|
// Find optimal PLL parameters for the requested frequency
|
|
let sys_pll_params = find_pll_params(crystal_hz, sys_freq_hz)
|
|
.unwrap_or_else(|| panic!("Could not find valid PLL parameters for system clock"));
|
|
|
|
// Set the voltage scale based on the target frequency
|
|
// Higher frequencies require higher voltage
|
|
let voltage_scale = match sys_freq_hz {
|
|
freq if freq > 200_000_000 => Some(VoltageScale::V1_20),
|
|
freq if freq > 133_000_000 => Some(VoltageScale::V1_15),
|
|
_ => None, // Use default voltage (V1_10)
|
|
};
|
|
|
|
// For USB PLL, we always want 48MHz for USB
|
|
let usb_pll_params = if crystal_hz == 12_000_000 {
|
|
// For standard 12MHz crystal, use the default parameters
|
|
PllConfig {
|
|
refdiv: 1,
|
|
fbdiv: 120,
|
|
post_div1: 6,
|
|
post_div2: 5,
|
|
}
|
|
} else {
|
|
// For other crystals, calculate parameters to get 48MHz
|
|
find_pll_params(crystal_hz, 48_000_000)
|
|
.unwrap_or_else(|| panic!("Could not find valid PLL parameters for USB clock"))
|
|
};
|
|
|
|
Self {
|
|
rosc: Some(RoscConfig {
|
|
hz: 6_500_000,
|
|
range: RoscRange::Medium,
|
|
drive_strength: [0; 8],
|
|
div: 16,
|
|
}),
|
|
xosc: Some(XoscConfig {
|
|
hz: crystal_hz,
|
|
sys_pll: Some(sys_pll_params),
|
|
usb_pll: Some(usb_pll_params),
|
|
delay_multiplier: 128,
|
|
}),
|
|
ref_clk: RefClkConfig {
|
|
src: RefClkSrc::Xosc,
|
|
div: 1,
|
|
},
|
|
sys_clk: SysClkConfig {
|
|
src: SysClkSrc::PllSys,
|
|
div_int: 1,
|
|
div_frac: 0,
|
|
},
|
|
peri_clk_src: Some(PeriClkSrc::Sys),
|
|
usb_clk: Some(UsbClkConfig {
|
|
src: UsbClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
}),
|
|
adc_clk: Some(AdcClkConfig {
|
|
src: AdcClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
}),
|
|
rtc_clk: Some(RtcClkConfig {
|
|
src: RtcClkSrc::PllUsb,
|
|
div_int: 1024,
|
|
div_frac: 0,
|
|
phase: 0,
|
|
}),
|
|
voltage_scale,
|
|
voltage_stabilization_delay_us: None,
|
|
}
|
|
}
|
|
|
|
/// Configure with manual PLL settings for full control over system clock
|
|
///
|
|
/// This method provides a simple way to configure the system with custom PLL parameters
|
|
/// without needing to understand the full nested configuration structure.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `xosc_hz` - The frequency of the external crystal in Hz
|
|
/// * `pll_config` - The PLL configuration parameters to achieve desired frequency
|
|
/// * `voltage_scale` - Optional voltage scaling for overclocking (required for >133MHz)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A ClockConfig configured with the specified PLL parameters
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust,ignore
|
|
/// // Configure for 200MHz operation
|
|
/// let config = Config::default();
|
|
/// config.clocks = ClockConfig::manual_pll(
|
|
/// 12_000_000,
|
|
/// PllConfig {
|
|
/// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz)
|
|
/// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO)
|
|
/// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz)
|
|
/// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz)
|
|
/// },
|
|
/// Some(VoltageScale::V1_15)
|
|
/// );
|
|
/// ```
|
|
#[cfg(feature = "rp2040")]
|
|
pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, voltage_scale: Option<VoltageScale>) -> Self {
|
|
// Calculate the actual output frequency for documentation
|
|
// let ref_freq = xosc_hz / pll_config.refdiv as u32;
|
|
// let vco_freq = ref_freq * pll_config.fbdiv as u32;
|
|
// let sys_freq = vco_freq / ((pll_config.post_div1 * pll_config.post_div2) as u32);
|
|
|
|
// Validate PLL parameters
|
|
assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters");
|
|
|
|
let mut config = Self::default();
|
|
|
|
config.xosc = Some(XoscConfig {
|
|
hz: xosc_hz,
|
|
sys_pll: Some(pll_config),
|
|
usb_pll: Some(PllConfig {
|
|
refdiv: 1,
|
|
fbdiv: 120,
|
|
post_div1: 6,
|
|
post_div2: 5,
|
|
}),
|
|
delay_multiplier: 128,
|
|
});
|
|
|
|
config.ref_clk = RefClkConfig {
|
|
src: RefClkSrc::Xosc,
|
|
div: 1,
|
|
};
|
|
|
|
config.sys_clk = SysClkConfig {
|
|
src: SysClkSrc::PllSys,
|
|
div_int: 1,
|
|
div_frac: 0,
|
|
};
|
|
|
|
config.voltage_scale = voltage_scale;
|
|
config.peri_clk_src = Some(PeriClkSrc::Sys);
|
|
|
|
// Set reasonable defaults for other clocks
|
|
config.usb_clk = Some(UsbClkConfig {
|
|
src: UsbClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
});
|
|
|
|
config.adc_clk = Some(AdcClkConfig {
|
|
src: AdcClkSrc::PllUsb,
|
|
div: 1,
|
|
phase: 0,
|
|
});
|
|
|
|
config.rtc_clk = Some(RtcClkConfig {
|
|
src: RtcClkSrc::PllUsb,
|
|
div_int: 1024,
|
|
div_frac: 0,
|
|
phase: 0,
|
|
});
|
|
|
|
config
|
|
}
|
|
}
|
|
|
|
/// ROSC freq range.
|
|
#[repr(u16)]
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum RoscRange {
|
|
/// Low range.
|
|
Low = pac::rosc::vals::FreqRange::LOW.0,
|
|
/// Medium range (1.33x low)
|
|
Medium = pac::rosc::vals::FreqRange::MEDIUM.0,
|
|
/// High range (2x low)
|
|
High = pac::rosc::vals::FreqRange::HIGH.0,
|
|
/// Too high. Should not be used.
|
|
TooHigh = pac::rosc::vals::FreqRange::TOOHIGH.0,
|
|
}
|
|
|
|
/// On-chip ring oscillator configuration.
|
|
pub struct RoscConfig {
|
|
/// Final frequency of the oscillator, after the divider has been applied.
|
|
/// The oscillator has a nominal frequency of 6.5MHz at medium range with
|
|
/// divider 16 and all drive strengths set to 0, other values should be
|
|
/// measured in situ.
|
|
pub hz: u32,
|
|
/// Oscillator range.
|
|
pub range: RoscRange,
|
|
/// Drive strength for oscillator.
|
|
pub drive_strength: [u8; 8],
|
|
/// Output divider.
|
|
pub div: u16,
|
|
}
|
|
|
|
/// Crystal oscillator configuration.
|
|
pub struct XoscConfig {
|
|
/// Final frequency of the oscillator.
|
|
pub hz: u32,
|
|
/// Configuring PLL for the system clock.
|
|
pub sys_pll: Option<PllConfig>,
|
|
/// Configuring PLL for the USB clock.
|
|
pub usb_pll: Option<PllConfig>,
|
|
/// Multiplier for the startup delay.
|
|
pub delay_multiplier: u32,
|
|
}
|
|
|
|
/// PLL configuration.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct PllConfig {
|
|
/// Reference divisor.
|
|
pub refdiv: u8,
|
|
/// Feedback divisor.
|
|
pub fbdiv: u16,
|
|
/// Output divisor 1.
|
|
pub post_div1: u8,
|
|
/// Output divisor 2.
|
|
pub post_div2: u8,
|
|
}
|
|
|
|
impl PllConfig {
|
|
/// Calculate the output frequency for this PLL configuration
|
|
/// given an input frequency.
|
|
pub fn output_frequency(&self, input_hz: u32) -> u32 {
|
|
let ref_freq = input_hz / self.refdiv as u32;
|
|
let vco_freq = ref_freq * self.fbdiv as u32;
|
|
vco_freq / ((self.post_div1 * self.post_div2) as u32)
|
|
}
|
|
|
|
/// Check if this PLL configuration is valid for the given input frequency.
|
|
pub fn is_valid(&self, input_hz: u32) -> bool {
|
|
// Check divisor constraints
|
|
if self.refdiv < 1 || self.refdiv > 63 {
|
|
return false;
|
|
}
|
|
if self.fbdiv < 16 || self.fbdiv > 320 {
|
|
return false;
|
|
}
|
|
if self.post_div1 < 1 || self.post_div1 > 7 {
|
|
return false;
|
|
}
|
|
if self.post_div2 < 1 || self.post_div2 > 7 {
|
|
return false;
|
|
}
|
|
if self.post_div2 > self.post_div1 {
|
|
return false;
|
|
}
|
|
|
|
// Calculate reference frequency
|
|
let ref_freq = input_hz / self.refdiv as u32;
|
|
|
|
// Check reference frequency range
|
|
if ref_freq < 5_000_000 || ref_freq > 800_000_000 {
|
|
return false;
|
|
}
|
|
|
|
// Calculate VCO frequency
|
|
let vco_freq = ref_freq * self.fbdiv as u32;
|
|
|
|
// Check VCO frequency range
|
|
vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000
|
|
}
|
|
}
|
|
|
|
/// Reference clock config.
|
|
pub struct RefClkConfig {
|
|
/// Reference clock source.
|
|
pub src: RefClkSrc,
|
|
/// Reference clock divider.
|
|
pub div: u8,
|
|
}
|
|
|
|
/// Reference clock source.
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum RefClkSrc {
|
|
/// XOSC.
|
|
Xosc,
|
|
/// ROSC.
|
|
Rosc,
|
|
/// PLL USB.
|
|
PllUsb,
|
|
// Gpin0,
|
|
// Gpin1,
|
|
}
|
|
|
|
/// SYS clock source.
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum SysClkSrc {
|
|
/// REF.
|
|
Ref,
|
|
/// PLL SYS.
|
|
PllSys,
|
|
/// PLL USB.
|
|
PllUsb,
|
|
/// ROSC.
|
|
Rosc,
|
|
/// XOSC.
|
|
Xosc,
|
|
// Gpin0,
|
|
// Gpin1,
|
|
}
|
|
|
|
/// SYS clock config.
|
|
pub struct SysClkConfig {
|
|
/// SYS clock source.
|
|
pub src: SysClkSrc,
|
|
/// SYS clock divider.
|
|
#[cfg(feature = "rp2040")]
|
|
pub div_int: u32,
|
|
/// SYS clock fraction.
|
|
#[cfg(feature = "rp2040")]
|
|
pub div_frac: u8,
|
|
/// SYS clock divider.
|
|
#[cfg(feature = "_rp235x")]
|
|
pub div_int: u16,
|
|
/// SYS clock fraction.
|
|
#[cfg(feature = "_rp235x")]
|
|
pub div_frac: u16,
|
|
}
|
|
|
|
/// USB clock source.
|
|
#[repr(u8)]
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum UsbClkSrc {
|
|
/// PLL USB.
|
|
PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
|
/// PLL SYS.
|
|
PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS as _,
|
|
/// ROSC.
|
|
Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _,
|
|
/// XOSC.
|
|
Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _,
|
|
// Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
|
|
// Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
|
|
}
|
|
|
|
/// USB clock config.
|
|
pub struct UsbClkConfig {
|
|
/// USB clock source.
|
|
pub src: UsbClkSrc,
|
|
/// USB clock divider.
|
|
pub div: u8,
|
|
/// USB clock phase.
|
|
pub phase: u8,
|
|
}
|
|
|
|
/// ADC clock source.
|
|
#[repr(u8)]
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum AdcClkSrc {
|
|
/// PLL USB.
|
|
PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
|
/// PLL SYS.
|
|
PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS as _,
|
|
/// ROSC.
|
|
Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
|
|
/// XOSC.
|
|
Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _,
|
|
// Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
|
|
// Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
|
|
}
|
|
|
|
/// ADC clock config.
|
|
pub struct AdcClkConfig {
|
|
/// ADC clock source.
|
|
pub src: AdcClkSrc,
|
|
/// ADC clock divider.
|
|
pub div: u8,
|
|
/// ADC clock phase.
|
|
pub phase: u8,
|
|
}
|
|
|
|
/// RTC clock source.
|
|
#[repr(u8)]
|
|
#[non_exhaustive]
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
#[cfg(feature = "rp2040")]
|
|
pub enum RtcClkSrc {
|
|
/// PLL USB.
|
|
PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
|
/// PLL SYS.
|
|
PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS as _,
|
|
/// ROSC.
|
|
Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _,
|
|
/// XOSC.
|
|
Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _,
|
|
// Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
|
|
// Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
|
|
}
|
|
|
|
/// RTC clock config.
|
|
#[cfg(feature = "rp2040")]
|
|
pub struct RtcClkConfig {
|
|
/// RTC clock source.
|
|
pub src: RtcClkSrc,
|
|
/// RTC clock divider.
|
|
pub div_int: u32,
|
|
/// RTC clock divider fraction.
|
|
pub div_frac: u8,
|
|
/// RTC clock phase.
|
|
pub phase: u8,
|
|
}
|
|
|
|
/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency
|
|
/// based on the input frequency.
|
|
///
|
|
/// This function searches for the best PLL configuration to achieve the requested target frequency
|
|
/// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability
|
|
/// over exact frequency matching by using larger divisors where possible.
|
|
///
|
|
/// # Parameters
|
|
///
|
|
/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz)
|
|
/// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Some(PllConfig)` if valid parameters were found
|
|
/// * `None` if no valid parameters could be found for the requested combination
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```
|
|
/// // Find parameters for 133MHz system clock from 12MHz crystal
|
|
/// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap();
|
|
/// ```
|
|
#[cfg(feature = "rp2040")]
|
|
fn find_pll_params(input_hz: u32, target_hz: u32) -> Option<PllConfig> {
|
|
// Fixed reference divider for system PLL
|
|
const PLL_SYS_REFDIV: u8 = 1;
|
|
|
|
// Calculate reference frequency
|
|
let reference_freq = input_hz / PLL_SYS_REFDIV as u32;
|
|
|
|
// Start from highest fbdiv for better stability (like SDK does)
|
|
for fbdiv in (16..=320).rev() {
|
|
let vco_freq = reference_freq * fbdiv;
|
|
|
|
// Check VCO frequency is within valid range
|
|
if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 {
|
|
continue;
|
|
}
|
|
|
|
// Try all possible postdiv combinations starting from larger values
|
|
// (more conservative/stable approach)
|
|
for post_div1 in (1..=7).rev() {
|
|
for post_div2 in (1..=post_div1).rev() {
|
|
let out_freq = vco_freq / (post_div1 * post_div2) as u32;
|
|
|
|
// Check if we get the exact target frequency without remainder
|
|
if out_freq == target_hz && (vco_freq % (post_div1 * post_div2) as u32 == 0) {
|
|
return Some(PllConfig {
|
|
refdiv: PLL_SYS_REFDIV,
|
|
fbdiv: fbdiv as u16,
|
|
post_div1: post_div1 as u8,
|
|
post_div2: post_div2 as u8,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we couldn't find an exact match, find the closest match
|
|
let mut best_config = None;
|
|
let mut min_diff = u32::MAX;
|
|
|
|
for fbdiv in (16..=320).rev() {
|
|
let vco_freq = reference_freq * fbdiv;
|
|
|
|
if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 {
|
|
continue;
|
|
}
|
|
|
|
for post_div1 in (1..=7).rev() {
|
|
for post_div2 in (1..=post_div1).rev() {
|
|
let out_freq = vco_freq / (post_div1 * post_div2) as u32;
|
|
let diff = if out_freq > target_hz {
|
|
out_freq - target_hz
|
|
} else {
|
|
target_hz - out_freq
|
|
};
|
|
|
|
// If this is closer to the target, save it
|
|
if diff < min_diff {
|
|
min_diff = diff;
|
|
best_config = Some(PllConfig {
|
|
refdiv: PLL_SYS_REFDIV,
|
|
fbdiv: fbdiv as u16,
|
|
post_div1: post_div1 as u8,
|
|
post_div2: post_div2 as u8,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the closest match if we found one
|
|
best_config
|
|
}
|
|
|
|
/// safety: must be called exactly once at bootup
|
|
pub(crate) unsafe fn init(config: ClockConfig) {
|
|
// Reset everything except:
|
|
// - QSPI (we're using it to run this code!)
|
|
// - PLLs (it may be suicide if that's what's clocking us)
|
|
// - USB, SYSCFG (breaks usb-to-swd on core1)
|
|
// - RTC (else there would be no more time...)
|
|
let mut peris = reset::ALL_PERIPHERALS;
|
|
peris.set_io_qspi(false);
|
|
// peris.set_io_bank0(false); // might be suicide if we're clocked from gpin
|
|
peris.set_pads_qspi(false);
|
|
peris.set_pll_sys(false);
|
|
peris.set_pll_usb(false);
|
|
peris.set_usbctrl(false);
|
|
peris.set_syscfg(false);
|
|
//peris.set_rtc(false);
|
|
reset::reset(peris);
|
|
|
|
// Disable resus that may be enabled from previous software
|
|
let c = pac::CLOCKS;
|
|
c.clk_sys_resus_ctrl()
|
|
.write_value(pac::clocks::regs::ClkSysResusCtrl(0));
|
|
|
|
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
|
|
c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF));
|
|
#[cfg(feature = "rp2040")]
|
|
while c.clk_sys_selected().read() != 1 {}
|
|
#[cfg(feature = "_rp235x")]
|
|
while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1) {}
|
|
c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH));
|
|
#[cfg(feature = "rp2040")]
|
|
while c.clk_ref_selected().read() != 1 {}
|
|
#[cfg(feature = "_rp235x")]
|
|
while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {}
|
|
|
|
// Set Core Voltage (RP2040 only), if we have config for it and we're not using the default
|
|
#[cfg(feature = "rp2040")]
|
|
if let Some(voltage) = config.voltage_scale {
|
|
let vreg = pac::VREG_AND_CHIP_RESET;
|
|
let current_vsel = vreg.vreg().read().vsel();
|
|
let target_vsel = voltage as u8;
|
|
|
|
if target_vsel != current_vsel {
|
|
// Use modify() instead of write() to preserve the HIZ and EN bits - otherwise we will disable the regulator when changing voltage
|
|
vreg.vreg().modify(|w| w.set_vsel(target_vsel));
|
|
|
|
// Wait for the voltage to stabilize. Use the provided delay or default based on voltage
|
|
let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| {
|
|
match voltage {
|
|
VoltageScale::V1_15 => 1000, // 1ms for 1.15V
|
|
VoltageScale::V1_20 | VoltageScale::V1_25 | VoltageScale::V1_30 => 2000, // 2ms for higher voltages
|
|
_ => 0, // no delay for all others
|
|
}
|
|
});
|
|
|
|
// We need a clock that's guaranteed to be running at this point
|
|
// Use ROSC which should be configured by now
|
|
let rosc_freq_rough = 6_000_000; // Rough ROSC frequency estimate
|
|
let cycles_per_us = rosc_freq_rough / 1_000_000;
|
|
let delay_cycles = settling_time_us * cycles_per_us;
|
|
|
|
// Wait for voltage to stabilize
|
|
cortex_m::asm::delay(delay_cycles);
|
|
|
|
// Only now set the BOD level after voltage has stabilized
|
|
vreg.bod().write(|w| w.set_vsel(voltage.recommended_bod()));
|
|
}
|
|
}
|
|
|
|
// Configure ROSC first if present
|
|
let rosc_freq = match config.rosc {
|
|
Some(config) => configure_rosc(config),
|
|
None => 0,
|
|
};
|
|
CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed);
|
|
|
|
// Configure XOSC - we'll need this for our temporary stable clock
|
|
let xosc_freq = match &config.xosc {
|
|
Some(config) => {
|
|
start_xosc(config.hz, config.delay_multiplier);
|
|
config.hz
|
|
}
|
|
None => 0,
|
|
};
|
|
CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed);
|
|
|
|
// SETUP TEMPORARY STABLE CLOCKS FIRST
|
|
// Configure USB PLL for our stable temporary clock
|
|
// This follows the SDK's approach of using USB PLL as a stable intermediate clock
|
|
let pll_usb_freq = match &config.xosc {
|
|
Some(config) => match &config.usb_pll {
|
|
Some(pll_usb_config) => {
|
|
// Reset USB PLL
|
|
let mut peris = reset::Peripherals(0);
|
|
peris.set_pll_usb(true);
|
|
reset::reset(peris);
|
|
reset::unreset_wait(peris);
|
|
|
|
// Configure the USB PLL - this should give us 48MHz
|
|
let usb_pll_freq = configure_pll(pac::PLL_USB, xosc_freq, *pll_usb_config);
|
|
CLOCKS.pll_usb.store(usb_pll_freq, Ordering::Relaxed);
|
|
usb_pll_freq
|
|
}
|
|
None => 0,
|
|
},
|
|
None => 0,
|
|
};
|
|
|
|
// Configure REF clock to use XOSC
|
|
c.clk_ref_ctrl().write(|w| {
|
|
w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC);
|
|
});
|
|
#[cfg(feature = "rp2040")]
|
|
while c.clk_ref_selected().read() != (1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {}
|
|
#[cfg(feature = "_rp235x")]
|
|
while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ClkRefCtrlSrc::XOSC_CLKSRC as u32) {}
|
|
|
|
// First switch the system clock to a stable source (USB PLL at 48MHz)
|
|
// This follows the Pico SDK's approach to ensure stability during reconfiguration
|
|
c.clk_sys_ctrl().write(|w| {
|
|
w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_USB);
|
|
w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX);
|
|
});
|
|
|
|
#[cfg(feature = "rp2040")]
|
|
while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32) {}
|
|
#[cfg(feature = "_rp235x")]
|
|
while c.clk_sys_selected().read()
|
|
!= pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX as u32)
|
|
{}
|
|
|
|
// Short delay after switching to USB PLL to ensure stability
|
|
cortex_m::asm::delay(100);
|
|
|
|
// NOW CONFIGURE THE SYSTEM PLL (safely, since we're running from the USB PLL)
|
|
let pll_sys_freq = match &config.xosc {
|
|
Some(config) => match &config.sys_pll {
|
|
Some(sys_pll_config) => {
|
|
// Reset SYS PLL
|
|
let mut peris = reset::Peripherals(0);
|
|
peris.set_pll_sys(true);
|
|
reset::reset(peris);
|
|
reset::unreset_wait(peris);
|
|
|
|
// Configure the SYS PLL
|
|
let pll_sys_freq = configure_pll(pac::PLL_SYS, xosc_freq, *sys_pll_config);
|
|
|
|
// Ensure PLL is locked and stable
|
|
cortex_m::asm::delay(100);
|
|
|
|
CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed);
|
|
pll_sys_freq
|
|
}
|
|
None => 0,
|
|
},
|
|
None => 0,
|
|
};
|
|
|
|
// Configure tick generation using REF clock
|
|
let clk_ref_freq = xosc_freq; // REF clock is now XOSC
|
|
CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed);
|
|
#[cfg(feature = "rp2040")]
|
|
pac::WATCHDOG.tick().write(|w| {
|
|
w.set_cycles((clk_ref_freq / 1_000_000) as u16);
|
|
w.set_enable(true);
|
|
});
|
|
// Configure tick generator on the 2350
|
|
#[cfg(feature = "_rp235x")]
|
|
{
|
|
let cycle_count = clk_ref_freq / 1_000_000;
|
|
|
|
pac::TICKS.timer0_cycles().write(|w| w.0 = cycle_count);
|
|
pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true));
|
|
|
|
pac::TICKS.watchdog_cycles().write(|w| w.0 = cycle_count);
|
|
pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true));
|
|
}
|
|
|
|
// NOW SWITCH THE SYSTEM CLOCK TO THE CONFIGURED SOURCE
|
|
// The SYS PLL is now stable and we can safely switch to it
|
|
let (sys_src, sys_aux, clk_sys_freq) = {
|
|
use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src};
|
|
let (src, aux, freq) = match config.sys_clk.src {
|
|
SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq),
|
|
SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq),
|
|
SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq),
|
|
SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq),
|
|
SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq),
|
|
// SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq),
|
|
// SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq),
|
|
};
|
|
let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64;
|
|
(src, aux, ((freq as u64 * 256) / div) as u32)
|
|
};
|
|
assert!(clk_sys_freq != 0);
|
|
CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed);
|
|
|
|
// Set the divider before changing the source if it's increasing
|
|
if config.sys_clk.div_int > 1 || config.sys_clk.div_frac > 0 {
|
|
c.clk_sys_div().write(|w| {
|
|
w.set_int(config.sys_clk.div_int);
|
|
w.set_frac(config.sys_clk.div_frac);
|
|
});
|
|
}
|
|
|
|
// Configure aux source first if needed
|
|
if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX {
|
|
c.clk_sys_ctrl().modify(|w| {
|
|
w.set_auxsrc(sys_aux);
|
|
});
|
|
}
|
|
|
|
// Now set the source
|
|
c.clk_sys_ctrl().write(|w| {
|
|
if sys_src == ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX {
|
|
w.set_auxsrc(sys_aux);
|
|
}
|
|
w.set_src(sys_src);
|
|
});
|
|
|
|
// Wait for the clock to be selected
|
|
#[cfg(feature = "rp2040")]
|
|
while c.clk_sys_selected().read() != (1 << sys_src as u32) {}
|
|
#[cfg(feature = "_rp235x")]
|
|
while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {}
|
|
|
|
// Short delay after final clock switch to ensure stability
|
|
cortex_m::asm::delay(100);
|
|
|
|
// Set the divider after changing the source if it's decreasing
|
|
if config.sys_clk.div_int == 1 && config.sys_clk.div_frac == 0 {
|
|
c.clk_sys_div().write(|w| {
|
|
w.set_int(config.sys_clk.div_int);
|
|
w.set_frac(config.sys_clk.div_frac);
|
|
});
|
|
}
|
|
|
|
// CONFIGURE PERIPHERAL CLOCK
|
|
let mut peris = reset::ALL_PERIPHERALS;
|
|
|
|
if let Some(src) = config.peri_clk_src {
|
|
c.clk_peri_ctrl().write(|w| {
|
|
w.set_enable(true);
|
|
w.set_auxsrc(ClkPeriCtrlAuxsrc::from_bits(src as _));
|
|
});
|
|
let peri_freq = match src {
|
|
PeriClkSrc::Sys => clk_sys_freq,
|
|
PeriClkSrc::PllSys => pll_sys_freq,
|
|
PeriClkSrc::PllUsb => pll_usb_freq,
|
|
PeriClkSrc::Rosc => rosc_freq,
|
|
PeriClkSrc::Xosc => xosc_freq,
|
|
// PeriClkSrc::Gpin0 => gpin0_freq,
|
|
// PeriClkSrc::Gpin1 => gpin1_freq,
|
|
};
|
|
assert!(peri_freq != 0);
|
|
CLOCKS.peri.store(peri_freq, Ordering::Relaxed);
|
|
} else {
|
|
peris.set_spi0(false);
|
|
peris.set_spi1(false);
|
|
peris.set_uart0(false);
|
|
peris.set_uart1(false);
|
|
CLOCKS.peri.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
// CONFIGURE USB CLOCK
|
|
if let Some(conf) = config.usb_clk {
|
|
c.clk_usb_div().write(|w| w.set_int(conf.div));
|
|
c.clk_usb_ctrl().write(|w| {
|
|
w.set_phase(conf.phase);
|
|
w.set_enable(true);
|
|
w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _));
|
|
});
|
|
let usb_freq = match conf.src {
|
|
UsbClkSrc::PllUsb => pll_usb_freq,
|
|
UsbClkSrc::PllSys => pll_sys_freq,
|
|
UsbClkSrc::Rosc => rosc_freq,
|
|
UsbClkSrc::Xosc => xosc_freq,
|
|
// UsbClkSrc::Gpin0 => gpin0_freq,
|
|
// UsbClkSrc::Gpin1 => gpin1_freq,
|
|
};
|
|
assert!(usb_freq != 0);
|
|
assert!(conf.div >= 1 && conf.div <= 4);
|
|
CLOCKS.usb.store(usb_freq / conf.div as u32, Ordering::Relaxed);
|
|
} else {
|
|
peris.set_usbctrl(false);
|
|
CLOCKS.usb.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
// CONFIGURE ADC CLOCK
|
|
if let Some(conf) = config.adc_clk {
|
|
c.clk_adc_div().write(|w| w.set_int(conf.div));
|
|
c.clk_adc_ctrl().write(|w| {
|
|
w.set_phase(conf.phase);
|
|
w.set_enable(true);
|
|
w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _));
|
|
});
|
|
let adc_in_freq = match conf.src {
|
|
AdcClkSrc::PllUsb => pll_usb_freq,
|
|
AdcClkSrc::PllSys => pll_sys_freq,
|
|
AdcClkSrc::Rosc => rosc_freq,
|
|
AdcClkSrc::Xosc => xosc_freq,
|
|
// AdcClkSrc::Gpin0 => gpin0_freq,
|
|
// AdcClkSrc::Gpin1 => gpin1_freq,
|
|
};
|
|
assert!(adc_in_freq != 0);
|
|
assert!(conf.div >= 1 && conf.div <= 4);
|
|
CLOCKS.adc.store(adc_in_freq / conf.div as u32, Ordering::Relaxed);
|
|
} else {
|
|
peris.set_adc(false);
|
|
CLOCKS.adc.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
// CONFIGURE RTC CLOCK
|
|
#[cfg(feature = "rp2040")]
|
|
if let Some(conf) = config.rtc_clk {
|
|
c.clk_rtc_div().write(|w| {
|
|
w.set_int(conf.div_int);
|
|
w.set_frac(conf.div_frac);
|
|
});
|
|
c.clk_rtc_ctrl().write(|w| {
|
|
w.set_phase(conf.phase);
|
|
w.set_enable(true);
|
|
w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _));
|
|
});
|
|
let rtc_in_freq = match conf.src {
|
|
RtcClkSrc::PllUsb => pll_usb_freq,
|
|
RtcClkSrc::PllSys => pll_sys_freq,
|
|
RtcClkSrc::Rosc => rosc_freq,
|
|
RtcClkSrc::Xosc => xosc_freq,
|
|
// RtcClkSrc::Gpin0 => gpin0_freq,
|
|
// RtcClkSrc::Gpin1 => gpin1_freq,
|
|
};
|
|
assert!(rtc_in_freq != 0);
|
|
assert!(config.sys_clk.div_int <= 0x1000000);
|
|
CLOCKS.rtc.store(
|
|
((rtc_in_freq as u64 * 256) / (conf.div_int as u64 * 256 + conf.div_frac as u64)) as u16,
|
|
Ordering::Relaxed,
|
|
);
|
|
} else {
|
|
peris.set_rtc(false);
|
|
CLOCKS.rtc.store(0, Ordering::Relaxed);
|
|
}
|
|
|
|
// rp235x specific clocks
|
|
#[cfg(feature = "_rp235x")]
|
|
{
|
|
// TODO hstx clock
|
|
peris.set_hstx(false);
|
|
}
|
|
|
|
// Peripheral clocks should now all be running
|
|
reset::unreset_wait(peris);
|
|
}
|
|
|
|
fn configure_rosc(config: RoscConfig) -> u32 {
|
|
let p = pac::ROSC;
|
|
|
|
p.freqa().write(|w| {
|
|
w.set_passwd(pac::rosc::vals::Passwd::PASS);
|
|
w.set_ds0(config.drive_strength[0]);
|
|
w.set_ds1(config.drive_strength[1]);
|
|
w.set_ds2(config.drive_strength[2]);
|
|
w.set_ds3(config.drive_strength[3]);
|
|
});
|
|
|
|
p.freqb().write(|w| {
|
|
w.set_passwd(pac::rosc::vals::Passwd::PASS);
|
|
w.set_ds4(config.drive_strength[4]);
|
|
w.set_ds5(config.drive_strength[5]);
|
|
w.set_ds6(config.drive_strength[6]);
|
|
w.set_ds7(config.drive_strength[7]);
|
|
});
|
|
|
|
p.div().write(|w| {
|
|
w.set_div(pac::rosc::vals::Div(config.div + pac::rosc::vals::Div::PASS.0));
|
|
});
|
|
|
|
p.ctrl().write(|w| {
|
|
w.set_enable(pac::rosc::vals::Enable::ENABLE);
|
|
w.set_freq_range(pac::rosc::vals::FreqRange(config.range as u16));
|
|
});
|
|
|
|
config.hz
|
|
}
|
|
|
|
/// ROSC clock frequency.
|
|
pub fn rosc_freq() -> u32 {
|
|
CLOCKS.rosc.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// XOSC clock frequency.
|
|
pub fn xosc_freq() -> u32 {
|
|
CLOCKS.xosc.load(Ordering::Relaxed)
|
|
}
|
|
|
|
// pub fn gpin0_freq() -> u32 {
|
|
// CLOCKS.gpin0.load(Ordering::Relaxed)
|
|
// }
|
|
// pub fn gpin1_freq() -> u32 {
|
|
// CLOCKS.gpin1.load(Ordering::Relaxed)
|
|
// }
|
|
|
|
/// PLL SYS clock frequency.
|
|
pub fn pll_sys_freq() -> u32 {
|
|
CLOCKS.pll_sys.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// PLL USB clock frequency.
|
|
pub fn pll_usb_freq() -> u32 {
|
|
CLOCKS.pll_usb.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// SYS clock frequency.
|
|
pub fn clk_sys_freq() -> u32 {
|
|
CLOCKS.sys.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// REF clock frequency.
|
|
pub fn clk_ref_freq() -> u32 {
|
|
CLOCKS.reference.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// Peripheral clock frequency.
|
|
pub fn clk_peri_freq() -> u32 {
|
|
CLOCKS.peri.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// USB clock frequency.
|
|
pub fn clk_usb_freq() -> u32 {
|
|
CLOCKS.usb.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// ADC clock frequency.
|
|
pub fn clk_adc_freq() -> u32 {
|
|
CLOCKS.adc.load(Ordering::Relaxed)
|
|
}
|
|
|
|
/// RTC clock frequency.
|
|
#[cfg(feature = "rp2040")]
|
|
pub fn clk_rtc_freq() -> u16 {
|
|
CLOCKS.rtc.load(Ordering::Relaxed)
|
|
}
|
|
|
|
fn start_xosc(crystal_hz: u32, delay_multiplier: u32) {
|
|
let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256;
|
|
pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16));
|
|
pac::XOSC.ctrl().write(|w| {
|
|
w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ);
|
|
w.set_enable(pac::xosc::vals::Enable::ENABLE);
|
|
});
|
|
while !pac::XOSC.status().read().stable() {}
|
|
}
|
|
|
|
/// PLL (Phase-Locked Loop) configuration
|
|
#[inline(always)]
|
|
fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 {
|
|
// Calculate reference frequency
|
|
let ref_freq = input_freq / config.refdiv as u32;
|
|
|
|
// Validate PLL parameters
|
|
// Feedback divider (FBDIV) must be between 16 and 320
|
|
assert!(config.fbdiv >= 16 && config.fbdiv <= 320);
|
|
|
|
// Post divider 1 (POSTDIV1) must be between 1 and 7
|
|
assert!(config.post_div1 >= 1 && config.post_div1 <= 7);
|
|
|
|
// Post divider 2 (POSTDIV2) must be between 1 and 7
|
|
assert!(config.post_div2 >= 1 && config.post_div2 <= 7);
|
|
|
|
// Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1)
|
|
assert!(config.post_div2 <= config.post_div1);
|
|
|
|
// Reference divider (REFDIV) must be between 1 and 63
|
|
assert!(config.refdiv >= 1 && config.refdiv <= 63);
|
|
|
|
// Reference frequency (REF_FREQ) must be between 5MHz and 800MHz
|
|
assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000);
|
|
|
|
// Calculate VCO frequency
|
|
let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32);
|
|
|
|
// VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz
|
|
assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000);
|
|
|
|
// We follow the SDK's approach to PLL configuration which is:
|
|
// 1. Power down PLL
|
|
// 2. Configure the reference divider
|
|
// 3. Configure the feedback divider
|
|
// 4. Power up PLL and VCO
|
|
// 5. Wait for PLL to lock
|
|
// 6. Configure post-dividers
|
|
// 7. Enable post-divider output
|
|
|
|
// 1. Power down PLL before configuration
|
|
p.pwr().write(|w| {
|
|
w.set_pd(true); // Power down the PLL
|
|
w.set_vcopd(true); // Power down the VCO
|
|
w.set_postdivpd(true); // Power down the post divider
|
|
w.set_dsmpd(true); // Disable fractional mode
|
|
*w
|
|
});
|
|
|
|
// Short delay after powering down
|
|
cortex_m::asm::delay(10);
|
|
|
|
// 2. Configure reference divider first
|
|
p.cs().write(|w| w.set_refdiv(config.refdiv as _));
|
|
|
|
// 3. Configure feedback divider
|
|
p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv));
|
|
|
|
// 4. Power up PLL and VCO, but keep post divider powered down during initial lock
|
|
p.pwr().write(|w| {
|
|
w.set_pd(false); // Power up the PLL
|
|
w.set_vcopd(false); // Power up the VCO
|
|
w.set_postdivpd(true); // Keep post divider powered down during initial lock
|
|
w.set_dsmpd(true); // Disable fractional mode (simpler configuration)
|
|
*w
|
|
});
|
|
|
|
// 5. Wait for PLL to lock with a timeout
|
|
let mut timeout = 1_000_000; // Reasonable timeout value
|
|
while !p.cs().read().lock() {
|
|
timeout -= 1;
|
|
if timeout == 0 {
|
|
// PLL failed to lock, return 0 to indicate failure
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// 6. Configure post dividers after PLL is locked
|
|
p.prim().write(|w| {
|
|
w.set_postdiv1(config.post_div1);
|
|
w.set_postdiv2(config.post_div2);
|
|
});
|
|
|
|
// 7. Enable the post divider output
|
|
p.pwr().modify(|w| {
|
|
w.set_postdivpd(false); // Power up post divider
|
|
*w
|
|
});
|
|
|
|
// Final delay to ensure everything is stable
|
|
cortex_m::asm::delay(100);
|
|
|
|
// Calculate and return actual output frequency
|
|
vco_freq / ((config.post_div1 * config.post_div2) as u32)
|
|
}
|
|
|
|
/// General purpose input clock pin.
|
|
pub trait GpinPin: crate::gpio::Pin {
|
|
/// Pin number.
|
|
const NR: usize;
|
|
}
|
|
|
|
macro_rules! impl_gpinpin {
|
|
($name:ident, $pin_num:expr, $gpin_num:expr) => {
|
|
impl GpinPin for crate::peripherals::$name {
|
|
const NR: usize = $gpin_num;
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_gpinpin!(PIN_20, 20, 0);
|
|
impl_gpinpin!(PIN_22, 22, 1);
|
|
|
|
/// General purpose clock input driver.
|
|
pub struct Gpin<'d, T: GpinPin> {
|
|
gpin: Peri<'d, AnyPin>,
|
|
_phantom: PhantomData<T>,
|
|
}
|
|
|
|
impl<'d, T: GpinPin> Gpin<'d, T> {
|
|
/// Create new gpin driver.
|
|
pub fn new(gpin: Peri<'d, T>) -> Self {
|
|
#[cfg(feature = "rp2040")]
|
|
gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08));
|
|
|
|
// On RP2350 GPIN changed from F8 toF9
|
|
#[cfg(feature = "_rp235x")]
|
|
gpin.gpio().ctrl().write(|w| w.set_funcsel(0x09));
|
|
|
|
#[cfg(feature = "_rp235x")]
|
|
gpin.pad_ctrl().write(|w| {
|
|
w.set_iso(false);
|
|
});
|
|
|
|
Gpin {
|
|
gpin: gpin.into(),
|
|
_phantom: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'d, T: GpinPin> Drop for Gpin<'d, T> {
|
|
fn drop(&mut self) {
|
|
self.gpin.pad_ctrl().write(|_| {});
|
|
self.gpin
|
|
.gpio()
|
|
.ctrl()
|
|
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _));
|
|
}
|
|
}
|
|
|
|
/// General purpose clock output pin.
|
|
pub trait GpoutPin: crate::gpio::Pin {
|
|
/// Pin number.
|
|
fn number(&self) -> usize;
|
|
}
|
|
|
|
macro_rules! impl_gpoutpin {
|
|
($name:ident, $gpout_num:expr) => {
|
|
impl GpoutPin for crate::peripherals::$name {
|
|
fn number(&self) -> usize {
|
|
$gpout_num
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_gpoutpin!(PIN_21, 0);
|
|
impl_gpoutpin!(PIN_23, 1);
|
|
impl_gpoutpin!(PIN_24, 2);
|
|
impl_gpoutpin!(PIN_25, 3);
|
|
|
|
/// Gpout clock source.
|
|
#[repr(u8)]
|
|
pub enum GpoutSrc {
|
|
/// Sys PLL.
|
|
PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _,
|
|
// Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ ,
|
|
// Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ ,
|
|
/// USB PLL.
|
|
PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB as _,
|
|
/// ROSC.
|
|
Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC as _,
|
|
/// XOSC.
|
|
Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC as _,
|
|
/// SYS.
|
|
Sys = ClkGpoutCtrlAuxsrc::CLK_SYS as _,
|
|
/// USB.
|
|
Usb = ClkGpoutCtrlAuxsrc::CLK_USB as _,
|
|
/// ADC.
|
|
Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _,
|
|
/// RTC.
|
|
#[cfg(feature = "rp2040")]
|
|
Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _,
|
|
/// REF.
|
|
Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _,
|
|
}
|
|
|
|
/// General purpose clock output driver.
|
|
pub struct Gpout<'d, T: GpoutPin> {
|
|
gpout: Peri<'d, T>,
|
|
}
|
|
|
|
impl<'d, T: GpoutPin> Gpout<'d, T> {
|
|
/// Create new general purpose clock output.
|
|
pub fn new(gpout: Peri<'d, T>) -> Self {
|
|
#[cfg(feature = "rp2040")]
|
|
gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08));
|
|
|
|
// On RP2350 GPOUT changed from F8 toF9
|
|
#[cfg(feature = "_rp235x")]
|
|
gpout.gpio().ctrl().write(|w| w.set_funcsel(0x09));
|
|
|
|
#[cfg(feature = "_rp235x")]
|
|
gpout.pad_ctrl().write(|w| {
|
|
w.set_iso(false);
|
|
});
|
|
|
|
Self { gpout }
|
|
}
|
|
|
|
/// Set clock divider.
|
|
#[cfg(feature = "rp2040")]
|
|
pub fn set_div(&self, int: u32, frac: u8) {
|
|
let c = pac::CLOCKS;
|
|
c.clk_gpout_div(self.gpout.number()).write(|w| {
|
|
w.set_int(int);
|
|
w.set_frac(frac);
|
|
});
|
|
}
|
|
|
|
/// Set clock divider.
|
|
#[cfg(feature = "_rp235x")]
|
|
pub fn set_div(&self, int: u16, frac: u16) {
|
|
let c = pac::CLOCKS;
|
|
c.clk_gpout_div(self.gpout.number()).write(|w| {
|
|
w.set_int(int);
|
|
w.set_frac(frac);
|
|
});
|
|
}
|
|
|
|
/// Set clock source.
|
|
pub fn set_src(&self, src: GpoutSrc) {
|
|
let c = pac::CLOCKS;
|
|
c.clk_gpout_ctrl(self.gpout.number()).modify(|w| {
|
|
w.set_auxsrc(ClkGpoutCtrlAuxsrc::from_bits(src as _));
|
|
});
|
|
}
|
|
|
|
/// Enable clock.
|
|
pub fn enable(&self) {
|
|
let c = pac::CLOCKS;
|
|
c.clk_gpout_ctrl(self.gpout.number()).modify(|w| {
|
|
w.set_enable(true);
|
|
});
|
|
}
|
|
|
|
/// Disable clock.
|
|
pub fn disable(&self) {
|
|
let c = pac::CLOCKS;
|
|
c.clk_gpout_ctrl(self.gpout.number()).modify(|w| {
|
|
w.set_enable(false);
|
|
});
|
|
}
|
|
|
|
/// Clock frequency.
|
|
pub fn get_freq(&self) -> u32 {
|
|
let c = pac::CLOCKS;
|
|
let src = c.clk_gpout_ctrl(self.gpout.number()).read().auxsrc();
|
|
|
|
let base = match src {
|
|
ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(),
|
|
// ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(),
|
|
// ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(),
|
|
ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(),
|
|
ClkGpoutCtrlAuxsrc::ROSC_CLKSRC => rosc_freq(),
|
|
ClkGpoutCtrlAuxsrc::XOSC_CLKSRC => xosc_freq(),
|
|
ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(),
|
|
ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(),
|
|
ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(),
|
|
//ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _,
|
|
ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(),
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let div = c.clk_gpout_div(self.gpout.number()).read();
|
|
let int = if div.int() == 0 { 0xFFFF } else { div.int() } as u64;
|
|
let frac = div.frac() as u64;
|
|
|
|
((base as u64 * 256) / (int * 256 + frac)) as u32
|
|
}
|
|
}
|
|
|
|
impl<'d, T: GpoutPin> Drop for Gpout<'d, T> {
|
|
fn drop(&mut self) {
|
|
self.disable();
|
|
self.gpout.pad_ctrl().write(|_| {});
|
|
self.gpout
|
|
.gpio()
|
|
.ctrl()
|
|
.write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _));
|
|
}
|
|
}
|
|
|
|
/// Random number generator based on the ROSC RANDOMBIT register.
|
|
///
|
|
/// This will not produce random values if the ROSC is stopped or run at some
|
|
/// harmonic of the bus frequency. With default clock settings these are not
|
|
/// issues.
|
|
pub struct RoscRng;
|
|
|
|
impl RoscRng {
|
|
fn next_u8() -> u8 {
|
|
let random_reg = pac::ROSC.randombit();
|
|
let mut acc = 0;
|
|
for _ in 0..u8::BITS {
|
|
acc <<= 1;
|
|
acc |= random_reg.read().randombit() as u8;
|
|
}
|
|
acc
|
|
}
|
|
}
|
|
|
|
impl rand_core::RngCore for RoscRng {
|
|
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
|
|
Ok(self.fill_bytes(dest))
|
|
}
|
|
|
|
fn next_u32(&mut self) -> u32 {
|
|
rand_core::impls::next_u32_via_fill(self)
|
|
}
|
|
|
|
fn next_u64(&mut self) -> u64 {
|
|
rand_core::impls::next_u64_via_fill(self)
|
|
}
|
|
|
|
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
|
dest.fill_with(Self::next_u8)
|
|
}
|
|
}
|
|
|
|
/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks
|
|
/// and can only be exited through resets, dormant-wake GPIO interrupts,
|
|
/// and RTC interrupts. If RTC is clocked from an internal clock source
|
|
/// it will be stopped and not function as a wakeup source.
|
|
#[cfg(all(target_arch = "arm", feature = "rp2040"))]
|
|
pub fn dormant_sleep() {
|
|
struct Set<T: Copy, F: Fn()>(Reg<T, RW>, T, F);
|
|
|
|
impl<T: Copy, F: Fn()> Drop for Set<T, F> {
|
|
fn drop(&mut self) {
|
|
self.0.write_value(self.1);
|
|
self.2();
|
|
}
|
|
}
|
|
|
|
fn set_with_post_restore<T: Copy, After: Fn(), F: FnOnce(&mut T) -> After>(
|
|
reg: Reg<T, RW>,
|
|
f: F,
|
|
) -> Set<T, impl Fn()> {
|
|
reg.modify(|w| {
|
|
let old = *w;
|
|
let after = f(w);
|
|
Set(reg, old, after)
|
|
})
|
|
}
|
|
|
|
fn set<T: Copy, F: FnOnce(&mut T)>(reg: Reg<T, RW>, f: F) -> Set<T, impl Fn()> {
|
|
set_with_post_restore(reg, |r| {
|
|
f(r);
|
|
|| ()
|
|
})
|
|
}
|
|
|
|
// disable all clocks that are not vital in preparation for disabling clock sources.
|
|
// we'll keep gpout and rtc clocks untouched, gpout because we don't care about them
|
|
// and rtc because it's a possible wakeup source. if clk_rtc is not configured for
|
|
// gpin we'll never wake from rtc, but that's what the user asked for then.
|
|
let _stop_adc = set(pac::CLOCKS.clk_adc_ctrl(), |w| w.set_enable(false));
|
|
let _stop_usb = set(pac::CLOCKS.clk_usb_ctrl(), |w| w.set_enable(false));
|
|
let _stop_peri = set(pac::CLOCKS.clk_peri_ctrl(), |w| w.set_enable(false));
|
|
// set up rosc. we could ask the user to tell us which clock source to wake from like
|
|
// the C SDK does, but that seems rather unfriendly. we *may* disturb rtc by changing
|
|
// rosc configuration if it's currently the rtc clock source, so we'll configure rosc
|
|
// to the slowest frequency to minimize that impact.
|
|
let _configure_rosc = (
|
|
set(pac::ROSC.ctrl(), |w| {
|
|
w.set_enable(pac::rosc::vals::Enable::ENABLE);
|
|
w.set_freq_range(pac::rosc::vals::FreqRange::LOW);
|
|
}),
|
|
// div=32
|
|
set(pac::ROSC.div(), |w| w.set_div(pac::rosc::vals::Div(0xaa0))),
|
|
);
|
|
while !pac::ROSC.status().read().stable() {}
|
|
// switch over to rosc as the system clock source. this will change clock sources for
|
|
// watchdog and timer clocks, but timers won't be a concern and the watchdog won't
|
|
// speed up by enough to worry about (unless it's clocked from gpin, which we don't
|
|
// support anyway).
|
|
let _switch_clk_ref = set(pac::CLOCKS.clk_ref_ctrl(), |w| {
|
|
w.set_src(pac::clocks::vals::ClkRefCtrlSrc::ROSC_CLKSRC_PH);
|
|
});
|
|
let _switch_clk_sys = set(pac::CLOCKS.clk_sys_ctrl(), |w| {
|
|
w.set_src(pac::clocks::vals::ClkSysCtrlSrc::CLK_REF);
|
|
});
|
|
// oscillator dormancy does not power down plls, we have to do that ourselves. we'll
|
|
// restore them to their prior glory when woken though since the system may be clocked
|
|
// from either (and usb/adc will probably need the USB PLL anyway)
|
|
let _stop_pll_sys = set_with_post_restore(pac::PLL_SYS.pwr(), |w| {
|
|
let wake = !w.pd() && !w.vcopd();
|
|
w.set_pd(true);
|
|
w.set_vcopd(true);
|
|
move || while wake && !pac::PLL_SYS.cs().read().lock() {}
|
|
});
|
|
let _stop_pll_usb = set_with_post_restore(pac::PLL_USB.pwr(), |w| {
|
|
let wake = !w.pd() && !w.vcopd();
|
|
w.set_pd(true);
|
|
w.set_vcopd(true);
|
|
move || while wake && !pac::PLL_USB.cs().read().lock() {}
|
|
});
|
|
// dormancy only stops the oscillator we're telling to go dormant, the other remains
|
|
// running. nothing can use xosc at this point any more. not doing this costs an 200µA.
|
|
let _stop_xosc = set_with_post_restore(pac::XOSC.ctrl(), |w| {
|
|
let wake = w.enable() == pac::xosc::vals::Enable::ENABLE;
|
|
if wake {
|
|
w.set_enable(pac::xosc::vals::Enable::DISABLE);
|
|
}
|
|
move || while wake && !pac::XOSC.status().read().stable() {}
|
|
});
|
|
let _power_down_xip_cache = set(pac::XIP_CTRL.ctrl(), |w| w.set_power_down(true));
|
|
|
|
// only power down memory if we're running from XIP (or ROM? how?).
|
|
// powering down memory otherwise would require a lot of exacting checks that
|
|
// are better done by the user in a local copy of this function.
|
|
// powering down memories saves ~100µA, so it's well worth doing.
|
|
unsafe {
|
|
let is_in_flash = {
|
|
// we can't rely on the address of this function as rust sees it since linker
|
|
// magic or even boot2 may place it into ram.
|
|
let pc: usize;
|
|
asm!(
|
|
"mov {pc}, pc",
|
|
pc = out (reg) pc
|
|
);
|
|
pc < 0x20000000
|
|
};
|
|
if is_in_flash {
|
|
// we will be powering down memories, so we must be *absolutely*
|
|
// certain that we're running entirely from XIP and registers until
|
|
// memories are powered back up again. accessing memory that's powered
|
|
// down may corrupt memory contents (see section 2.11.4 of the manual).
|
|
// additionally a 20ns wait time is needed after powering up memories
|
|
// again. rosc is likely to run at only a few MHz at most, so the
|
|
// inter-instruction delay alone will be enough to satisfy this bound.
|
|
asm!(
|
|
"ldr {old_mem}, [{mempowerdown}]",
|
|
"str {power_down_mems}, [{mempowerdown}]",
|
|
"str {coma}, [{dormant}]",
|
|
"str {old_mem}, [{mempowerdown}]",
|
|
old_mem = out (reg) _,
|
|
mempowerdown = in (reg) pac::SYSCFG.mempowerdown().as_ptr(),
|
|
power_down_mems = in (reg) 0b11111111,
|
|
dormant = in (reg) pac::ROSC.dormant().as_ptr(),
|
|
coma = in (reg) 0x636f6d61,
|
|
);
|
|
} else {
|
|
pac::ROSC.dormant().write_value(rp_pac::rosc::regs::Dormant(0x636f6d61));
|
|
}
|
|
}
|
|
}
|