rockchip_pm/lib.rs
1#![no_std]
2//! # RK3588 Power Management Driver
3//!
4//! This crate provides power management functionality for RK3588 series SoCs,
5//! particularly for NPU power domain control.
6//!
7//! # Features
8//!
9//! - Dynamic power domain on/off control
10//! - Support for multiple SoC variants (RK3588, RK3568)
11//! - Device tree compatible string based auto-detection
12//! - Safe register access and status checking
13//!
14//! # Example
15//!
16//! ```no_run
17//! use rockchip_pm::{RockchipPM, RkBoard, PowerDomain};
18//! use core::ptr::NonNull;
19//!
20//! // Create driver instance with base address and board type
21//! let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
22//! let mut pm = RockchipPM::new(base, RkBoard::Rk3588);
23//!
24//! // Turn on NPU power domain
25//! pm.power_domain_on(PowerDomain::NPU).unwrap();
26//!
27//! // Turn off NPU power domain
28//! pm.power_domain_off(PowerDomain::NPU).unwrap();
29//! ```
30
31extern crate alloc;
32
33use mbarrier::mb;
34use rdif_base::DriverGeneric;
35
36use crate::{registers::PmuRegs, variants::RockchipPmuInfo};
37use core::ptr::NonNull;
38
39mod registers;
40mod variants;
41
42pub use variants::PowerDomain;
43
44/// Supported Rockchip SoC board types
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum RkBoard {
47 /// RK3588 SoC
48 Rk3588,
49 /// RK3568 SoC
50 Rk3568,
51}
52
53/// Power management operation errors
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum PmError {
56 /// The specified power domain does not exist
57 DomainNotFound,
58 /// Timeout waiting for power domain status
59 Timeout,
60 /// Hardware error
61 HardwareError,
62}
63
64/// Result type for power management operations
65pub type PmResult<T> = Result<T, PmError>;
66
67/// Rockchip Power Management Unit driver
68///
69/// This structure provides control over power domains for Rockchip SoCs,
70/// allowing dynamic power gating of various IP blocks like GPU, NPU, VCODEC, etc.
71pub struct RockchipPM {
72 _board: RkBoard,
73 reg: PmuRegs,
74 info: RockchipPmuInfo,
75}
76
77impl RockchipPM {
78 /// Create a new RockchipPM driver instance
79 ///
80 /// # Arguments
81 ///
82 /// * `base` - Base address of the PMU registers
83 /// * `board` - The specific board type (RK3588 or RK3568)
84 ///
85 /// # Example
86 ///
87 /// ```no_run
88 /// use rockchip_pm::{RockchipPM, RkBoard};
89 /// use core::ptr::NonNull;
90 ///
91 /// let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
92 /// let pm = RockchipPM::new(base, RkBoard::Rk3588);
93 /// ```
94 pub fn new(base: NonNull<u8>, board: RkBoard) -> Self {
95 Self {
96 _board: board,
97 info: RockchipPmuInfo::new(board),
98 reg: PmuRegs::new(base),
99 }
100 }
101
102 /// Create a new RockchipPM driver instance using device tree compatible string
103 ///
104 /// # Arguments
105 ///
106 /// * `base` - Base address of the PMU registers
107 /// * `compatible` - Device tree compatible string (e.g., "rockchip,rk3588-power-controller")
108 ///
109 /// # Panics
110 ///
111 /// Panics if the compatible string is not supported
112 ///
113 /// # Example
114 ///
115 /// ```no_run
116 /// use rockchip_pm::RockchipPM;
117 /// use core::ptr::NonNull;
118 ///
119 /// let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
120 /// let pm = RockchipPM::new_with_compatible(base, "rockchip,rk3588-power-controller");
121 /// ```
122 pub fn new_with_compatible(base: NonNull<u8>, compatible: &str) -> Self {
123 let board = match compatible {
124 "rockchip,rk3568-power-controller" => RkBoard::Rk3568,
125 "rockchip,rk3588-power-controller" => RkBoard::Rk3588,
126 _ => panic!("Unsupported compatible string: {compatible}"),
127 };
128
129 Self {
130 _board: board,
131 info: RockchipPmuInfo::new(board),
132 reg: PmuRegs::new(base),
133 }
134 }
135
136 /// Find a power domain by its name
137 ///
138 /// # Arguments
139 ///
140 /// * `name` - Name of the power domain (e.g., "npu", "gpu", "vcodec")
141 ///
142 /// # Returns
143 ///
144 /// `Some(PowerDomain)` if found, `None` otherwise
145 ///
146 /// # Example
147 ///
148 /// ```no_run
149 /// # use rockchip_pm::{RockchipPM, RkBoard, PowerDomain};
150 /// # use core::ptr::NonNull;
151 /// # let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
152 /// # let pm = RockchipPM::new(base, RkBoard::Rk3588);
153 /// let domain = pm.get_power_dowain_by_name("npu");
154 /// assert_eq!(domain, Some(PowerDomain::NPU));
155 /// ```
156 pub fn get_power_dowain_by_name(&self, name: &str) -> Option<PowerDomain> {
157 for (domain, info) in &self.info.domains {
158 if info.name == name {
159 return Some(*domain);
160 }
161 }
162 None
163 }
164
165 /// Turn on the specified power domain
166 ///
167 /// This function enables power to the specified domain, initializing the
168 /// associated hardware blocks.
169 ///
170 /// # Arguments
171 ///
172 /// * `domain` - The power domain to turn on
173 ///
174 /// # Errors
175 ///
176 /// Returns `PmError::DomainNotFound` if the domain does not exist
177 /// Returns `PmError::Timeout` if the power domain fails to turn on within the timeout period
178 ///
179 /// # Example
180 ///
181 /// ```no_run
182 /// # use rockchip_pm::{RockchipPM, RkBoard, PowerDomain};
183 /// # use core::ptr::NonNull;
184 /// # let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
185 /// # let mut pm = RockchipPM::new(base, RkBoard::Rk3588);
186 /// pm.power_domain_on(PowerDomain::NPU).unwrap();
187 /// ```
188 pub fn power_domain_on(&mut self, domain: PowerDomain) -> PmResult<()> {
189 self.set_power_domain(domain, true)
190 }
191
192 /// Turn off the specified power domain
193 ///
194 /// This function cuts power to the specified domain, putting the associated
195 /// hardware blocks into a low-power state.
196 ///
197 /// # Arguments
198 ///
199 /// * `domain` - The power domain to turn off
200 ///
201 /// # Errors
202 ///
203 /// Returns `PmError::DomainNotFound` if the domain does not exist
204 /// Returns `PmError::Timeout` if the power domain fails to turn off within the timeout period
205 ///
206 /// # Example
207 ///
208 /// ```no_run
209 /// # use rockchip_pm::{RockchipPM, RkBoard, PowerDomain};
210 /// # use core::ptr::NonNull;
211 /// # let base = unsafe { NonNull::new_unchecked(0xfd5d8000 as *mut u8) };
212 /// # let mut pm = RockchipPM::new(base, RkBoard::Rk3588);
213 /// pm.power_domain_off(PowerDomain::NPU).unwrap();
214 /// ```
215 pub fn power_domain_off(&mut self, domain: PowerDomain) -> PmResult<()> {
216 self.set_power_domain(domain, false)
217 }
218
219 /// Set power domain state
220 ///
221 /// Internal function that handles the actual power control sequence.
222 /// This includes writing to power control registers and waiting for
223 /// the power domain to reach the desired state.
224 ///
225 /// # Arguments
226 ///
227 /// * `domain` - The power domain to control
228 /// * `power_on` - `true` to turn on, `false` to turn off
229 fn set_power_domain(&mut self, domain: PowerDomain, power_on: bool) -> PmResult<()> {
230 let domain_info = self
231 .info
232 .domains
233 .get(&domain)
234 .ok_or(PmError::DomainNotFound)?;
235
236 if domain_info.pwr_mask == 0 {
237 return Ok(());
238 }
239
240 // Write power control register
241 self.write_power_control(&domain, power_on)?;
242
243 // Wait for power domain status to stabilize
244 self.wait_power_domain_stable(&domain, power_on)?;
245
246 Ok(())
247 }
248
249 /// Write to power control register
250 ///
251 /// Internal function that handles writing to the PMU power control registers.
252 /// Supports both write-enable mask mode and read-modify-write mode.
253 ///
254 /// # Arguments
255 ///
256 /// * `domain` - The power domain to control
257 /// * `power_on` - `true` to turn on, `false` to turn off
258 fn write_power_control(&mut self, domain: &PowerDomain, power_on: bool) -> PmResult<()> {
259 let domain_info = self
260 .info
261 .domains
262 .get(domain)
263 .ok_or(PmError::DomainNotFound)?;
264 let pwr_offset = self.info.pwr_offset + domain_info.pwr_offset;
265
266 if domain_info.pwr_w_mask != 0 {
267 // Use write-enable mask mode
268 let value = if power_on {
269 domain_info.pwr_w_mask
270 } else {
271 domain_info.pwr_mask | domain_info.pwr_w_mask
272 };
273 self.reg.write_u32(pwr_offset as usize, value as u32);
274 } else {
275 // Use read-modify-write mode
276 let current = self.reg.read_u32(pwr_offset as usize);
277 let new_value = if power_on {
278 current & !(domain_info.pwr_mask as u32)
279 } else {
280 current | (domain_info.pwr_mask as u32)
281 };
282 self.reg.write_u32(pwr_offset as usize, new_value);
283 }
284
285 mb();
286
287 Ok(())
288 }
289
290 /// Wait for power domain status to stabilize
291 ///
292 /// Polls the power domain status register until the expected state is reached
293 /// or a timeout occurs.
294 ///
295 /// # Arguments
296 ///
297 /// * `domain` - The power domain to monitor
298 /// * `expected_on` - The expected power state (`true` for on, `false` for off)
299 ///
300 /// # Errors
301 ///
302 /// Returns `PmError::Timeout` if the domain does not reach the expected state
303 /// within the timeout period
304 fn wait_power_domain_stable(&self, domain: &PowerDomain, expected_on: bool) -> PmResult<()> {
305 for _ in 0..10000 {
306 if self.is_domain_on(domain)? == expected_on {
307 return Ok(());
308 }
309 }
310 Err(PmError::Timeout)
311 }
312
313 /// Check if a power domain is powered on
314 ///
315 /// Reads the appropriate status register to determine if a power domain
316 /// is currently powered on. Supports multiple status register types.
317 ///
318 /// # Arguments
319 ///
320 /// * `domain` - The power domain to check
321 ///
322 /// # Returns
323 ///
324 /// `true` if the domain is powered on, `false` otherwise
325 fn is_domain_on(&self, domain: &PowerDomain) -> PmResult<bool> {
326 let domain_info = self
327 .info
328 .domains
329 .get(domain)
330 .ok_or(PmError::DomainNotFound)?;
331
332 if domain_info.repair_status_mask != 0 {
333 // Use repair status register
334 let val = self.reg.read_u32(self.info.repair_status_offset as usize);
335 // 1'b1: power on, 1'b0: power off
336 return Ok((val & (domain_info.repair_status_mask as u32)) != 0);
337 }
338
339 if domain_info.status_mask == 0 {
340 // Domain only has idle status
341 return Ok(!self.is_domain_idle(domain)?);
342 }
343
344 let val = self.reg.read_u32(self.info.status_offset as usize);
345 // 1'b0: power on, 1'b1: power off
346 Ok((val & (domain_info.status_mask as u32)) == 0)
347 }
348
349 /// Check if a power domain is idle
350 ///
351 /// Reads the idle status register to determine if a power domain
352 /// is in idle state.
353 ///
354 /// # Arguments
355 ///
356 /// * `domain` - The power domain to check
357 ///
358 /// # Returns
359 ///
360 /// `true` if the domain is idle, `false` otherwise
361 fn is_domain_idle(&self, domain: &PowerDomain) -> PmResult<bool> {
362 let domain_info = self
363 .info
364 .domains
365 .get(domain)
366 .ok_or(PmError::DomainNotFound)?;
367
368 let val = self.reg.read_u32(self.info.idle_offset as usize);
369 Ok((val & (domain_info.idle_mask as u32)) == (domain_info.idle_mask as u32))
370 }
371}
372
373impl DriverGeneric for RockchipPM {
374 fn open(&mut self) -> Result<(), rdif_base::KError> {
375 Ok(())
376 }
377
378 fn close(&mut self) -> Result<(), rdif_base::KError> {
379 Ok(())
380 }
381}