2018-08-16 10:39:40 +05:30
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 Intel Corporation.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <zephyr.h>
|
|
|
|
#include <kernel.h>
|
|
|
|
#include <init.h>
|
|
|
|
#include <string.h>
|
2019-06-25 12:26:13 -04:00
|
|
|
#include <power/power.h>
|
2020-12-07 21:51:46 -08:00
|
|
|
#include <power/power_state.h>
|
2018-09-17 13:16:47 +05:30
|
|
|
#include "policy/pm_policy.h"
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
#define PM_STATES_LEN (PM_STATE_SOFT_OFF - PM_STATE_ACTIVE)
|
2020-09-01 18:31:40 -04:00
|
|
|
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL
|
2018-08-16 10:39:40 +05:30
|
|
|
#include <logging/log.h>
|
2018-08-09 08:48:18 -05:00
|
|
|
LOG_MODULE_REGISTER(power);
|
2018-08-16 10:39:40 +05:30
|
|
|
|
|
|
|
static int post_ops_done = 1;
|
2020-12-07 21:51:46 -08:00
|
|
|
static bool z_forced_power_state;
|
|
|
|
static enum pm_state z_power_state;
|
2020-10-14 21:59:51 -07:00
|
|
|
static sys_slist_t pm_notifiers = SYS_SLIST_STATIC_INIT(&pm_notifiers);
|
|
|
|
static struct k_spinlock pm_notifier_lock;
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2020-09-01 18:31:40 -04:00
|
|
|
#ifdef CONFIG_PM_DEBUG
|
2018-10-24 13:54:20 +05:30
|
|
|
|
|
|
|
struct pm_debug_info {
|
2020-05-27 11:26:57 -05:00
|
|
|
uint32_t count;
|
|
|
|
uint32_t last_res;
|
|
|
|
uint32_t total_res;
|
2018-10-24 13:54:20 +05:30
|
|
|
};
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
static struct pm_debug_info pm_dbg_info[PM_STATES_LEN];
|
2020-05-27 11:26:57 -05:00
|
|
|
static uint32_t timer_start, timer_end;
|
2018-10-24 13:54:20 +05:30
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
static inline void pm_debug_start_timer(void)
|
2018-10-24 13:54:20 +05:30
|
|
|
{
|
|
|
|
timer_start = k_cycle_get_32();
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
static inline void pm_debug_stop_timer(void)
|
2018-10-24 13:54:20 +05:30
|
|
|
{
|
|
|
|
timer_end = k_cycle_get_32();
|
|
|
|
}
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
static void pm_log_debug_info(enum pm_state state)
|
2018-10-24 13:54:20 +05:30
|
|
|
{
|
2020-05-27 11:26:57 -05:00
|
|
|
uint32_t res = timer_end - timer_start;
|
2018-10-24 13:54:20 +05:30
|
|
|
|
|
|
|
pm_dbg_info[state].count++;
|
|
|
|
pm_dbg_info[state].last_res = res;
|
|
|
|
pm_dbg_info[state].total_res += res;
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
void pm_dump_debug_info(void)
|
2018-10-24 13:54:20 +05:30
|
|
|
{
|
2020-12-07 21:51:46 -08:00
|
|
|
for (int i = 0; i < PM_STATES_LEN; i++) {
|
2018-10-24 13:54:20 +05:30
|
|
|
LOG_DBG("PM:state = %d, count = %d last_res = %d, "
|
|
|
|
"total_res = %d\n", i, pm_dbg_info[i].count,
|
|
|
|
pm_dbg_info[i].last_res, pm_dbg_info[i].total_res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
2020-09-01 21:46:30 -04:00
|
|
|
static inline void pm_debug_start_timer(void) { }
|
|
|
|
static inline void pm_debug_stop_timer(void) { }
|
2020-12-07 21:51:46 -08:00
|
|
|
static void pm_log_debug_info(enum pm_state state) { }
|
2020-09-01 21:46:30 -04:00
|
|
|
void pm_dump_debug_info(void) { }
|
2018-10-24 13:54:20 +05:30
|
|
|
#endif
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
void pm_power_state_force(enum pm_state state)
|
2019-01-17 13:25:31 +01:00
|
|
|
{
|
2020-12-07 21:51:46 -08:00
|
|
|
__ASSERT(state < PM_STATES_LEN,
|
2019-01-17 13:25:31 +01:00
|
|
|
"Invalid power state %d!", state);
|
|
|
|
|
2020-09-01 18:31:40 -04:00
|
|
|
#ifdef CONFIG_PM_DIRECT_FORCE_MODE
|
2020-01-22 03:36:21 -05:00
|
|
|
(void)arch_irq_lock();
|
2020-12-07 21:51:46 -08:00
|
|
|
z_forced_power_state = true;
|
|
|
|
z_power_state = state;
|
2020-09-04 10:52:31 -04:00
|
|
|
pm_system_suspend(K_TICKS_FOREVER);
|
2020-01-22 03:36:21 -05:00
|
|
|
#else
|
2020-12-07 21:51:46 -08:00
|
|
|
z_power_state = state;
|
|
|
|
z_forced_power_state = true;
|
2020-01-22 03:36:21 -05:00
|
|
|
#endif
|
2019-01-17 13:25:31 +01:00
|
|
|
}
|
|
|
|
|
2020-10-14 21:59:51 -07:00
|
|
|
/*
|
|
|
|
* Function called to notify when the system is entering / exiting a
|
|
|
|
* power state
|
|
|
|
*/
|
|
|
|
static inline void pm_state_notify(bool entering_state)
|
|
|
|
{
|
|
|
|
struct pm_notifier *notifier;
|
|
|
|
k_spinlock_key_t pm_notifier_key;
|
2020-12-07 21:51:46 -08:00
|
|
|
void (*callback)(enum pm_state state);
|
2020-10-14 21:59:51 -07:00
|
|
|
|
|
|
|
pm_notifier_key = k_spin_lock(&pm_notifier_lock);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&pm_notifiers, notifier, _node) {
|
|
|
|
if (entering_state) {
|
|
|
|
callback = notifier->state_entry;
|
|
|
|
} else {
|
|
|
|
callback = notifier->state_exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (callback) {
|
2020-12-07 21:51:46 -08:00
|
|
|
callback(z_power_state);
|
2020-10-14 21:59:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
k_spin_unlock(&pm_notifier_lock, pm_notifier_key);
|
|
|
|
}
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
static enum pm_state _handle_device_abort(enum pm_state state)
|
2020-09-07 12:23:30 -04:00
|
|
|
{
|
|
|
|
LOG_DBG("Some devices didn't enter suspend state!");
|
|
|
|
pm_resume_devices();
|
2020-10-14 21:59:51 -07:00
|
|
|
pm_state_notify(false);
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
z_power_state = PM_STATE_ACTIVE;
|
|
|
|
return z_power_state;
|
2020-09-07 12:23:30 -04:00
|
|
|
}
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
static enum pm_state pm_policy_mgr(int32_t ticks)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
2019-01-16 14:49:16 +01:00
|
|
|
bool deep_sleep;
|
2020-09-01 18:31:40 -04:00
|
|
|
#if CONFIG_PM_DEVICE
|
2020-01-04 13:18:08 +08:00
|
|
|
bool low_power = false;
|
|
|
|
#endif
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
if (z_forced_power_state == false) {
|
|
|
|
z_power_state = pm_policy_next_state(ticks);
|
|
|
|
}
|
2019-01-17 13:25:31 +01:00
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
if (z_power_state == PM_STATE_ACTIVE) {
|
|
|
|
LOG_DBG("No PM operations done.");
|
|
|
|
return z_power_state;
|
2019-01-16 14:49:16 +01:00
|
|
|
}
|
2018-10-24 13:54:20 +05:30
|
|
|
|
2021-01-07 09:29:17 -08:00
|
|
|
deep_sleep = pm_is_deep_sleep_state(z_power_state);
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2019-02-04 15:05:23 +01:00
|
|
|
post_ops_done = 0;
|
2020-10-14 21:59:51 -07:00
|
|
|
pm_state_notify(true);
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2019-01-16 14:49:16 +01:00
|
|
|
if (deep_sleep) {
|
|
|
|
/* Suspend peripherals. */
|
2020-09-07 12:23:30 -04:00
|
|
|
if (IS_ENABLED(CONFIG_PM_DEVICE) && pm_suspend_devices()) {
|
2020-12-07 21:51:46 -08:00
|
|
|
return _handle_device_abort(z_power_state);
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
2019-01-16 14:49:16 +01:00
|
|
|
/*
|
|
|
|
* Disable idle exit notification as it is not needed
|
|
|
|
* in deep sleep mode.
|
|
|
|
*/
|
2020-09-06 08:46:59 -04:00
|
|
|
pm_idle_exit_notification_disable();
|
2020-09-01 18:31:40 -04:00
|
|
|
#if CONFIG_PM_DEVICE
|
2020-01-04 13:18:08 +08:00
|
|
|
} else {
|
2020-12-07 21:51:46 -08:00
|
|
|
if (pm_policy_low_power_devices(z_power_state)) {
|
2020-01-04 13:18:08 +08:00
|
|
|
/* low power peripherals. */
|
2020-09-01 21:46:30 -04:00
|
|
|
if (pm_low_power_devices()) {
|
2020-12-07 21:51:46 -08:00
|
|
|
return _handle_device_abort(z_power_state);
|
2020-01-04 13:18:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
low_power = true;
|
|
|
|
}
|
|
|
|
#endif
|
2019-01-16 14:49:16 +01:00
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
pm_debug_start_timer();
|
2020-09-07 12:23:30 -04:00
|
|
|
/* Enter power state */
|
2020-12-07 21:51:46 -08:00
|
|
|
pm_power_state_set(z_power_state);
|
2020-09-01 21:46:30 -04:00
|
|
|
pm_debug_stop_timer();
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2020-09-07 12:23:30 -04:00
|
|
|
/* Wake up sequence starts here */
|
2020-09-01 18:31:40 -04:00
|
|
|
#if CONFIG_PM_DEVICE
|
2020-01-04 13:18:08 +08:00
|
|
|
if (deep_sleep || low_power) {
|
2018-08-16 10:39:40 +05:30
|
|
|
/* Turn on peripherals and restore device states as necessary */
|
2020-09-01 21:46:30 -04:00
|
|
|
pm_resume_devices();
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
2019-01-29 12:14:17 +01:00
|
|
|
#endif
|
2020-12-07 21:51:46 -08:00
|
|
|
pm_log_debug_info(z_power_state);
|
2018-10-24 13:54:20 +05:30
|
|
|
|
2019-01-16 14:49:16 +01:00
|
|
|
if (!post_ops_done) {
|
|
|
|
post_ops_done = 1;
|
2020-12-07 21:51:46 -08:00
|
|
|
/* clear z_forced_power_state */
|
|
|
|
z_forced_power_state = false;
|
2020-10-14 21:59:51 -07:00
|
|
|
pm_state_notify(false);
|
2020-12-07 21:51:46 -08:00
|
|
|
pm_power_state_exit_post_ops(z_power_state);
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
return z_power_state;
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
|
|
|
|
2020-09-07 12:23:30 -04:00
|
|
|
|
2020-12-07 21:51:46 -08:00
|
|
|
enum pm_state pm_system_suspend(int32_t ticks)
|
2020-09-07 12:23:30 -04:00
|
|
|
{
|
|
|
|
return pm_policy_mgr(ticks);
|
|
|
|
}
|
|
|
|
|
2020-09-04 10:52:31 -04:00
|
|
|
void pm_system_resume(void)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2020-09-06 08:46:59 -04:00
|
|
|
* Call pm_idle_exit_notification_disable() if this
|
2018-08-16 10:39:40 +05:30
|
|
|
* notification is not required.
|
|
|
|
*/
|
|
|
|
if (!post_ops_done) {
|
|
|
|
post_ops_done = 1;
|
2020-10-14 21:59:51 -07:00
|
|
|
pm_state_notify(false);
|
2020-12-07 21:51:46 -08:00
|
|
|
pm_power_state_exit_post_ops(z_power_state);
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-14 21:59:51 -07:00
|
|
|
void pm_notifier_register(struct pm_notifier *notifier)
|
|
|
|
{
|
|
|
|
k_spinlock_key_t pm_notifier_key = k_spin_lock(&pm_notifier_lock);
|
|
|
|
|
|
|
|
sys_slist_append(&pm_notifiers, ¬ifier->_node);
|
|
|
|
k_spin_unlock(&pm_notifier_lock, pm_notifier_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
int pm_notifier_unregister(struct pm_notifier *notifier)
|
|
|
|
{
|
|
|
|
int ret = -EINVAL;
|
|
|
|
k_spinlock_key_t pm_notifier_key;
|
|
|
|
|
|
|
|
pm_notifier_key = k_spin_lock(&pm_notifier_lock);
|
|
|
|
if (sys_slist_find_and_remove(&pm_notifiers, &(notifier->_node))) {
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
k_spin_unlock(&pm_notifier_lock, pm_notifier_key);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-09-01 18:31:40 -04:00
|
|
|
#if CONFIG_PM_DEVICE
|
2020-09-01 21:46:30 -04:00
|
|
|
static int pm_init(const struct device *dev)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
pm_create_device_list();
|
2018-08-16 10:39:40 +05:30
|
|
|
return 0;
|
|
|
|
}
|
2020-09-01 21:46:30 -04:00
|
|
|
SYS_INIT(pm_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
2020-09-01 18:31:40 -04:00
|
|
|
#endif /* CONFIG_PM_DEVICE */
|