pub const ROSC_FREQ_HZ: u32 = 6_500_000;
pub const XOSC_FREQ_HZ: u32 = 12_000_000;
pub const RP2350_SYS_CLK_HZ: u32 = 150_000_000;
pub const RP2350_ADC_CLK_HZ: u32 = 48_000_000;
#[derive(Debug, Clone, Copy)]
pub struct ClockTree {
pub sys_clk_hz: u32,
pub ref_clk_hz: u32,
pub peri_clk_hz: u32,
}
impl Default for ClockTree {
fn default() -> Self {
Self {
sys_clk_hz: ROSC_FREQ_HZ,
ref_clk_hz: ROSC_FREQ_HZ,
peri_clk_hz: ROSC_FREQ_HZ,
}
}
}
impl ClockTree {
#[inline]
pub fn peri_hz(&self) -> u64 {
self.peri_clk_hz as u64
}
}
pub fn pll_output_hz(regs: &[u32; 4]) -> u32 {
let fbdiv = (regs[2] & 0xFFF) as u64;
if fbdiv == 0 {
return 0;
}
let refdiv = ((regs[0] & 0x3F).max(1)) as u64;
let postdiv1 = (((regs[3] >> 16) & 0x7).max(1)) as u64;
let postdiv2 = (((regs[3] >> 12) & 0x7).max(1)) as u64;
let vco_hz = (XOSC_FREQ_HZ as u64 / refdiv) * fbdiv;
let out_hz_64 = vco_hz / (postdiv1 * postdiv2);
out_hz_64.min(u32::MAX as u64) as u32
}
pub const PLL_LOCK_DELAY_SYSCLKS: u64 = 2_000;
pub fn pll_is_locked_base(regs: &[u32; 4]) -> bool {
let pwr = regs[1];
let fbdiv = regs[2] & 0xFFF;
let pd = (pwr & 0x01) != 0;
let vcopd = (pwr & 0x20) != 0;
fbdiv != 0 && !pd && !vcopd
}
pub fn pll_cs_read_with_lock(regs: &[u32; 4], lock_at: Option<u64>, now: u64) -> u32 {
let locked_time = matches!(lock_at, Some(arm) if now >= arm);
let locked = pll_is_locked_base(regs) && locked_time;
if locked {
regs[0] | (1 << 31)
} else {
regs[0] & !(1 << 31)
}
}
pub fn pll_should_arm_lock(
old_regs: &[u32; 4],
new_regs: &[u32; 4],
prev_lock_at: Option<u64>,
now: u64,
) -> Option<u64> {
if !pll_is_locked_base(new_regs) {
None
} else if prev_lock_at.is_none() {
Some(now + PLL_LOCK_DELAY_SYSCLKS)
} else if (old_regs[0] & 0x3F) != (new_regs[0] & 0x3F) {
Some(now + PLL_LOCK_DELAY_SYSCLKS)
} else if (old_regs[2] & 0xFFF) != (new_regs[2] & 0xFFF) {
Some(now + PLL_LOCK_DELAY_SYSCLKS)
} else {
prev_lock_at
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pll_is_locked_base_false_when_pd_set() {
let regs: [u32; 4] = [0x01, 0x01, 100, 0];
assert!(!pll_is_locked_base(®s));
}
#[test]
fn pll_is_locked_base_false_when_vcopd_set() {
let regs: [u32; 4] = [0x01, 0x20, 100, 0];
assert!(!pll_is_locked_base(®s));
}
#[test]
fn pll_is_locked_base_false_when_fbdiv_zero() {
let regs: [u32; 4] = [0x01, 0, 0, 0];
assert!(!pll_is_locked_base(®s));
}
#[test]
fn pll_is_locked_base_true_when_powered_and_configured() {
let regs: [u32; 4] = [0x01, 0, 100, 0];
assert!(pll_is_locked_base(®s));
}
#[test]
fn pll_is_locked_base_false_at_reset_defaults() {
let regs: [u32; 4] = [0x0000_0001, 0x0000_002D, 0, 0x0007_7000];
assert!(!pll_is_locked_base(®s));
}
#[test]
fn pll_is_locked_base_ignores_bypass() {
let regs: [u32; 4] = [0x101, 0, 100, 0];
assert!(pll_is_locked_base(®s));
}
#[test]
fn pll_cs_read_returns_lock_set_when_locked_and_past_arm() {
let regs: [u32; 4] = [0x01, 0, 100, 0];
let cs = pll_cs_read_with_lock(®s, Some(10), 1_000);
assert_eq!(cs & (1 << 31), 1 << 31);
assert_eq!(cs & 0x3F, 0x01, "REFDIV bits preserved under LOCK merge");
}
#[test]
fn pll_cs_read_returns_lock_clear_before_arm() {
let regs: [u32; 4] = [0x01, 0, 100, 0];
let cs = pll_cs_read_with_lock(®s, Some(5_000), 10);
assert_eq!(cs & (1 << 31), 0, "LOCK must be 0 before arm cycle");
}
#[test]
fn pll_cs_read_returns_lock_clear_when_base_false() {
let regs: [u32; 4] = [0x01, 0x2D, 100, 0];
let cs = pll_cs_read_with_lock(®s, Some(10), 10_000);
assert_eq!(cs & (1 << 31), 0);
}
#[test]
fn pll_cs_read_returns_lock_clear_when_arm_none() {
let regs: [u32; 4] = [0x01, 0, 100, 0];
let cs = pll_cs_read_with_lock(®s, None, 10_000);
assert_eq!(cs & (1 << 31), 0, "None arm → LOCK=0 regardless of now");
}
#[test]
fn pll_should_arm_lock_drops_when_powered_down() {
let old: [u32; 4] = [0x01, 0, 100, 0];
let new: [u32; 4] = [0x01, 0x2D, 100, 0];
let prev = Some(2_000);
let now = 500;
assert_eq!(pll_should_arm_lock(&old, &new, prev, now), None);
}
#[test]
fn pll_should_arm_lock_arms_from_powered_down() {
let old: [u32; 4] = [0x01, 0x2D, 0, 0];
let new: [u32; 4] = [0x01, 0, 100, 0];
let now = 100;
assert_eq!(
pll_should_arm_lock(&old, &new, None, now),
Some(100 + PLL_LOCK_DELAY_SYSCLKS)
);
}
#[test]
fn pll_should_arm_lock_rearms_on_refdiv_change() {
let old: [u32; 4] = [0x01, 0, 100, 0];
let new: [u32; 4] = [0x05, 0, 100, 0];
let prev = Some(500);
let now = 10_000;
assert_eq!(
pll_should_arm_lock(&old, &new, prev, now),
Some(10_000 + PLL_LOCK_DELAY_SYSCLKS)
);
}
#[test]
fn pll_should_arm_lock_rearms_on_fbdiv_change() {
let old: [u32; 4] = [0x01, 0, 100, 0];
let new: [u32; 4] = [0x01, 0, 125, 0];
let prev = Some(500);
let now = 10_000;
assert_eq!(
pll_should_arm_lock(&old, &new, prev, now),
Some(10_000 + PLL_LOCK_DELAY_SYSCLKS)
);
}
#[test]
fn pll_should_arm_lock_keeps_existing_on_prim_change() {
let old: [u32; 4] = [0x01, 0, 100, 0x0007_7000];
let new: [u32; 4] = [0x01, 0, 100, 0x0002_2000];
let prev = Some(500);
let now = 10_000;
assert_eq!(pll_should_arm_lock(&old, &new, prev, now), Some(500));
}
#[test]
fn pll_should_arm_lock_noop_when_nothing_changes() {
let regs: [u32; 4] = [0x01, 0, 100, 0];
assert_eq!(pll_should_arm_lock(®s, ®s, Some(42), 1_000), Some(42));
let unconf: [u32; 4] = [0x01, 0x2D, 0, 0];
assert_eq!(pll_should_arm_lock(&unconf, &unconf, None, 1_000), None);
}
}