1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-26 00:18:15 +00:00

feat(core): manage background ops during suspend

[no changelog]
This commit is contained in:
cepetr 2025-06-20 14:07:04 +02:00 committed by cepetr
parent 995caca9c7
commit bec455c9e5
7 changed files with 206 additions and 32 deletions

View File

@ -59,6 +59,19 @@ bool pmic_init(void);
// Deinitializes PMIC driver // Deinitializes PMIC driver
void pmic_deinit(void); 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 // Gets the cause of the last restart
uint8_t pmic_restart_cause(void); uint8_t pmic_restart_cause(void);

View File

@ -107,6 +107,15 @@ typedef struct {
// Current state of the FSM // Current state of the FSM
npm1300_fsm_state_t state; 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) // ADC register (global buffer used for ADC measurements)
npm1300_adc_regs_t adc_regs; npm1300_adc_regs_t adc_regs;
// Charging limit registers (global buffer used for charging limit) // Charging limit registers (global buffer used for charging limit)
@ -418,6 +427,53 @@ void pmic_deinit(void) {
memset(drv, 0, sizeof(npm1300_driver_t)); 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) { bool pmic_enter_shipmode(void) {
npm1300_driver_t* drv = &g_npm1300_driver; 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: case NPM1300_STATE_CLEAR_EVENTS:
drv->clear_events_requested = false; drv->clear_events_requested = false;
drv->state = NPM1300_STATE_IDLE; drv->state = NPM1300_STATE_IDLE;
#ifdef USE_SUSPEND
wakeup_flags_set(WAKEUP_FLAG_POWER);
#endif
break; break;
case NPM1300_STATE_CHARGING_ENABLE: case NPM1300_STATE_CHARGING_ENABLE:
@ -903,10 +962,6 @@ void NPM1300_EXTI_INTERRUPT_HANDLER(void) {
return; return;
} }
#ifdef USE_SUSPEND
wakeup_flags_set(WAKEUP_FLAG_POWER);
#endif
drv->clear_events_requested = true; drv->clear_events_requested = true;
npm1300_fsm_continue(drv); 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. // This function is called in the irq context or when interrupts are disabled.
static void npm1300_fsm_continue(npm1300_driver_t* drv) { static void npm1300_fsm_continue(npm1300_driver_t* drv) {
if (drv->state != NPM1300_STATE_IDLE) { if (drv->state != NPM1300_STATE_IDLE || drv->suspended) {
return; return;
} }
@ -971,6 +1026,16 @@ static void npm1300_fsm_continue(npm1300_driver_t* drv) {
drv->shipmode_requested = false; drv->shipmode_requested = false;
drv->state = NPM1300_STATE_ENTER_SHIPMODE; 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 #endif // KERNEL_MODE

View File

@ -215,15 +215,19 @@ pm_status_t pm_get_state(pm_state_t* state) {
// - The callback can schedule the next wake-up by calling // - The callback can schedule the next wake-up by calling
// rtc_wakeup_timer_start(). // rtc_wakeup_timer_start().
// - If the callback return with wakeup_flags set, system_suspend() returns. // - If the callback return with wakeup_flags set, system_suspend() returns.
#ifdef USE_RTC
void pm_rtc_wakeup_callback(void* context) { void pm_rtc_wakeup_callback(void* context) {
// TODO: update fuel gauge state // TODO: update fuel gauge state
// TODO: decide whether to reschedule the next wake-up or wake up the coreapp
if (true) { 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 { } else {
// Wake up the coreapp
wakeup_flags_set(WAKEUP_FLAG_RTC); wakeup_flags_set(WAKEUP_FLAG_RTC);
} }
} }
#endif
pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) { pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) {
pm_driver_t* drv = &g_pm; 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(); wakeup_flags_t wakeup_flags = system_suspend();
#ifdef USE_RTC
rtc_wakeup_timer_stop(); rtc_wakeup_timer_stop();
#endif
// TODO: Handle wake-up flags // TODO: Handle wake-up flags
// UNUSED(wakeup_flags); // UNUSED(wakeup_flags);

View File

@ -156,6 +156,53 @@ cleanup:
return false; 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) { bool stwlc38_enable(bool enable) {
stwlc38_driver_t *drv = &g_stwlc38_driver; stwlc38_driver_t *drv = &g_stwlc38_driver;
@ -329,8 +376,6 @@ void STWLC38_EXTI_INTERRUPT_HANDLER(void) {
} }
if (drv->state == STWLC38_STATE_POWER_DOWN) { 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; drv->report_readout_requested = true;
stwlc38_fsm_continue(drv); stwlc38_fsm_continue(drv);
} }
@ -339,30 +384,42 @@ void STWLC38_EXTI_INTERRUPT_HANDLER(void) {
static void stwlc38_fsm_continue(stwlc38_driver_t *drv) { static void stwlc38_fsm_continue(stwlc38_driver_t *drv) {
// The order of the following conditions defines the priority // The order of the following conditions defines the priority
if (drv->state == STWLC38_STATE_POWER_DOWN && drv->report_readout_requested) { if (drv->suspended) {
// Check if the i2c interface is ready
stwlc38_i2c_submit(drv, stwlc38_ops_report_readout);
drv->state = STWLC38_STATE_REPORT_READOUT;
return; return;
} }
if (drv->state != STWLC38_STATE_IDLE) { if (drv->state == STWLC38_STATE_POWER_DOWN) {
return; if (drv->report_readout_requested) {
} // Check if the i2c interface is ready
stwlc38_i2c_submit(drv, stwlc38_ops_report_readout);
if (drv->vout_enabled != drv->vout_enabled_requested) { drv->state = STWLC38_STATE_REPORT_READOUT;
// Enable/Disable the main LDO output }
if (drv->vout_enabled_requested) { } else if (drv->state == STWLC38_STATE_IDLE) {
stwlc38_i2c_submit(drv, stwlc38_ops_vout_enable); if (drv->vout_enabled != drv->vout_enabled_requested) {
drv->state = STWLC38_STATE_VOUT_ENABLE; // Enable/Disable the main LDO output
} else { if (drv->vout_enabled_requested) {
stwlc38_i2c_submit(drv, stwlc38_ops_vout_disable); stwlc38_i2c_submit(drv, stwlc38_ops_vout_enable);
drv->state = STWLC38_STATE_VOUT_DISABLE; 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;
} }
} }

View File

@ -90,6 +90,19 @@ bool stwlc38_init(void);
// Deinitializes STWLC38 driver // Deinitializes STWLC38 driver
void stwlc38_deinit(void); 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 // Enables or disables the STWLC38. This can be used to enable/disable
// wireless charging functionality. // wireless charging functionality.
// //

View File

@ -72,6 +72,15 @@ typedef struct {
// Timer used for periodic report readout // Timer used for periodic report readout
systimer_t *timer; 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 // Main LDO output current state
bool vout_enabled; bool vout_enabled;
// Main LDO output requested state // Main LDO output requested state

View File

@ -25,6 +25,9 @@
#include <sys/suspend.h> #include <sys/suspend.h>
#include <sys/suspend_io.h> #include <sys/suspend_io.h>
#include <../../power_manager/stwlc38/stwlc38.h>
#include <sys/pmic.h>
static wakeup_flags_t g_wakeup_flags = 0; static wakeup_flags_t g_wakeup_flags = 0;
static void background_tasks_suspend(void); static void background_tasks_suspend(void);
@ -106,10 +109,18 @@ wakeup_flags_t system_suspend(void) {
return wakeup_flags; 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) #endif // defined(KERNEL_MODE) && !defined(SECMON)