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
525 lines
18 KiB
Rust
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)
|
|
}
|
|
}
|