Skip to main content

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}