rk3588_clk/lib.rs
1//! Clock driver for RK3588
2//!
3//! # Overview
4//!
5//! Clock is the heart of synchronous digital systems. All the events in an SoC are
6//! controlled by the active edge of the clock and clock frequency is
7//! often synonymous with throughput and performance.
8//!
9//! ## Clock tree
10//! The clock tree is a hierarchical structure that distributes the clock signal
11//! from a single source to various components in the system. The clock tree is
12//! designed to minimize skew and ensure that all components receive the clock signal
13//! at the same time. The clock tree is typically implemented using a combination of
14//! buffers, inverters, and multiplexers. The clock tree is also responsible for
15//! generating different clock frequencies for different components in the system.
16//!
17//! ## CRU
18//! The Clock Reset Unit (CRU) is responsible for managing the clock and reset signals
19//! for the various components in the RK3588 SoC. The CRU is responsible for generating
20//! the clock signals for the CPU, GPU, NPU, and other peripherals. The CRU is also
21//! responsible for managing the reset signals for the various components in the RK3588 SoC.
22//!
23//! # About the driver
24//!
25//! The driver is designed to be used in a no_std environment, and provides
26//! abstractions for configuring clocks on the RK3588 SoC. It supports:
27//!
28//! - MMC (eMMC/SDIO) clock configuration
29//! - NPU clock gate control
30//! - USB clock management
31//! - PLL clock management
32//!
33//! ## Usage
34//!
35//! ```rust,ignore
36//! use rk3588_clk::{Rk3588Cru, constant::*};
37//! use core::ptr::NonNull;
38//!
39//! let cru = Rk3588Cru::new(NonNull::new(clk_addr as *mut u8).unwrap());
40//!
41//! // Get clock frequency
42//! let rate = cru.mmc_get_clk(CCLK_EMMC)?;
43//!
44//! // Set clock frequency
45//! cru.mmc_set_clk(CCLK_EMMC, 200_000_000)?;
46//!
47//! // Enable NPU clock gates
48//! cru.npu_gate_enable(ACLK_NPU0)?;
49//! ```
50//!
51#![no_std]
52// Allow Clippy warnings for specific patterns
53#![allow(clippy::manual_is_multiple_of)]
54#![allow(clippy::result_unit_err)]
55
56extern crate alloc;
57
58pub mod constant;
59pub mod npu;
60pub mod registers;
61pub mod tools;
62pub mod usb;
63
64use core::ptr::NonNull;
65use log::debug;
66use tock_registers::interfaces::{Readable, Writeable};
67
68use crate::{
69 constant::*,
70 registers::autocs::ModeRegisters,
71 registers::clksel::ClkSelRegisters,
72 registers::gate::GateRegisters,
73 registers::pll::{AupllRegisters, CpllRegisters, GpllRegisters, NpllRegisters, V0pllRegisters},
74 registers::softrst::SoftRstRegisters,
75 tools::{div_round_up, div_to_rate},
76};
77
78pub const OFFSET: usize = 0x160;
79
80pub const OSC_HZ: usize = 24 * 1000 * 1000;
81pub const APLL_L_HZ: usize = 800 * 1000 * 1000;
82pub const APLL_B_HZ: usize = 816 * 1000 * 1000;
83pub const GPLL_HZ: usize = 1188 * 1000 * 1000;
84pub const CPLL_HZ: usize = 1500 * 1000 * 1000;
85pub const B0PLL_HZ: usize = 24 * 1000 * 1000;
86pub const B1PLL_HZ: usize = 24 * 1000 * 1000;
87pub const LPLL_HZ: usize = 24 * 1000 * 1000;
88pub const V0PLL_HZ: usize = 24 * 1000 * 1000;
89pub const AUPLL_HZ: usize = 786431 * 1000;
90pub const NPLL_HZ: usize = 850 * 1000 * 1000;
91pub const PPLL_HZ: usize = 1100 * 1000 * 1000;
92pub const ACLK_CENTER_ROOT_HZ: usize = 702 * 1000 * 1000;
93pub const PCLK_CENTER_ROOT_HZ: usize = 100 * 1000 * 1000;
94pub const HCLK_CENTER_ROOT_HZ: usize = 396 * 1000 * 1000;
95pub const ACLK_CENTER_LOW_ROOT_HZ: usize = 500 * 1000 * 1000;
96pub const ACLK_TOP_ROOT_HZ: usize = 594 * 1000 * 1000;
97pub const PCLK_TOP_ROOT_HZ: usize = 100 * 1000 * 1000;
98pub const ACLK_LOW_TOP_ROOT_HZ: usize = 396 * 1000 * 1000;
99
100/// RK3588 Clock and Reset Unit (CRU) driver
101///
102/// This struct provides an interface to configure and manage clocks on the RK3588 SoC.
103/// It uses memory-mapped I/O to access the CRU registers.
104pub struct Rk3588Cru {
105 addr: NonNull<u8>,
106 cpll_hz: usize,
107 gpll_hz: usize,
108}
109
110impl Rk3588Cru {
111 /// Create a new CRU driver instance
112 ///
113 /// # Arguments
114 ///
115 /// * `addr` - Base address of the CRU registers
116 ///
117 /// # Safety
118 ///
119 /// The caller must ensure that `addr` points to valid memory-mapped CRU registers.
120 pub fn new(addr: NonNull<u8>) -> Self {
121 Self {
122 addr,
123 cpll_hz: CPLL_HZ,
124 gpll_hz: GPLL_HZ,
125 }
126 }
127
128 /// Initialize the CRU
129 ///
130 /// This function can be extended to perform any necessary initialization
131 /// of the CRU hardware.
132 pub fn init(&self) {
133 // Initialize the CRU if needed
134 }
135
136 /// Get a reference to the CRU registers
137 ///
138 /// # Safety
139 ///
140 /// The caller must ensure that the underlying memory is valid for the lifetime of the returned reference.
141 pub fn registers(&self) -> &Rk3588CruRegisters {
142 unsafe { &*(self.addr.as_ptr().add(OFFSET) as *const Rk3588CruRegisters) }
143 }
144
145 /// Get the current clock frequency for a MMC clock ID
146 ///
147 /// # Arguments
148 ///
149 /// * `clk_id` - The clock identifier (e.g., `CCLK_EMMC`, `CCLK_SRC_SDIO`)
150 ///
151 /// # Returns
152 ///
153 /// Returns the clock frequency in Hz, or an error if the clock ID is unsupported.
154 pub fn mmc_get_clk(&self, clk_id: u32) -> Result<usize, ()> {
155 debug!("Getting clk_id {}", clk_id);
156
157 let clksel = &self.registers().clksel;
158
159 match clk_id {
160 CCLK_SRC_SDIO => {
161 todo!("Implement mmc_get_clk for CCLK_SRC_SDIO");
162 }
163 CCLK_EMMC => {
164 let config = clksel.cru_clksel_con77.get();
165 let div = (config & CCLK_EMMC_DIV_MASK) >> CCLK_EMMC_DIV_SHIFT;
166 let sel = (config & CCLK_EMMC_SEL_MASK) >> CCLK_EMMC_SEL_SHIFT;
167 let prate = if sel == CCLK_EMMC_SEL_GPLL {
168 self.gpll_hz
169 } else if sel == CCLK_EMMC_SEL_CPLL {
170 self.cpll_hz
171 } else {
172 OSC_HZ
173 };
174
175 Ok(div_to_rate(prate, div))
176 }
177 BCLK_EMMC => {
178 todo!("Implement mmc_get_clk for BCLK_EMMC");
179 }
180 SCLK_SFC => {
181 todo!("Implement mmc_get_clk for SCLK_SFC");
182 }
183 DCLK_DECOM => {
184 todo!("Implement mmc_get_clk for DCLK_DECOM");
185 }
186 _ => {
187 panic!("Unsupported clk_id: {}", clk_id);
188 }
189 }
190 }
191
192 /// Set the clock frequency for a MMC clock ID
193 ///
194 /// # Arguments
195 ///
196 /// * `clk_id` - The MMC clock identifier (e.g., `CCLK_EMMC`, `CCLK_SRC_SDIO`)
197 /// * `rate` - Target clock frequency in Hz
198 ///
199 /// # Returns
200 ///
201 /// Returns the actual clock frequency that was set, or an error if the clock ID is unsupported.
202 pub fn mmc_set_clk(&self, clk_id: u32, rate: usize) -> Result<usize, ()> {
203 debug!("Setting clk_id {} to rate {}", clk_id, rate);
204
205 let clksel = &self.registers().clksel;
206
207 let (src_clk, div) = match clk_id {
208 CCLK_SRC_SDIO => {
209 todo!("Implement mmc_set_clk for CCLK_SRC_SDIO");
210 }
211 CCLK_EMMC => {
212 if OSC_HZ % rate == 0 {
213 let div = div_round_up(OSC_HZ, rate);
214 (SCLK_SFC_SEL_24M, div)
215 } else if self.cpll_hz % rate == 0 {
216 let div = div_round_up(self.cpll_hz, rate);
217 (SCLK_SFC_SEL_CPLL, div)
218 } else {
219 let div = div_round_up(self.gpll_hz, rate);
220 (SCLK_SFC_SEL_GPLL, div)
221 }
222 }
223 BCLK_EMMC => {
224 todo!("Implement mmc_set_clk for BCLK_EMMC");
225 }
226 SCLK_SFC => {
227 todo!("Implement mmc_set_clk for SCLK_SFC");
228 }
229 DCLK_DECOM => {
230 todo!("Implement mmc_set_clk for DCLK_DECOM");
231 }
232 _ => {
233 return Err(());
234 }
235 };
236
237 match clk_id {
238 CCLK_EMMC => {
239 let new_value =
240 (src_clk << CCLK_EMMC_SEL_SHIFT) | (((div as u32) - 1) << CCLK_EMMC_DIV_SHIFT);
241 let mask = CCLK_EMMC_SEL_MASK | CCLK_EMMC_DIV_MASK;
242 let final_value = (mask | new_value) << 16 | new_value;
243
244 debug!(
245 "CCLK_EMMC: src_clk {}, div {}, new_value {:#x}, final_value {:#x}",
246 src_clk, div, new_value, final_value
247 );
248
249 clksel.cru_clksel_con77.set(final_value);
250 }
251 _ => {
252 return Err(());
253 }
254 }
255
256 match self.mmc_get_clk(clk_id) {
257 Ok(freq) => Ok(freq),
258 Err(_) => Err(()),
259 }
260 }
261}
262
263/// CRU register layout for RK3588
264///
265/// This struct represents the memory-mapped register layout of the Clock Reset Unit.
266#[repr(C)]
267pub struct Rk3588CruRegisters {
268 v0pll: V0pllRegisters, // 0x160
269 aupll: AupllRegisters, // 0x180
270 cpll: CpllRegisters, // 0x1A0
271 gpll: GpllRegisters, // 0x1C0
272 npll: NpllRegisters, // 0x1E0
273 _reserved0: [u8; 0x80],
274 mode: ModeRegisters, // 0x280
275 clksel: ClkSelRegisters, // 0x300
276 _reserved2: [u8; 0x200],
277 gate: GateRegisters, // 0x800
278 _reserved3: [u8; 0xC8],
279 softrst: SoftRstRegisters, // 0xA00
280}