zynq7000_hal/clocks/
pll.rs

1use core::sync::atomic::AtomicBool;
2
3use arbitrary_int::{u4, u7, u10};
4
5use crate::{BootMode, time::Hertz};
6
7/// Minimal value based on Zynq-7000 TRM Table 25-6, p.744
8pub const PLL_MUL_MIN: u32 = 13;
9/// Maximum value based on Zynq-7000 TRM Table 25-6, p.744
10pub const PLL_MUL_MAX: u32 = 66;
11
12static ARM_PLL_INIT: AtomicBool = AtomicBool::new(false);
13static IO_PLL_INIT: AtomicBool = AtomicBool::new(false);
14static DDR_PLL_INIT: AtomicBool = AtomicBool::new(false);
15
16#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
17#[error("pll muliplier value {0} is out of range ({PLL_MUL_MIN}..={PLL_MUL_MAX})")]
18pub struct MulOutOfRangeError(pub u32);
19
20#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PllConfigCtorError {
22    #[error("invalid input")]
23    InvalidInput,
24    #[error("pll multiplier out of range: {0}")]
25    MulOutOfRange(#[from] MulOutOfRangeError),
26}
27
28pub struct PllConfig {
29    fdiv: u7,
30    charge_pump: u4,
31    loop_resistor: u4,
32    lock_count: u10,
33}
34
35impl PllConfig {
36    pub fn new_from_target_clock(
37        ps_clk: Hertz,
38        target_clk: Hertz,
39    ) -> Result<Self, PllConfigCtorError> {
40        if ps_clk.raw() == 0 {
41            return Err(PllConfigCtorError::InvalidInput);
42        }
43        let mul = target_clk / ps_clk;
44        Self::new(mul).map_err(PllConfigCtorError::MulOutOfRange)
45    }
46    /// Create a new PLL configuration based on the multiplier value.
47    ///
48    /// These configuration values are based on the Zynq-7000 TRM Table 25-6, p.744.
49    pub fn new(pll_mul: u32) -> Result<Self, MulOutOfRangeError> {
50        if !(PLL_MUL_MIN..=PLL_MUL_MAX).contains(&pll_mul) {
51            return Err(MulOutOfRangeError(pll_mul));
52        }
53
54        Ok(match pll_mul {
55            13 => Self::new_raw(
56                u7::new(pll_mul as u8),
57                u4::new(2),
58                u4::new(6),
59                u10::new(750),
60            ),
61            14 => Self::new_raw(
62                u7::new(pll_mul as u8),
63                u4::new(2),
64                u4::new(6),
65                u10::new(700),
66            ),
67            15 => Self::new_raw(
68                u7::new(pll_mul as u8),
69                u4::new(2),
70                u4::new(6),
71                u10::new(650),
72            ),
73            16 => Self::new_raw(
74                u7::new(pll_mul as u8),
75                u4::new(2),
76                u4::new(10),
77                u10::new(625),
78            ),
79            17 => Self::new_raw(
80                u7::new(pll_mul as u8),
81                u4::new(2),
82                u4::new(10),
83                u10::new(575),
84            ),
85            18 => Self::new_raw(
86                u7::new(pll_mul as u8),
87                u4::new(2),
88                u4::new(10),
89                u10::new(550),
90            ),
91            19 => Self::new_raw(
92                u7::new(pll_mul as u8),
93                u4::new(2),
94                u4::new(10),
95                u10::new(525),
96            ),
97            20 => Self::new_raw(
98                u7::new(pll_mul as u8),
99                u4::new(2),
100                u4::new(12),
101                u10::new(500),
102            ),
103            21 => Self::new_raw(
104                u7::new(pll_mul as u8),
105                u4::new(2),
106                u4::new(12),
107                u10::new(475),
108            ),
109            22 => Self::new_raw(
110                u7::new(pll_mul as u8),
111                u4::new(2),
112                u4::new(12),
113                u10::new(450),
114            ),
115            23 => Self::new_raw(
116                u7::new(pll_mul as u8),
117                u4::new(2),
118                u4::new(12),
119                u10::new(425),
120            ),
121            24..=25 => Self::new_raw(
122                u7::new(pll_mul as u8),
123                u4::new(2),
124                u4::new(12),
125                u10::new(400),
126            ),
127            26 => Self::new_raw(
128                u7::new(pll_mul as u8),
129                u4::new(2),
130                u4::new(12),
131                u10::new(375),
132            ),
133            27..=28 => Self::new_raw(
134                u7::new(pll_mul as u8),
135                u4::new(2),
136                u4::new(12),
137                u10::new(350),
138            ),
139
140            29..=30 => Self::new_raw(
141                u7::new(pll_mul as u8),
142                u4::new(2),
143                u4::new(12),
144                u10::new(325),
145            ),
146            31..=33 => Self::new_raw(
147                u7::new(pll_mul as u8),
148                u4::new(2),
149                u4::new(2),
150                u10::new(300),
151            ),
152            34..=36 => Self::new_raw(
153                u7::new(pll_mul as u8),
154                u4::new(2),
155                u4::new(2),
156                u10::new(275),
157            ),
158            37..=40 => Self::new_raw(
159                u7::new(pll_mul as u8),
160                u4::new(2),
161                u4::new(2),
162                u10::new(250),
163            ),
164            41..=47 => Self::new_raw(
165                u7::new(pll_mul as u8),
166                u4::new(3),
167                u4::new(12),
168                u10::new(250),
169            ),
170            48..=66 => Self::new_raw(
171                u7::new(pll_mul as u8),
172                u4::new(2),
173                u4::new(4),
174                u10::new(250),
175            ),
176            _ => {
177                unreachable!()
178            }
179        })
180    }
181
182    /// Create a new PLL configuration with raw values.
183    ///
184    /// It is recommended to use [Self::new] instead, which creates a configuration
185    /// based on a look-up table provided in the Zynq-7000 TRM.
186    pub fn new_raw(fdiv: u7, charge_pump: u4, loop_resistor: u4, lock_count: u10) -> Self {
187        Self {
188            fdiv,
189            charge_pump,
190            loop_resistor,
191            lock_count,
192        }
193    }
194}
195
196/// This function configures the ARM PLL based on the provided [PllConfig].
197pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
198    if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
199        return;
200    }
201    // Safety: This will only run at most once because of the atomic boolean check.
202    unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
203}
204
205/// This function configures the IO PLL based on the provided [PllConfig].
206pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
207    if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
208        return;
209    }
210    // Safety: This will only run at most once because of the atomic boolean check.
211    unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
212}
213
214/// This function configures the DDR PLL based on the provided [PllConfig].
215pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
216    if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
217        return;
218    }
219    // Safety: This will only run at most once because of the atomic boolean check.
220    unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
221}
222
223/// This function configures the ARM PLL based on the provided [PllConfig].
224///
225/// # Safety
226///
227/// This function should only be called once during system initialization, for example in the
228/// first-stage bootloader (FSBL).
229pub unsafe fn configure_arm_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
230    unsafe {
231        crate::slcr::Slcr::with(|slcr| {
232            let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_arm_pll_ctrl();
233            let pll_cfg_reg = slcr.clk_ctrl().pointer_to_arm_pll_cfg();
234            configure_pll_unchecked(
235                boot_mode,
236                pll_config,
237                PllType::Arm,
238                slcr,
239                pll_ctrl_reg,
240                pll_cfg_reg,
241            );
242        });
243    }
244}
245
246/// This function configures the IO PLL based on the provided [PllConfig].
247///
248/// # Safety
249///
250/// This function should only be called once during system initialization, for example in the
251/// first-stage bootloader (FSBL).
252pub unsafe fn configure_io_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
253    unsafe {
254        crate::slcr::Slcr::with(|slcr| {
255            let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_io_pll_ctrl();
256            let pll_cfg_reg = slcr.clk_ctrl().pointer_to_io_pll_cfg();
257            configure_pll_unchecked(
258                boot_mode,
259                pll_config,
260                PllType::Io,
261                slcr,
262                pll_ctrl_reg,
263                pll_cfg_reg,
264            );
265        });
266    }
267}
268
269/// This function configures the DDR PLL based on the provided [PllConfig].
270///
271/// # Safety
272///
273/// This function should only be called once during system initialization, for example in the
274/// first-stage bootloader (FSBL).
275pub unsafe fn configure_ddr_pll_unchecked(boot_mode: BootMode, pll_config: PllConfig) {
276    unsafe {
277        crate::slcr::Slcr::with(|slcr| {
278            let pll_ctrl_reg = slcr.clk_ctrl().pointer_to_ddr_pll_ctrl();
279            let pll_cfg_reg = slcr.clk_ctrl().pointer_to_ddr_pll_cfg();
280            configure_pll_unchecked(
281                boot_mode,
282                pll_config,
283                PllType::Ddr,
284                slcr,
285                pll_ctrl_reg,
286                pll_cfg_reg,
287            );
288        });
289    }
290}
291
292enum PllType {
293    Io,
294    Ddr,
295    Arm,
296}
297
298impl PllType {
299    pub const fn bit_offset_pll_locked(&self) -> usize {
300        match self {
301            PllType::Io => 2,
302            PllType::Ddr => 1,
303            PllType::Arm => 0,
304        }
305    }
306}
307
308unsafe fn configure_pll_unchecked(
309    boot_mode: BootMode,
310    cfg: PllConfig,
311    pll_type: PllType,
312    slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
313    pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
314    pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
315) {
316    // Step 1: Program the multiplier and other PLL configuration parameters.
317    // New values will only be consumed once the PLL is reset.
318    let mut pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
319    pll_ctrl.set_fdiv(cfg.fdiv);
320    unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
321
322    let mut pll_cfg = unsafe { core::ptr::read_volatile(pll_cfg_reg) };
323    pll_cfg.set_charge_pump(cfg.charge_pump);
324    pll_cfg.set_loop_resistor(cfg.loop_resistor);
325    pll_cfg.set_lock_count(cfg.lock_count);
326    unsafe { core::ptr::write_volatile(pll_cfg_reg, pll_cfg) };
327
328    // Step 2: Force the PLL into bypass mode. If the PLL bypass mode pin is tied high,
329    // the PLLs need to be enabled. According to the TRM, this is done by setting the
330    // PLL_BYPASS_QUAL bit to 0, which de-asserts the reset to the Arm PLL.
331    pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
332    if boot_mode.pll_config() == zynq7000::slcr::BootPllConfig::Bypassed {
333        pll_ctrl.set_bypass_qual(false);
334    }
335    pll_ctrl.set_bypass_force(true);
336    pll_ctrl.set_pwrdwn(false);
337    unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
338
339    // Step 3: Reset the PLL. This also loads the new configuration.
340    pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
341    pll_ctrl.set_reset(true);
342    unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
343    pll_ctrl.set_reset(false);
344    unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
345
346    while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
347        & 0b1)
348        != 1
349    {
350        cortex_ar::asm::nop();
351    }
352
353    pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
354    pll_ctrl.set_bypass_force(false);
355    unsafe { core::ptr::write_volatile(pll_ctrl_reg, pll_ctrl) };
356}