use crate::{regs, sys};
use super::{Bypass, Multiplier};
#[derive(Debug)]
#[non_exhaustive]
pub struct Fll;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FllConfig {
pub multiplier: u32,
pub output_divider: bool,
pub reference_divider: u16,
#[cfg(mxs40ssrss)]
pub update_tolerance: u8,
#[cfg(mxs40ssrss)]
pub lock_tolerance: u8,
#[cfg(mxs40srss)]
pub lock_tolerance: u16,
pub p_gain: u8,
pub i_gain: u8,
pub settling_count: u16,
pub open_loop: bool,
pub cco_frequency: CcoConfig,
pub cco_limit: u8,
pub bypass_mode: Bypass,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CcoConfig {
pub range: u8,
pub trim: u16,
}
impl FllConfig {
pub const fn from_frequency(reference: u32, target: u32) -> Self {
debug_assert!(target >= 24_000_000 && target <= 100_000_000);
let cco_freq = target * 2;
let cco_config = CcoConfig::from_frequency(cco_freq);
let ref_div = ((reference as u64) * 250 / (target as u64)) as u16;
let multiplier = (cco_freq as u64 * ref_div as u64 / reference as u64) as u32;
#[expect(clippy::zero_prefixed_literal)]
let lock_tolerance = multiplier * 0_018_939 / 1_000_000;
let (_, base_freq, trim_step) = CcoConfig::cco_constants(cco_freq);
let k_cco = base_freq as f32 * (trim_step - 1.);
let gain = 0.85 / (k_cco * ref_div as f32 / reference as f32);
let scaled_gain = gain * 256.;
let i_gain = (scaled_gain.min(8. * 256.) as u16).checked_ilog2();
let i_gain = if let Some(i_gain) = i_gain {
i_gain as u8
} else {
0
};
let scaled_p_gain = scaled_gain - (1 << i_gain) as f32;
let p_gain = (scaled_p_gain.min(8. * 256.) as u16).checked_ilog2();
let p_gain = if let Some(p_gain) = p_gain {
p_gain as u8
} else {
0
};
let settling_count = (reference / 1_000_000) as u16;
FllConfig {
multiplier,
output_divider: true,
reference_divider: ref_div,
#[cfg(mxs40ssrss)]
update_tolerance: 0,
lock_tolerance: lock_tolerance as _,
p_gain,
i_gain,
settling_count,
open_loop: false,
cco_frequency: cco_config,
cco_limit: 0xFF,
bypass_mode: Bypass::Auto,
}
}
}
impl Multiplier for Fll {
type Config = FllConfig;
unsafe fn steal() -> &'static mut Self {
unsafe { &mut *core::ptr::dangling_mut() }
}
fn configure(&mut self, config: Option<&FllConfig>) {
unsafe {
if let Some(config) = config {
if regs::SRSS.clk_fll_config().read().fll_enable().get() {
self.configure(None);
}
debug_assert!(config.multiplier <= 0x3FFFF);
let fll_config = regs::srss::ClkFllConfig::default()
.fll_output_div()
.set(config.output_divider)
.fll_mult()
.set(config.multiplier)
.fll_enable()
.set(false);
regs::SRSS.clk_fll_config().write(fll_config);
debug_assert!(matches!(config.reference_divider, 1..=8191));
#[cfg(mxs40srss)]
core::debug_assert!(matches!(config.lock_tolerance, 0..=511));
regs::SRSS.clk_fll_config2().init(|mut r| {
r = r
.fll_ref_div()
.set(config.reference_divider)
.lock_tol()
.set(config.lock_tolerance);
#[cfg(mxs40ssrss)]
{
r = r.update_tol().set(config.update_tolerance);
}
r
});
debug_assert!(config.p_gain <= 11);
debug_assert!(config.i_gain <= 11);
regs::SRSS.clk_fll_config3().init(|r| {
r.fll_lf_pgain()
.set(config.p_gain)
.fll_lf_igain()
.set(config.i_gain)
.settling_count()
.set(config.settling_count)
.bypass_sel()
.set(
(match config.bypass_mode {
Bypass::MultiplierOutputOnly => Bypass::Auto,
m => m,
} as u8)
.into(),
)
});
debug_assert!(config.cco_frequency.range <= 4);
debug_assert!(config.cco_frequency.trim <= 0x1FF);
regs::SRSS.clk_fll_config4().init(|r| {
r.cco_hw_update_dis()
.set(config.open_loop)
.cco_range()
.set(regs::srss::clk_fll_config4::CcoRange::new(
config.cco_frequency.range,
))
.cco_freq()
.set(config.cco_frequency.trim)
.cco_enable()
.set(true)
});
while !regs::SRSS.clk_fll_status().read().cco_ready().get() {}
regs::SRSS
.clk_fll_config()
.write(fll_config.fll_enable().set(true));
if config.bypass_mode == Bypass::MultiplierOutputOnly {
while !regs::SRSS.clk_fll_status().read().locked().get() {}
regs::SRSS.clk_fll_config3().modify(|r| {
r.bypass_sel()
.set((Bypass::MultiplierOutputOnly as u8).into())
});
}
} else {
regs::SRSS.clk_fll_config3().modify(|r| {
r.bypass_sel()
.set((Bypass::ReferenceInputOnly as u8).into())
});
_ = regs::SRSS.clk_fll_config3().read();
sys::delay_microseconds(2);
regs::SRSS
.clk_fll_config4()
.modify(|r| r.cco_enable().set(false));
}
}
}
}
impl CcoConfig {
const fn cco_constants(freq: u32) -> (u8, u32, f32) {
#[allow(clippy::excessive_precision)]
match freq {
48_000_000..63_885_600 => (0, 43_600_000, 1.00110340),
63_885_600..84_948_700 => (1, 58_100_000, 1.00110200),
84_948_700..113_009_380 => (2, 77_200_000, 1.00110000),
113_009_380..150_339_200 => (3, 103_000_000, 1.00110000),
150_339_200..=200_000_000 => (4, 132_000_000, 1.00117062),
_ => panic!("CCO frequency out of range"),
}
}
pub const fn from_frequency(freq: u32) -> Self {
let (range, base_freq, trim_step) = Self::cco_constants(freq);
let target_multiplier = freq as f32 / base_freq as f32;
let mut trim = 0;
let mut current_multiplier = 1.;
while current_multiplier < target_multiplier {
trim += 1;
current_multiplier *= trim_step;
}
CcoConfig { range, trim }
}
}