ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
power_mgmt.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file power_mgmt.h
6 * @brief SoC power governor: frequency scaling, thermal throttle, brownout recovery, gating
7 * (PC_ENABLE_POWER_MGMT).
8 *
9 * services/system/radio_power owns the radio and services/system/sleep_sched decides how *long* to sleep. Neither
10 * owns the SoC itself, which is where the rest of the power budget goes: the CPU clock, the die
11 * temperature, and the peripherals nobody is using.
12 *
13 * The governor answers one question - given the current load, die temperature, and how the board
14 * last reset, what should the CPU clock be right now:
15 *
16 * - **Scaling.** Busy work runs at the ceiling; an idle server drops to the floor. Running a
17 * 240 MHz core to poll an idle socket is the single easiest power win on this part.
18 * - **Thermal throttle.** Hot parts clock down, and the restore threshold is *lower* than the
19 * throttle threshold. Without that gap a device sitting exactly at the limit oscillates between
20 * full speed and floor forever, which is worse than either.
21 * - **Brownout recovery.** A board that just browned out is on a supply that could not hold up the
22 * last load it saw, so slamming straight back to full speed invites the same collapse and a boot
23 * loop. After a brownout reset it comes up at the floor and stays there for a settle window.
24 * - **Gating.** Blocks the firmware never uses still burn current; Bluetooth is the big one, since
25 * the controller draws power whether or not anything is connected.
26 *
27 * The decision is pure and takes every input explicitly - load, temperature, the brownout flag, the
28 * time since boot, and the previous throttle state for the hysteresis - so the whole governor is
29 * host-testable with no hardware. The binding only reads the sensors and applies the result.
30 *
31 * @author Douglas Quigg (dstroy0)
32 * @date 2026
33 */
34
35#ifndef PROTOCORE_POWER_MGMT_H
36#define PROTOCORE_POWER_MGMT_H
37
38#include "protocore_config.h"
39#include <stddef.h>
40#include <stdint.h>
41
42#if PC_ENABLE_POWER_MGMT
43
44/** @brief Governor limits. Temperatures in whole degrees C; frequencies in MHz. */
45struct PowerCfg
46{
47 uint16_t mhz_max; ///< clock when there is work to do.
48 uint16_t mhz_min; ///< clock when idle, throttled, or recovering.
49 uint8_t busy_pct; ///< load at/above which the ceiling is used.
50 int16_t temp_hot_c; ///< throttle at/above this die temperature.
51 int16_t temp_cool_c; ///< release the throttle at/below this one (must be < temp_hot_c).
52 uint32_t recover_ms; ///< how long to stay at the floor after a brownout reset.
53};
54
55/** @brief What the governor decided this tick. */
56struct PowerPlan
57{
58 uint16_t cpu_mhz; ///< clock to apply.
59 bool throttled; ///< the thermal limit is holding the clock down.
60 bool recovering; ///< still inside the post-brownout settle window.
61};
62
63/**
64 * @brief Decide the clock for this tick.
65 *
66 * Precedence is deliberate: brownout recovery outranks thermal, which outranks load. A board that
67 * cannot hold its supply must not be clocked up because it happens to be busy, and a hot board must
68 * not be clocked up for the same reason.
69 *
70 * @param load_pct 0-100; work done in the last window (values above 100 are clamped).
71 * @param temp_c die temperature in whole degrees C.
72 * @param brownout_boot true if the last reset was a brownout.
73 * @param since_boot_ms milliseconds since boot, for the recovery window.
74 * @param was_throttled the previous tick's `throttled` - this is what gives the thermal decision
75 * its hysteresis, so pass the plan's own output back in.
76 */
77PowerPlan pc_power_plan(const PowerCfg *cfg, uint8_t load_pct, int16_t temp_c, bool brownout_boot,
78 uint32_t since_boot_ms, bool was_throttled);
79
80/** @brief Defaults from the PC_POWER_* build flags. */
81void pc_power_cfg_defaults(PowerCfg *cfg);
82
83/**
84 * @brief Serialize a plan as `{"cpu_mhz":N,"throttled":bool,"recovering":bool,"temp_c":N}`.
85 * @return length written (excl NUL), or 0 on overflow / bad args.
86 */
87size_t pc_power_json(const PowerPlan *plan, int16_t temp_c, char *out, size_t cap);
88
89#if defined(ARDUINO)
90// --- device binding -----------------------------------------------------------------------
91
92/** @brief True if the last reset was a brownout (esp_reset_reason). Latched, so it stays true. */
93bool pc_power_brownout_boot(void);
94
95/** @brief Die temperature in whole degrees C, or INT16_MIN if this part has no sensor. */
96int16_t pc_power_temp_c(void);
97
98/** @brief Apply @p plan's clock (no-op if it already matches). @return true if the clock changed. */
99bool pc_power_apply(const PowerPlan *plan);
100
101/** @brief Current CPU clock in MHz. */
102uint16_t pc_power_cpu_mhz(void);
103
104/**
105 * @brief Release the Bluetooth controller's power domain when the firmware does not use BT.
106 *
107 * The controller draws current whether or not anything is connected, so on a build with no BLE this
108 * is free power back. Safe to call when BT was never initialized, and safe to call twice.
109 * @return true if a release actually happened.
110 */
111bool pc_power_gate_bt(void);
112#endif // ARDUINO
113
114#endif // PC_ENABLE_POWER_MGMT
115#endif // PROTOCORE_POWER_MGMT_H
User-facing configuration for ProtoCore.