From 188954ddc7d644c59598edfb650381976a546eef Mon Sep 17 00:00:00 2001 From: Brad Bondurant Date: Thu, 17 Nov 2022 15:46:27 -0500 Subject: [PATCH] zynq_us: clocks (WIP) --- libboard_zynq_us/src/clocks/mod.rs | 89 +++++++++ libboard_zynq_us/src/clocks/source.rs | 255 ++++++++++++++++++++++++++ libboard_zynq_us/src/lib.rs | 2 +- 3 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 libboard_zynq_us/src/clocks/mod.rs create mode 100644 libboard_zynq_us/src/clocks/source.rs diff --git a/libboard_zynq_us/src/clocks/mod.rs b/libboard_zynq_us/src/clocks/mod.rs new file mode 100644 index 0000000..bea2a38 --- /dev/null +++ b/libboard_zynq_us/src/clocks/mod.rs @@ -0,0 +1,89 @@ +use libregister::{RegisterR, RegisterRW}; +use super::slcr::{crf_apb, crl_apb, common::SlcrRegisterBlock}; +pub use super::slcr::crf_apb::ApuClkSource; + +pub mod source; +use source::*; + + +#[derive(Debug, Clone)] +pub struct Clocks { + /// ARM PLL: Recommended clock source for the APUs and the FPD interconnect + pub apu: u32, + /// DDR PLL: Recommended clock for the DDR DRAM controller and AXI_HP interfaces + pub ddr: u32, + /// Video PLL: Recommended clock for display port + pub video: u32, + /// I/O PLL: Recommended clock for I/O peripherals + pub io: u32, + /// RPU PLL: Recommended clock for RPUs and LPD interconnect + pub rpu: u32, +} + +impl Clocks { + pub fn get() -> Self { + let fpd_regs = crf_apb::RegisterBlock::slcr(); + let lpd_regs = crl_apb::RegisterBlock::slcr(); + Clocks { + apu: ApuPll::freq(&mut fpd_regs.apu_pll_ctrl), + ddr: DdrPll::freq(&mut fpd_regs.ddr_pll_ctrl), + video: VideoPll::freq(&mut fpd_regs.video_pll_ctrl), + io: IoPll::freq(&mut lpd_regs.io_pll_ctrl), + rpu: RpuPll::freq(&mut lpd_regs.rpu_pll_ctrl), + } + } + + pub fn set_cpu_freq(target_freq: u32) { + let fpd_regs = crf_apb::RegisterBlock::slcr(); + let apu_pll = ApuPll::freq(&mut fpd_regs.apu_pll_ctrl); + let mut div = 1u8; + while div < 63 && apu_pll / u32::from(div) > target_freq { + div += 1; + } + + crf_apb::RegisterBlock::unlocked(|slcr| { + slcr.apu_clk_ctrl.modify(|_, w| w + .srcsel(ApuClkSource::ApuPll) + .divisor0(div) + ); + }) + } + + pub fn uart0_ref_clk(&self) -> u32 { + let lpd_regs = crl_apb::RegisterBlock::slcr(); + self.uart_ref_clk(&mut lpd_regs.uart0_clk_ctrl) + } + + pub fn uart1_ref_clk(&self) -> u32 { + let lpd_regs = crl_apb::RegisterBlock::slcr(); + self.uart_ref_clk(&mut lpd_regs.uart1_clk_ctrl) + } + + fn uart_ref_clk(&self, uart_regs: &mut crl_apb::UartClkCtrl) -> u32 { + let uart_clk_ctrl = uart_regs.read(); + let pll = match uart_clk_ctrl.srcsel() { + crl_apb::IoClkSource::IoPll => self.io, + crl_apb::IoClkSource::RpuPll => self.rpu, + crl_apb::IoClkSource::DdrPllToLpd => { + let fpd_regs = crf_apb::RegisterBlock::slcr(); + let divisor = u32::from(fpd_regs.ddr_pll_to_lpd_ctrl.read().divisor0()); + self.ddr / divisor + } + }; + pll / (u32::from(uart_clk_ctrl.divisor0()) * u32::from(uart_clk_ctrl.divisor1())) + } + + // pub fn sdio_ref_clk(&self) -> u32 { + // let regs = slcr::RegisterBlock::slcr(); + // let sdio_clk_ctrl = regs.sdio_clk_ctrl.read(); + // let pll = match sdio_clk_ctrl.srcsel() { + // slcr::PllSource::ArmPll => + // self.arm, + // slcr::PllSource::DdrPll => + // self.ddr, + // slcr::PllSource::IoPll => + // self.io, + // }; + // pll / u32::from(sdio_clk_ctrl.divisor()) + // } +} diff --git a/libboard_zynq_us/src/clocks/source.rs b/libboard_zynq_us/src/clocks/source.rs new file mode 100644 index 0000000..c48dd5d --- /dev/null +++ b/libboard_zynq_us/src/clocks/source.rs @@ -0,0 +1,255 @@ +use crate::slcr::common::{PllCfg, PllCtrl, PllFracCfg, SlcrRegisterBlock}; +use crate::slcr::{crf_apb, crl_apb}; +use libregister::{RegisterR, RegisterRW}; +use log::debug; + +pub const PS_CLK: u32 = 33_333_000; + +// DS926 Table: PS PLL Switching Characteristics (same for both speed grades) +// const PS_PLL_MAX_LOCK_TIME: f32 = 100e-6; // 100 us +const PS_PLL_MAX_OUT_FREQ: u32 = 1_600_000_000; +const PS_PLL_MIN_OUT_FREQ: u32 = 750_000_000; +// const PS_PLL_MAX_VCO_FREQ: u32 = 3_000_000_000; +const PS_PLL_MIN_VCO_FREQ: u32 = 1_500_000_000; + +/// UG1085 table 37-1 +/// (pll_fdiv_max, (pll_cp, pll_res, lfhf, lock_dly, lock_cnt)) +const PLL_FDIV_LOCK_PARAM: &[(u8, (u8, u8, u8, u8, u16))] = &[ + (25, (3, 10, 3, 63, 1000)), + (26, (3, 10, 3, 63, 1000)), + (27, (4, 6, 3, 63, 1000)), + (28, (4, 6, 3, 63, 1000)), + (29, (4, 6, 3, 63, 1000)), + (30, (4, 6, 3, 63, 1000)), + (31, (6, 1, 3, 63, 1000)), + (32, (6, 1, 3, 63, 1000)), + (33, (4, 10, 3, 63, 1000)), + (34, (5, 6, 3, 63, 1000)), + (35, (5, 6, 3, 63, 1000)), + (36, (5, 6, 3, 63, 1000)), + (37, (5, 6, 3, 63, 1000)), + (38, (5, 6, 3, 63, 975)), + (39, (3, 12, 3, 63, 950)), + (40, (3, 12, 3, 63, 925)), + (41, (3, 12, 3, 63, 900)), + (42, (3, 12, 3, 63, 875)), + (43, (3, 12, 3, 63, 850)), + (44, (3, 12, 3, 63, 850)), + (45, (3, 12, 3, 63, 825)), + (46, (3, 12, 3, 63, 800)), + (47, (3, 12, 3, 63, 775)), + (48, (3, 12, 3, 63, 775)), + (49, (3, 12, 3, 63, 750)), + (50, (3, 12, 3, 63, 750)), + (51, (3, 2, 3, 63, 725)), + (52, (3, 2, 3, 63, 700)), + (53, (3, 2, 3, 63, 700)), + (54, (3, 2, 3, 63, 675)), + (55, (3, 2, 3, 63, 675)), + (56, (3, 2, 3, 63, 650)), + (57, (3, 2, 3, 63, 650)), + (58, (3, 2, 3, 63, 625)), + (59, (3, 2, 3, 63, 625)), + (60, (3, 2, 3, 63, 625)), + (82, (3, 2, 3, 63, 600)), // 61-82 + (102, (4, 2, 3, 63, 600)), // 83-102 + (103, (5, 2, 3, 63, 600)), + (104, (5, 2, 3, 63, 600)), + (105, (5, 2, 3, 63, 600)), + (106, (5, 2, 3, 63, 600)), + (125, (3, 4, 3, 63, 600)), // 107-125 +]; + +pub trait ClockSource { + /// picks this ClockSource's registers from the SLCR block + fn pll_ctrl_regs(slcr: &mut T) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg); + + /// query PLL lock status + fn pll_locked() -> bool; + + // todo: is there any situation in which we'll actually want to use fractional mode? + /// query fraction mode enable bit + fn frac_enabled(pll_frac_cfg: &mut PllFracCfg) -> bool { + bool::from(pll_frac_cfg.read().enabled()) + } + + /// get configured frequency + fn freq(pll_ctrl: &mut PllCtrl) -> u32 { + // todo: take into account fractional part (if enabled) + u32::from(pll_ctrl.read().pll_fdiv()) * PS_CLK + } + + fn name() -> &'static str; + + // UG1085 Chapter 37: PS Clock Subsystem + fn setup(target_freq: u32) { + assert!(target_freq >= PS_PLL_MIN_OUT_FREQ && target_freq <= PS_PLL_MAX_OUT_FREQ); + let div2 = target_freq < PS_PLL_MIN_VCO_FREQ; + let divisor = u32::from(div2) + 1; + let fdiv = (target_freq * divisor / PS_CLK).min(125) as u8; + let (pll_cp, pll_res, lfhf, lock_dly, lock_cnt) = PLL_FDIV_LOCK_PARAM + .iter() + .filter(|(fdiv_max, _)| fdiv <= *fdiv_max) + .nth(0) + .expect("PLL_FDIV_LOCK_PARAM") + .1 + .clone(); + + debug!("Set {} to {} Hz", Self::name(), target_freq); + T::unlocked(|slcr| { + let (pll_ctrl, pll_cfg, _) = Self::pll_ctrl_regs(slcr); + + // Write fdiv, div2 + pll_ctrl.modify(|_, w| w.pll_fdiv(fdiv).pll_div2(div2)); + // Configure + // no need to zero as we're writing every field + pll_cfg.modify(|_, w| { + w.lock_dly(lock_dly) + .lock_cnt(lock_cnt) + .lfhf(lfhf) + .pll_cp(pll_cp) + .pll_res(pll_res) + }); + // Bypass + pll_ctrl.modify(|_, w| w.pll_bypass_force(true)); + // Reset + pll_ctrl.modify(|_, w| w.pll_reset(true)); + pll_ctrl.modify(|_, w| w.pll_reset(false)); + // Wait for PLL lock + // todo: add timeout here according to the 100 us spec? + while !Self::pll_locked() {} + // Remove bypass + pll_ctrl.modify(|_, w| w.pll_bypass_force(false)); + }); + } +} + +/// APU PLL: Recommended clock source for the APUs and the FPD interconnect +pub struct ApuPll; + +impl ClockSource for ApuPll { + #[inline] + fn pll_ctrl_regs( + slcr: &mut crf_apb::RegisterBlock, + ) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg) { + ( + &mut slcr.apu_pll_ctrl, + &mut slcr.apu_pll_cfg, + &mut slcr.apu_pll_frac_cfg, + ) + } + + #[inline] + fn pll_locked() -> bool { + let slcr = crf_apb::RegisterBlock::slcr(); + slcr.pll_status.read().apu_pll_lock() + } + + fn name() -> &'static str { + &"APU_PLL" + } +} + +/// DDR PLL: Recommended clock for the DDR DRAM controller and AXI_HP interfaces +pub struct DdrPll; + +impl ClockSource for DdrPll { + #[inline] + fn pll_ctrl_regs( + slcr: &mut crf_apb::RegisterBlock, + ) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg) { + ( + &mut slcr.ddr_pll_ctrl, + &mut slcr.ddr_pll_cfg, + &mut slcr.ddr_pll_frac_cfg, + ) + } + + #[inline] + fn pll_locked() -> bool { + let slcr = crf_apb::RegisterBlock::slcr(); + slcr.pll_status.read().ddr_pll_lock() + } + + fn name() -> &'static str { + &"DDR_PLL" + } +} + +/// Video PLL: Recommended clock for DisplayPort +pub struct VideoPll; + +impl ClockSource for VideoPll { + #[inline] + fn pll_ctrl_regs( + slcr: &mut crf_apb::RegisterBlock, + ) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg) { + ( + &mut slcr.video_pll_ctrl, + &mut slcr.video_pll_cfg, + &mut slcr.video_pll_frac_cfg, + ) + } + + #[inline] + fn pll_locked() -> bool { + let slcr = crf_apb::RegisterBlock::slcr(); + slcr.pll_status.read().video_pll_lock() + } + + fn name() -> &'static str { + &"VIDEO_PLL" + } +} + +/// I/O PLL: Recommended clock for I/O peripherals +pub struct IoPll; + +impl ClockSource for IoPll { + #[inline] + fn pll_ctrl_regs( + slcr: &mut crl_apb::RegisterBlock, + ) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg) { + ( + &mut slcr.io_pll_ctrl, + &mut slcr.io_pll_cfg, + &mut slcr.io_pll_frac_cfg, + ) + } + + #[inline] + fn pll_locked() -> bool { + let slcr = crl_apb::RegisterBlock::slcr(); + slcr.pll_status.read().io_pll_lock() + } + + fn name() -> &'static str { + &"IO_PLL" + } +} + +/// RPU PLL: Recommended clock for RPUs and LPD interconnect +pub struct RpuPll; + +impl ClockSource for RpuPll { + #[inline] + fn pll_ctrl_regs( + slcr: &mut crl_apb::RegisterBlock, + ) -> (&mut PllCtrl, &mut PllCfg, &mut PllFracCfg) { + ( + &mut slcr.io_pll_ctrl, + &mut slcr.io_pll_cfg, + &mut slcr.io_pll_frac_cfg, + ) + } + + #[inline] + fn pll_locked() -> bool { + let slcr = crl_apb::RegisterBlock::slcr(); + slcr.pll_status.read().rpu_pll_lock() + } + + fn name() -> &'static str { + &"RPU_PLL" + } +} diff --git a/libboard_zynq_us/src/lib.rs b/libboard_zynq_us/src/lib.rs index ae65f81..c71fd9d 100644 --- a/libboard_zynq_us/src/lib.rs +++ b/libboard_zynq_us/src/lib.rs @@ -6,4 +6,4 @@ extern crate alloc; pub use libboard_zynq::smoltcp; pub mod slcr; -pub mod uart; +pub mod clocks;