use crate::{
regs::{self, RegisterValue},
sys,
};
use super::{Bypass, Multiplier};
#[derive(Debug)]
#[non_exhaustive]
pub struct LpDpll<const N: usize>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LpDpllConfig {
pub bypass: Bypass,
pub reference_divider: u8,
pub feedback_divider: u8,
pub fractional_divider: Option<u32>,
pub output_divider: u8,
pub dither: bool,
pub spread_spectrum: Option<SpreadSpectrumConfig>,
pub pfd_range: PfdRange,
pub cold_start_mode: ColdStartMode,
pub p_gain: u8,
pub i_gain: u8,
pub p_cold_start: u8,
pub i_cold_start: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(mxs40ssrss)]
pub enum PfdRange {
LessThan8Mhz,
GreaterThan8Mhz,
}
#[cfg(mxs40ssrss)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpreadSpectrumConfig {
pub rate: SpreadSpectrumRate,
pub depth: SpreadSpectrumDepth,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg(mxs40ssrss)]
#[repr(u8)]
pub enum SpreadSpectrumRate {
Div4096 = 0,
Div2048,
Div1024,
Div512,
Div256,
Div128,
Div744,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg(mxs40ssrss)]
pub enum SpreadSpectrumDepth {
Percent0_5,
Percent1,
Percent2,
Percent3,
}
#[cfg(mxs40ssrss)]
impl SpreadSpectrumDepth {
const fn value(&self) -> u16 {
match self {
SpreadSpectrumDepth::Percent0_5 => 0x29,
SpreadSpectrumDepth::Percent1 => 0x52,
SpreadSpectrumDepth::Percent2 => 0xa4,
SpreadSpectrumDepth::Percent3 => 0xf6,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(mxs40ssrss)]
pub enum ColdStartMode {
Counter,
Lock,
}
impl<const N: usize> Multiplier for LpDpll<N> {
type Config = LpDpllConfig;
unsafe fn steal() -> &'static mut Self {
unsafe { &mut *core::ptr::dangling_mut() }
}
#[inline]
fn configure(&mut self, config: Option<&LpDpllConfig>) {
unsafe {
let reg = ®s::SRSS.clk_dpll_lp()[N];
if let Some(config) = config {
if reg.config().read().enable().get() {
self.configure(None);
}
let config_reg = regs::srss::clk_dpll_lp::Config::default()
.enable()
.set(false)
.bypass_sel()
.set(
(match config.bypass {
Bypass::MultiplierOutputOnly => Bypass::Auto,
m => m,
} as u8)
.into(),
)
.pll_dco_code_mult()
.set(config.pfd_range == PfdRange::GreaterThan8Mhz)
.output_div()
.set(config.output_divider)
.reference_div()
.set(config.reference_divider)
.feedback_div()
.set(config.feedback_divider);
reg.config().write(config_reg);
reg.config2().init(|r| {
r.frac_en()
.set(config.fractional_divider.is_some())
.frac_div()
.set(config.fractional_divider.unwrap_or(0))
.frac_dither_en()
.set(config.dither as u8)
});
reg.config3().init(|r| {
r.sscg_en()
.set(config.spread_spectrum.is_some())
.sscg_rate()
.set(config.spread_spectrum.map_or(0, |s| s.rate as u8))
.sscg_depth()
.set(config.spread_spectrum.map_or(0, |s| s.depth.value()))
});
reg.config4().init(|r| {
r.acc_cnt_lock()
.set(config.cold_start_mode == ColdStartMode::Lock)
.pll_tg()
.set(if config.fractional_divider.unwrap_or(0) <= (1 << 23) {
0
} else {
2
})
});
let gains = regs::srss::clk_dpll_lp::Config5::default()
.kp_int()
.set(config.p_gain)
.ki_int()
.set(config.i_gain)
.kp_acc_int()
.set(config.p_cold_start)
.ki_acc_int()
.set(config.i_cold_start);
reg.config5().write_raw(gains.get_raw());
reg.config6().write_raw(gains.get_raw());
reg.config7().write_raw(gains.get_raw());
reg.config().write(config_reg.enable().set(true));
if config.bypass == Bypass::MultiplierOutputOnly {
while !reg.status().read().locked().get() {}
reg.config().modify(|r| {
r.bypass_sel()
.set((Bypass::MultiplierOutputOnly as u8).into())
});
}
} else {
if reg.config().read().enable().get() {
reg.config().modify(|r| {
r.bypass_sel()
.set((Bypass::ReferenceInputOnly as u8).into())
});
sys::delay_microseconds(2);
reg.config().modify(|r| r.enable().set(false));
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LpDllMode {
Integer,
Fractional,
SpreadSpectrum {
depth: SpreadSpectrumDepth,
},
}
impl LpDpllConfig {
pub const fn from_frequency(reference: u32, target: u32, mode: LpDllMode) -> Self {
debug_assert!(reference >= 4_000_000 && reference <= 64_000_000);
debug_assert!(target >= 10_000_000 && target <= 400_000_000);
let target = target * 2;
let target_ratio = target as f32 / reference as f32;
let mut best: Option<(f32, u8, u8, u8, u32)> = None;
let mut reference_divider = 1;
'search: while reference_divider <= 16 {
let mut output_divider = 1;
while output_divider <= (16 / 2) {
let feedback_divider = target_ratio * (reference_divider * output_divider) as f32;
let (mut div_int, mut div_frac) = if let LpDllMode::Fractional = mode {
let div_int = feedback_divider as u8;
let div_frac =
((feedback_divider - div_int as f32) * (1 << 24) as f32 + 0.5) as u32;
(div_int, div_frac)
} else {
((feedback_divider + 0.5) as u8, 0)
};
if div_int < 1 {
div_int = 1;
div_frac = 0;
} else if div_int > 125 {
div_int = 125;
if let LpDllMode::Fractional = mode {
div_frac = (1 << 24) - 1;
}
}
let actual_divider = div_int as f32 + (div_frac as f32 / (1 << 24) as f32);
let error = (actual_divider - feedback_divider).abs();
if best.is_none() || error < best.unwrap().0 {
best = Some((error, reference_divider, output_divider, div_int, div_frac));
}
if error == 0. {
break 'search;
}
output_divider += 1;
}
reference_divider += 1;
}
let (_, reference_divider, output_divider, feedback_divider_int, feedback_divider_frac) =
best.unwrap();
let output_divider = output_divider * 2;
let spread_spectrum = if let LpDllMode::SpreadSpectrum { depth } = mode {
let rate = reference / 32_000;
match rate {
0..256 => SpreadSpectrumRate::Div256,
256..512 => SpreadSpectrumRate::Div512,
512..744 => SpreadSpectrumRate::Div744,
744..1024 => SpreadSpectrumRate::Div1024,
1024..2048 => SpreadSpectrumRate::Div2048,
2048..4096 => SpreadSpectrumRate::Div4096,
_ => panic!("Reference frequency out of range"),
};
Some(SpreadSpectrumConfig {
rate: SpreadSpectrumRate::Div512,
depth,
})
} else {
None
};
let (p_gain, i_gain, p_cold_start, i_cold_start) = match mode {
LpDllMode::Integer => (0x1C, 0x24, 0x1A, 0x23),
LpDllMode::Fractional => (0x20, 0x24, 0x1A, 0x23),
LpDllMode::SpreadSpectrum { .. } => (0x18, 0x18, 0x14, 0x16),
};
LpDpllConfig {
bypass: Bypass::Auto,
reference_divider,
feedback_divider: feedback_divider_int,
fractional_divider: if let LpDllMode::Fractional = mode {
Some(feedback_divider_frac)
} else {
None
},
output_divider,
dither: false,
spread_spectrum,
pfd_range: if reference <= (8_000_000 * reference_divider as u32) {
PfdRange::LessThan8Mhz
} else {
PfdRange::GreaterThan8Mhz
},
cold_start_mode: ColdStartMode::Counter,
p_gain,
i_gain,
p_cold_start,
i_cold_start,
}
}
}