From bec455c9e5e46e38e9e13307e53307ec33a330fb Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 20 Jun 2025 14:07:04 +0200 Subject: [PATCH] feat(core): manage background ops during suspend [no changelog] --- core/embed/sys/power_manager/inc/sys/pmic.h | 13 +++ .../embed/sys/power_manager/npm1300/npm1300.c | 75 ++++++++++++- .../sys/power_manager/stm32u5/power_manager.c | 10 +- .../embed/sys/power_manager/stwlc38/stwlc38.c | 101 ++++++++++++++---- .../embed/sys/power_manager/stwlc38/stwlc38.h | 13 +++ .../power_manager/stwlc38/stwlc38_internal.h | 9 ++ core/embed/sys/suspend/stm32u5/suspend.c | 17 ++- 7 files changed, 206 insertions(+), 32 deletions(-) diff --git a/core/embed/sys/power_manager/inc/sys/pmic.h b/core/embed/sys/power_manager/inc/sys/pmic.h index b5cc5db55e..296023b3f9 100644 --- a/core/embed/sys/power_manager/inc/sys/pmic.h +++ b/core/embed/sys/power_manager/inc/sys/pmic.h @@ -59,6 +59,19 @@ bool pmic_init(void); // Deinitializes PMIC driver void pmic_deinit(void); +// Suspends driver activity so the CPU can enter low-power mode. +// +// Suspending may take some time if the driver is currently +// performing an operation. Caller may check the status by +// pmic_is_suspended(). +bool pmic_suspend(void); + +// Resumes the driver operation after it has been suspended. +bool pmic_resume(void); + +// Checks whether the driver is suspended. +bool pmic_is_suspended(void); + // Gets the cause of the last restart uint8_t pmic_restart_cause(void); diff --git a/core/embed/sys/power_manager/npm1300/npm1300.c b/core/embed/sys/power_manager/npm1300/npm1300.c index 12b5adf6c7..ae9abf820f 100644 --- a/core/embed/sys/power_manager/npm1300/npm1300.c +++ b/core/embed/sys/power_manager/npm1300/npm1300.c @@ -107,6 +107,15 @@ typedef struct { // Current state of the FSM npm1300_fsm_state_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. + // In suspended mode, the driver does not start any new operations. + bool suspended; + // ADC register (global buffer used for ADC measurements) npm1300_adc_regs_t adc_regs; // Charging limit registers (global buffer used for charging limit) @@ -418,6 +427,53 @@ void pmic_deinit(void) { memset(drv, 0, sizeof(npm1300_driver_t)); } +bool pmic_suspend(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->suspending = true; + npm1300_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +bool pmic_resume(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->suspending = false; + drv->suspended = false; + npm1300_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +bool pmic_is_suspended(void) { + npm1300_driver_t* drv = &g_npm1300_driver; + + if (!drv->initialized) { + return false; + } + + bool is_suspended; + + irq_key_t irq_key = irq_lock(); + is_suspended = drv->suspended; + irq_unlock(irq_key); + + return is_suspended; +} + bool pmic_enter_shipmode(void) { npm1300_driver_t* drv = &g_npm1300_driver; @@ -830,6 +886,9 @@ static void npm1300_i2c_callback(void* context, i2c_packet_t* packet) { case NPM1300_STATE_CLEAR_EVENTS: drv->clear_events_requested = false; drv->state = NPM1300_STATE_IDLE; +#ifdef USE_SUSPEND + wakeup_flags_set(WAKEUP_FLAG_POWER); +#endif break; case NPM1300_STATE_CHARGING_ENABLE: @@ -903,10 +962,6 @@ void NPM1300_EXTI_INTERRUPT_HANDLER(void) { return; } -#ifdef USE_SUSPEND - wakeup_flags_set(WAKEUP_FLAG_POWER); -#endif - drv->clear_events_requested = true; npm1300_fsm_continue(drv); } @@ -915,7 +970,7 @@ void NPM1300_EXTI_INTERRUPT_HANDLER(void) { // // This function is called in the irq context or when interrupts are disabled. static void npm1300_fsm_continue(npm1300_driver_t* drv) { - if (drv->state != NPM1300_STATE_IDLE) { + if (drv->state != NPM1300_STATE_IDLE || drv->suspended) { return; } @@ -971,6 +1026,16 @@ static void npm1300_fsm_continue(npm1300_driver_t* drv) { drv->shipmode_requested = false; drv->state = NPM1300_STATE_ENTER_SHIPMODE; } + + // After processing all requests, check if we need to + // suspend the driver + if (drv->state == NPM1300_STATE_IDLE) { + // No more requests to process + if (drv->suspending) { + drv->suspending = false; + drv->suspended = true; + } + } } #endif // KERNEL_MODE diff --git a/core/embed/sys/power_manager/stm32u5/power_manager.c b/core/embed/sys/power_manager/stm32u5/power_manager.c index a38afe618c..71e599b03f 100644 --- a/core/embed/sys/power_manager/stm32u5/power_manager.c +++ b/core/embed/sys/power_manager/stm32u5/power_manager.c @@ -215,15 +215,19 @@ pm_status_t pm_get_state(pm_state_t* state) { // - The callback can schedule the next wake-up by calling // rtc_wakeup_timer_start(). // - If the callback return with wakeup_flags set, system_suspend() returns. +#ifdef USE_RTC void pm_rtc_wakeup_callback(void* context) { // TODO: update fuel gauge state - + // TODO: decide whether to reschedule the next wake-up or wake up the coreapp if (true) { - rtc_wakeup_timer_start(0, pm_rtc_wakeup_callback, NULL); + // Reschedule the next wake-up + rtc_wakeup_timer_start(10, pm_rtc_wakeup_callback, NULL); } else { + // Wake up the coreapp wakeup_flags_set(WAKEUP_FLAG_RTC); } } +#endif pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) { pm_driver_t* drv = &g_pm; @@ -256,7 +260,9 @@ pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) { wakeup_flags_t wakeup_flags = system_suspend(); +#ifdef USE_RTC rtc_wakeup_timer_stop(); +#endif // TODO: Handle wake-up flags // UNUSED(wakeup_flags); diff --git a/core/embed/sys/power_manager/stwlc38/stwlc38.c b/core/embed/sys/power_manager/stwlc38/stwlc38.c index 05236b2061..17506a23cd 100644 --- a/core/embed/sys/power_manager/stwlc38/stwlc38.c +++ b/core/embed/sys/power_manager/stwlc38/stwlc38.c @@ -156,6 +156,53 @@ cleanup: return false; } +bool stwlc38_suspend(void) { + stwlc38_driver_t *drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->suspending = true; + stwlc38_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +bool stwlc38_resume(void) { + stwlc38_driver_t *drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + irq_key_t irq_key = irq_lock(); + drv->suspending = false; + drv->suspended = false; + stwlc38_fsm_continue(drv); + irq_unlock(irq_key); + + return true; +} + +bool stwlc38_is_suspended(void) { + stwlc38_driver_t *drv = &g_stwlc38_driver; + + if (!drv->initialized) { + return false; + } + + bool is_suspended; + + irq_key_t irq_key = irq_lock(); + is_suspended = drv->suspended; + irq_unlock(irq_key); + + return is_suspended; +} + bool stwlc38_enable(bool enable) { stwlc38_driver_t *drv = &g_stwlc38_driver; @@ -329,8 +376,6 @@ void STWLC38_EXTI_INTERRUPT_HANDLER(void) { } if (drv->state == STWLC38_STATE_POWER_DOWN) { - // Inform the powerctl module about the WPC - // wakeup_flags_set(WAKEUP_FLAGS_WPC); drv->report_readout_requested = true; stwlc38_fsm_continue(drv); } @@ -339,30 +384,42 @@ void STWLC38_EXTI_INTERRUPT_HANDLER(void) { static void stwlc38_fsm_continue(stwlc38_driver_t *drv) { // The order of the following conditions defines the priority - if (drv->state == STWLC38_STATE_POWER_DOWN && drv->report_readout_requested) { - // Check if the i2c interface is ready - stwlc38_i2c_submit(drv, stwlc38_ops_report_readout); - drv->state = STWLC38_STATE_REPORT_READOUT; + if (drv->suspended) { return; } - if (drv->state != STWLC38_STATE_IDLE) { - return; - } - - if (drv->vout_enabled != drv->vout_enabled_requested) { - // Enable/Disable the main LDO output - if (drv->vout_enabled_requested) { - stwlc38_i2c_submit(drv, stwlc38_ops_vout_enable); - drv->state = STWLC38_STATE_VOUT_ENABLE; - } else { - stwlc38_i2c_submit(drv, stwlc38_ops_vout_disable); - drv->state = STWLC38_STATE_VOUT_DISABLE; + if (drv->state == STWLC38_STATE_POWER_DOWN) { + if (drv->report_readout_requested) { + // Check if the i2c interface is ready + stwlc38_i2c_submit(drv, stwlc38_ops_report_readout); + drv->state = STWLC38_STATE_REPORT_READOUT; + } + } else if (drv->state == STWLC38_STATE_IDLE) { + if (drv->vout_enabled != drv->vout_enabled_requested) { + // Enable/Disable the main LDO output + if (drv->vout_enabled_requested) { + stwlc38_i2c_submit(drv, stwlc38_ops_vout_enable); + drv->state = STWLC38_STATE_VOUT_ENABLE; + } else { + stwlc38_i2c_submit(drv, stwlc38_ops_vout_disable); + drv->state = STWLC38_STATE_VOUT_DISABLE; + } + } else if (drv->report_readout_requested) { + // Read status registers + stwlc38_i2c_submit(drv, stwlc38_ops_report_readout); + drv->state = STWLC38_STATE_REPORT_READOUT; + } + } + + // After processing all requests, check if we need to + // suspend the driver + if (drv->state == STWLC38_STATE_IDLE || + drv->state == STWLC38_STATE_POWER_DOWN) { + // No more requests to process + if (drv->suspending) { + drv->suspending = false; + drv->suspended = true; } - } else if (drv->report_readout_requested) { - // Read status registers - stwlc38_i2c_submit(drv, stwlc38_ops_report_readout); - drv->state = STWLC38_STATE_REPORT_READOUT; } } diff --git a/core/embed/sys/power_manager/stwlc38/stwlc38.h b/core/embed/sys/power_manager/stwlc38/stwlc38.h index 8d04e555d2..77dde9a77b 100644 --- a/core/embed/sys/power_manager/stwlc38/stwlc38.h +++ b/core/embed/sys/power_manager/stwlc38/stwlc38.h @@ -90,6 +90,19 @@ bool stwlc38_init(void); // Deinitializes STWLC38 driver void stwlc38_deinit(void); +// Suspends driver activity so the CPU can enter low-power mode. +// +// Suspending may take some time if the driver is currently +// performing an operation. Caller may check the status by +// stwlc38_is_suspended(). +bool stwlc38_suspend(void); + +// Resumes the driver operation after it has been suspended. +bool stwlc38_resume(void); + +// Checks whether the driver is suspended. +bool stwlc38_is_suspended(void); + // Enables or disables the STWLC38. This can be used to enable/disable // wireless charging functionality. // diff --git a/core/embed/sys/power_manager/stwlc38/stwlc38_internal.h b/core/embed/sys/power_manager/stwlc38/stwlc38_internal.h index b5f589fb02..00fbc1d027 100644 --- a/core/embed/sys/power_manager/stwlc38/stwlc38_internal.h +++ b/core/embed/sys/power_manager/stwlc38/stwlc38_internal.h @@ -72,6 +72,15 @@ typedef struct { // Timer used for periodic report readout systimer_t *timer; + // 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. + // In suspended mode, the driver does not start any new operations. + bool suspended; + // Main LDO output current state bool vout_enabled; // Main LDO output requested state diff --git a/core/embed/sys/suspend/stm32u5/suspend.c b/core/embed/sys/suspend/stm32u5/suspend.c index 3add9c4b92..76e4b33b39 100644 --- a/core/embed/sys/suspend/stm32u5/suspend.c +++ b/core/embed/sys/suspend/stm32u5/suspend.c @@ -25,6 +25,9 @@ #include #include +#include <../../power_manager/stwlc38/stwlc38.h> +#include + static wakeup_flags_t g_wakeup_flags = 0; static void background_tasks_suspend(void); @@ -106,10 +109,18 @@ wakeup_flags_t system_suspend(void) { return wakeup_flags; } -static void background_tasks_suspend(void) {} +static void background_tasks_suspend(void) { + pmic_suspend(); + stwlc38_suspend(); +} -static bool background_tasks_suspended(void) { return true; } +static bool background_tasks_suspended(void) { + return pmic_is_suspended() && stwlc38_is_suspended(); +} -static void background_tasks_resume(void) {} +static void background_tasks_resume(void) { + stwlc38_resume(); + pmic_resume(); +} #endif // defined(KERNEL_MODE) && !defined(SECMON)