opentitanlib/test_utils/
extclk.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5//! Utilities for enabling or disabling external clock injection through JTAG to the clock manager.
6
7use std::time::Duration;
8
9use thiserror::Error;
10
11use top_earlgrey::top_earlgrey;
12
13use crate::chip::boolean::MultiBitBool4;
14use crate::dif::clkmgr::{ClkmgrExtclkCtrl, ClkmgrReg};
15use crate::io::jtag::Jtag;
16use crate::test_utils::poll;
17
18/// Available speeds for the external clock.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum ClockSpeed {
21    /// Approximately 1/2 of nominal frequency.
22    Low,
23    /// Approximately nominal frequency.
24    High,
25}
26
27/// Controls for enabling or disabling external clock injection.
28pub struct ExternalClock;
29
30impl ExternalClock {
31    /// Base address of the clock manager.
32    const CLKMGR_BASE_ADDR: u32 = top_earlgrey::CLKMGR_AON_BASE_ADDR as u32;
33
34    /// Addresses of clock manager registers in memory.
35    const EXTCLK_CTRL_REGWEN_ADDR: u32 =
36        Self::CLKMGR_BASE_ADDR + ClkmgrReg::ExtclkCtrlRegwen as u32;
37    const EXTCLK_CTRL_ADDR: u32 = Self::CLKMGR_BASE_ADDR + ClkmgrReg::ExtclkCtrl as u32;
38    const EXTCLK_STATUS_ADDR: u32 = Self::CLKMGR_BASE_ADDR + ClkmgrReg::ExtclkStatus as u32;
39
40    /// Durations for polling of the STATUS register.
41    const POLL_TIMEOUT: Duration = Duration::from_millis(500);
42    const POLL_DELAY: Duration = Duration::from_millis(5);
43
44    /// Enable the external clock at the given speed.
45    pub fn enable(jtag: &mut dyn Jtag, speed: ClockSpeed) -> Result<(), ExternalClockError> {
46        if !Self::write_enabled(jtag)? {
47            return Err(ExternalClockError::WriteDisabled);
48        }
49
50        // Fields and their values for the EXTCLK_CTRL register.
51        let hi_speed = match speed {
52            ClockSpeed::Low => MultiBitBool4::False,
53            ClockSpeed::High => MultiBitBool4::True,
54        };
55        let fields = [
56            (ClkmgrExtclkCtrl::SEL, MultiBitBool4::True),
57            (ClkmgrExtclkCtrl::HI_SPEED_SEL, hi_speed),
58        ];
59
60        // OR the fields together to get the register value.
61        let extclk_ctrl = fields.into_iter().fold(0, |acc, (field, value)| {
62            acc | field.emplace(u8::from(value) as u32)
63        });
64
65        // Set the EXTCLK_CTRL register.
66        jtag.write_memory32(Self::EXTCLK_CTRL_ADDR, &[extclk_ctrl])
67            .map_err(|source| ExternalClockError::Jtag { source })?;
68
69        Self::wait_for_ack(jtag)?;
70
71        Ok(())
72    }
73
74    /// Disable the external clock.
75    pub fn disable(jtag: &mut dyn Jtag) -> Result<(), ExternalClockError> {
76        if !Self::write_enabled(jtag)? {
77            return Err(ExternalClockError::WriteDisabled);
78        }
79
80        // Set the EXTCLK_CTRL register.
81        let extclk_ctrl = ClkmgrExtclkCtrl::SEL.emplace(u8::from(MultiBitBool4::False) as u32);
82        jtag.write_memory32(Self::EXTCLK_CTRL_ADDR, &[extclk_ctrl])
83            .map_err(|source| ExternalClockError::Jtag { source })?;
84
85        Self::wait_for_ack(jtag)?;
86
87        Ok(())
88    }
89
90    /// Returns the CLKMGR_EXTCLK_REGWEN setting which shows whether the
91    /// `EXTCLK_CTRL` register can be written.
92    fn write_enabled(jtag: &mut dyn Jtag) -> Result<bool, ExternalClockError> {
93        let mut buf = [0];
94        jtag.read_memory32(Self::EXTCLK_CTRL_REGWEN_ADDR, &mut buf)
95            .map_err(|source| ExternalClockError::Jtag { source })?;
96
97        Ok(buf[0] & 1 == 1)
98    }
99
100    /// Wait until the clock manager reports that the clock has changed to the external source.
101    fn wait_for_ack(jtag: &mut dyn Jtag) -> Result<(), ExternalClockError> {
102        poll::poll_until(Self::POLL_TIMEOUT, Self::POLL_DELAY, || {
103            // Check the status register.
104            let mut status_buf = [0];
105            jtag.read_memory32(Self::EXTCLK_STATUS_ADDR, &mut status_buf)
106                .map_err(|source| ExternalClockError::Jtag { source })?;
107
108            Ok(status_buf[0] == u8::from(MultiBitBool4::True) as u32)
109        })
110        .map_err(|source| ExternalClockError::Nack { source })
111    }
112}
113
114/// Failures when setting the external clock.
115#[derive(Debug, Error)]
116#[non_exhaustive]
117pub enum ExternalClockError {
118    #[error("failed to communicate over JTAG")]
119    Jtag { source: anyhow::Error },
120
121    #[error("did not read ACK for external clock change")]
122    Nack { source: anyhow::Error },
123
124    #[error("writing to external clock registers is disabled")]
125    WriteDisabled,
126}