zynq_us: clocks (WIP)

feature/zcu111
Brad Bondurant 2022-11-17 15:46:27 -05:00
parent 0f2376410f
commit 188954ddc7
3 changed files with 345 additions and 1 deletions

View File

@ -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())
// }
}

View File

@ -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<T: SlcrRegisterBlock> {
/// 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<crf_apb::RegisterBlock> 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<crf_apb::RegisterBlock> 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<crf_apb::RegisterBlock> 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<crl_apb::RegisterBlock> 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<crl_apb::RegisterBlock> 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"
}
}

View File

@ -6,4 +6,4 @@ extern crate alloc;
pub use libboard_zynq::smoltcp;
pub mod slcr;
pub mod uart;
pub mod clocks;