sifive/
prci.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Power Reset Clock Interrupt controller driver.
6
7use core::cell::Cell;
8use kernel::utilities::registers::interfaces::ReadWriteable;
9use kernel::utilities::registers::interfaces::Readable;
10use kernel::utilities::registers::{register_bitfields, ReadWrite};
11use kernel::utilities::StaticRef;
12use rv32i::csr;
13
14#[repr(C)]
15pub struct PrciRegisters {
16    /// Clock Configuration Register
17    hfrosccfg: ReadWrite<u32, hfrosccfg::Register>,
18    /// Clock Configuration Register
19    hfxosccfg: ReadWrite<u32, hfxosccfg::Register>,
20    /// PLL Configuration Register
21    pllcfg: ReadWrite<u32, pllcfg::Register>,
22    /// PLL Divider Register
23    plloutdiv: ReadWrite<u32, plloutdiv::Register>,
24    /// Clock Configuration Register
25    coreclkcfg: ReadWrite<u32>,
26}
27
28register_bitfields![u32,
29    hfrosccfg [
30        ready OFFSET(31) NUMBITS(1) [],
31        enable OFFSET(30) NUMBITS(1) [],
32        trim OFFSET(16) NUMBITS(5) [],
33        div OFFSET(0) NUMBITS(6) []
34    ],
35    hfxosccfg [
36        ready OFFSET(31) NUMBITS(1) [],
37        enable OFFSET(30) NUMBITS(1) []
38    ],
39    pllcfg [
40        lock OFFSET(31) NUMBITS(1) [],
41        bypass OFFSET(18) NUMBITS(1) [],
42        refsel OFFSET(17) NUMBITS(1) [],
43        sel OFFSET(16) NUMBITS(1) [],
44        pllq OFFSET(10) NUMBITS(2) [],
45        pllf OFFSET(4) NUMBITS(6) [],
46        pllr OFFSET(0) NUMBITS(3) [
47            R1 = 0
48        ]
49    ],
50    plloutdiv [
51        divby1 OFFSET(8) NUMBITS(1) [],
52        div OFFSET(0) NUMBITS(6) []
53    ]
54];
55
56pub enum ClockFrequency {
57    Freq16Mhz,
58    Freq344Mhz,
59}
60
61pub struct Prci {
62    registers: StaticRef<PrciRegisters>,
63    current_frequency: Cell<ClockFrequency>,
64}
65
66impl Prci {
67    pub const fn new(base: StaticRef<PrciRegisters>) -> Prci {
68        Prci {
69            registers: base,
70            current_frequency: Cell::new(ClockFrequency::Freq16Mhz),
71        }
72    }
73
74    pub fn switch_to_internal_clock(&self) {
75        let regs = self.registers;
76        // Enable internal high-frequency clock if it's not enabled
77        if regs.hfrosccfg.read(hfrosccfg::enable) == 0 {
78            regs.hfrosccfg.modify(hfrosccfg::enable::SET);
79        }
80        // ... Wait until the clock is ready
81        while regs.hfrosccfg.read(hfrosccfg::ready) == 0 {}
82        // ... and now actually switch
83        regs.pllcfg
84            .modify(pllcfg::sel::CLEAR + pllcfg::bypass::CLEAR);
85    }
86
87    pub fn set_internal_clock_default(&self) {
88        let regs = self.registers;
89        // Set to defaults, which according to data sheet should set to 14.4MHz +- 50%,
90        regs.hfrosccfg
91            .modify(hfrosccfg::div.val(4) + hfrosccfg::trim.val(0x10));
92    }
93
94    pub fn enable_external_clock(&self) {
95        let regs = self.registers;
96        // Make sure external crystal oscillator is enabled
97        if regs.hfxosccfg.read(hfxosccfg::enable) == 0 {
98            regs.hfxosccfg.modify(hfxosccfg::enable::SET);
99        }
100        // ... Wait until the clock is ready
101        while regs.hfxosccfg.read(hfxosccfg::ready) == 0 {}
102    }
103
104    pub fn set_clock_frequency(&self, frequency: ClockFrequency) {
105        let regs = self.registers;
106        // According to someone affiliated with SiFive in forum post:
107        // https://forums.sifive.com/t/is-it-possible-to-brick-the-hifive-board/751/6,
108        // it is safe to adjust the internal high frequency clock while it
109        // is in use, but not the PLL output.
110        //
111        // So first switch to internal clock before doing any other clock manipulation
112
113        // Reset internal clock to defaults so we can estimate how long we're spinning for the PLL
114        // lock delay. At default, the frequency should be 14.4MHz +- 50%, so a maximum frequency
115        // of just under 22MHz
116        self.set_internal_clock_default();
117        self.switch_to_internal_clock();
118
119        // Make sure external clock subsystem is enabled before adjusting the PLL
120        self.enable_external_clock();
121
122        match frequency {
123            ClockFrequency::Freq16Mhz => {
124                // Bypass enabled, feeds clock directly from external clock. For HiFive1 revB, this
125                // is a 16 MHz clock
126                regs.pllcfg
127                    .modify(pllcfg::bypass::SET + pllcfg::refsel::SET);
128                // ... configure final PLL divider to divide by 1
129                regs.plloutdiv
130                    .modify(plloutdiv::divby1.val(1) + plloutdiv::div.val(0));
131                // ... and finally enable the output
132                regs.pllcfg.modify(pllcfg::sel::SET);
133            }
134            ClockFrequency::Freq344Mhz => {
135                // Disable bypass, and set external clock as source for PLL
136                // divide 16 MHz input by 2 (pllr(1)), times 86 (pllf(42)), divide by 2 (pllq(1))
137                // for a frequency of 344MHz
138                regs.pllcfg.modify(
139                    pllcfg::bypass::CLEAR
140                        + pllcfg::refsel::SET
141                        + pllcfg::pllr.val(1)
142                        + pllcfg::pllf.val(42)
143                        + pllcfg::pllq.val(1),
144                );
145                // Divide PLL output by 1
146                regs.plloutdiv
147                    .modify(plloutdiv::divby1.val(1) + plloutdiv::div.val(0));
148
149                // We need to wait for PLL to settle before checking if it's stable, which takes
150                // about 100 microseconds. Assuming internal clock is worst case of 22MHz (14.7 MHz
151                // +- 50%), that's about 2200 cycles.
152                let start = csr::CSR.mcycle.get();
153                while csr::CSR.mcycle.get() - start < 2200 {}
154                // ... and now wait for the PLL lock
155                while regs.pllcfg.read(pllcfg::lock) == 0 {}
156                // ... and finally switch to the PLL output
157                regs.pllcfg.modify(pllcfg::sel::SET);
158            }
159        }
160        self.current_frequency.set(frequency);
161
162        // Finally, disable internal clock as we've now switched to something else.
163        regs.hfrosccfg.modify(hfrosccfg::enable::CLEAR);
164    }
165}