zephyr/subsys/power/power.c

263 lines
6.1 KiB
C

/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <init.h>
#include <string.h>
#include <power/power.h>
#include <power/power_state.h>
#include "policy/pm_policy.h"
#define PM_STATES_LEN (1 + PM_STATE_SOFT_OFF - PM_STATE_ACTIVE)
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(power);
static int post_ops_done = 1;
static struct pm_state_info z_power_state;
static sys_slist_t pm_notifiers = SYS_SLIST_STATIC_INIT(&pm_notifiers);
static struct k_spinlock pm_notifier_lock;
#ifdef CONFIG_PM_DEBUG
struct pm_debug_info {
uint32_t count;
uint32_t last_res;
uint32_t total_res;
};
static struct pm_debug_info pm_dbg_info[PM_STATES_LEN];
static uint32_t timer_start, timer_end;
static inline void pm_debug_start_timer(void)
{
timer_start = k_cycle_get_32();
}
static inline void pm_debug_stop_timer(void)
{
timer_end = k_cycle_get_32();
}
static void pm_log_debug_info(enum pm_state state)
{
uint32_t res = timer_end - timer_start;
pm_dbg_info[state].count++;
pm_dbg_info[state].last_res = res;
pm_dbg_info[state].total_res += res;
}
void pm_dump_debug_info(void)
{
for (int i = 0; i < PM_STATES_LEN; i++) {
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
static inline void pm_debug_start_timer(void) { }
static inline void pm_debug_stop_timer(void) { }
static void pm_log_debug_info(enum pm_state state) { }
void pm_dump_debug_info(void) { }
#endif
__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.
*/
}
__weak void pm_power_state_set(struct pm_state_info info)
{
/*
* This function is supposed to be overridden to do SoC or
* architecture specific post ops after sleep state exits.
*/
}
/*
* 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;
void (*callback)(enum pm_state state);
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) {
callback(z_power_state.state);
}
}
k_spin_unlock(&pm_notifier_lock, pm_notifier_key);
}
void pm_power_state_force(struct pm_state_info info)
{
__ASSERT(info.state < PM_STATES_LEN,
"Invalid power state %d!", info.state);
if (info.state == PM_STATE_ACTIVE) {
return;
}
(void)arch_irq_lock();
z_power_state = info;
post_ops_done = 0;
pm_state_notify(true);
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);
}
}
#if CONFIG_PM_DEVICE
static enum pm_state _handle_device_abort(struct pm_state_info info)
{
LOG_DBG("Some devices didn't enter suspend state!");
pm_resume_devices();
z_power_state.state = PM_STATE_ACTIVE;
return PM_STATE_ACTIVE;
}
#endif
enum pm_state pm_system_suspend(int32_t ticks)
{
z_power_state = pm_policy_next_state(ticks);
if (z_power_state.state == PM_STATE_ACTIVE) {
LOG_DBG("No PM operations done.");
return z_power_state.state;
}
post_ops_done = 0;
#if CONFIG_PM_DEVICE
bool should_resume_devices = true;
switch (z_power_state.state) {
case PM_STATE_RUNTIME_IDLE:
__fallthrough;
case PM_STATE_SUSPEND_TO_IDLE:
__fallthrough;
case PM_STATE_STANDBY:
/* low power peripherals. */
if (pm_low_power_devices()) {
return _handle_device_abort(z_power_state);
} break;
case PM_STATE_SUSPEND_TO_RAM:
__fallthrough;
case PM_STATE_SUSPEND_TO_DISK:
if (pm_suspend_devices()) {
return _handle_device_abort(z_power_state);
}
break;
default:
should_resume_devices = false;
break;
}
#endif
pm_debug_start_timer();
/* Enter power state */
pm_state_notify(true);
pm_power_state_set(z_power_state);
pm_debug_stop_timer();
/* Wake up sequence starts here */
#if CONFIG_PM_DEVICE
if (should_resume_devices) {
/* Turn on peripherals and restore device states as necessary */
pm_resume_devices();
}
#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);
}
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);
sys_slist_append(&pm_notifiers, &notifier->_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;
}
#if CONFIG_PM_DEVICE
static int pm_init(const struct device *dev)
{
ARG_UNUSED(dev);
pm_create_device_list();
return 0;
}
SYS_INIT(pm_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif /* CONFIG_PM_DEVICE */