ove/
pm.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! Power management framework for oveRTOS.
8//!
9//! Provides a unified interface for sleep state management, peripheral power
10//! domains, wake source registration, pluggable power policies, and runtime
11//! power statistics.
12//!
13//! The PM subsystem is a singleton — there is one system-wide power state.
14//! Initialise with [`init`] and tear down with [`deinit`].
15
16use crate::bindings;
17use crate::error::{Error, Result};
18
19// ── Enumerations ────────────────────────────────────────────────────────
20
21/// System power states, ordered by increasing sleep depth.
22#[repr(u32)]
23#[derive(Debug, Copy, Clone, PartialEq, Eq)]
24pub enum State {
25    /// Full speed, all clocks running.
26    Active = 0,
27    /// Light sleep, fast wakeup, peripherals on.
28    Idle = 1,
29    /// Deep idle, some peripherals off.
30    Standby = 2,
31    /// Lowest power, RAM retained, slow wakeup.
32    DeepSleep = 3,
33}
34
35/// Wake source types.
36#[repr(u32)]
37#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38pub enum WakeType {
39    /// GPIO pin edge.
40    Gpio = 0,
41    /// Timer expiry.
42    Timer = 1,
43    /// UART RX activity.
44    Uart = 2,
45    /// RTC alarm.
46    Rtc = 3,
47}
48
49/// Peripheral power domain identifiers.
50#[repr(u32)]
51#[derive(Debug, Copy, Clone, PartialEq, Eq)]
52pub enum Domain {
53    Radio = 0,
54    Sensor = 1,
55    Display = 2,
56    Audio = 3,
57    Storage = 4,
58    Comms = 5,
59    User0 = 6,
60    User1 = 7,
61}
62
63/// Transition event type for notification callbacks.
64#[repr(u32)]
65#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66pub enum Event {
67    /// About to enter low-power state.
68    PreSleep = 0,
69    /// Just woke from low-power state.
70    PostWake = 1,
71}
72
73// ── Configuration ───────────────────────────────────────────────────────
74
75/// PM subsystem configuration.
76#[derive(Debug, Copy, Clone)]
77pub struct Cfg {
78    /// Idle ms before ACTIVE to IDLE transition.
79    pub idle_threshold_ms: u32,
80    /// Idle ms before IDLE to STANDBY transition.
81    pub standby_threshold_ms: u32,
82    /// Idle ms before DEEP_SLEEP transition.
83    pub deep_sleep_threshold_ms: u32,
84}
85
86/// Runtime power statistics.
87#[derive(Debug, Copy, Clone)]
88pub struct Stats {
89    /// Cumulative microseconds in each state.
90    pub time_in_state_us: [u64; 4],
91    /// Number of entries per state.
92    pub transition_count: [u32; 4],
93    /// Total tracked runtime in microseconds.
94    pub total_runtime_us: u64,
95    /// Active percentage in hundredths (0..10000).
96    pub active_pct_x100: u32,
97}
98
99// ── Lifecycle ───────────────────────────────────────────────────────────
100
101/// Initialise the PM subsystem.
102///
103/// # Errors
104/// Returns [`Error::InvalidParam`] if `cfg` values are invalid.
105pub fn init(cfg: &Cfg) -> Result<()> {
106    let c_cfg = bindings::ove_pm_cfg {
107        idle_threshold_ms: cfg.idle_threshold_ms,
108        standby_threshold_ms: cfg.standby_threshold_ms,
109        deep_sleep_threshold_ms: cfg.deep_sleep_threshold_ms,
110    };
111    let rc = unsafe { bindings::ove_pm_init(&c_cfg) };
112    Error::from_code(rc)
113}
114
115/// Tear down the PM subsystem and release resources.
116pub fn deinit() {
117    unsafe { bindings::ove_pm_deinit() }
118}
119
120// ── State machine ───────────────────────────────────────────────────────
121
122/// Request an explicit power state transition.
123///
124/// # Errors
125/// Returns [`Error::InvalidParam`] if `state` is out of range.
126pub fn set_state(state: State) -> Result<()> {
127    let rc = unsafe { bindings::ove_pm_set_state(state as bindings::ove_pm_state_t) };
128    Error::from_code(rc)
129}
130
131/// Query the current power state.
132pub fn get_state() -> State {
133    let raw = unsafe { bindings::ove_pm_get_state() };
134    match raw {
135        0 => State::Active,
136        1 => State::Idle,
137        2 => State::Standby,
138        3 => State::DeepSleep,
139        _ => State::Active,
140    }
141}
142
143/// Report system activity (ISR-safe). Resets the idle timer.
144pub fn activity() {
145    unsafe { bindings::ove_pm_activity() }
146}
147
148// ── Wake sources ────────────────────────────────────────────────────────
149
150/// Register a GPIO wake source.
151///
152/// # Errors
153/// Returns [`Error::NoMemory`] if the wake source table is full.
154pub fn wake_register_gpio(port: u32, pin: u32, edge: u32) -> Result<()> {
155    let mut src: bindings::ove_pm_wake_src = unsafe { core::mem::zeroed() };
156    src.type_ = WakeType::Gpio as bindings::ove_pm_wake_type_t;
157    unsafe {
158        src.__bindgen_anon_1.gpio.port = port;
159        src.__bindgen_anon_1.gpio.pin = pin;
160        src.__bindgen_anon_1.gpio.edge = edge as bindings::ove_gpio_irq_mode_t;
161    }
162    let rc = unsafe { bindings::ove_pm_wake_register(&src) };
163    Error::from_code(rc)
164}
165
166/// Register a timer wake source.
167///
168/// # Errors
169/// Returns [`Error::NoMemory`] if the wake source table is full.
170pub fn wake_register_timer(timeout_ms: u32) -> Result<()> {
171    let mut src: bindings::ove_pm_wake_src = unsafe { core::mem::zeroed() };
172    src.type_ = WakeType::Timer as bindings::ove_pm_wake_type_t;
173    unsafe { src.__bindgen_anon_1.timer.timeout_ms = timeout_ms };
174    let rc = unsafe { bindings::ove_pm_wake_register(&src) };
175    Error::from_code(rc)
176}
177
178/// Register a UART wake source.
179///
180/// # Errors
181/// Returns [`Error::NoMemory`] if the wake source table is full.
182pub fn wake_register_uart(instance: u32) -> Result<()> {
183    let mut src: bindings::ove_pm_wake_src = unsafe { core::mem::zeroed() };
184    src.type_ = WakeType::Uart as bindings::ove_pm_wake_type_t;
185    unsafe { src.__bindgen_anon_1.uart.instance = instance };
186    let rc = unsafe { bindings::ove_pm_wake_register(&src) };
187    Error::from_code(rc)
188}
189
190/// Unregister a GPIO wake source.
191///
192/// # Errors
193/// Returns [`Error::NotRegistered`] if the wake source was not found.
194pub fn wake_unregister_gpio(port: u32, pin: u32) -> Result<()> {
195    let mut src: bindings::ove_pm_wake_src = unsafe { core::mem::zeroed() };
196    src.type_ = WakeType::Gpio as bindings::ove_pm_wake_type_t;
197    unsafe {
198        src.__bindgen_anon_1.gpio.port = port;
199        src.__bindgen_anon_1.gpio.pin = pin;
200    }
201    let rc = unsafe { bindings::ove_pm_wake_unregister(&src) };
202    Error::from_code(rc)
203}
204
205// ── Peripheral power domains ────────────────────────────────────────────
206
207/// Increment the reference count for a peripheral power domain.
208///
209/// On the first request (0 to 1), the domain hardware is powered on.
210///
211/// # Errors
212/// Returns [`Error::InvalidParam`] if `domain` is out of range.
213pub fn domain_request(domain: Domain) -> Result<()> {
214    let rc = unsafe { bindings::ove_pm_domain_request(domain as bindings::ove_pm_domain_t) };
215    Error::from_code(rc)
216}
217
218/// Decrement the reference count for a peripheral power domain.
219///
220/// When the count reaches zero, the domain hardware is powered off.
221///
222/// # Errors
223/// Returns [`Error::InvalidParam`] on underflow.
224pub fn domain_release(domain: Domain) -> Result<()> {
225    let rc = unsafe { bindings::ove_pm_domain_release(domain as bindings::ove_pm_domain_t) };
226    Error::from_code(rc)
227}
228
229/// Query the current reference count for a domain.
230///
231/// # Errors
232/// Returns a negative error code if `domain` is invalid.
233pub fn domain_get_refcount(domain: Domain) -> Result<i32> {
234    let rc = unsafe { bindings::ove_pm_domain_get_refcount(domain as bindings::ove_pm_domain_t) };
235    if rc < 0 {
236        Error::from_code(rc)?;
237    }
238    Ok(rc)
239}
240
241// ── Policy ──────────────────────────────────────────────────────────────
242
243/// Register a custom power policy callback. Pass `None` to restore default.
244///
245/// # Safety
246/// The callback and `user_data` must remain valid for the lifetime of the
247/// registration.
248pub unsafe fn set_policy(
249    policy: bindings::ove_pm_policy_fn,
250    user_data: *mut core::ffi::c_void,
251) -> Result<()> {
252    let rc = unsafe { bindings::ove_pm_set_policy(policy, user_data) };
253    Error::from_code(rc)
254}
255
256// ── Notifications ───────────────────────────────────────────────────────
257
258/// Register a transition notification callback.
259///
260/// # Safety
261/// The callback and `user_data` must remain valid until unregistered.
262///
263/// # Errors
264/// Returns [`Error::NoMemory`] if the notifier table is full.
265pub unsafe fn notify_register(
266    cb: bindings::ove_pm_notify_fn,
267    user_data: *mut core::ffi::c_void,
268) -> Result<()> {
269    let rc = unsafe { bindings::ove_pm_notify_register(cb, user_data) };
270    Error::from_code(rc)
271}
272
273/// Unregister a transition notification callback.
274///
275/// # Safety
276/// Must match a previously registered (cb, user_data) pair.
277///
278/// # Errors
279/// Returns [`Error::NotRegistered`] if the pair was not found.
280pub unsafe fn notify_unregister(
281    cb: bindings::ove_pm_notify_fn,
282    user_data: *mut core::ffi::c_void,
283) -> Result<()> {
284    let rc = unsafe { bindings::ove_pm_notify_unregister(cb, user_data) };
285    Error::from_code(rc)
286}
287
288// ── Statistics ──────────────────────────────────────────────────────────
289
290/// Query accumulated power statistics.
291///
292/// # Errors
293/// Returns [`Error::InvalidParam`] if the PM subsystem is not initialised.
294pub fn get_stats() -> Result<Stats> {
295    let mut raw: bindings::ove_pm_stats = unsafe { core::mem::zeroed() };
296    let rc = unsafe { bindings::ove_pm_get_stats(&mut raw) };
297    Error::from_code(rc)?;
298    Ok(Stats {
299        time_in_state_us: raw.time_in_state_us,
300        transition_count: raw.transition_count,
301        total_runtime_us: raw.total_runtime_us,
302        active_pct_x100: raw.active_pct_x100,
303    })
304}
305
306/// Reset all accumulated power statistics to zero.
307pub fn reset_stats() {
308    unsafe { bindings::ove_pm_reset_stats() }
309}
310
311// ── Power budget ────────────────────────────────────────────────────────
312
313/// Set a target percentage of time in low-power states.
314///
315/// # Errors
316/// Returns [`Error::InvalidParam`] if the PM subsystem is not initialised.
317pub fn set_budget(target_low_power_pct_x100: u32) -> Result<()> {
318    let rc = unsafe { bindings::ove_pm_set_budget(target_low_power_pct_x100) };
319    Error::from_code(rc)
320}
321
322/// Query actual low-power percentage vs. budget target.
323///
324/// Returns the actual low-power percentage in hundredths (0..10000).
325///
326/// # Errors
327/// Returns [`Error::InvalidParam`] if the PM subsystem is not initialised.
328pub fn get_budget_status() -> Result<u32> {
329    let mut actual: u32 = 0;
330    let rc = unsafe { bindings::ove_pm_get_budget_status(&mut actual) };
331    Error::from_code(rc)?;
332    Ok(actual)
333}