diff --git a/doc/reference/power_management/index.rst b/doc/reference/power_management/index.rst index 828198cf677527..2d2dfbb5fa45d2 100644 --- a/doc/reference/power_management/index.rst +++ b/doc/reference/power_management/index.rst @@ -93,6 +93,27 @@ have higher wake latencies. Following is a thorough list of available states: .. doxygenenumvalue:: PM_STATE_SOFT_OFF :project: Zephyr +.. _pm_constraints: + +Power States Constraint +======================= + +The power management subsystem allows different Zephyr components and +applications to set constraints on various power states preventing the +system to go these states. This can be used by devices when executing +tasks in background to avoid the system to go to state where it would +lose context. Constraints can be set, released and checked using the +follow APIs: + +.. doxygenfunction:: pm_constraint_set + :project: Zephyr + +.. doxygenfunction:: pm_constraint_release + :project: Zephyr + +.. doxygenfunction:: pm_constraint_get + :project: Zephyr + Power Management Policies ========================= @@ -102,12 +123,20 @@ The power management subsystem supports the following power management policies: * Application * Dummy +The policy manager is responsible to inform the power subsystem which +power state the system should go based on states available in the +platform and possible runtime :ref:`constraints` + +Information about states can be get from device tree, see +:zephyr_file:`dts/bindings/power/state.yaml`. + Residency --------- The power management system enters the power state which offers the highest -power savings, and with a minimum residency value (defined by the respective -Kconfig option) less than or equal to the scheduled system idle time duration. +power savings, and with a minimum residency value (in device tree, see +:zephyr_file:`dts/bindings/power/state.yaml`) less than or equal to +the scheduled system idle time duration. Application ----------- @@ -119,6 +148,10 @@ the following function. struct pm_state_info pm_policy_next_state(int32_t ticks); +In this policy the application is free to decide which power state the +system should go based on the remaining time for the next scheduled +timeout. + Dummy ----- @@ -147,7 +180,7 @@ in power saving mode. This method allows saving power even when the CPU is active. The components that use the devices need to be power aware and should be able to make decisions related to managing device power. In this method, the SOC interface can enter CPU or SOC power states quickly when -:code:`sys_suspend()` gets called. This is because it does not need to +:code:`pm_system_suspend()` gets called. This is because it does not need to spend time doing device power management if the devices are already put in the appropriate power state by the application or component managing the devices. @@ -156,7 +189,7 @@ Central method ============== In this method device power management is mostly done inside -:code:`sys_suspend()` along with entering a CPU or SOC power state. +:code:`pm_system_suspend()` along with entering a CPU or SOC power state. If a decision to enter deep sleep is made, the implementation would enter it only after checking if the devices are not in the middle of a hardware @@ -299,21 +332,21 @@ off, then such transactions would be left in an inconsistent state. This infrastructure guards such transactions by indicating to the SOC interface that the device is in the middle of a hardware transaction. -When the :code:`sys_suspend()` is called, the SOC interface checks if any device +When the :code:`pm_system_suspend()` is called, the SOC interface checks if any device is busy. The SOC interface can then decide to execute a power management scheme other than deep sleep or to defer power management operations until the next call of -:code:`sys_suspend()`. +:code:`pm_system_suspend()`. An alternative to using the busy status mechanism is to use the `distributed method`_ of device power management. In such a method where the device power management is handled in a distributed manner rather than centrally in -:code:`sys_suspend()`, the decision to enter deep sleep can be made based +:code:`pm_system_suspend()`, the decision to enter deep sleep can be made based on whether all devices are already turned off. This feature can be also used to emulate a hardware feature found in some SOCs that causes the system to automatically enter deep sleep when all devices are idle. In such an usage, the busy status can be set by default and cleared as each -device becomes idle. When :code:`sys_suspend()` is called, deep sleep can +device becomes idle. When :code:`pm_system_suspend()` is called, deep sleep can be entered if no device is found to be busy. Here are the APIs used to set, clear, and check the busy status of devices. diff --git a/drivers/i2c/i2c_dw.c b/drivers/i2c/i2c_dw.c index e1e2bf73898a5c..232755f86855c6 100644 --- a/drivers/i2c/i2c_dw.c +++ b/drivers/i2c/i2c_dw.c @@ -423,7 +423,7 @@ static int i2c_dw_transfer(const struct device *dev, /* * While waiting at device_sync_sem, kernel can switch to idle - * task which in turn can call sys_suspend() hook of Power + * task which in turn can call pm_system_suspend() hook of Power * Management App (PMA). * device_busy_set() call here, would indicate to PMA that it should not * execute PM policies that would turn off this ip block, causing an diff --git a/include/power/power.h b/include/power/power.h index e2eece1a4e8fc2..f206e8b2a15c96 100644 --- a/include/power/power.h +++ b/include/power/power.h @@ -25,8 +25,6 @@ extern "C" { #ifdef CONFIG_PM -extern unsigned char pm_idle_exit_notify; - /** * @brief System Power Management API * @@ -48,6 +46,10 @@ extern unsigned char pm_idle_exit_notify; * * @note These callbacks can be called from the ISR of the event * that caused the kernel exit from idling. + * + * @note It is not allowed to call @ref pm_notifier_unregister or + * @ref pm_notifier_register from these callbacks because they are called + * with the spin locked in those functions. */ struct pm_notifier { sys_snode_t _node; @@ -76,17 +78,6 @@ struct pm_notifier { */ void pm_power_state_force(struct pm_state_info info); -/** - * @brief Put processor into a power state. - * - * This function implements the SoC specific details necessary - * to put the processor into available power states. - * - * @param info Power state which should be used in the ongoing - * suspend operation. - */ -void pm_power_state_set(struct pm_state_info info); - #ifdef CONFIG_PM_DEBUG /** * @brief Dump Low Power states related debug info @@ -97,6 +88,41 @@ void pm_dump_debug_info(void); #endif /* CONFIG_PM_DEBUG */ +/** + * @brief Register a power management notifier + * + * Register the given notifier from the power management notification + * list. + * + * @param notifier pm_notifier object to be registered. + */ +void pm_notifier_register(struct pm_notifier *notifier); + +/** + * @brief Unregister a power management notifier + * + * Remove the given notifier from the power management notification + * list. After that this object callbacks will not be called. + * + * @param notifier pm_notifier object to be unregistered. + * + * @return 0 if the notifier was successfully removed, a negative value + * otherwise. + */ +int pm_notifier_unregister(struct pm_notifier *notifier); + +/** + * @} + */ + +/** + * @brief System Power Management Constraint API + * + * @defgroup system_power_management_constraint_api Constraint API + * @ingroup power_management_api + * @{ + */ + /** * @brief Set a constraint for a power state * @@ -135,7 +161,6 @@ void pm_constraint_release(enum pm_state state); */ bool pm_constraint_get(enum pm_state state); - /** * @} */ @@ -148,6 +173,17 @@ bool pm_constraint_get(enum pm_state state); * @{ */ +/** + * @brief Put processor into a power state. + * + * This function implements the SoC specific details necessary + * to put the processor into available power states. + * + * @param info Power state which should be used in the ongoing + * suspend operation. + */ +void pm_power_state_set(struct pm_state_info info); + /** * @brief Do any SoC or architecture specific post ops after sleep state exits. * @@ -158,29 +194,6 @@ bool pm_constraint_get(enum pm_state state); */ void pm_power_state_exit_post_ops(struct pm_state_info info); -/** - * @brief Register a power management notifier - * - * Register the given notifier from the power management notification - * list. - * - * @param notifier pm_notifier object to be registered. - */ -void pm_notifier_register(struct pm_notifier *notifier); - -/** - * @brief Unregister a power management notifier - * - * Remove the given notifier from the power management notification - * list. After that this object callbacks will not be called. - * - * @param notifier pm_notifier object to be unregistered. - * - * @return 0 if the notifier was successfully removed, a negative value - * otherwise. - */ -int pm_notifier_unregister(struct pm_notifier *notifier); - /** * @} */ diff --git a/subsys/power/power.c b/subsys/power/power.c index 232e3c8644077c..9e66641f40bc01 100644 --- a/subsys/power/power.c +++ b/subsys/power/power.c @@ -72,7 +72,11 @@ __weak void pm_power_state_exit_post_ops(struct pm_state_info info) /* * This function is supposed to be overridden to do SoC or * architecture specific post ops after sleep state exits. + * + * The kernel expects that irqs are unlocked after this. */ + + irq_unlock(0); } __weak void pm_power_state_set(struct pm_state_info info) @@ -108,6 +112,31 @@ static inline void pm_state_notify(bool entering_state) k_spin_unlock(&pm_notifier_lock, pm_notifier_key); } +void pm_system_resume(void) +{ + /* + * This notification is called from the ISR of the event + * that caused exit from kernel idling after PM operations. + * + * Some CPU low power states require enabling of interrupts + * atomically when entering those states. The wake up from + * such a state first executes code in the ISR of the interrupt + * that caused the wake. This hook will be called from the ISR. + * For such CPU LPS states, do post operations and restores here. + * The kernel scheduler will get control after the ISR finishes + * and it may schedule another thread. + * + * Call pm_idle_exit_notification_disable() if this + * notification is not required. + */ + if (!post_ops_done) { + post_ops_done = 1; + pm_power_state_exit_post_ops(z_power_state); + pm_state_notify(false); + k_sched_unlock(); + } +} + void pm_power_state_force(struct pm_state_info info) { __ASSERT(info.state < PM_STATES_LEN, @@ -122,16 +151,13 @@ void pm_power_state_force(struct pm_state_info info) post_ops_done = 0; pm_state_notify(true); + k_sched_lock(); pm_debug_start_timer(); /* Enter power state */ pm_power_state_set(z_power_state); pm_debug_stop_timer(); - if (!post_ops_done) { - post_ops_done = 1; - pm_state_notify(false); - pm_power_state_exit_post_ops(z_power_state); - } + pm_system_resume(); } #if CONFIG_PM_DEVICE @@ -180,6 +206,16 @@ enum pm_state pm_system_suspend(int32_t ticks) break; } #endif + /* + * This function runs with interruptions locked but it is + * expected the SoC to unlock them in + * pm_power_state_exit_post_ops() when returning to active + * state. We don't want to be scheduled out yet, first we need + * to send a notification about leaving the idle state. So, + * we lock the scheduler here and unlock just after we have + * sent the notification in pm_system_resume(). + */ + k_sched_lock(); pm_debug_start_timer(); /* Enter power state */ pm_state_notify(true); @@ -194,40 +230,11 @@ enum pm_state pm_system_suspend(int32_t ticks) } #endif pm_log_debug_info(z_power_state.state); - - if (!post_ops_done) { - post_ops_done = 1; - pm_state_notify(false); - pm_power_state_exit_post_ops(z_power_state); - } + pm_system_resume(); return z_power_state.state; } -void pm_system_resume(void) -{ - /* - * This notification is called from the ISR of the event - * that caused exit from kernel idling after PM operations. - * - * Some CPU low power states require enabling of interrupts - * atomically when entering those states. The wake up from - * such a state first executes code in the ISR of the interrupt - * that caused the wake. This hook will be called from the ISR. - * For such CPU LPS states, do post operations and restores here. - * The kernel scheduler will get control after the ISR finishes - * and it may schedule another thread. - * - * Call pm_idle_exit_notification_disable() if this - * notification is not required. - */ - if (!post_ops_done) { - post_ops_done = 1; - pm_state_notify(false); - pm_power_state_exit_post_ops(z_power_state); - } -} - void pm_notifier_register(struct pm_notifier *notifier) { k_spinlock_key_t pm_notifier_key = k_spin_lock(&pm_notifier_lock);