zynq7000_hal/clocks/
mod.rs

1//! # Clock module
2//!
3//! ## Examples
4//!
5//! - PLL initialization in [Zedboard FSBL](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-fsbl)
6use arbitrary_int::{prelude::*, u6};
7
8pub mod pll;
9
10use zynq7000::slcr::{
11    ClockControl,
12    clocks::{
13        ClockkRatioSelect, DualCommonPeriphIoClockControl, FpgaClockControl, GigEthClockControl,
14        SingleCommonPeriphIoClockControl,
15    },
16};
17
18use super::time::Hertz;
19
20#[derive(Debug)]
21pub struct ArmClocks {
22    ref_clk: Hertz,
23    cpu_1x_clk: Hertz,
24    cpu_2x_clk: Hertz,
25    cpu_3x2x_clk: Hertz,
26    cpu_6x4x_clk: Hertz,
27}
28
29impl ArmClocks {
30    /// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
31    pub const fn ref_clk(&self) -> Hertz {
32        self.ref_clk
33    }
34
35    pub const fn cpu_1x_clk(&self) -> Hertz {
36        self.cpu_1x_clk
37    }
38
39    pub const fn cpu_2x_clk(&self) -> Hertz {
40        self.cpu_2x_clk
41    }
42
43    pub const fn cpu_3x2x_clk(&self) -> Hertz {
44        self.cpu_3x2x_clk
45    }
46
47    pub const fn cpu_6x4x_clk(&self) -> Hertz {
48        self.cpu_6x4x_clk
49    }
50}
51
52#[derive(Debug)]
53pub struct DdrClocks {
54    /// DDR reference clock generated by the DDR PLL.
55    ref_clk: Hertz,
56    ddr_3x_clk: Hertz,
57    ddr_2x_clk: Hertz,
58}
59
60impl DdrClocks {
61    /// Update the DDR 3x and 2x clocks in the SLCR.
62    ///
63    /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
64    /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
65    /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
66    /// operating frequency.
67    ///
68    /// # Safety
69    ///
70    /// This should only be called once during start-up. It accesses the SLCR register.
71    pub unsafe fn configure_2x_3x_clk(ddr_3x_div: u6, ddr_2x_div: u6) {
72        // Safety: The DDR clock structure is a singleton.
73        unsafe {
74            crate::slcr::Slcr::with(|slcr| {
75                slcr.clk_ctrl().modify_ddr_clk_ctrl(|mut val| {
76                    val.set_div_3x_clk(ddr_3x_div);
77                    val.set_div_2x_clk(ddr_2x_div);
78                    val
79                });
80            });
81        }
82    }
83
84    /// Update the DDR 3x and 2x clocks in the SLCR and creates a DDR clock information structure.
85    ///
86    /// Usually, the DDR PLL output clock will be set to an even multiple of the DDR operating
87    /// frequency. In that case, the multiplicator should be used as the DDR 3x clock divisor.
88    /// The DDR 2x clock divisor should be set so that the resulting clock is 2/3 of the DDR
89    /// operating frequency.
90    ///
91    /// # Safety
92    ///
93    /// This should only be called once during start-up. It accesses the SLCR register.
94    pub unsafe fn new_with_2x_3x_init(ref_clk: Hertz, ddr_3x_div: u6, ddr_2x_div: u6) -> Self {
95        unsafe { Self::configure_2x_3x_clk(ddr_3x_div, ddr_2x_div) };
96        Self {
97            ref_clk,
98            ddr_3x_clk: ref_clk / ddr_3x_div.as_u32(),
99            ddr_2x_clk: ref_clk / ddr_2x_div.as_u32(),
100        }
101    }
102
103    /// Reference clock provided by DDR PLL which is used to calculate all other clock frequencies.
104    pub const fn ref_clk(&self) -> Hertz {
105        self.ref_clk
106    }
107
108    /// DDR 3x clock which is used by the DRAM and must be set to the operating frequency.
109    pub fn ddr_3x_clk(&self) -> Hertz {
110        self.ddr_3x_clk
111    }
112
113    /// DDR 2x clock is used by the interconnect and is typically set to 2/3 of the operating
114    /// frequency.
115    pub fn ddr_2x_clk(&self) -> Hertz {
116        self.ddr_2x_clk
117    }
118}
119
120#[derive(Debug)]
121pub struct IoClocks {
122    /// Reference clock provided by IO PLL which is used to calculate all other clock frequencies.
123    ref_clk: Hertz,
124    smc_clk: Hertz,
125    qspi_clk: Hertz,
126    sdio_clk: Hertz,
127    uart_clk: Hertz,
128    spi_clk: Hertz,
129    can_clk: Hertz,
130    pcap_2x_clk: Hertz,
131    trace_clk: Option<Hertz>,
132}
133
134impl IoClocks {
135    pub const fn ref_clk(&self) -> Hertz {
136        self.ref_clk
137    }
138
139    pub const fn smc_clk(&self) -> Hertz {
140        self.smc_clk
141    }
142
143    pub fn update_smc_clk(&mut self, clk: Hertz) {
144        self.smc_clk = clk
145    }
146
147    pub const fn qspi_clk(&self) -> Hertz {
148        self.qspi_clk
149    }
150
151    pub fn update_qspi_clk(&mut self, clk: Hertz) {
152        self.qspi_clk = clk
153    }
154
155    pub const fn sdio_clk(&self) -> Hertz {
156        self.sdio_clk
157    }
158
159    pub fn update_sdio_clk(&mut self, clk: Hertz) {
160        self.sdio_clk = clk
161    }
162
163    pub const fn uart_clk(&self) -> Hertz {
164        self.uart_clk
165    }
166
167    pub fn update_uart_clk(&mut self, clk: Hertz) {
168        self.uart_clk = clk
169    }
170
171    pub const fn spi_clk(&self) -> Hertz {
172        self.spi_clk
173    }
174
175    pub fn update_spi_clk(&mut self, clk: Hertz) {
176        self.spi_clk = clk
177    }
178
179    pub fn can_clk(&self) -> Hertz {
180        self.can_clk
181    }
182
183    pub fn update_can_clk(&mut self, clk: Hertz) {
184        self.can_clk = clk
185    }
186
187    pub fn pcap_2x_clk(&self) -> Hertz {
188        self.pcap_2x_clk
189    }
190
191    pub fn update_pcap_2x_clk(&mut self, clk: Hertz) {
192        self.pcap_2x_clk = clk
193    }
194
195    /// Returns [None] if the trace clock is configured to use the EMIO trace clock.
196    pub fn trace_clk(&self) -> Option<Hertz> {
197        self.trace_clk
198    }
199}
200
201#[derive(Debug)]
202pub struct Clocks {
203    ps_clk: Hertz,
204    arm_pll_out: Hertz,
205    io_pll_out: Hertz,
206    ddr_pll_out: Hertz,
207    arm: ArmClocks,
208    ddr: DdrClocks,
209    io: IoClocks,
210    pl: [Hertz; 4],
211}
212
213#[derive(Debug, Copy, Clone, PartialEq, Eq)]
214pub enum ClockModuleId {
215    Ddr,
216    Arm,
217    Smc,
218    Qspi,
219    Sdio,
220    Uart,
221    Spi,
222    Pcap,
223    Can,
224    Fpga,
225    Trace,
226    Gem0,
227    Gem1,
228}
229
230#[derive(Debug)]
231pub struct DivisorZero(pub ClockModuleId);
232
233#[derive(Debug, thiserror::Error)]
234pub enum ClockReadError {
235    /// The feedback value for the PLL clock output calculation is zero.
236    #[error("PLL feedback divisor is zero")]
237    PllFeedbackZero,
238    /// Detected a divisor of zero.
239    #[error("divisor is zero")]
240    DivisorZero(DivisorZero),
241    /// Detected a divisor that is not even and should be.
242    #[error("divisor is not even")]
243    DivisorNotEven,
244}
245
246impl Clocks {
247    /// Processing system clock, which is generally dependent on the board and the used crystal.
248    pub fn ps_clk(&self) -> Hertz {
249        self.ps_clk
250    }
251
252    /// This generates the clock configuration by reading the SLCR clock registers.
253    ///
254    /// It assumes that the clock already has been configured, for example by a first-stage
255    /// bootloader, or the PS7 initialization script.
256    pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
257        let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() };
258
259        let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
260        let io_pll_cfg = clk_regs.read_io_pll_ctrl();
261        let ddr_pll_cfg = clk_regs.read_ddr_pll_ctrl();
262
263        if arm_pll_cfg.fdiv().as_u32() == 0
264            || io_pll_cfg.fdiv().as_u32() == 0
265            || ddr_pll_cfg.fdiv().as_u32() == 0
266        {
267            return Err(ClockReadError::PllFeedbackZero);
268        }
269        let arm_pll_out = ps_clk_freq * arm_pll_cfg.fdiv().into();
270        let io_pll_out = ps_clk_freq * io_pll_cfg.fdiv().into();
271        let ddr_pll_out = ps_clk_freq * ddr_pll_cfg.fdiv().into();
272
273        let arm_clk_ctrl = clk_regs.read_arm_clk_ctrl();
274        let arm_base_clk = match arm_clk_ctrl.srcsel() {
275            zynq7000::slcr::clocks::SrcSelArm::ArmPll
276            | zynq7000::slcr::clocks::SrcSelArm::ArmPllAlt => arm_pll_out,
277            zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
278            zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
279        };
280        let clk_sel = clk_regs.read_clk_621_true();
281        if arm_clk_ctrl.divisor().as_u32() == 0 {
282            return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
283        }
284        let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
285        let arm_clks = match clk_sel.sel() {
286            ClockkRatioSelect::FourToTwoToOne => ArmClocks {
287                ref_clk: arm_pll_out,
288                cpu_1x_clk: arm_clk_divided / 4,
289                cpu_2x_clk: arm_clk_divided / 2,
290                cpu_3x2x_clk: arm_clk_divided / 2,
291                cpu_6x4x_clk: arm_clk_divided,
292            },
293            ClockkRatioSelect::SixToTwoToOne => ArmClocks {
294                ref_clk: arm_pll_out,
295                cpu_1x_clk: arm_clk_divided / 6,
296                cpu_2x_clk: arm_clk_divided / 3,
297                cpu_3x2x_clk: arm_clk_divided / 2,
298                cpu_6x4x_clk: arm_clk_divided,
299            },
300        };
301
302        let ddr_clk_ctrl = clk_regs.read_ddr_clk_ctrl();
303        if ddr_clk_ctrl.div_3x_clk().as_u32() == 0 || ddr_clk_ctrl.div_2x_clk().as_u32() == 0 {
304            return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Ddr)));
305        }
306        let ddr_clks = DdrClocks {
307            ref_clk: ddr_pll_out,
308            ddr_3x_clk: ddr_pll_out / ddr_clk_ctrl.div_3x_clk().as_u32(),
309            ddr_2x_clk: ddr_pll_out / ddr_clk_ctrl.div_2x_clk().as_u32(),
310        };
311
312        let handle_common_single_clock_config = |single_block: SingleCommonPeriphIoClockControl,
313                                                 id: ClockModuleId|
314         -> Result<Hertz, ClockReadError> {
315            if single_block.divisor().as_u32() == 0 {
316                return Err(ClockReadError::DivisorZero(DivisorZero(id)));
317            }
318            Ok(match single_block.srcsel() {
319                zynq7000::slcr::clocks::SrcSelIo::IoPll
320                | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
321                    io_pll_out / single_block.divisor().as_u32()
322                }
323                zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
324                    arm_pll_out / single_block.divisor().as_u32()
325                }
326                zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
327                    ddr_pll_out / single_block.divisor().as_u32()
328                }
329            })
330        };
331        let handle_common_dual_clock_config = |dual_block: DualCommonPeriphIoClockControl,
332                                               id: ClockModuleId|
333         -> Result<Hertz, ClockReadError> {
334            if dual_block.divisor().as_u32() == 0 {
335                return Err(ClockReadError::DivisorZero(DivisorZero(id)));
336            }
337            Ok(match dual_block.srcsel() {
338                zynq7000::slcr::clocks::SrcSelIo::IoPll
339                | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
340                    io_pll_out / dual_block.divisor().as_u32()
341                }
342                zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
343                    arm_pll_out / dual_block.divisor().as_u32()
344                }
345                zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
346                    ddr_pll_out / dual_block.divisor().as_u32()
347                }
348            })
349        };
350
351        let smc_clk =
352            handle_common_single_clock_config(clk_regs.read_smc_clk_ctrl(), ClockModuleId::Smc)?;
353        let qspi_clk =
354            handle_common_single_clock_config(clk_regs.read_lqspi_clk_ctrl(), ClockModuleId::Qspi)?;
355        let sdio_clk =
356            handle_common_dual_clock_config(clk_regs.read_sdio_clk_ctrl(), ClockModuleId::Sdio)?;
357        let uart_clk =
358            handle_common_dual_clock_config(clk_regs.read_uart_clk_ctrl(), ClockModuleId::Uart)?;
359        let spi_clk =
360            handle_common_dual_clock_config(clk_regs.read_spi_clk_ctrl(), ClockModuleId::Spi)?;
361        let pcap_2x_clk =
362            handle_common_single_clock_config(clk_regs.read_pcap_clk_ctrl(), ClockModuleId::Pcap)?;
363        let can_clk_ctrl = clk_regs.read_can_clk_ctrl();
364        let can_clk_ref_clk = match can_clk_ctrl.srcsel() {
365            zynq7000::slcr::clocks::SrcSelIo::IoPll
366            | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => io_pll_out,
367            zynq7000::slcr::clocks::SrcSelIo::ArmPll => arm_pll_out,
368            zynq7000::slcr::clocks::SrcSelIo::DdrPll => ddr_pll_out,
369        };
370        if can_clk_ctrl.divisor_0().as_u32() == 0 || can_clk_ctrl.divisor_1().as_u32() == 0 {
371            return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Can)));
372        }
373        let can_clk =
374            can_clk_ref_clk / can_clk_ctrl.divisor_0().as_u32() / can_clk_ctrl.divisor_1().as_u32();
375
376        let trace_clk_ctrl = clk_regs.read_dbg_clk_ctrl();
377        if trace_clk_ctrl.divisor().as_u32() == 0 {
378            return Err(ClockReadError::DivisorZero(DivisorZero(
379                ClockModuleId::Trace,
380            )));
381        }
382        let trace_clk = match trace_clk_ctrl.srcsel() {
383            zynq7000::slcr::clocks::SrcSelTpiu::IoPll
384            | zynq7000::slcr::clocks::SrcSelTpiu::IoPllAlt => {
385                Some(io_pll_out / trace_clk_ctrl.divisor().as_u32())
386            }
387            zynq7000::slcr::clocks::SrcSelTpiu::ArmPll => {
388                Some(arm_pll_out / trace_clk_ctrl.divisor().as_u32())
389            }
390            zynq7000::slcr::clocks::SrcSelTpiu::DdrPll => {
391                Some(ddr_pll_out / trace_clk_ctrl.divisor().as_u32())
392            }
393            zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClk
394            | zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt0
395            | zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt1
396            | zynq7000::slcr::clocks::SrcSelTpiu::EmioTraceClkAlt2 => None,
397        };
398        let calculate_fpga_clk =
399            |fpga_clk_ctrl: FpgaClockControl| -> Result<Hertz, ClockReadError> {
400                if fpga_clk_ctrl.divisor_0().as_u32() == 0
401                    || fpga_clk_ctrl.divisor_1().as_u32() == 0
402                {
403                    return Err(ClockReadError::DivisorZero(DivisorZero(
404                        ClockModuleId::Fpga,
405                    )));
406                }
407                Ok(match fpga_clk_ctrl.srcsel() {
408                    zynq7000::slcr::clocks::SrcSelIo::IoPll
409                    | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => {
410                        io_pll_out
411                            / fpga_clk_ctrl.divisor_0().as_u32()
412                            / fpga_clk_ctrl.divisor_1().as_u32()
413                    }
414                    zynq7000::slcr::clocks::SrcSelIo::ArmPll => {
415                        arm_pll_out
416                            / fpga_clk_ctrl.divisor_0().as_u32()
417                            / fpga_clk_ctrl.divisor_1().as_u32()
418                    }
419                    zynq7000::slcr::clocks::SrcSelIo::DdrPll => {
420                        ddr_pll_out
421                            / fpga_clk_ctrl.divisor_0().as_u32()
422                            / fpga_clk_ctrl.divisor_1().as_u32()
423                    }
424                })
425            };
426
427        Ok(Self {
428            ps_clk: ps_clk_freq,
429            io_pll_out,
430            ddr_pll_out,
431            arm_pll_out,
432            arm: arm_clks,
433            ddr: ddr_clks,
434            io: IoClocks {
435                ref_clk: io_pll_out,
436                smc_clk,
437                qspi_clk,
438                sdio_clk,
439                uart_clk,
440                spi_clk,
441                can_clk,
442                pcap_2x_clk,
443                trace_clk,
444            },
445            // TODO: There should be a mut and a non-mut getter for an inner block. We only do pure
446            // reads with the inner block here.
447            pl: [
448                calculate_fpga_clk(clk_regs.fpga_0_clk_ctrl().read_ctrl())?,
449                calculate_fpga_clk(clk_regs.fpga_1_clk_ctrl().read_ctrl())?,
450                calculate_fpga_clk(clk_regs.fpga_2_clk_ctrl().read_ctrl())?,
451                calculate_fpga_clk(clk_regs.fpga_3_clk_ctrl().read_ctrl())?,
452            ],
453        })
454    }
455
456    pub fn arm_clocks(&self) -> &ArmClocks {
457        &self.arm
458    }
459
460    pub fn ddr_clocks(&self) -> &DdrClocks {
461        &self.ddr
462    }
463
464    pub fn io_clocks(&self) -> &IoClocks {
465        &self.io
466    }
467
468    pub fn io_clocks_mut(&mut self) -> &mut IoClocks {
469        &mut self.io
470    }
471
472    /// Programmable Logic (PL) FCLK clocks.
473    pub fn pl_clocks(&self) -> &[Hertz; 4] {
474        &self.pl
475    }
476
477    fn calculate_gem_ref_clock(
478        &self,
479        reg: GigEthClockControl,
480        module: ClockModuleId,
481    ) -> Result<Hertz, DivisorZero> {
482        let source_clk = match reg.srcsel() {
483            zynq7000::slcr::clocks::SrcSelIo::IoPll
484            | zynq7000::slcr::clocks::SrcSelIo::IoPllAlt => self.io_pll_out,
485            zynq7000::slcr::clocks::SrcSelIo::ArmPll => self.arm_pll_out,
486            zynq7000::slcr::clocks::SrcSelIo::DdrPll => self.ddr_pll_out,
487        };
488        let div0 = reg.divisor_0().as_u32();
489        if div0 == 0 {
490            return Err(DivisorZero(module));
491        }
492        let div1 = reg.divisor_1().as_u32();
493        if div1 == 0 {
494            return Err(DivisorZero(module));
495        }
496        Ok(source_clk / reg.divisor_0().as_u32() / reg.divisor_1().as_u32())
497    }
498
499    /// Calculate the reference clock for GEM0.
500    ///
501    /// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
502    /// configuration before calling this function.
503    ///
504    /// It should be noted that the GEM has a separate TX and RX clock.
505    /// The reference clock will only be the RX clock in loopback mode. For the TX block,
506    /// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
507    pub fn calculate_gem_0_ref_clock(&self) -> Result<Hertz, DivisorZero> {
508        let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
509        self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem0)
510    }
511
512    /// Calculate the reference clock for GEM1.
513    ///
514    /// The divisor 1 of the GEM is 0 on reset. You have to properly initialize the clock
515    /// configuration before calling this function.
516    ///
517    /// It should be noted that the GEM has a separate TX and RX clock.
518    /// The reference clock will only be the RX clock in loopback mode. For the TX block,
519    /// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
520    pub fn calculate_gem_1_ref_clock(&self) -> Result<Hertz, DivisorZero> {
521        let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
522        self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem1)
523    }
524}