Skip to main content

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}