refactor(core): make power manager driver selectable
What changed, and why it matters
This is a code reorganization in Trezor's firmware that moves the existing power-management implementation from a folder named after the STM32U5 chip to a folder named after the actual power-management chip (Nordic nPM1300). It also makes the power manager and PMIC drivers selectable via build features and board configuration files, so future boards can use different power chips. The actual power-management logic is copied almost unchanged; there is no evidence this commit fixes or introduces a security vulnerability.
No security action required. Treat as a normal build/structural refactor. If reviewing for release integrity, verify that the moved files are byte-for-byte equivalent to the originals aside from include paths and that board configurations still enable the intended npm1300 backend.
Security signals we found
Large file move/rename with near-identical content
Build-system feature gating added for PMIC and power-manager drivers
Board configuration now explicitly selects npm1300 PMIC and power-manager backend
No changes to input validation, cryptography, memory allocation, or trust boundaries
No vendor changelog entry ([no changelog])
Evidence from the diff
The commit refactors the embedded power manager so the backend is selected by Cargo features (pmic_npm1300, power_manager_npm1300) and board TOML (pmic = "io/pmic_npm1300", driver = "io/power_manager_npm1300") rather than being hard-wired to mcu_stm32u5. The C source files are moved from power_manager/stm32u5/ to power_manager/npm1300/ with only build-system and include-path changes. The board configs for T3W1 revA/B/C are updated to select the npm1300 driver. No functional security fixes or obvious security bugs are visible in the diff.
Changed components
core/embed/io/Cargo.tomlcore/embed/io/power_manager/build.rscore/embed/io/power_manager/npm1300/power_manager.ccore/embed/io/power_manager/npm1300/power_manager_internal.hcore/embed/io/power_manager/npm1300/power_monitoring.ccore/embed/io/power_manager/npm1300/power_states.ccore/embed/models/T3W1/boards/revA.tomlcore/embed/models/T3W1/boards/revB.tomlcore/embed/models/T3W1/boards/revC.tomlInspect captured patch +1637 / −1615
diff --git a/core/embed/io/Cargo.toml b/core/embed/io/Cargo.toml
index f61ef39a..8eac808f 100644
--- a/core/embed/io/Cargo.toml
+++ b/core/embed/io/Cargo.toml
@@ -52,8 +52,10 @@ nrf = []
nrf_auth = ["sec/nrf_auth"]
hw_jpeg_decoder = []
lockable_bootloader = ["sec/lockable_bootloader"]
-pmic = ["sys/i2c_bus"]
-power_manager = ["pmic", "sys/i2c_bus"]
+pmic = []
+pmic_npm1300 = ["pmic", "sys/i2c_bus"]
+power_manager = []
+power_manager_npm1300 = ["pmic_npm1300"]
wireless_stwlc38 = ["sys/i2c_bus"]
production = ["sec/production"]
raspi_emulator = []
diff --git a/core/embed/io/power_manager/build.rs b/core/embed/io/power_manager/build.rs
index 7391509c..f517253d 100644
--- a/core/embed/io/power_manager/build.rs
+++ b/core/embed/io/power_manager/build.rs
@@ -3,14 +3,24 @@ use xbuild::{CLibrary, Result, bail_unsupported};
pub fn def_module(lib: &mut CLibrary) -> Result<()> {
lib.add_include("power_manager/inc");
- lib.add_define("USE_PMIC", Some("1"));
+ // --- PMIC driver (low-level power IC) ---------------------------------
+ // Handled before the power_manager early-return below, because pmic-only
+ // builds (e.g. the boardloader) need the driver without the full backend.
+ // The board selects a concrete driver via the `[power_manager]` peripheral
+ // `pmic = "io/pmic_..."` specifier; currently only npm1300 exists.
+ if cfg!(feature = "pmic") {
+ lib.add_define("USE_PMIC", Some("1"));
+ }
- if cfg!(feature = "emulator") {
- // No implementation
- } else if cfg!(feature = "mcu_stm32u5") {
- lib.add_source("power_manager/npm1300/npm1300.c");
- } else {
- bail_unsupported!();
+ if cfg!(feature = "pmic_npm1300") {
+ // npm1300 is an STM32U5 part with no emulator implementation.
+ if cfg!(feature = "emulator") {
+ // no hardware PMIC in the emulator
+ } else if cfg!(feature = "mcu_stm32u5") {
+ lib.add_source("power_manager/npm1300/npm1300.c");
+ } else {
+ bail_unsupported!();
+ }
}
if cfg!(not(feature = "power_manager")) {
@@ -36,13 +46,17 @@ pub fn def_module(lib: &mut CLibrary) -> Result<()> {
}
// --- Power manager backend --------------------------------------------
+ // The board selects a backend via the `[power_manager] driver = "io/..."`
+ // specifier. The full npm1300 backend (charging state machine + fuel gauge)
+ // is one option; future boards (e.g. a GPIO power latch) plug in their own
+ // minimal backend here.
if cfg!(feature = "emulator") {
lib.add_source("power_manager/unix/power_manager.c");
- } else if cfg!(feature = "mcu_stm32u5") {
+ } else if cfg!(feature = "power_manager_npm1300") {
lib.add_sources([
- "power_manager/stm32u5/power_manager.c",
- "power_manager/stm32u5/power_monitoring.c",
- "power_manager/stm32u5/power_states.c",
+ "power_manager/npm1300/power_manager.c",
+ "power_manager/npm1300/power_monitoring.c",
+ "power_manager/npm1300/power_states.c",
"power_manager/battery/battery.c",
"power_manager/battery/fuel_gauge.c",
"power_manager/battery/battery_model.c",
diff --git a/core/embed/io/power_manager/npm1300/power_manager.c b/core/embed/io/power_manager/npm1300/power_manager.c
new file mode 100644
index 00000000..9aadad13
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/power_manager.c
@@ -0,0 +1,800 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <trezor_rtl.h>
+
+#include <io/pmic.h>
+#include <io/suspend.h>
+#include <sec/backup_ram.h>
+#include <sys/irq.h>
+#include <sys/systick.h>
+#include <sys/systimer.h>
+
+#ifdef USE_RTC
+#include <sys/rtc.h>
+#include <sys/rtc_scheduler.h>
+#endif
+
+#ifdef USE_TELEMETRY
+#include <sec/telemetry.h>
+#endif
+
+#include "../battery/battery.h"
+#include "../power_manager_poll.h"
+#include "../stwlc38/stwlc38.h"
+#include "power_manager_internal.h"
+
+// Global driver instance
+pm_driver_t g_pm = {
+ .initialized = false,
+};
+
+// Forward declarations of static functions
+static void pm_monitoring_timer_handler(void* context);
+static void pm_shutdown_timer_handler(void* context);
+static bool pm_load_recovery_data(pm_recovery_data_t* recovery);
+static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms);
+
+pm_status_t pm_init(bool inherit_state) {
+ pm_driver_t* drv = &g_pm;
+
+ if (drv->initialized) {
+ return PM_OK;
+ }
+
+ // Clear driver instance
+ memset(drv, 0, sizeof(pm_driver_t));
+
+ // Initialize hardware subsystems
+ if (!pmic_init()) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+#ifdef USE_WIRELESS_CHARGER
+ if (!stwlc38_init()) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+#endif
+
+ if (!pm_poll_init()) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ // Initialize battery model with fuel gauge estimator
+ bat_init();
+
+ // Create monitoring timer
+ drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
+ if (drv->monitoring_timer == NULL) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ // Create shutdown timer
+ drv->shutdown_timer = systimer_create(pm_shutdown_timer_handler, NULL);
+ if (drv->shutdown_timer == NULL) {
+ pm_deinit();
+ return PM_ERROR;
+ }
+
+ systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
+
+ // Initial power source measurement
+ pmic_measure(pm_pmic_data_ready, NULL);
+
+ // Try to recover SoC from the backup RAM
+ pm_recovery_data_t recovery;
+ bool recovery_ok = pm_load_recovery_data(&recovery);
+
+ if (!recovery_ok) {
+ // Wait for 1s to sample battery data
+ systick_delay_ms(1000);
+ }
+
+ // In this part of the code, power monitoring timer is already running, so
+ // we have to prevent simultaneous access to the driver instance by locking
+ // the IRQs.
+ irq_key_t irq_key = irq_lock();
+
+ if (recovery_ok) {
+#ifdef USE_RTC
+
+ // RTC compensation should happen only during initialization in bootloader
+ if (!inherit_state) {
+ // Get RTC timestamp and compare it with the timestamp from recovery data
+ // to estimate time off and compensate self-discharge of the battery.
+ uint32_t rtc_timestamp;
+ if (recovery.last_capture_timestamp != 0 &&
+ rtc_get_timestamp(&rtc_timestamp)) {
+ // If the RTC timestamp is older than the last captured timestamp,
+ // we will not use it.
+ if (rtc_timestamp >= recovery.last_capture_timestamp) {
+ bat_fg_compensate_soc(&recovery.soc,
+ rtc_timestamp - recovery.last_capture_timestamp,
+ PM_SELF_DISG_RATE_HIBERNATION_MA, 25.0f);
+ }
+ }
+ }
+
+#endif
+
+ drv->battery_critical = recovery.bat_critical;
+ bat_fg_set_soc(recovery.soc, recovery.P);
+ } else {
+ bat_fg_initial_guess();
+ }
+
+ if (inherit_state) {
+ // Inherit power manager state left in backup RAM from bootloader.
+ // in case of error, start with PM_STATE_POWER_SAVE as a lowest state in
+ // active mode.
+ if (!recovery_ok &&
+ (recovery.bootloader_exit_state != PM_STATE_POWER_SAVE &&
+ recovery.bootloader_exit_state != PM_STATE_ACTIVE)) {
+ drv->state = PM_STATE_POWER_SAVE;
+
+ } else {
+ // Backup RAM contain valid data
+ drv->state = recovery.bootloader_exit_state;
+ }
+
+ } else {
+ // Start in lowest state and wait for the bootup sequence to
+ // finish (call of pm_turn_on())
+ drv->state = PM_STATE_HIBERNATE;
+ }
+
+ // Enable charging by default to max current
+ drv->charging_enabled = true;
+
+ // Set default SOC target and max charging current limit
+ drv->soc_target = 100;
+ drv->i_chg_max_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ drv->i_chg_temp_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+#endif
+
+ irq_unlock(irq_key);
+
+ // Wait to stabilize the state machine
+ pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
+ if (status != PM_OK) {
+ pm_deinit();
+ return status;
+ }
+
+ drv->initialized = true;
+
+ return PM_OK;
+}
+
+void pm_deinit(void) {
+ pm_driver_t* drv = &g_pm;
+
+ pm_poll_deinit();
+
+ if (drv->monitoring_timer) {
+ systimer_delete(drv->monitoring_timer);
+ drv->monitoring_timer = NULL;
+ }
+
+ if (drv->shutdown_timer) {
+ systimer_delete(drv->shutdown_timer);
+ drv->shutdown_timer = NULL;
+ }
+
+ if (bat_fg_is_locked()) {
+ pm_store_data_to_backup_ram();
+ }
+
+ pmic_deinit();
+#ifdef USE_WIRELESS_CHARGER
+ stwlc38_deinit();
+#endif
+
+ drv->initialized = false;
+}
+
+pm_status_t pm_get_state(pm_state_t* state) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ state->usb_connected = drv->usb_connected;
+ state->wireless_connected = drv->wireless_connected;
+ state->ntc_connected = !drv->pmic_data.ntc_disconnected;
+
+ if (pm_is_charging()) {
+ state->charging_status = PM_BATTERY_CHARGING;
+ } else if (drv->pmic_data.ibat > 0.0f) {
+ state->charging_status = PM_BATTERY_DISCHARGING;
+ } else {
+ state->charging_status = PM_BATTERY_IDLE;
+ }
+
+ // Charging-limited detection with 5s filter
+ // Conditions to consider:
+ // - Only when charging
+ // - Only when PMIC reports constant-current phase (decoded flag)
+ // - Consider measured current vs target with a small margin
+ // - Assert after predicate holds continuously for >= 5000 ms
+ // - Clear immediately when predicate breaks or not charging
+ const bool is_charging = (state->charging_status == PM_BATTERY_CHARGING);
+ const float MAX_DIFF_MA = 15; // tolerance below target current
+ const uint32_t FILTER_ASSERT_MS = 5000;
+
+ bool predicate = false;
+ if (is_charging) {
+ const bool cc_phase = drv->pmic_data.cc_phase;
+ float iabs_ma = drv->pmic_data.ibat;
+ if (iabs_ma < 0.0f) {
+ iabs_ma = -iabs_ma; // ibat < 0 => charging
+ }
+ predicate = cc_phase && (iabs_ma < (drv->i_chg_target_ma - MAX_DIFF_MA));
+ }
+
+ if (predicate) {
+ uint32_t now = systick_ms();
+ if (drv->charging_limited_start_ms == 0U) {
+ drv->charging_limited_start_ms = now;
+ } else if (!drv->charging_limited_latched &&
+ (now - drv->charging_limited_start_ms) >= FILTER_ASSERT_MS) {
+ drv->charging_limited_latched = true;
+ }
+ } else {
+ drv->charging_limited_start_ms = 0U;
+ drv->charging_limited_latched = false;
+ }
+
+ state->charging_limited = drv->charging_limited_latched;
+ state->battery_connected = !drv->battery_disconnected;
+ state->power_status = drv->state;
+ state->soc = drv->soc_ceiled;
+ state->battery_temp = drv->pmic_data.ntc_temp;
+ state->battery_ocv = drv->battery_ocv;
+
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+// This callback is called from inside the system_suspend() function
+// when the rtc wake-up timer expires.
+#ifdef USE_RTC
+void pm_rtc_wakeup_callback(void* context) {
+ pm_driver_t* drv = &g_pm;
+
+ // Clear autohibernate event reference
+ drv->autohibernate_event_id = 0;
+}
+#endif
+
+pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) {
+ pm_driver_t* drv = &g_pm;
+
+ if (wakeup_reason != NULL) {
+ *wakeup_reason = 0;
+ }
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_suspend = true;
+
+ pm_process_state_machine();
+
+ // Something went wrong, suspend request was not accepted
+ if (drv->request_suspend == true || drv->state != PM_STATE_SUSPEND) {
+ drv->request_suspend = false;
+ irq_unlock(irq_key);
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_unlock(irq_key);
+
+#ifdef USE_RTC
+ // Read the current timestamp before entering suspend mode
+ if (!rtc_get_timestamp(&drv->suspend_timestamp)) {
+ return PM_ERROR;
+ }
+#endif
+
+ wakeup_flags_t wakeup_flags = system_suspend();
+
+#ifdef USE_RTC
+ // Cancel autohibernate event if scheduled
+ if (drv->autohibernate_event_id != 0) {
+ rtc_cancel_wakeup_event(drv->autohibernate_event_id);
+ drv->autohibernate_event_id = 0;
+ }
+#endif
+
+ // Wait for pmic measurements to stabilize the fuel gauge estimation.
+ pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
+ if (status != PM_OK) {
+ // timeout during state machine stabilization
+ return PM_TIMEOUT;
+ }
+
+ // TODO: Handle wake-up flags
+ // UNUSED(wakeup_flags);
+
+ // Exit hibernation state if it was requested
+ irq_key = irq_lock();
+ drv->request_exit_suspend = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ if (wakeup_reason != NULL) {
+ *wakeup_reason = wakeup_flags;
+ }
+
+ return PM_OK;
+}
+
+pm_status_t pm_hibernate(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_hibernate = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ systick_delay_ms(50);
+
+ // Whenever hibernation request fall through, request was rejected
+ return PM_REQUEST_REJECTED;
+}
+
+pm_status_t pm_turn_on(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ // Poll until at least single PMIC measurement is done
+ uint32_t pmic_last_update_us;
+ do {
+ irq_key_t irq_key = irq_lock();
+ pmic_last_update_us = drv->pmic_last_update_us;
+ irq_unlock(irq_key);
+ } while (pmic_last_update_us == 0);
+
+ // Check if device has enough power to startup
+ if (drv->battery_critical) {
+ irq_key_t irq_key = irq_lock();
+ drv->battery_critical = true;
+ pm_store_data_to_backup_ram();
+ irq_unlock(irq_key);
+
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->request_turn_on = true;
+ pm_process_state_machine();
+ irq_unlock(irq_key);
+
+ if (drv->state == PM_STATE_HIBERNATE || drv->state == PM_STATE_CHARGING) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ return PM_OK;
+}
+
+pm_status_t pm_get_report(pm_report_t* report) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ // Copy current data into report
+ report->power_state = drv->state;
+ report->usb_connected = drv->usb_connected;
+ report->wireless_charger_connected = drv->wireless_connected;
+ report->system_voltage_v = drv->pmic_data.vsys;
+ report->battery_voltage_v = drv->pmic_data.vbat;
+ report->battery_current_ma = drv->pmic_data.ibat;
+ report->battery_temp_c = drv->pmic_data.ntc_temp;
+
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+ report->battery_soc = fg_state.soc;
+ report->battery_soc_latched = fg_state.soc_latched;
+
+ report->pmic_temp_c = drv->pmic_data.die_temp;
+ report->wireless_rectifier_voltage_v = drv->wireless_data.vrect;
+ report->wireless_output_voltage_v = drv->wireless_data.vout;
+ report->wireless_current_ma = drv->wireless_data.icur;
+ report->wireless_temp_c = drv->wireless_data.tmeas;
+
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_enable(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->charging_enabled = true;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_disable(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->charging_enabled = false;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_charging_set_max_current(uint16_t current_ma) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ if (current_ma > PM_BATTERY_CHARGING_CURRENT_MAX) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ if (current_ma < PM_BATTERY_CHARGING_CURRENT_MIN) {
+ return PM_REQUEST_REJECTED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->i_chg_max_limit_ma = current_ma;
+ pm_charging_controller(drv);
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+pm_status_t pm_store_data_to_backup_ram() {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ pm_recovery_data_t recovery = {.version = PM_RECOVERY_DATA_VERSION};
+
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+
+ recovery.soc = fg_state.soc;
+ recovery.P = fg_state.P;
+
+ // Power manager state
+ recovery.bat_critical = drv->battery_critical;
+ recovery.bootloader_exit_state = drv->state;
+
+#ifdef USE_RTC
+ if (!rtc_get_timestamp(&recovery.last_capture_timestamp)) {
+ // If RTC timestamp cannot be obtained, set it to 0
+ recovery.last_capture_timestamp = 0;
+ }
+#endif
+
+ irq_unlock(irq_key);
+
+ bool write_ok =
+ backup_ram_write(BACKUP_RAM_KEY_PM_RECOVERY, BACKUP_RAM_ITEM_PUBLIC,
+ &recovery, sizeof(recovery));
+
+ if (!write_ok) {
+ return PM_ERROR;
+ }
+
+#ifdef USE_TELEMETRY
+ // Update battery cycle counter telemetry
+ float cycle_increment = bat_fetch_cycle_increment();
+ telemetry_update_battery_cycles(cycle_increment);
+#endif
+
+ return PM_OK;
+}
+
+static bool pm_load_recovery_data(pm_recovery_data_t* recovery) {
+ union {
+ uint16_t version;
+ pm_recovery_data_t v1; // v1 is the only version currently supported
+ // pm_recovery_data_t v2;
+ } data;
+
+ size_t data_size = 0;
+
+ memset(recovery, 0, sizeof(*recovery));
+
+ bool read_ok = backup_ram_read(BACKUP_RAM_KEY_PM_RECOVERY, &data,
+ sizeof(data), &data_size);
+
+ if (!read_ok) {
+ return false;
+ }
+
+ // Incremental migration logic can be added here if needed
+ // if (data.version == PM_RECOVERY_DATA_VERSION_V1) {
+ // migrate_pm_recovery_data_v1_to_v2(&data.v1, &date_v2);
+ // }
+
+ if (data.version != PM_RECOVERY_DATA_VERSION) {
+ return false;
+ }
+
+ *recovery = data.v1;
+
+ if (recovery->soc < 0.0f || recovery->soc > 1.0f) {
+ return false;
+ }
+
+ return true;
+}
+
+pm_status_t pm_set_soc_target(uint8_t target) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return PM_NOT_INITIALIZED;
+ }
+
+ if (target > 100) {
+ return PM_ERROR;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ drv->soc_target = target;
+ irq_unlock(irq_key);
+
+ return PM_OK;
+}
+
+// Timer handlers
+static void pm_monitoring_timer_handler(void* context) {
+ pm_monitor_power_sources();
+}
+
+static void pm_shutdown_timer_handler(void* context) {
+ pm_driver_t* drv = &g_pm;
+ drv->shutdown_timer_elapsed = true;
+ pm_process_state_machine();
+}
+
+bool pm_driver_suspend(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ irq_key_t irq_key = irq_lock();
+
+ if (drv->woke_up_from_suspend) {
+ // Driver just woke up from suspend and have no data available yet.
+ // Request the suspend but wait for the next pmic_meausrement
+ drv->suspending = true;
+ } else {
+#ifdef USE_RTC
+ // Schedule auto-hibernation rtc event
+ pm_schedule_rtc_wakeup();
+#endif
+ drv->suspended = true;
+ }
+
+ // Delete the monitoring timer to stop the periodic sampling
+ systimer_delete(drv->monitoring_timer);
+
+ irq_unlock(irq_key);
+
+ // Suspend the sub-drivers owned by the power manager.
+ pmic_suspend();
+#ifdef USE_WIRELESS_CHARGER
+ stwlc38_suspend();
+#endif
+
+ return true;
+}
+
+#ifdef USE_RTC
+
+bool pm_schedule_rtc_wakeup(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ // Capture the timestamp when device was active for the last time.
+ if (!rtc_get_timestamp(&drv->last_active_timestamp)) {
+ return false;
+ }
+
+ if ((drv->last_active_timestamp - drv->suspend_timestamp) >=
+ PM_AUTO_HIBERNATE_TIMEOUT_S) {
+ // Device is very long time in suspend mode without external power source,
+ // hibernate it to save power.
+ pm_hibernate();
+ }
+
+ if (drv->autohibernate_event_id == 0) {
+ rtc_schedule_wakeup_event(
+ drv->suspend_timestamp + PM_AUTO_HIBERNATE_TIMEOUT_S,
+ pm_rtc_wakeup_callback, NULL, &drv->autohibernate_event_id);
+ }
+
+ return true;
+}
+
+#endif
+
+bool pm_is_charging(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ bool is_charging = false;
+
+ irq_key_t irq_key = irq_lock();
+ if (drv->charging_enabled &&
+ (!drv->fully_charged && !drv->soc_target_reached) &&
+ (drv->usb_connected || drv->wireless_connected)) {
+ is_charging = true;
+ }
+ irq_unlock(irq_key);
+
+ return is_charging;
+}
+
+bool pm_usb_is_connected(void) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ bool usb_connected;
+ irq_key_t irq_key = irq_lock();
+ usb_connected = drv->usb_connected;
+ irq_unlock(irq_key);
+
+ return usb_connected;
+}
+
+bool pm_driver_resume(void) {
+ // Resume the sub-drivers owned by the power manager.
+#ifdef USE_WIRELESS_CHARGER
+ stwlc38_resume();
+#endif
+ pmic_resume();
+
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ if (!drv->suspended && !drv->suspending) {
+ // Already resumed, nothing to do
+ return true;
+ }
+
+ drv->suspended = false;
+ drv->suspending = false;
+ drv->woke_up_from_suspend = true;
+ drv->state_machine_stabilized = false;
+
+#ifdef USE_RTC
+
+ uint32_t rtc_timestamp;
+ rtc_get_timestamp(&rtc_timestamp);
+ drv->time_in_suspend_s = (rtc_timestamp - drv->last_active_timestamp);
+
+#endif
+
+ // Recreate the monitoring timer
+ drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
+ if (drv->monitoring_timer == NULL) {
+ return false;
+ }
+
+ // Request new pmic measurement
+ pmic_measure(pm_pmic_data_ready, NULL);
+
+ // Set the periodic sampling period
+ systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
+
+ return true;
+}
+
+bool pm_driver_is_suspended(void) {
+ pm_driver_t* drv = &g_pm;
+
+ bool suspended;
+ irq_key_t irq_key = irq_lock();
+ suspended = drv->suspended;
+ irq_unlock(irq_key);
+
+ // The power manager is only fully suspended once its sub-drivers are too.
+ suspended = suspended && pmic_is_suspended();
+#ifdef USE_WIRELESS_CHARGER
+ suspended = suspended && stwlc38_is_suspended();
+#endif
+
+ return suspended;
+}
+
+static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms) {
+ uint32_t expire_time = ticks_timeout(timeout_ms);
+
+ // Poll until fuel_gauge is initialized and first PMIC & WLC measurements
+ // propagates into power_monitor.
+ bool state_machine_stabilized;
+ do {
+ if (ticks_expired(expire_time)) {
+ return PM_TIMEOUT;
+ }
+
+ irq_key_t irq_key = irq_lock();
+ state_machine_stabilized = drv->state_machine_stabilized;
+ irq_unlock(irq_key);
+ } while (!state_machine_stabilized);
+
+ return PM_OK;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/npm1300/power_manager_internal.h b/core/embed/io/power_manager/npm1300/power_manager_internal.h
new file mode 100644
index 00000000..1c91a2d2
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/power_manager_internal.h
@@ -0,0 +1,169 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include <io/pmic.h>
+#include <io/power_manager.h>
+#include <sys/rtc_scheduler.h>
+#include <sys/systimer.h>
+
+#include "../stwlc38/stwlc38.h"
+
+// Power manager thresholds & timings
+#define PM_TIMER_PERIOD_MS 100
+#define PM_SHUTDOWN_TIMEOUT_MS 15000
+#define PM_BATTERY_UNDERVOLT_THR_V 3.0f
+#define PM_BATTERY_CRITICAL_RECOVERY_SOC 0.02f
+#define PM_BATTERY_LOW_THRESHOLD_SOC 15
+#define PM_BATTERY_CHARGING_CURRENT_MAX PMIC_CHARGING_LIMIT_MAX
+#define PM_BATTERY_CHARGING_CURRENT_MIN PMIC_CHARGING_LIMIT_MIN
+
+#define PM_BATTERY_DISCONNECTED_THR_V 0.5f // battery disconnection detection
+#define PM_BATTERY_DISCONNECTED_REC_V \
+ 0.8f // recovery from disconnect detection
+
+#define PM_SELF_DISG_RATE_HIBERNATION_MA 0.004f
+#define PM_SELF_DISG_RATE_SUSPEND_MA 0.032f
+
+// Timeout after which the device automatically transit from suspend to
+// hibernation
+#define PM_AUTO_HIBERNATE_TIMEOUT_S (2 * 60 * 60) // 2 hours
+
+#define PM_STABILIZATION_TIMEOUT_MS 2000
+
+// Thermal controller switch, comment out to disable the thermal controller
+#define PM_ENABLE_TEMP_CONTROL
+
+// Temperature controller parameters
+#define PM_TEMP_CONTROL_IDLE_PERIOD_MS 2 * 60 * 1000 // 2 minutes
+#define PM_TEMP_CONTROL_BAND_1_MAX_TEMP 39.0f
+#define PM_TEMP_CONTROL_BAND_2_MAX_TEMP 43.0f
+#define PM_TEMP_CONTROL_BAND_3_MAX_TEMP 45.0f
+#define PM_TEMP_CONTROL_BAND_4_MAX_TEMP 47.0f
+
+// Power manager core driver structure
+typedef struct {
+ bool initialized;
+ bool state_machine_stabilized;
+ pm_power_status_t state;
+
+ // Set if the driver was requested to suspend background operations.
+ // IF so, the driver waits until the last operation is finished,
+ // then enters suspended mode.
+ bool suspending;
+
+ // Set if the driver's background operations are suspended.
+ bool suspended;
+
+ uint8_t soc_ceiled;
+ uint8_t soc_target;
+ bool soc_target_reached;
+ float target_battery_ocv_v_tau;
+ float battery_ocv;
+
+ // Battery charging state
+ bool charging_enabled;
+ uint16_t i_chg_target_ma;
+ uint16_t i_chg_max_limit_ma;
+
+ // Charging-limited detection filter state
+ // - charging_limited_latched: current filtered state exposed to pm_state_t
+ // - charging_limited_start_ms: timestamp when low-current-in-CC predicate
+ // started being true (0 when not timing)
+ bool charging_limited_latched;
+ uint32_t charging_limited_start_ms;
+
+ // battery disconnection detection, voltage based
+ bool battery_disconnected;
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ // Temp controller
+ uint32_t temp_control_timeout;
+ uint16_t i_chg_temp_limit_ma;
+ bool temp_control_active;
+#endif
+
+ // Power source hardware state
+ pmic_report_t pmic_data;
+ stwlc38_report_t wireless_data;
+ uint64_t pmic_last_update_us;
+ uint32_t pmic_sampling_period_ms;
+ bool pmic_measurement_ready;
+ bool woke_up_from_suspend;
+
+ // Power source logical state
+ bool usb_connected;
+ bool wireless_connected;
+ bool fully_charged;
+ bool battery_low;
+ bool battery_critical;
+
+ // Power mode request flags
+ bool request_suspend;
+ bool request_exit_suspend;
+ bool request_hibernate;
+ bool request_turn_on;
+ bool shutdown_timer_elapsed;
+
+ // Timers and timestamps
+ systimer_t* monitoring_timer;
+ systimer_t* shutdown_timer;
+ uint32_t suspend_timestamp;
+ uint32_t last_active_timestamp;
+ uint32_t time_in_suspend_s;
+ rtc_event_id_t autohibernate_event_id;
+
+} pm_driver_t;
+
+// State handler function definition
+typedef struct {
+ void (*enter)(pm_driver_t* drv);
+ pm_power_status_t (*handle)(pm_driver_t* drv);
+ void (*exit)(pm_driver_t* drv);
+} pm_state_handler_t;
+
+// Shared global driver instance
+extern pm_driver_t g_pm;
+
+// Power manager monitoring function called periodically to process data from
+// PMIC and WLC, run fuel gauge, run charging controller and stimulates
+// internal state machine.
+void pm_monitor_power_sources(void);
+
+// Power manager state machine automat driving internal state machine
+// transitions.
+void pm_process_state_machine(void);
+
+// PMIC callback function called when PMIC measurement acquisition is ready.
+void pm_pmic_data_ready(void* context, pmic_report_t* report);
+
+// Power manager charging controller function called periodically from
+// pm_monitor_power_sources() to control the charging current and state.
+void pm_charging_controller(pm_driver_t* drv);
+
+// Store power manager data to backup RAM
+pm_status_t pm_store_data_to_backup_ram(void);
+
+// Schedule the RTC wakeup when going into suspend mode.
+// Return false if the driver was not initialized or the RTC timestamp is
+// not available.
+bool pm_schedule_rtc_wakeup(void);
diff --git a/core/embed/io/power_manager/npm1300/power_monitoring.c b/core/embed/io/power_manager/npm1300/power_monitoring.c
new file mode 100644
index 00000000..832092b0
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/power_monitoring.c
@@ -0,0 +1,349 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <io/notify.h>
+#include <io/pmic.h>
+#include <sec/backup_ram.h>
+#include <sys/irq.h>
+#include <sys/systick.h>
+#include <trezor_rtl.h>
+
+#ifdef USE_TELEMETRY
+#include <sec/telemetry.h>
+#endif
+
+#include "../battery/battery.h"
+#include "../stwlc38/stwlc38.h"
+#include "power_manager_internal.h"
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+static void pm_temperature_controller(pm_driver_t* drv);
+#endif
+
+static void pm_parse_power_source_state(pm_driver_t* drv);
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+
+// Temperature controller LUT
+static const struct {
+ float max_temp;
+ float current_limit_factor;
+} temp_bands[] = {
+ {PM_TEMP_CONTROL_BAND_1_MAX_TEMP, 1.0f},
+ {PM_TEMP_CONTROL_BAND_2_MAX_TEMP, 0.7f},
+ {PM_TEMP_CONTROL_BAND_3_MAX_TEMP, 0.5f},
+ {PM_TEMP_CONTROL_BAND_4_MAX_TEMP, 0.3f},
+};
+
+#endif
+
+void pm_monitor_power_sources(void) {
+ // Periodically called timer to request PMIC measurements. PMIC will call
+ // pm_pmic_data_ready() callback when the measurements are ready.
+ pmic_measure(pm_pmic_data_ready, NULL);
+}
+
+// pmic measurement callback
+void pm_pmic_data_ready(void* context, pmic_report_t* report) {
+ pm_driver_t* drv = &g_pm;
+
+ // Store measurement timestamp
+ if (drv->pmic_last_update_us == 0) {
+ drv->pmic_sampling_period_ms = PM_TIMER_PERIOD_MS;
+ } else {
+ // Calculate the time since the last PMIC update
+ drv->pmic_sampling_period_ms =
+ (systick_us() - drv->pmic_last_update_us) / 1000;
+ }
+ drv->pmic_last_update_us = systick_us();
+ // Copy pmic data
+ memcpy(&drv->pmic_data, report, sizeof(pmic_report_t));
+
+ // Get wireless charger data
+#ifdef USE_WIRELESS_CHARGER
+ stwlc38_get_report(&drv->wireless_data);
+#endif
+
+#ifdef USE_TELEMETRY
+ // Update telemetry with the current battery temperature
+ if (!drv->pmic_data.ntc_disconnected) {
+ telemetry_update_battery_temp(drv->pmic_data.ntc_temp);
+ }
+#endif
+
+ // detect battery disconnection
+ drv->battery_disconnected =
+ (drv->pmic_data.vbat < PM_BATTERY_DISCONNECTED_THR_V) ||
+ (drv->battery_disconnected &&
+ drv->pmic_data.vbat < PM_BATTERY_DISCONNECTED_REC_V);
+
+ pm_parse_power_source_state(drv);
+
+ // Run battery charging controller
+ pm_charging_controller(drv);
+
+ drv->battery_ocv = bat_meas_to_ocv(drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
+
+ if (!bat_fg_is_locked()) {
+ // Fuel gauge not locked yet, battery SoC not available, just sample the
+ // battery data into the circular buffer.
+ bat_fg_feed_sample(drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
+
+ } else {
+ bat_fg_state_t fg_state;
+
+ if (drv->woke_up_from_suspend) {
+#ifdef USE_RTC
+
+ // Use known battery self-discharge rate to compensate the fuel gauge
+ // estimation during the suspend period. Since this period may be very
+ // long and the battery temperature may vary, use the average ambient
+ // temperature.
+
+ bat_fg_get_state(&fg_state);
+ bat_fg_compensate_soc(&fg_state.soc, drv->time_in_suspend_s,
+ PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f);
+
+ // TODO: Currently in suspend mode we use single self-discharge rate
+ // but in practice the discharge rate may change in case some components
+ // remains active. Since the device is very likely to stay in suspend
+ // mode for limited time, for now we decided to neglect this. but in
+ // the future we may want to distinguish between different suspend modes
+ // and use different self-discharge rates.
+ bat_fg_set_soc(fg_state.soc, fg_state.P);
+
+#endif // USE_RTC
+
+ // clear the flag
+ drv->woke_up_from_suspend = false;
+
+ } else {
+ bat_fg_update(drv->pmic_sampling_period_ms, drv->pmic_data.vbat,
+ drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+ }
+
+ // Charging completed flag from PMIC controller
+ if (drv->pmic_data.charge_status & 0x2) {
+ // Force fuel gauge to 100%, keep the covariance
+ drv->fully_charged = true;
+ bat_fg_get_state(&fg_state);
+ bat_fg_set_soc(1.0f, fg_state.P);
+ } else {
+ if (drv->pmic_data.ibat > 0) {
+ drv->fully_charged = false;
+ }
+ }
+
+ // Ceil the float soc to user-friendly integer
+ bat_fg_get_state(&fg_state);
+ drv->soc_ceiled = (uint8_t)(fg_state.soc_latched * 100 + 0.999f);
+
+ // Check battery voltage for low threshold
+ if (drv->soc_ceiled <= PM_BATTERY_LOW_THRESHOLD_SOC && !drv->battery_low) {
+ drv->battery_low = true;
+ } else if (drv->soc_ceiled > PM_BATTERY_LOW_THRESHOLD_SOC &&
+ drv->battery_low) {
+ drv->battery_low = false;
+ }
+
+ // Process state machine with updated battery and power source information
+ pm_process_state_machine();
+
+ pm_store_data_to_backup_ram();
+
+ if (drv->suspending) {
+#ifdef USE_RTC
+ // Schedule auto-hibernation rtc event
+ pm_schedule_rtc_wakeup();
+#endif
+ drv->suspending = false;
+ drv->suspended = true;
+ }
+
+ drv->state_machine_stabilized = true;
+ }
+}
+
+void pm_charging_controller(pm_driver_t* drv) {
+ if (drv->charging_enabled == false) {
+ // Charging is disabled
+ if (drv->i_chg_target_ma != 0) {
+ drv->i_chg_target_ma = 0;
+ } else {
+ // No action required
+ return;
+ }
+ } else if (drv->usb_connected) {
+ drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+ } else if (drv->wireless_connected) {
+ drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
+
+ } else {
+ // Charging enabled but no external power source, clear charging target
+ drv->i_chg_target_ma = 0;
+ }
+
+ // charging current software limit
+ if (drv->i_chg_target_ma > drv->i_chg_max_limit_ma) {
+ drv->i_chg_target_ma = drv->i_chg_max_limit_ma;
+ }
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+ pm_temperature_controller(drv);
+#endif
+
+ if (drv->pmic_data.ntc_disconnected) {
+ drv->i_chg_target_ma = 0;
+ }
+
+ if (drv->soc_target == 100) {
+ drv->soc_target_reached = false;
+ } else if (fabsf((-drv->pmic_data.ibat) - (float)drv->i_chg_target_ma) <=
+ 20.0f) {
+ // Translate SoC target to charging voltage via battery model
+ float target_ocv_voltage_v = bat_soc_to_ocv(drv->soc_target / 100.0f,
+ drv->pmic_data.ntc_temp, false);
+
+ float battery_ocv_v = bat_meas_to_ocv(
+ drv->pmic_data.vbat, drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+
+ drv->target_battery_ocv_v_tau =
+ (drv->target_battery_ocv_v_tau * 0.95f) +
+ (battery_ocv_v * 0.05f); // Exponential smoothing
+
+ if (drv->target_battery_ocv_v_tau > target_ocv_voltage_v) {
+ // current voltage is within tight bounds of target voltage,
+ // we may also force SoC estimate to target value.
+ if (drv->target_battery_ocv_v_tau < target_ocv_voltage_v + 0.15f) {
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+ bat_fg_set_soc((drv->soc_target / 100.0f) - 0.0001f, fg_state.P);
+ }
+
+ drv->soc_target_reached = true;
+ }
+ } else if (drv->soc_ceiled < drv->soc_target) {
+ drv->soc_target_reached = false;
+ }
+
+ if (drv->soc_target_reached) {
+ drv->i_chg_target_ma = 0;
+ }
+
+ // Set charging target
+ if (drv->i_chg_target_ma != pmic_get_charging_limit()) {
+ // Set charging current limit
+ pmic_set_charging_limit(drv->i_chg_target_ma);
+ }
+
+ if (drv->i_chg_target_ma == 0) {
+ pmic_set_charging(false);
+ } else {
+ // Clear and release charger if it has any errors
+ if (drv->pmic_data.charge_err || drv->pmic_data.charge_sensor_err) {
+ pmic_clear_charger_errors();
+ }
+
+ pmic_set_charging(true);
+ }
+}
+
+#ifdef PM_ENABLE_TEMP_CONTROL
+
+static void pm_temperature_controller(pm_driver_t* drv) {
+ if (ticks_expired(drv->temp_control_timeout)) {
+ uint16_t i_chg_temp_limit_ma = 0;
+
+ i_chg_temp_limit_ma = 0; // Default to safety limit
+ for (size_t i = 0; i < sizeof(temp_bands) / sizeof(temp_bands[0]); ++i) {
+ if (drv->pmic_data.ntc_temp < temp_bands[i].max_temp) {
+ i_chg_temp_limit_ma = (uint16_t)(PM_BATTERY_CHARGING_CURRENT_MAX *
+ temp_bands[i].current_limit_factor);
+ break;
+ }
+ }
+
+ // If the temperature limit has changed, update the limit and reset the
+ // debounce timer
+ if (drv->i_chg_temp_limit_ma != i_chg_temp_limit_ma) {
+ drv->i_chg_temp_limit_ma = i_chg_temp_limit_ma;
+ drv->temp_control_timeout = ticks_timeout(PM_TEMP_CONTROL_IDLE_PERIOD_MS);
+ }
+ }
+
+ if (drv->i_chg_target_ma > drv->i_chg_temp_limit_ma) {
+ // Limit the charging current by temperature controller
+ drv->i_chg_target_ma = drv->i_chg_temp_limit_ma;
+ drv->temp_control_active = true;
+ } else {
+ drv->temp_control_active = false;
+ }
+}
+
+#endif
+
+static void pm_parse_power_source_state(pm_driver_t* drv) {
+ // Check USB power source status
+ if (drv->pmic_data.usb_status != 0x0) {
+ if (!drv->usb_connected) {
+ drv->usb_connected = true;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ } else {
+ if (drv->usb_connected) {
+ drv->usb_connected = false;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ }
+
+ // Check wireless charger status
+ if (drv->wireless_data.vout_ready) {
+ if (!drv->wireless_connected) {
+ drv->wireless_connected = true;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ } else {
+ if (drv->wireless_connected) {
+ drv->wireless_connected = false;
+ notify_send(NOTIFY_POWER_STATUS_CHANGE);
+ }
+ }
+
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+
+ // Check battery voltage for critical (undervoltage) threshold
+ if ((drv->pmic_data.vbat < PM_BATTERY_UNDERVOLT_THR_V) &&
+ !drv->battery_critical && !drv->usb_connected) {
+ // Force Fuel gauge to 0, keep the covariance
+ bat_fg_set_soc(0.0f, fg_state.P);
+ drv->battery_critical = true;
+
+ } else if (fg_state.soc_latched >= (PM_BATTERY_CRITICAL_RECOVERY_SOC) ||
+ drv->usb_connected) {
+ // Restore the battery critical state
+ drv->battery_critical = false;
+ }
+}
+
+#endif
diff --git a/core/embed/io/power_manager/npm1300/power_states.c b/core/embed/io/power_manager/npm1300/power_states.c
new file mode 100644
index 00000000..d084e054
--- /dev/null
+++ b/core/embed/io/power_manager/npm1300/power_states.c
@@ -0,0 +1,284 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <io/backlight.h>
+#include <io/pmic.h>
+#include <sys/bootutils.h>
+#include <sys/systick.h>
+#include <sys/systimer.h>
+
+#include "power_manager_internal.h"
+
+// Power manager internal state machine handlers and entry/exit funtions
+static pm_power_status_t pm_handle_state_active(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv);
+static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv);
+
+static void pm_enter_hibernate(pm_driver_t* drv);
+static void pm_enter_charging(pm_driver_t* drv);
+static void pm_enter_shutting_down(pm_driver_t* drv);
+static void pm_enter_power_save(pm_driver_t* drv);
+static void pm_enter_active(pm_driver_t* drv);
+static void pm_exit_shutting_down(pm_driver_t* drv);
+
+// State handler lookup table
+static const pm_state_handler_t state_handlers[] = {
+ [PM_STATE_ACTIVE] =
+ {
+ .enter = pm_enter_active,
+ .handle = pm_handle_state_active,
+ .exit = NULL,
+ },
+ [PM_STATE_POWER_SAVE] =
+ {
+ .enter = pm_enter_power_save,
+ .handle = pm_handle_state_power_save,
+ .exit = NULL,
+ },
+ [PM_STATE_SHUTTING_DOWN] =
+ {
+ .enter = pm_enter_shutting_down,
+ .handle = pm_handle_state_shutting_down,
+ .exit = pm_exit_shutting_down,
+ },
+ [PM_STATE_SUSPEND] =
+ {
+ .enter = NULL,
+ .handle = pm_handle_state_suspend,
+ .exit = NULL,
+ },
+ [PM_STATE_CHARGING] =
+ {
+ .enter = pm_enter_charging,
+ .handle = pm_handle_state_charging,
+ .exit = NULL,
+ },
+ [PM_STATE_HIBERNATE] =
+ {
+ .enter = pm_enter_hibernate,
+ .handle = pm_handle_state_hibernate,
+ .exit = NULL,
+ },
+};
+
+void pm_process_state_machine(void) {
+ pm_driver_t* drv = &g_pm;
+ pm_power_status_t old_state;
+ pm_power_status_t new_state;
+
+ // Loop until state machine converge to a stable state
+ while (true) {
+ // Get current state
+ old_state = drv->state;
+
+ // Call state handler to process the current state
+ new_state = state_handlers[old_state].handle(drv);
+
+ // Check if the state has changed
+ if (new_state != old_state) {
+ // Exit old state
+ if (state_handlers[old_state].exit != NULL) {
+ state_handlers[old_state].exit(drv);
+ }
+
+ // Update state
+ drv->state = new_state;
+
+ // Enter new state
+ if (state_handlers[new_state].enter != NULL) {
+ state_handlers[new_state].enter(drv);
+ }
+
+ } else {
+ // State has not changed, exit the loop
+ break;
+ }
+ }
+}
+
+// State handler implementations
+
+static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv) {
+ if (drv->request_turn_on) {
+ drv->request_turn_on = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // External power source, start charging
+ if (drv->usb_connected || drv->wireless_connected) {
+ return PM_STATE_CHARGING;
+ }
+
+ // Hibernate again
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+
+ // Put PMIC into ship mode (ultra-low power)
+ pmic_enter_shipmode();
+ return PM_STATE_HIBERNATE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv) {
+ if (drv->request_turn_on) {
+ drv->request_turn_on = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // Go back to hibernate if external power was removed.
+ if (!drv->usb_connected && !drv->wireless_connected) {
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Hibernate again
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+
+ // Device is charging, request is rejected with no action
+ return PM_STATE_CHARGING;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv) {
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ if (drv->request_exit_suspend) {
+ drv->request_exit_suspend = false;
+ return PM_STATE_POWER_SAVE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv) {
+ // System is shutting down, but user can still hibernate the device early.
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Return to power save if external power or battery recovered
+ if (drv->usb_connected || !drv->battery_critical) {
+ return PM_STATE_POWER_SAVE;
+ }
+
+ // Enter hibernate when shutdown timer elapses
+ if (drv->shutdown_timer_elapsed) {
+ return PM_STATE_HIBERNATE;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv) {
+ // Handle hibernate request
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Handle suspend request
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Return to active if external power or battery recovered
+ if (drv->usb_connected || !drv->battery_low) {
+ return PM_STATE_ACTIVE;
+ }
+
+ // Go to shutdown if battery critical
+ if (!drv->usb_connected && drv->battery_critical) {
+ return PM_STATE_SHUTTING_DOWN;
+ }
+
+ return drv->state;
+}
+
+static pm_power_status_t pm_handle_state_active(pm_driver_t* drv) {
+ // Handle hibernate request
+ if (drv->request_hibernate) {
+ drv->request_hibernate = false;
+ return PM_STATE_HIBERNATE;
+ }
+
+ // Handle suspend request
+ if (drv->request_suspend) {
+ drv->request_suspend = false;
+ return PM_STATE_SUSPEND;
+ }
+
+ // Handle low battery with no external power
+ if (!drv->usb_connected && drv->battery_low) {
+ return PM_STATE_POWER_SAVE;
+ }
+
+ return drv->state;
+}
+
+// State enter/exit actions
+
+static void pm_enter_hibernate(pm_driver_t* drv) {
+ // Store power manager data with request to hibernate, power manager
+ // will try to hibernate immediately after reboot.
+ pm_store_data_to_backup_ram();
+ reboot_to_off();
+}
+
+static void pm_enter_charging(pm_driver_t* drv) {}
+
+static void pm_enter_shutting_down(pm_driver_t* drv) {
+ // Set shutdown timer
+ systimer_set(drv->shutdown_timer, PM_SHUTDOWN_TIMEOUT_MS);
+}
+
+static void pm_enter_power_save(pm_driver_t* drv) {
+ // Limit backlight
+ backlight_set_max_level(130);
+}
+
+static void pm_enter_active(pm_driver_t* drv) {
+ // Set unlimited backlight
+ backlight_set_max_level(255);
+}
+
+static void pm_exit_shutting_down(pm_driver_t* drv) {
+ // Stop the shutdown timer
+ systimer_unset(drv->shutdown_timer);
+ drv->shutdown_timer_elapsed = false;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/stm32u5/power_manager.c b/core/embed/io/power_manager/stm32u5/power_manager.c
deleted file mode 100644
index 9aadad13..00000000
--- a/core/embed/io/power_manager/stm32u5/power_manager.c
+++ /dev/null
@@ -1,800 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifdef KERNEL_MODE
-
-#include <trezor_rtl.h>
-
-#include <io/pmic.h>
-#include <io/suspend.h>
-#include <sec/backup_ram.h>
-#include <sys/irq.h>
-#include <sys/systick.h>
-#include <sys/systimer.h>
-
-#ifdef USE_RTC
-#include <sys/rtc.h>
-#include <sys/rtc_scheduler.h>
-#endif
-
-#ifdef USE_TELEMETRY
-#include <sec/telemetry.h>
-#endif
-
-#include "../battery/battery.h"
-#include "../power_manager_poll.h"
-#include "../stwlc38/stwlc38.h"
-#include "power_manager_internal.h"
-
-// Global driver instance
-pm_driver_t g_pm = {
- .initialized = false,
-};
-
-// Forward declarations of static functions
-static void pm_monitoring_timer_handler(void* context);
-static void pm_shutdown_timer_handler(void* context);
-static bool pm_load_recovery_data(pm_recovery_data_t* recovery);
-static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms);
-
-pm_status_t pm_init(bool inherit_state) {
- pm_driver_t* drv = &g_pm;
-
- if (drv->initialized) {
- return PM_OK;
- }
-
- // Clear driver instance
- memset(drv, 0, sizeof(pm_driver_t));
-
- // Initialize hardware subsystems
- if (!pmic_init()) {
- pm_deinit();
- return PM_ERROR;
- }
-
-#ifdef USE_WIRELESS_CHARGER
- if (!stwlc38_init()) {
- pm_deinit();
- return PM_ERROR;
- }
-#endif
-
- if (!pm_poll_init()) {
- pm_deinit();
- return PM_ERROR;
- }
-
- // Initialize battery model with fuel gauge estimator
- bat_init();
-
- // Create monitoring timer
- drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
- if (drv->monitoring_timer == NULL) {
- pm_deinit();
- return PM_ERROR;
- }
-
- // Create shutdown timer
- drv->shutdown_timer = systimer_create(pm_shutdown_timer_handler, NULL);
- if (drv->shutdown_timer == NULL) {
- pm_deinit();
- return PM_ERROR;
- }
-
- systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
-
- // Initial power source measurement
- pmic_measure(pm_pmic_data_ready, NULL);
-
- // Try to recover SoC from the backup RAM
- pm_recovery_data_t recovery;
- bool recovery_ok = pm_load_recovery_data(&recovery);
-
- if (!recovery_ok) {
- // Wait for 1s to sample battery data
- systick_delay_ms(1000);
- }
-
- // In this part of the code, power monitoring timer is already running, so
- // we have to prevent simultaneous access to the driver instance by locking
- // the IRQs.
- irq_key_t irq_key = irq_lock();
-
- if (recovery_ok) {
-#ifdef USE_RTC
-
- // RTC compensation should happen only during initialization in bootloader
- if (!inherit_state) {
- // Get RTC timestamp and compare it with the timestamp from recovery data
- // to estimate time off and compensate self-discharge of the battery.
- uint32_t rtc_timestamp;
- if (recovery.last_capture_timestamp != 0 &&
- rtc_get_timestamp(&rtc_timestamp)) {
- // If the RTC timestamp is older than the last captured timestamp,
- // we will not use it.
- if (rtc_timestamp >= recovery.last_capture_timestamp) {
- bat_fg_compensate_soc(&recovery.soc,
- rtc_timestamp - recovery.last_capture_timestamp,
- PM_SELF_DISG_RATE_HIBERNATION_MA, 25.0f);
- }
- }
- }
-
-#endif
-
- drv->battery_critical = recovery.bat_critical;
- bat_fg_set_soc(recovery.soc, recovery.P);
- } else {
- bat_fg_initial_guess();
- }
-
- if (inherit_state) {
- // Inherit power manager state left in backup RAM from bootloader.
- // in case of error, start with PM_STATE_POWER_SAVE as a lowest state in
- // active mode.
- if (!recovery_ok &&
- (recovery.bootloader_exit_state != PM_STATE_POWER_SAVE &&
- recovery.bootloader_exit_state != PM_STATE_ACTIVE)) {
- drv->state = PM_STATE_POWER_SAVE;
-
- } else {
- // Backup RAM contain valid data
- drv->state = recovery.bootloader_exit_state;
- }
-
- } else {
- // Start in lowest state and wait for the bootup sequence to
- // finish (call of pm_turn_on())
- drv->state = PM_STATE_HIBERNATE;
- }
-
- // Enable charging by default to max current
- drv->charging_enabled = true;
-
- // Set default SOC target and max charging current limit
- drv->soc_target = 100;
- drv->i_chg_max_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
-
-#ifdef PM_ENABLE_TEMP_CONTROL
- drv->i_chg_temp_limit_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
-#endif
-
- irq_unlock(irq_key);
-
- // Wait to stabilize the state machine
- pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
- if (status != PM_OK) {
- pm_deinit();
- return status;
- }
-
- drv->initialized = true;
-
- return PM_OK;
-}
-
-void pm_deinit(void) {
- pm_driver_t* drv = &g_pm;
-
- pm_poll_deinit();
-
- if (drv->monitoring_timer) {
- systimer_delete(drv->monitoring_timer);
- drv->monitoring_timer = NULL;
- }
-
- if (drv->shutdown_timer) {
- systimer_delete(drv->shutdown_timer);
- drv->shutdown_timer = NULL;
- }
-
- if (bat_fg_is_locked()) {
- pm_store_data_to_backup_ram();
- }
-
- pmic_deinit();
-#ifdef USE_WIRELESS_CHARGER
- stwlc38_deinit();
-#endif
-
- drv->initialized = false;
-}
-
-pm_status_t pm_get_state(pm_state_t* state) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
-
- state->usb_connected = drv->usb_connected;
- state->wireless_connected = drv->wireless_connected;
- state->ntc_connected = !drv->pmic_data.ntc_disconnected;
-
- if (pm_is_charging()) {
- state->charging_status = PM_BATTERY_CHARGING;
- } else if (drv->pmic_data.ibat > 0.0f) {
- state->charging_status = PM_BATTERY_DISCHARGING;
- } else {
- state->charging_status = PM_BATTERY_IDLE;
- }
-
- // Charging-limited detection with 5s filter
- // Conditions to consider:
- // - Only when charging
- // - Only when PMIC reports constant-current phase (decoded flag)
- // - Consider measured current vs target with a small margin
- // - Assert after predicate holds continuously for >= 5000 ms
- // - Clear immediately when predicate breaks or not charging
- const bool is_charging = (state->charging_status == PM_BATTERY_CHARGING);
- const float MAX_DIFF_MA = 15; // tolerance below target current
- const uint32_t FILTER_ASSERT_MS = 5000;
-
- bool predicate = false;
- if (is_charging) {
- const bool cc_phase = drv->pmic_data.cc_phase;
- float iabs_ma = drv->pmic_data.ibat;
- if (iabs_ma < 0.0f) {
- iabs_ma = -iabs_ma; // ibat < 0 => charging
- }
- predicate = cc_phase && (iabs_ma < (drv->i_chg_target_ma - MAX_DIFF_MA));
- }
-
- if (predicate) {
- uint32_t now = systick_ms();
- if (drv->charging_limited_start_ms == 0U) {
- drv->charging_limited_start_ms = now;
- } else if (!drv->charging_limited_latched &&
- (now - drv->charging_limited_start_ms) >= FILTER_ASSERT_MS) {
- drv->charging_limited_latched = true;
- }
- } else {
- drv->charging_limited_start_ms = 0U;
- drv->charging_limited_latched = false;
- }
-
- state->charging_limited = drv->charging_limited_latched;
- state->battery_connected = !drv->battery_disconnected;
- state->power_status = drv->state;
- state->soc = drv->soc_ceiled;
- state->battery_temp = drv->pmic_data.ntc_temp;
- state->battery_ocv = drv->battery_ocv;
-
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-// This callback is called from inside the system_suspend() function
-// when the rtc wake-up timer expires.
-#ifdef USE_RTC
-void pm_rtc_wakeup_callback(void* context) {
- pm_driver_t* drv = &g_pm;
-
- // Clear autohibernate event reference
- drv->autohibernate_event_id = 0;
-}
-#endif
-
-pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) {
- pm_driver_t* drv = &g_pm;
-
- if (wakeup_reason != NULL) {
- *wakeup_reason = 0;
- }
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->request_suspend = true;
-
- pm_process_state_machine();
-
- // Something went wrong, suspend request was not accepted
- if (drv->request_suspend == true || drv->state != PM_STATE_SUSPEND) {
- drv->request_suspend = false;
- irq_unlock(irq_key);
- return PM_REQUEST_REJECTED;
- }
-
- irq_unlock(irq_key);
-
-#ifdef USE_RTC
- // Read the current timestamp before entering suspend mode
- if (!rtc_get_timestamp(&drv->suspend_timestamp)) {
- return PM_ERROR;
- }
-#endif
-
- wakeup_flags_t wakeup_flags = system_suspend();
-
-#ifdef USE_RTC
- // Cancel autohibernate event if scheduled
- if (drv->autohibernate_event_id != 0) {
- rtc_cancel_wakeup_event(drv->autohibernate_event_id);
- drv->autohibernate_event_id = 0;
- }
-#endif
-
- // Wait for pmic measurements to stabilize the fuel gauge estimation.
- pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS);
- if (status != PM_OK) {
- // timeout during state machine stabilization
- return PM_TIMEOUT;
- }
-
- // TODO: Handle wake-up flags
- // UNUSED(wakeup_flags);
-
- // Exit hibernation state if it was requested
- irq_key = irq_lock();
- drv->request_exit_suspend = true;
- pm_process_state_machine();
- irq_unlock(irq_key);
-
- if (wakeup_reason != NULL) {
- *wakeup_reason = wakeup_flags;
- }
-
- return PM_OK;
-}
-
-pm_status_t pm_hibernate(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->request_hibernate = true;
- pm_process_state_machine();
- irq_unlock(irq_key);
-
- systick_delay_ms(50);
-
- // Whenever hibernation request fall through, request was rejected
- return PM_REQUEST_REJECTED;
-}
-
-pm_status_t pm_turn_on(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- // Poll until at least single PMIC measurement is done
- uint32_t pmic_last_update_us;
- do {
- irq_key_t irq_key = irq_lock();
- pmic_last_update_us = drv->pmic_last_update_us;
- irq_unlock(irq_key);
- } while (pmic_last_update_us == 0);
-
- // Check if device has enough power to startup
- if (drv->battery_critical) {
- irq_key_t irq_key = irq_lock();
- drv->battery_critical = true;
- pm_store_data_to_backup_ram();
- irq_unlock(irq_key);
-
- return PM_REQUEST_REJECTED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->request_turn_on = true;
- pm_process_state_machine();
- irq_unlock(irq_key);
-
- if (drv->state == PM_STATE_HIBERNATE || drv->state == PM_STATE_CHARGING) {
- return PM_REQUEST_REJECTED;
- }
-
- return PM_OK;
-}
-
-pm_status_t pm_get_report(pm_report_t* report) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
-
- // Copy current data into report
- report->power_state = drv->state;
- report->usb_connected = drv->usb_connected;
- report->wireless_charger_connected = drv->wireless_connected;
- report->system_voltage_v = drv->pmic_data.vsys;
- report->battery_voltage_v = drv->pmic_data.vbat;
- report->battery_current_ma = drv->pmic_data.ibat;
- report->battery_temp_c = drv->pmic_data.ntc_temp;
-
- bat_fg_state_t fg_state;
- bat_fg_get_state(&fg_state);
- report->battery_soc = fg_state.soc;
- report->battery_soc_latched = fg_state.soc_latched;
-
- report->pmic_temp_c = drv->pmic_data.die_temp;
- report->wireless_rectifier_voltage_v = drv->wireless_data.vrect;
- report->wireless_output_voltage_v = drv->wireless_data.vout;
- report->wireless_current_ma = drv->wireless_data.icur;
- report->wireless_temp_c = drv->wireless_data.tmeas;
-
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-pm_status_t pm_charging_enable(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->charging_enabled = true;
- pm_charging_controller(drv);
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-pm_status_t pm_charging_disable(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->charging_enabled = false;
- pm_charging_controller(drv);
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-pm_status_t pm_charging_set_max_current(uint16_t current_ma) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- if (current_ma > PM_BATTERY_CHARGING_CURRENT_MAX) {
- return PM_REQUEST_REJECTED;
- }
-
- if (current_ma < PM_BATTERY_CHARGING_CURRENT_MIN) {
- return PM_REQUEST_REJECTED;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->i_chg_max_limit_ma = current_ma;
- pm_charging_controller(drv);
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-pm_status_t pm_store_data_to_backup_ram() {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- irq_key_t irq_key = irq_lock();
-
- pm_recovery_data_t recovery = {.version = PM_RECOVERY_DATA_VERSION};
-
- bat_fg_state_t fg_state;
- bat_fg_get_state(&fg_state);
-
- recovery.soc = fg_state.soc;
- recovery.P = fg_state.P;
-
- // Power manager state
- recovery.bat_critical = drv->battery_critical;
- recovery.bootloader_exit_state = drv->state;
-
-#ifdef USE_RTC
- if (!rtc_get_timestamp(&recovery.last_capture_timestamp)) {
- // If RTC timestamp cannot be obtained, set it to 0
- recovery.last_capture_timestamp = 0;
- }
-#endif
-
- irq_unlock(irq_key);
-
- bool write_ok =
- backup_ram_write(BACKUP_RAM_KEY_PM_RECOVERY, BACKUP_RAM_ITEM_PUBLIC,
- &recovery, sizeof(recovery));
-
- if (!write_ok) {
- return PM_ERROR;
- }
-
-#ifdef USE_TELEMETRY
- // Update battery cycle counter telemetry
- float cycle_increment = bat_fetch_cycle_increment();
- telemetry_update_battery_cycles(cycle_increment);
-#endif
-
- return PM_OK;
-}
-
-static bool pm_load_recovery_data(pm_recovery_data_t* recovery) {
- union {
- uint16_t version;
- pm_recovery_data_t v1; // v1 is the only version currently supported
- // pm_recovery_data_t v2;
- } data;
-
- size_t data_size = 0;
-
- memset(recovery, 0, sizeof(*recovery));
-
- bool read_ok = backup_ram_read(BACKUP_RAM_KEY_PM_RECOVERY, &data,
- sizeof(data), &data_size);
-
- if (!read_ok) {
- return false;
- }
-
- // Incremental migration logic can be added here if needed
- // if (data.version == PM_RECOVERY_DATA_VERSION_V1) {
- // migrate_pm_recovery_data_v1_to_v2(&data.v1, &date_v2);
- // }
-
- if (data.version != PM_RECOVERY_DATA_VERSION) {
- return false;
- }
-
- *recovery = data.v1;
-
- if (recovery->soc < 0.0f || recovery->soc > 1.0f) {
- return false;
- }
-
- return true;
-}
-
-pm_status_t pm_set_soc_target(uint8_t target) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return PM_NOT_INITIALIZED;
- }
-
- if (target > 100) {
- return PM_ERROR;
- }
-
- irq_key_t irq_key = irq_lock();
- drv->soc_target = target;
- irq_unlock(irq_key);
-
- return PM_OK;
-}
-
-// Timer handlers
-static void pm_monitoring_timer_handler(void* context) {
- pm_monitor_power_sources();
-}
-
-static void pm_shutdown_timer_handler(void* context) {
- pm_driver_t* drv = &g_pm;
- drv->shutdown_timer_elapsed = true;
- pm_process_state_machine();
-}
-
-bool pm_driver_suspend(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return false;
- }
-
- irq_key_t irq_key = irq_lock();
-
- if (drv->woke_up_from_suspend) {
- // Driver just woke up from suspend and have no data available yet.
- // Request the suspend but wait for the next pmic_meausrement
- drv->suspending = true;
- } else {
-#ifdef USE_RTC
- // Schedule auto-hibernation rtc event
- pm_schedule_rtc_wakeup();
-#endif
- drv->suspended = true;
- }
-
- // Delete the monitoring timer to stop the periodic sampling
- systimer_delete(drv->monitoring_timer);
-
- irq_unlock(irq_key);
-
- // Suspend the sub-drivers owned by the power manager.
- pmic_suspend();
-#ifdef USE_WIRELESS_CHARGER
- stwlc38_suspend();
-#endif
-
- return true;
-}
-
-#ifdef USE_RTC
-
-bool pm_schedule_rtc_wakeup(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return false;
- }
-
- // Capture the timestamp when device was active for the last time.
- if (!rtc_get_timestamp(&drv->last_active_timestamp)) {
- return false;
- }
-
- if ((drv->last_active_timestamp - drv->suspend_timestamp) >=
- PM_AUTO_HIBERNATE_TIMEOUT_S) {
- // Device is very long time in suspend mode without external power source,
- // hibernate it to save power.
- pm_hibernate();
- }
-
- if (drv->autohibernate_event_id == 0) {
- rtc_schedule_wakeup_event(
- drv->suspend_timestamp + PM_AUTO_HIBERNATE_TIMEOUT_S,
- pm_rtc_wakeup_callback, NULL, &drv->autohibernate_event_id);
- }
-
- return true;
-}
-
-#endif
-
-bool pm_is_charging(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return false;
- }
-
- bool is_charging = false;
-
- irq_key_t irq_key = irq_lock();
- if (drv->charging_enabled &&
- (!drv->fully_charged && !drv->soc_target_reached) &&
- (drv->usb_connected || drv->wireless_connected)) {
- is_charging = true;
- }
- irq_unlock(irq_key);
-
- return is_charging;
-}
-
-bool pm_usb_is_connected(void) {
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return false;
- }
-
- bool usb_connected;
- irq_key_t irq_key = irq_lock();
- usb_connected = drv->usb_connected;
- irq_unlock(irq_key);
-
- return usb_connected;
-}
-
-bool pm_driver_resume(void) {
- // Resume the sub-drivers owned by the power manager.
-#ifdef USE_WIRELESS_CHARGER
- stwlc38_resume();
-#endif
- pmic_resume();
-
- pm_driver_t* drv = &g_pm;
-
- if (!drv->initialized) {
- return false;
- }
-
- if (!drv->suspended && !drv->suspending) {
- // Already resumed, nothing to do
- return true;
- }
-
- drv->suspended = false;
- drv->suspending = false;
- drv->woke_up_from_suspend = true;
- drv->state_machine_stabilized = false;
-
-#ifdef USE_RTC
-
- uint32_t rtc_timestamp;
- rtc_get_timestamp(&rtc_timestamp);
- drv->time_in_suspend_s = (rtc_timestamp - drv->last_active_timestamp);
-
-#endif
-
- // Recreate the monitoring timer
- drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
- if (drv->monitoring_timer == NULL) {
- return false;
- }
-
- // Request new pmic measurement
- pmic_measure(pm_pmic_data_ready, NULL);
-
- // Set the periodic sampling period
- systimer_set_periodic(drv->monitoring_timer, PM_TIMER_PERIOD_MS);
-
- return true;
-}
-
-bool pm_driver_is_suspended(void) {
- pm_driver_t* drv = &g_pm;
-
- bool suspended;
- irq_key_t irq_key = irq_lock();
- suspended = drv->suspended;
- irq_unlock(irq_key);
-
- // The power manager is only fully suspended once its sub-drivers are too.
- suspended = suspended && pmic_is_suspended();
-#ifdef USE_WIRELESS_CHARGER
- suspended = suspended && stwlc38_is_suspended();
-#endif
-
- return suspended;
-}
-
-static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms) {
- uint32_t expire_time = ticks_timeout(timeout_ms);
-
- // Poll until fuel_gauge is initialized and first PMIC & WLC measurements
- // propagates into power_monitor.
- bool state_machine_stabilized;
- do {
- if (ticks_expired(expire_time)) {
- return PM_TIMEOUT;
- }
-
- irq_key_t irq_key = irq_lock();
- state_machine_stabilized = drv->state_machine_stabilized;
- irq_unlock(irq_key);
- } while (!state_machine_stabilized);
-
- return PM_OK;
-}
-
-#endif
diff --git a/core/embed/io/power_manager/stm32u5/power_manager_internal.h b/core/embed/io/power_manager/stm32u5/power_manager_internal.h
deleted file mode 100644
index 1c91a2d2..00000000
--- a/core/embed/io/power_manager/stm32u5/power_manager_internal.h
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <trezor_types.h>
-
-#include <io/pmic.h>
-#include <io/power_manager.h>
-#include <sys/rtc_scheduler.h>
-#include <sys/systimer.h>
-
-#include "../stwlc38/stwlc38.h"
-
-// Power manager thresholds & timings
-#define PM_TIMER_PERIOD_MS 100
-#define PM_SHUTDOWN_TIMEOUT_MS 15000
-#define PM_BATTERY_UNDERVOLT_THR_V 3.0f
-#define PM_BATTERY_CRITICAL_RECOVERY_SOC 0.02f
-#define PM_BATTERY_LOW_THRESHOLD_SOC 15
-#define PM_BATTERY_CHARGING_CURRENT_MAX PMIC_CHARGING_LIMIT_MAX
-#define PM_BATTERY_CHARGING_CURRENT_MIN PMIC_CHARGING_LIMIT_MIN
-
-#define PM_BATTERY_DISCONNECTED_THR_V 0.5f // battery disconnection detection
-#define PM_BATTERY_DISCONNECTED_REC_V \
- 0.8f // recovery from disconnect detection
-
-#define PM_SELF_DISG_RATE_HIBERNATION_MA 0.004f
-#define PM_SELF_DISG_RATE_SUSPEND_MA 0.032f
-
-// Timeout after which the device automatically transit from suspend to
-// hibernation
-#define PM_AUTO_HIBERNATE_TIMEOUT_S (2 * 60 * 60) // 2 hours
-
-#define PM_STABILIZATION_TIMEOUT_MS 2000
-
-// Thermal controller switch, comment out to disable the thermal controller
-#define PM_ENABLE_TEMP_CONTROL
-
-// Temperature controller parameters
-#define PM_TEMP_CONTROL_IDLE_PERIOD_MS 2 * 60 * 1000 // 2 minutes
-#define PM_TEMP_CONTROL_BAND_1_MAX_TEMP 39.0f
-#define PM_TEMP_CONTROL_BAND_2_MAX_TEMP 43.0f
-#define PM_TEMP_CONTROL_BAND_3_MAX_TEMP 45.0f
-#define PM_TEMP_CONTROL_BAND_4_MAX_TEMP 47.0f
-
-// Power manager core driver structure
-typedef struct {
- bool initialized;
- bool state_machine_stabilized;
- pm_power_status_t state;
-
- // Set if the driver was requested to suspend background operations.
- // IF so, the driver waits until the last operation is finished,
- // then enters suspended mode.
- bool suspending;
-
- // Set if the driver's background operations are suspended.
- bool suspended;
-
- uint8_t soc_ceiled;
- uint8_t soc_target;
- bool soc_target_reached;
- float target_battery_ocv_v_tau;
- float battery_ocv;
-
- // Battery charging state
- bool charging_enabled;
- uint16_t i_chg_target_ma;
- uint16_t i_chg_max_limit_ma;
-
- // Charging-limited detection filter state
- // - charging_limited_latched: current filtered state exposed to pm_state_t
- // - charging_limited_start_ms: timestamp when low-current-in-CC predicate
- // started being true (0 when not timing)
- bool charging_limited_latched;
- uint32_t charging_limited_start_ms;
-
- // battery disconnection detection, voltage based
- bool battery_disconnected;
-
-#ifdef PM_ENABLE_TEMP_CONTROL
- // Temp controller
- uint32_t temp_control_timeout;
- uint16_t i_chg_temp_limit_ma;
- bool temp_control_active;
-#endif
-
- // Power source hardware state
- pmic_report_t pmic_data;
- stwlc38_report_t wireless_data;
- uint64_t pmic_last_update_us;
- uint32_t pmic_sampling_period_ms;
- bool pmic_measurement_ready;
- bool woke_up_from_suspend;
-
- // Power source logical state
- bool usb_connected;
- bool wireless_connected;
- bool fully_charged;
- bool battery_low;
- bool battery_critical;
-
- // Power mode request flags
- bool request_suspend;
- bool request_exit_suspend;
- bool request_hibernate;
- bool request_turn_on;
- bool shutdown_timer_elapsed;
-
- // Timers and timestamps
- systimer_t* monitoring_timer;
- systimer_t* shutdown_timer;
- uint32_t suspend_timestamp;
- uint32_t last_active_timestamp;
- uint32_t time_in_suspend_s;
- rtc_event_id_t autohibernate_event_id;
-
-} pm_driver_t;
-
-// State handler function definition
-typedef struct {
- void (*enter)(pm_driver_t* drv);
- pm_power_status_t (*handle)(pm_driver_t* drv);
- void (*exit)(pm_driver_t* drv);
-} pm_state_handler_t;
-
-// Shared global driver instance
-extern pm_driver_t g_pm;
-
-// Power manager monitoring function called periodically to process data from
-// PMIC and WLC, run fuel gauge, run charging controller and stimulates
-// internal state machine.
-void pm_monitor_power_sources(void);
-
-// Power manager state machine automat driving internal state machine
-// transitions.
-void pm_process_state_machine(void);
-
-// PMIC callback function called when PMIC measurement acquisition is ready.
-void pm_pmic_data_ready(void* context, pmic_report_t* report);
-
-// Power manager charging controller function called periodically from
-// pm_monitor_power_sources() to control the charging current and state.
-void pm_charging_controller(pm_driver_t* drv);
-
-// Store power manager data to backup RAM
-pm_status_t pm_store_data_to_backup_ram(void);
-
-// Schedule the RTC wakeup when going into suspend mode.
-// Return false if the driver was not initialized or the RTC timestamp is
-// not available.
-bool pm_schedule_rtc_wakeup(void);
diff --git a/core/embed/io/power_manager/stm32u5/power_monitoring.c b/core/embed/io/power_manager/stm32u5/power_monitoring.c
deleted file mode 100644
index 832092b0..00000000
--- a/core/embed/io/power_manager/stm32u5/power_monitoring.c
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifdef KERNEL_MODE
-
-#include <io/notify.h>
-#include <io/pmic.h>
-#include <sec/backup_ram.h>
-#include <sys/irq.h>
-#include <sys/systick.h>
-#include <trezor_rtl.h>
-
-#ifdef USE_TELEMETRY
-#include <sec/telemetry.h>
-#endif
-
-#include "../battery/battery.h"
-#include "../stwlc38/stwlc38.h"
-#include "power_manager_internal.h"
-
-#ifdef PM_ENABLE_TEMP_CONTROL
-static void pm_temperature_controller(pm_driver_t* drv);
-#endif
-
-static void pm_parse_power_source_state(pm_driver_t* drv);
-
-#ifdef PM_ENABLE_TEMP_CONTROL
-
-// Temperature controller LUT
-static const struct {
- float max_temp;
- float current_limit_factor;
-} temp_bands[] = {
- {PM_TEMP_CONTROL_BAND_1_MAX_TEMP, 1.0f},
- {PM_TEMP_CONTROL_BAND_2_MAX_TEMP, 0.7f},
- {PM_TEMP_CONTROL_BAND_3_MAX_TEMP, 0.5f},
- {PM_TEMP_CONTROL_BAND_4_MAX_TEMP, 0.3f},
-};
-
-#endif
-
-void pm_monitor_power_sources(void) {
- // Periodically called timer to request PMIC measurements. PMIC will call
- // pm_pmic_data_ready() callback when the measurements are ready.
- pmic_measure(pm_pmic_data_ready, NULL);
-}
-
-// pmic measurement callback
-void pm_pmic_data_ready(void* context, pmic_report_t* report) {
- pm_driver_t* drv = &g_pm;
-
- // Store measurement timestamp
- if (drv->pmic_last_update_us == 0) {
- drv->pmic_sampling_period_ms = PM_TIMER_PERIOD_MS;
- } else {
- // Calculate the time since the last PMIC update
- drv->pmic_sampling_period_ms =
- (systick_us() - drv->pmic_last_update_us) / 1000;
- }
- drv->pmic_last_update_us = systick_us();
- // Copy pmic data
- memcpy(&drv->pmic_data, report, sizeof(pmic_report_t));
-
- // Get wireless charger data
-#ifdef USE_WIRELESS_CHARGER
- stwlc38_get_report(&drv->wireless_data);
-#endif
-
-#ifdef USE_TELEMETRY
- // Update telemetry with the current battery temperature
- if (!drv->pmic_data.ntc_disconnected) {
- telemetry_update_battery_temp(drv->pmic_data.ntc_temp);
- }
-#endif
-
- // detect battery disconnection
- drv->battery_disconnected =
- (drv->pmic_data.vbat < PM_BATTERY_DISCONNECTED_THR_V) ||
- (drv->battery_disconnected &&
- drv->pmic_data.vbat < PM_BATTERY_DISCONNECTED_REC_V);
-
- pm_parse_power_source_state(drv);
-
- // Run battery charging controller
- pm_charging_controller(drv);
-
- drv->battery_ocv = bat_meas_to_ocv(drv->pmic_data.vbat, drv->pmic_data.ibat,
- drv->pmic_data.ntc_temp);
-
- if (!bat_fg_is_locked()) {
- // Fuel gauge not locked yet, battery SoC not available, just sample the
- // battery data into the circular buffer.
- bat_fg_feed_sample(drv->pmic_data.vbat, drv->pmic_data.ibat,
- drv->pmic_data.ntc_temp);
-
- } else {
- bat_fg_state_t fg_state;
-
- if (drv->woke_up_from_suspend) {
-#ifdef USE_RTC
-
- // Use known battery self-discharge rate to compensate the fuel gauge
- // estimation during the suspend period. Since this period may be very
- // long and the battery temperature may vary, use the average ambient
- // temperature.
-
- bat_fg_get_state(&fg_state);
- bat_fg_compensate_soc(&fg_state.soc, drv->time_in_suspend_s,
- PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f);
-
- // TODO: Currently in suspend mode we use single self-discharge rate
- // but in practice the discharge rate may change in case some components
- // remains active. Since the device is very likely to stay in suspend
- // mode for limited time, for now we decided to neglect this. but in
- // the future we may want to distinguish between different suspend modes
- // and use different self-discharge rates.
- bat_fg_set_soc(fg_state.soc, fg_state.P);
-
-#endif // USE_RTC
-
- // clear the flag
- drv->woke_up_from_suspend = false;
-
- } else {
- bat_fg_update(drv->pmic_sampling_period_ms, drv->pmic_data.vbat,
- drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
- }
-
- // Charging completed flag from PMIC controller
- if (drv->pmic_data.charge_status & 0x2) {
- // Force fuel gauge to 100%, keep the covariance
- drv->fully_charged = true;
- bat_fg_get_state(&fg_state);
- bat_fg_set_soc(1.0f, fg_state.P);
- } else {
- if (drv->pmic_data.ibat > 0) {
- drv->fully_charged = false;
- }
- }
-
- // Ceil the float soc to user-friendly integer
- bat_fg_get_state(&fg_state);
- drv->soc_ceiled = (uint8_t)(fg_state.soc_latched * 100 + 0.999f);
-
- // Check battery voltage for low threshold
- if (drv->soc_ceiled <= PM_BATTERY_LOW_THRESHOLD_SOC && !drv->battery_low) {
- drv->battery_low = true;
- } else if (drv->soc_ceiled > PM_BATTERY_LOW_THRESHOLD_SOC &&
- drv->battery_low) {
- drv->battery_low = false;
- }
-
- // Process state machine with updated battery and power source information
- pm_process_state_machine();
-
- pm_store_data_to_backup_ram();
-
- if (drv->suspending) {
-#ifdef USE_RTC
- // Schedule auto-hibernation rtc event
- pm_schedule_rtc_wakeup();
-#endif
- drv->suspending = false;
- drv->suspended = true;
- }
-
- drv->state_machine_stabilized = true;
- }
-}
-
-void pm_charging_controller(pm_driver_t* drv) {
- if (drv->charging_enabled == false) {
- // Charging is disabled
- if (drv->i_chg_target_ma != 0) {
- drv->i_chg_target_ma = 0;
- } else {
- // No action required
- return;
- }
- } else if (drv->usb_connected) {
- drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
-
- } else if (drv->wireless_connected) {
- drv->i_chg_target_ma = PM_BATTERY_CHARGING_CURRENT_MAX;
-
- } else {
- // Charging enabled but no external power source, clear charging target
- drv->i_chg_target_ma = 0;
- }
-
- // charging current software limit
- if (drv->i_chg_target_ma > drv->i_chg_max_limit_ma) {
- drv->i_chg_target_ma = drv->i_chg_max_limit_ma;
- }
-
-#ifdef PM_ENABLE_TEMP_CONTROL
- pm_temperature_controller(drv);
-#endif
-
- if (drv->pmic_data.ntc_disconnected) {
- drv->i_chg_target_ma = 0;
- }
-
- if (drv->soc_target == 100) {
- drv->soc_target_reached = false;
- } else if (fabsf((-drv->pmic_data.ibat) - (float)drv->i_chg_target_ma) <=
- 20.0f) {
- // Translate SoC target to charging voltage via battery model
- float target_ocv_voltage_v = bat_soc_to_ocv(drv->soc_target / 100.0f,
- drv->pmic_data.ntc_temp, false);
-
- float battery_ocv_v = bat_meas_to_ocv(
- drv->pmic_data.vbat, drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
-
- drv->target_battery_ocv_v_tau =
- (drv->target_battery_ocv_v_tau * 0.95f) +
- (battery_ocv_v * 0.05f); // Exponential smoothing
-
- if (drv->target_battery_ocv_v_tau > target_ocv_voltage_v) {
- // current voltage is within tight bounds of target voltage,
- // we may also force SoC estimate to target value.
- if (drv->target_battery_ocv_v_tau < target_ocv_voltage_v + 0.15f) {
- bat_fg_state_t fg_state;
- bat_fg_get_state(&fg_state);
- bat_fg_set_soc((drv->soc_target / 100.0f) - 0.0001f, fg_state.P);
- }
-
- drv->soc_target_reached = true;
- }
- } else if (drv->soc_ceiled < drv->soc_target) {
- drv->soc_target_reached = false;
- }
-
- if (drv->soc_target_reached) {
- drv->i_chg_target_ma = 0;
- }
-
- // Set charging target
- if (drv->i_chg_target_ma != pmic_get_charging_limit()) {
- // Set charging current limit
- pmic_set_charging_limit(drv->i_chg_target_ma);
- }
-
- if (drv->i_chg_target_ma == 0) {
- pmic_set_charging(false);
- } else {
- // Clear and release charger if it has any errors
- if (drv->pmic_data.charge_err || drv->pmic_data.charge_sensor_err) {
- pmic_clear_charger_errors();
- }
-
- pmic_set_charging(true);
- }
-}
-
-#ifdef PM_ENABLE_TEMP_CONTROL
-
-static void pm_temperature_controller(pm_driver_t* drv) {
- if (ticks_expired(drv->temp_control_timeout)) {
- uint16_t i_chg_temp_limit_ma = 0;
-
- i_chg_temp_limit_ma = 0; // Default to safety limit
- for (size_t i = 0; i < sizeof(temp_bands) / sizeof(temp_bands[0]); ++i) {
- if (drv->pmic_data.ntc_temp < temp_bands[i].max_temp) {
- i_chg_temp_limit_ma = (uint16_t)(PM_BATTERY_CHARGING_CURRENT_MAX *
- temp_bands[i].current_limit_factor);
- break;
- }
- }
-
- // If the temperature limit has changed, update the limit and reset the
- // debounce timer
- if (drv->i_chg_temp_limit_ma != i_chg_temp_limit_ma) {
- drv->i_chg_temp_limit_ma = i_chg_temp_limit_ma;
- drv->temp_control_timeout = ticks_timeout(PM_TEMP_CONTROL_IDLE_PERIOD_MS);
- }
- }
-
- if (drv->i_chg_target_ma > drv->i_chg_temp_limit_ma) {
- // Limit the charging current by temperature controller
- drv->i_chg_target_ma = drv->i_chg_temp_limit_ma;
- drv->temp_control_active = true;
- } else {
- drv->temp_control_active = false;
- }
-}
-
-#endif
-
-static void pm_parse_power_source_state(pm_driver_t* drv) {
- // Check USB power source status
- if (drv->pmic_data.usb_status != 0x0) {
- if (!drv->usb_connected) {
- drv->usb_connected = true;
- notify_send(NOTIFY_POWER_STATUS_CHANGE);
- }
- } else {
- if (drv->usb_connected) {
- drv->usb_connected = false;
- notify_send(NOTIFY_POWER_STATUS_CHANGE);
- }
- }
-
- // Check wireless charger status
- if (drv->wireless_data.vout_ready) {
- if (!drv->wireless_connected) {
- drv->wireless_connected = true;
- notify_send(NOTIFY_POWER_STATUS_CHANGE);
- }
- } else {
- if (drv->wireless_connected) {
- drv->wireless_connected = false;
- notify_send(NOTIFY_POWER_STATUS_CHANGE);
- }
- }
-
- bat_fg_state_t fg_state;
- bat_fg_get_state(&fg_state);
-
- // Check battery voltage for critical (undervoltage) threshold
- if ((drv->pmic_data.vbat < PM_BATTERY_UNDERVOLT_THR_V) &&
- !drv->battery_critical && !drv->usb_connected) {
- // Force Fuel gauge to 0, keep the covariance
- bat_fg_set_soc(0.0f, fg_state.P);
- drv->battery_critical = true;
-
- } else if (fg_state.soc_latched >= (PM_BATTERY_CRITICAL_RECOVERY_SOC) ||
- drv->usb_connected) {
- // Restore the battery critical state
- drv->battery_critical = false;
- }
-}
-
-#endif
diff --git a/core/embed/io/power_manager/stm32u5/power_states.c b/core/embed/io/power_manager/stm32u5/power_states.c
deleted file mode 100644
index d084e054..00000000
--- a/core/embed/io/power_manager/stm32u5/power_states.c
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifdef KERNEL_MODE
-
-#include <io/backlight.h>
-#include <io/pmic.h>
-#include <sys/bootutils.h>
-#include <sys/systick.h>
-#include <sys/systimer.h>
-
-#include "power_manager_internal.h"
-
-// Power manager internal state machine handlers and entry/exit funtions
-static pm_power_status_t pm_handle_state_active(pm_driver_t* drv);
-static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv);
-static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv);
-static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv);
-static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv);
-static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv);
-
-static void pm_enter_hibernate(pm_driver_t* drv);
-static void pm_enter_charging(pm_driver_t* drv);
-static void pm_enter_shutting_down(pm_driver_t* drv);
-static void pm_enter_power_save(pm_driver_t* drv);
-static void pm_enter_active(pm_driver_t* drv);
-static void pm_exit_shutting_down(pm_driver_t* drv);
-
-// State handler lookup table
-static const pm_state_handler_t state_handlers[] = {
- [PM_STATE_ACTIVE] =
- {
- .enter = pm_enter_active,
- .handle = pm_handle_state_active,
- .exit = NULL,
- },
- [PM_STATE_POWER_SAVE] =
- {
- .enter = pm_enter_power_save,
- .handle = pm_handle_state_power_save,
- .exit = NULL,
- },
- [PM_STATE_SHUTTING_DOWN] =
- {
- .enter = pm_enter_shutting_down,
- .handle = pm_handle_state_shutting_down,
- .exit = pm_exit_shutting_down,
- },
- [PM_STATE_SUSPEND] =
- {
- .enter = NULL,
- .handle = pm_handle_state_suspend,
- .exit = NULL,
- },
- [PM_STATE_CHARGING] =
- {
- .enter = pm_enter_charging,
- .handle = pm_handle_state_charging,
- .exit = NULL,
- },
- [PM_STATE_HIBERNATE] =
- {
- .enter = pm_enter_hibernate,
- .handle = pm_handle_state_hibernate,
- .exit = NULL,
- },
-};
-
-void pm_process_state_machine(void) {
- pm_driver_t* drv = &g_pm;
- pm_power_status_t old_state;
- pm_power_status_t new_state;
-
- // Loop until state machine converge to a stable state
- while (true) {
- // Get current state
- old_state = drv->state;
-
- // Call state handler to process the current state
- new_state = state_handlers[old_state].handle(drv);
-
- // Check if the state has changed
- if (new_state != old_state) {
- // Exit old state
- if (state_handlers[old_state].exit != NULL) {
- state_handlers[old_state].exit(drv);
- }
-
- // Update state
- drv->state = new_state;
-
- // Enter new state
- if (state_handlers[new_state].enter != NULL) {
- state_handlers[new_state].enter(drv);
- }
-
- } else {
- // State has not changed, exit the loop
- break;
- }
- }
-}
-
-// State handler implementations
-
-static pm_power_status_t pm_handle_state_hibernate(pm_driver_t* drv) {
- if (drv->request_turn_on) {
- drv->request_turn_on = false;
- return PM_STATE_POWER_SAVE;
- }
-
- // External power source, start charging
- if (drv->usb_connected || drv->wireless_connected) {
- return PM_STATE_CHARGING;
- }
-
- // Hibernate again
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
-
- // Put PMIC into ship mode (ultra-low power)
- pmic_enter_shipmode();
- return PM_STATE_HIBERNATE;
- }
-
- return drv->state;
-}
-
-static pm_power_status_t pm_handle_state_charging(pm_driver_t* drv) {
- if (drv->request_turn_on) {
- drv->request_turn_on = false;
- return PM_STATE_POWER_SAVE;
- }
-
- // Go back to hibernate if external power was removed.
- if (!drv->usb_connected && !drv->wireless_connected) {
- return PM_STATE_HIBERNATE;
- }
-
- // Hibernate again
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
-
- // Device is charging, request is rejected with no action
- return PM_STATE_CHARGING;
- }
-
- return drv->state;
-}
-
-static pm_power_status_t pm_handle_state_suspend(pm_driver_t* drv) {
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
- return PM_STATE_HIBERNATE;
- }
-
- if (drv->request_exit_suspend) {
- drv->request_exit_suspend = false;
- return PM_STATE_POWER_SAVE;
- }
-
- return drv->state;
-}
-
-static pm_power_status_t pm_handle_state_shutting_down(pm_driver_t* drv) {
- // System is shutting down, but user can still hibernate the device early.
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
- return PM_STATE_HIBERNATE;
- }
-
- if (drv->request_suspend) {
- drv->request_suspend = false;
- return PM_STATE_SUSPEND;
- }
-
- // Return to power save if external power or battery recovered
- if (drv->usb_connected || !drv->battery_critical) {
- return PM_STATE_POWER_SAVE;
- }
-
- // Enter hibernate when shutdown timer elapses
- if (drv->shutdown_timer_elapsed) {
- return PM_STATE_HIBERNATE;
- }
-
- return drv->state;
-}
-
-static pm_power_status_t pm_handle_state_power_save(pm_driver_t* drv) {
- // Handle hibernate request
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
- return PM_STATE_HIBERNATE;
- }
-
- // Handle suspend request
- if (drv->request_suspend) {
- drv->request_suspend = false;
- return PM_STATE_SUSPEND;
- }
-
- // Return to active if external power or battery recovered
- if (drv->usb_connected || !drv->battery_low) {
- return PM_STATE_ACTIVE;
- }
-
- // Go to shutdown if battery critical
- if (!drv->usb_connected && drv->battery_critical) {
- return PM_STATE_SHUTTING_DOWN;
- }
-
- return drv->state;
-}
-
-static pm_power_status_t pm_handle_state_active(pm_driver_t* drv) {
- // Handle hibernate request
- if (drv->request_hibernate) {
- drv->request_hibernate = false;
- return PM_STATE_HIBERNATE;
- }
-
- // Handle suspend request
- if (drv->request_suspend) {
- drv->request_suspend = false;
- return PM_STATE_SUSPEND;
- }
-
- // Handle low battery with no external power
- if (!drv->usb_connected && drv->battery_low) {
- return PM_STATE_POWER_SAVE;
- }
-
- return drv->state;
-}
-
-// State enter/exit actions
-
-static void pm_enter_hibernate(pm_driver_t* drv) {
- // Store power manager data with request to hibernate, power manager
- // will try to hibernate immediately after reboot.
- pm_store_data_to_backup_ram();
- reboot_to_off();
-}
-
-static void pm_enter_charging(pm_driver_t* drv) {}
-
-static void pm_enter_shutting_down(pm_driver_t* drv) {
- // Set shutdown timer
- systimer_set(drv->shutdown_timer, PM_SHUTDOWN_TIMEOUT_MS);
-}
-
-static void pm_enter_power_save(pm_driver_t* drv) {
- // Limit backlight
- backlight_set_max_level(130);
-}
-
-static void pm_enter_active(pm_driver_t* drv) {
- // Set unlimited backlight
- backlight_set_max_level(255);
-}
-
-static void pm_exit_shutting_down(pm_driver_t* drv) {
- // Stop the shutdown timer
- systimer_unset(drv->shutdown_timer);
- drv->shutdown_timer_elapsed = false;
-}
-
-#endif
diff --git a/core/embed/models/T3W1/boards/revA.toml b/core/embed/models/T3W1/boards/revA.toml
index 1f0f185c..b8dd5342 100644
--- a/core/embed/models/T3W1/boards/revA.toml
+++ b/core/embed/models/T3W1/boards/revA.toml
@@ -25,6 +25,8 @@ panel = "io/touch_panel_lx250a2410a"
[rgb_led]
[power_manager]
+pmic = "io/pmic_npm1300"
+driver = "io/power_manager_npm1300"
wireless = "io/wireless_stwlc38"
[nfc]
diff --git a/core/embed/models/T3W1/boards/revB.toml b/core/embed/models/T3W1/boards/revB.toml
index 915da087..75a626c9 100644
--- a/core/embed/models/T3W1/boards/revB.toml
+++ b/core/embed/models/T3W1/boards/revB.toml
@@ -25,6 +25,8 @@ panel = "io/touch_panel_lx250a2410a"
[rgb_led]
[power_manager]
+pmic = "io/pmic_npm1300"
+driver = "io/power_manager_npm1300"
wireless = "io/wireless_stwlc38"
[nfc]
diff --git a/core/embed/models/T3W1/boards/revC.toml b/core/embed/models/T3W1/boards/revC.toml
index b939ec04..ef997c4e 100644
--- a/core/embed/models/T3W1/boards/revC.toml
+++ b/core/embed/models/T3W1/boards/revC.toml
@@ -25,6 +25,8 @@ panel = "io/touch_panel_lx250a2410a"
[rgb_led]
[power_manager]
+pmic = "io/pmic_npm1300"
+driver = "io/power_manager_npm1300"
wireless = "io/wireless_stwlc38"
[nfc]
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.