Files
zynq7000-rs/zynq/zynq7000-hal/src/clocks/mod.rs
Robin Mueller 789cad5bd5
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
rename register blocks
2025-10-18 23:24:36 +02:00

525 lines
18 KiB
Rust

//! # Clock module
//!
//! ## Examples
//!
//! - PLL initialization in [Zedboard FSBL](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-fsbl)
use arbitrary_int::{prelude::*, u6};
pub mod pll;
use zynq7000::slcr::{
ClockControlRegisters,
clocks::{
ClockkRatioSelect, DualCommonPeriphIoClockControl, FpgaClockControl, GigEthClockControl,
SingleCommonPeriphIoClockControl,
},
};
use super::time::Hertz;
#[derive(Debug)]
pub struct ArmClocks {
ref_clk: Hertz,
cpu_1x_clk: Hertz,
cpu_2x_clk: Hertz,
cpu_3x2x_clk: Hertz,
cpu_6x4x_clk: Hertz,
}
impl ArmClocks {
/// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
pub const fn cpu_1x_clk(&self) -> Hertz {
self.cpu_1x_clk
}
pub const fn cpu_2x_clk(&self) -> Hertz {
self.cpu_2x_clk
}
pub const fn cpu_3x2x_clk(&self) -> Hertz {
self.cpu_3x2x_clk
}
pub const fn cpu_6x4x_clk(&self) -> Hertz {
self.cpu_6x4x_clk
}
}
#[derive(Debug)]
pub struct DdrClocks {
/// DDR reference clock generated by the DDR PLL.
ref_clk: Hertz,
ddr_3x_clk: Hertz,
ddr_2x_clk: Hertz,
}
impl DdrClocks {
/// Update the DDR 3x and 2x clocks in the SLCR.
///
/// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
/// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
/// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
/// operating frequency.
///
/// # Safety
///
/// This should only be called once during start-up. It accesses the SLCR register.
pub unsafe fn configure_2x_3x_clk(ddr_3x_div: u6, ddr_2x_div: u6) {
// Safety: The DDR clock structure is a singleton.
unsafe {
crate::slcr::Slcr::with(|slcr| {
slcr.clk_ctrl().modify_ddr_clk_ctrl(|mut val| {
val.set_div_3x_clk(ddr_3x_div);
val.set_div_2x_clk(ddr_2x_div);
val
});
});
}
}
/// Update the DDR 3x and 2x clocks in the SLCR and creates a DDR clock information structure.
///
/// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
/// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
/// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
/// operating frequency.
///
/// # Safety
///
/// This should only be called once during start-up. It accesses the SLCR register.
pub unsafe fn new_with_2x_3x_init(ref_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
unsafe { Self::configure_2x_3x_clk(ddr_3x_div, ddr_2x_div) };
Self {
ref_clk,
ddr_3x_clk: ref_clk / ddr_3x_div.as_u32(),
ddr_2x_clk: ref_clk / ddr_2x_div.as_u32(),
}
}
/// Reference clock provided by DDR PLL which is used to calculate all other clock frequencies.
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
/// DDR 3x clock which is used by the DRAM and must be set to the operating frequency.
pub fn ddr_3x_clk(&self) -> Hertz {
self.ddr_3x_clk
}
/// DDR 2x clock is used by the interconnect and is typically set to 2/3 of the operating
/// frequency.
pub fn ddr_2x_clk(&self) -> Hertz {
self.ddr_2x_clk
}
}
#[derive(Debug)]
pub struct IoClocks {
/// Reference clock provided by IO PLL which is used to calculate all other clock frequencies.
ref_clk: Hertz,
smc_clk: Hertz,
qspi_clk: Hertz,
sdio_clk: Hertz,
uart_clk: Hertz,
spi_clk: Hertz,
can_clk: Hertz,
pcap_2x_clk: Hertz,
trace_clk: Option<Hertz>,
}
impl IoClocks {
pub const fn ref_clk(&self) -> Hertz {
self.ref_clk
}
pub const fn smc_clk(&self) -> Hertz {
self.smc_clk
}
pub fn update_smc_clk(&mut self, clk: Hertz) {
self.smc_clk = clk
}
pub const fn qspi_clk(&self) -> Hertz {
self.qspi_clk
}
pub fn update_qspi_clk(&mut self, clk: Hertz) {
self.qspi_clk = clk
}
pub const fn sdio_clk(&self) -> Hertz {
self.sdio_clk
}
pub fn update_sdio_clk(&mut self, clk: Hertz) {
self.sdio_clk = clk
}
pub const fn uart_clk(&self) -> Hertz {
self.uart_clk
}
pub fn update_uart_clk(&mut self, clk: Hertz) {
self.uart_clk = clk
}
pub const fn spi_clk(&self) -> Hertz {
self.spi_clk
}
pub fn update_spi_clk(&mut self, clk: Hertz) {
self.spi_clk = clk
}
pub fn can_clk(&self) -> Hertz {
self.can_clk
}
pub fn update_can_clk(&mut self, clk: Hertz) {
self.can_clk = clk
}
pub fn pcap_2x_clk(&self) -> Hertz {
self.pcap_2x_clk
}
pub fn update_pcap_2x_clk(&mut self, clk: Hertz) {
self.pcap_2x_clk = clk
}
/// Returns [None] if the trace clock is configured to use the EMIO trace clock.
pub fn trace_clk(&self) -> Option<Hertz> {
self.trace_clk
}
}
#[derive(Debug)]
pub struct Clocks {
ps_clk: Hertz,
arm_pll_out: Hertz,
io_pll_out: Hertz,
ddr_pll_out: Hertz,
arm: ArmClocks,
ddr: DdrClocks,
io: IoClocks,
pl: [Hertz; 4],
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ClockModuleId {
Ddr,
Arm,
Smc,
Qspi,
Sdio,
Uart,
Spi,
Pcap,
Can,
Fpga,
Trace,
Gem0,
Gem1,
}
#[derive(Debug)]
pub struct DivisorZero(pub ClockModuleId);
#[derive(Debug, thiserror::Error)]
pub enum ClockReadError {
/// The feedback value for the PLL clock output calculation is zero.
#[error("PLL feedback divisor is zero")]
PllFeedbackZero,
/// Detected a divisor of zero.
#[error("divisor is zero")]
DivisorZero(DivisorZero),
/// Detected a divisor that is not even and should be.
#[error("divisor is not even")]
DivisorNotEven,
}
impl Clocks {
/// Processing system clock, which is generally dependent on the board and the used crystal.
pub fn ps_clk(&self) -> Hertz {
self.ps_clk
}
/// This generates the clock configuration by reading the SLCR clock registers.
///
/// It assumes that the clock already has been configured, for example by a first-stage
/// bootloader, or the PS7 initialization script.
pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
let mut clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
let io_pll_cfg = clk_regs.read_io_pll_ctrl();
let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl();
if arm_pll_cfg.fdiv().as_u32() == 0
|| io_pll_cfg.fdiv().as_u32() == 0
|| ddr_pll_cfg.fdiv().as_u32() == 0
{
return Err(ClockReadError::PllFeedbackZero);
}
let arm_pll_out = ps_clk_freq * arm_pll_cfg.fdiv().into();
let io_pll_out = ps_clk_freq * io_pll_cfg.fdiv().into();
let ddr_pll_out = ps_clk_freq * ddr_pll_cfg.fdiv().into();
let arm_clk_ctrl = clk_regs.read_arm_clk_ctrl();
let arm_base_clk = match arm_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelArm::ArmPll
| zynq7000::slcr::clocks::SrcSelArm::ArmPllAlt => arm_pll_out,
zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
};
let clk_sel = clk_regs.read_clk_621_true();
if arm_clk_ctrl.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
}
let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
let arm_clks = match clk_sel.sel() {
ClockkRatioSelect::FourToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 4,
cpu_2x_clk: arm_clk_divided / 2,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
},
ClockkRatioSelect::SixToTwoToOne => ArmClocks {
ref_clk: arm_pll_out,
cpu_1x_clk: arm_clk_divided / 6,
cpu_2x_clk: arm_clk_divided / 3,
cpu_3x2x_clk: arm_clk_divided / 2,
cpu_6x4x_clk: arm_clk_divided,
},
};
let ddr_clk_ctrl = clk_regs.read_ddr_clk_ctrl();
if ddr_clk_ctrl.div_3x_clk().as_u32() == 0 || ddr_clk_ctrl.div_2x_clk().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Ddr)));
}
let ddr_clks = DdrClocks {
ref_clk: ddr_pll_out,
ddr_3x_clk: ddr_pll_out / ddr_clk_ctrl.div_3x_clk().as_u32(),
ddr_2x_clk: ddr_pll_out / ddr_clk_ctrl.div_2x_clk().as_u32(),
};
let handle_common_single_clock_config = |single_block: SingleCommonPeriphIoClockControl,
id: ClockModuleId|
-> Result<Hertz, ClockReadError> {
if single_block.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(id)));
}
Ok(match single_block.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out / single_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out / single_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out / single_block.divisor().as_u32()
}
})
};
let handle_common_dual_clock_config = |dual_block: DualCommonPeriphIoClockControl,
id: ClockModuleId|
-> Result<Hertz, ClockReadError> {
if dual_block.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(id)));
}
Ok(match dual_block.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out / dual_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out / dual_block.divisor().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out / dual_block.divisor().as_u32()
}
})
};
let smc_clk =
handle_common_single_clock_config(clk_regs.read_smc_clk_ctrl(), ClockModuleId::Smc)?;
let qspi_clk =
handle_common_single_clock_config(clk_regs.read_lqspi_clk_ctrl(), ClockModuleId::Qspi)?;
let sdio_clk =
handle_common_dual_clock_config(clk_regs.read_sdio_clk_ctrl(), ClockModuleId::Sdio)?;
let uart_clk =
handle_common_dual_clock_config(clk_regs.read_uart_clk_ctrl(), ClockModuleId::Uart)?;
let spi_clk =
handle_common_dual_clock_config(clk_regs.read_spi_clk_ctrl(), ClockModuleId::Spi)?;
let pcap_2x_clk =
handle_common_single_clock_config(clk_regs.read_pcap_clk_ctrl(), ClockModuleId::Pcap)?;
let can_clk_ctrl = clk_regs.read_can_clk_ctrl();
let can_clk_ref_clk = match can_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => io_pll_out,
zynq7000::slcr::clocks::SrcSelIo::ArmPll => arm_pll_out,
zynq7000::slcr::clocks::SrcSelIo::DdrPll => ddr_pll_out,
};
if can_clk_ctrl.divisor_0().as_u32() == 0 || can_clk_ctrl.divisor_1().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Can)));
}
let can_clk =
can_clk_ref_clk / can_clk_ctrl.divisor_0().as_u32() / can_clk_ctrl.divisor_1().as_u32();
let trace_clk_ctrl = clk_regs.read_dbg_clk_ctrl();
if trace_clk_ctrl.divisor().as_u32() == 0 {
return Err(ClockReadError::DivisorZero(DivisorZero(
ClockModuleId::Trace,
)));
}
let trace_clk = match trace_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelTpiu::IoPll
| zynq7000::slcr::clocks::SrcSelTpiu::IoPllAlt => {
Some(io_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::ArmPll => {
Some(arm_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::DdrPll => {
Some(ddr_pll_out / trace_clk_ctrl.divisor().as_u32())
}
zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClk
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt0
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt1
| zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt2 => None,
};
let calculate_fpga_clk =
|fpga_clk_ctrl: FpgaClockControl| -> Result<Hertz, ClockReadError> {
if fpga_clk_ctrl.divisor_0().as_u32() == 0
|| fpga_clk_ctrl.divisor_1().as_u32() == 0
{
return Err(ClockReadError::DivisorZero(DivisorZero(
ClockModuleId::Fpga,
)));
}
Ok(match fpga_clk_ctrl.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
io_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
arm_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
ddr_pll_out
/ fpga_clk_ctrl.divisor_0().as_u32()
/ fpga_clk_ctrl.divisor_1().as_u32()
}
})
};
Ok(Self {
ps_clk: ps_clk_freq,
io_pll_out,
ddr_pll_out,
arm_pll_out,
arm: arm_clks,
ddr: ddr_clks,
io: IoClocks {
ref_clk: io_pll_out,
smc_clk,
qspi_clk,
sdio_clk,
uart_clk,
spi_clk,
can_clk,
pcap_2x_clk,
trace_clk,
},
// TODO: There should be a mut and a non-mut getter for an inner block. We only do pure
// reads with the inner block here.
pl: [
calculate_fpga_clk(clk_regs.fpga_0_clk_ctrl().read_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_1_clk_ctrl().read_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_2_clk_ctrl().read_ctrl())?,
calculate_fpga_clk(clk_regs.fpga_3_clk_ctrl().read_ctrl())?,
],
})
}
pub fn arm_clocks(&self) -> &ArmClocks {
&self.arm
}
pub fn ddr_clocks(&self) -> &DdrClocks {
&self.ddr
}
pub fn io_clocks(&self) -> &IoClocks {
&self.io
}
pub fn io_clocks_mut(&mut self) -> &mut IoClocks {
&mut self.io
}
/// Programmable Logic (PL) FCLK clocks.
pub fn pl_clocks(&self) -> &[Hertz; 4] {
&self.pl
}
fn calculate_gem_ref_clock(
&self,
reg: GigEthClockControl,
module: ClockModuleId,
) -> Result<Hertz, DivisorZero> {
let source_clk = match reg.srcsel() {
zynq7000::slcr::clocks::SrcSelIo::IoPll
| zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => self.io_pll_out,
zynq7000::slcr::clocks::SrcSelIo::ArmPll => self.arm_pll_out,
zynq7000::slcr::clocks::SrcSelIo::DdrPll => self.ddr_pll_out,
};
let div0 = reg.divisor_0().as_u32();
if div0 == 0 {
return Err(DivisorZero(module));
}
let div1 = reg.divisor_1().as_u32();
if div1 == 0 {
return Err(DivisorZero(module));
}
Ok(source_clk / reg.divisor_0().as_u32() / reg.divisor_1().as_u32())
}
/// Calculate the reference clock for GEM0.
///
/// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
/// configuration before calling this function.
///
/// It should be noted that the GEM has a separate TX and RX clock.
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
pub fn calculate_gem_0_ref_clock(&self) -> Result<Hertz, DivisorZero> {
let clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem0)
}
/// Calculate the reference clock for GEM1.
///
/// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
/// configuration before calling this function.
///
/// It should be noted that the GEM has a separate TX and RX clock.
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
pub fn calculate_gem_1_ref_clock(&self) -> Result<Hertz, DivisorZero> {
let clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem1)
}
}