2018-08-16 10:39:40 +05:30
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 Intel Corporation.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
2021-08-19 21:07:15 -07:00
|
|
|
#include <device.h>
|
2018-08-16 10:39:40 +05:30
|
|
|
#include <zephyr.h>
|
|
|
|
#include <kernel.h>
|
2021-04-02 10:49:01 -07:00
|
|
|
#include <timeout_q.h>
|
2018-08-16 10:39:40 +05:30
|
|
|
#include <init.h>
|
|
|
|
#include <string.h>
|
2021-08-19 21:07:15 -07:00
|
|
|
#include <pm/device.h>
|
2021-11-05 19:21:42 -07:00
|
|
|
#include <pm/device_runtime.h>
|
2021-04-29 13:32:28 +02:00
|
|
|
#include <pm/pm.h>
|
|
|
|
#include <pm/state.h>
|
2021-06-11 09:22:33 -07:00
|
|
|
#include <pm/policy.h>
|
2021-05-13 09:49:18 -04:00
|
|
|
#include <tracing/tracing.h>
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-11-22 19:08:35 +01:00
|
|
|
#include "pm_stats.h"
|
|
|
|
|
2018-08-16 10:39:40 +05:30
|
|
|
#include <logging/log.h>
|
2021-10-27 13:39:12 +02:00
|
|
|
LOG_MODULE_REGISTER(pm, CONFIG_PM_LOG_LEVEL);
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-11-19 18:23:03 -08:00
|
|
|
static ATOMIC_DEFINE(z_post_ops_required, CONFIG_MP_NUM_CPUS);
|
2020-10-14 21:59:51 -07:00
|
|
|
static sys_slist_t pm_notifiers = SYS_SLIST_STATIC_INIT(&pm_notifiers);
|
2022-01-10 12:14:22 -08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Properly initialize cpu power states. Do not make assumptions that
|
|
|
|
* ACTIVE_STATE is 0
|
|
|
|
*/
|
|
|
|
#define CPU_PM_STATE_INIT(_, __) \
|
|
|
|
{ .state = PM_STATE_ACTIVE },
|
|
|
|
static struct pm_state_info z_power_states[] = {
|
|
|
|
UTIL_LISTIFY(CONFIG_MP_NUM_CPUS, CPU_PM_STATE_INIT)
|
|
|
|
};
|
|
|
|
|
2021-11-14 11:04:56 -08:00
|
|
|
/* bitmask to check if a power state was forced. */
|
|
|
|
static ATOMIC_DEFINE(z_power_states_forced, CONFIG_MP_NUM_CPUS);
|
2021-11-03 10:36:19 -07:00
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
|
|
static atomic_t z_cpus_active = ATOMIC_INIT(CONFIG_MP_NUM_CPUS);
|
|
|
|
#endif
|
2020-10-14 21:59:51 -07:00
|
|
|
static struct k_spinlock pm_notifier_lock;
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-10-12 08:52:30 -05:00
|
|
|
|
2018-10-24 13:54:20 +05:30
|
|
|
|
2021-08-19 21:07:15 -07:00
|
|
|
#ifdef CONFIG_PM_DEVICE
|
|
|
|
extern const struct device *__pm_device_slots_start[];
|
|
|
|
|
|
|
|
/* Number of devices successfully suspended. */
|
|
|
|
static size_t num_susp;
|
|
|
|
|
|
|
|
static int pm_suspend_devices(void)
|
|
|
|
{
|
|
|
|
const struct device *devs;
|
|
|
|
size_t devc;
|
|
|
|
|
|
|
|
devc = z_device_get_all_static(&devs);
|
|
|
|
|
|
|
|
num_susp = 0;
|
|
|
|
|
|
|
|
for (const struct device *dev = devs + devc - 1; dev >= devs; dev--) {
|
|
|
|
int ret;
|
|
|
|
|
2021-11-05 19:21:42 -07:00
|
|
|
/*
|
|
|
|
* ignore busy devices, wake up source and devices with
|
|
|
|
* runtime PM enabled.
|
|
|
|
*/
|
2021-11-17 21:50:08 -08:00
|
|
|
if (pm_device_is_busy(dev) || pm_device_state_is_locked(dev)
|
|
|
|
|| pm_device_wakeup_is_enabled(dev) ||
|
2021-10-13 12:11:40 +02:00
|
|
|
((dev->pm != NULL) && pm_device_runtime_is_enabled(dev))) {
|
2021-08-19 21:07:15 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-23 09:21:49 -08:00
|
|
|
ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
|
2021-08-19 21:07:15 -07:00
|
|
|
/* ignore devices not supporting or already at the given state */
|
|
|
|
if ((ret == -ENOSYS) || (ret == -ENOTSUP) || (ret == -EALREADY)) {
|
|
|
|
continue;
|
|
|
|
} else if (ret < 0) {
|
|
|
|
LOG_ERR("Device %s did not enter %s state (%d)",
|
|
|
|
dev->name,
|
|
|
|
pm_device_state_str(PM_DEVICE_STATE_SUSPENDED),
|
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
__pm_device_slots_start[num_susp] = dev;
|
|
|
|
num_susp++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pm_resume_devices(void)
|
|
|
|
{
|
2021-11-05 14:19:54 -07:00
|
|
|
for (int i = (num_susp - 1); i >= 0; i--) {
|
2021-11-23 09:21:49 -08:00
|
|
|
pm_device_action_run(__pm_device_slots_start[i],
|
|
|
|
PM_DEVICE_ACTION_RESUME);
|
2021-08-19 21:07:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
num_susp = 0;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_PM_DEVICE */
|
|
|
|
|
2021-05-13 13:02:25 -07:00
|
|
|
static inline void exit_pos_ops(struct pm_state_info info)
|
2021-01-07 13:17:57 -08:00
|
|
|
{
|
2021-05-13 13:02:25 -07:00
|
|
|
extern __weak void
|
|
|
|
pm_power_state_exit_post_ops(struct pm_state_info info);
|
|
|
|
|
|
|
|
if (pm_power_state_exit_post_ops != NULL) {
|
|
|
|
pm_power_state_exit_post_ops(info);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-03-10 15:34:00 -08:00
|
|
|
|
2021-05-13 13:02:25 -07:00
|
|
|
irq_unlock(0);
|
|
|
|
}
|
2021-01-07 13:17:57 -08:00
|
|
|
}
|
|
|
|
|
2021-05-13 13:02:25 -07:00
|
|
|
static inline void pm_state_set(struct pm_state_info info)
|
2021-01-07 13:17:57 -08:00
|
|
|
{
|
2021-05-13 13:02:25 -07:00
|
|
|
extern __weak void
|
|
|
|
pm_power_state_set(struct pm_state_info info);
|
|
|
|
|
|
|
|
if (pm_power_state_set != NULL) {
|
|
|
|
pm_power_state_set(info);
|
|
|
|
}
|
2021-01-07 13:17:57 -08: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) {
|
2021-10-22 10:44:55 -07:00
|
|
|
callback(z_power_states[_current_cpu->id].state);
|
2020-10-14 21:59:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
k_spin_unlock(&pm_notifier_lock, pm_notifier_key);
|
|
|
|
}
|
|
|
|
|
2021-03-11 10:29:22 -08:00
|
|
|
void pm_system_resume(void)
|
|
|
|
{
|
2021-11-19 18:23:03 -08:00
|
|
|
uint8_t id = _current_cpu->id;
|
|
|
|
|
2021-03-11 10:29:22 -08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-11-19 18:23:03 -08:00
|
|
|
if (atomic_test_and_clear_bit(z_post_ops_required, id)) {
|
2021-11-03 22:57:41 -07:00
|
|
|
exit_pos_ops(z_power_states[id]);
|
2021-03-11 15:02:09 -08:00
|
|
|
pm_state_notify(false);
|
2021-11-03 22:57:41 -07:00
|
|
|
z_power_states[id] = (struct pm_state_info){PM_STATE_ACTIVE,
|
|
|
|
0, 0};
|
2021-03-11 10:29:22 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-14 11:04:56 -08:00
|
|
|
bool pm_power_state_force(uint8_t cpu, struct pm_state_info info)
|
2021-02-23 10:31:35 -08:00
|
|
|
{
|
2021-11-14 11:04:56 -08:00
|
|
|
bool ret = false;
|
2021-11-03 23:18:57 -07:00
|
|
|
|
2021-11-22 18:48:01 +01:00
|
|
|
__ASSERT(info.state < PM_STATE_COUNT,
|
2021-02-23 10:31:35 -08:00
|
|
|
"Invalid power state %d!", info.state);
|
|
|
|
|
2021-11-03 23:18:57 -07:00
|
|
|
|
2021-11-14 11:04:56 -08:00
|
|
|
if (!atomic_test_and_set_bit(z_power_states_forced, cpu)) {
|
|
|
|
z_power_states[cpu] = info;
|
|
|
|
ret = true;
|
2021-11-03 23:18:57 -07:00
|
|
|
}
|
|
|
|
|
2021-11-14 11:04:56 -08:00
|
|
|
return ret;
|
2021-02-23 10:31:35 -08:00
|
|
|
}
|
|
|
|
|
2021-10-30 23:13:08 -07:00
|
|
|
bool pm_system_suspend(int32_t ticks)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
2021-11-14 11:28:41 -08:00
|
|
|
bool ret = true;
|
2021-10-22 10:44:55 -07:00
|
|
|
uint8_t id = _current_cpu->id;
|
|
|
|
|
2021-05-13 09:49:18 -04:00
|
|
|
SYS_PORT_TRACING_FUNC_ENTER(pm, system_suspend, ticks);
|
2021-11-14 11:04:56 -08:00
|
|
|
|
|
|
|
if (!atomic_test_and_set_bit(z_power_states_forced, id)) {
|
2021-12-20 20:31:08 +01:00
|
|
|
const struct pm_state_info *info;
|
|
|
|
|
|
|
|
info = pm_policy_next_state(id, ticks);
|
|
|
|
if (info != NULL) {
|
|
|
|
z_power_states[id] = *info;
|
|
|
|
}
|
2021-11-14 11:04:56 -08:00
|
|
|
}
|
|
|
|
|
2021-10-22 10:44:55 -07:00
|
|
|
if (z_power_states[id].state == PM_STATE_ACTIVE) {
|
2020-12-07 21:51:46 -08:00
|
|
|
LOG_DBG("No PM operations done.");
|
2021-10-22 10:44:55 -07:00
|
|
|
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
|
|
|
|
z_power_states[id].state);
|
2021-11-14 11:28:41 -08:00
|
|
|
ret = false;
|
|
|
|
goto end;
|
2019-01-16 14:49:16 +01:00
|
|
|
}
|
2018-10-24 13:54:20 +05:30
|
|
|
|
2021-04-02 10:49:01 -07:00
|
|
|
if (ticks != K_TICKS_FOREVER) {
|
|
|
|
/*
|
|
|
|
* We need to set the timer to interrupt a little bit early to
|
|
|
|
* accommodate the time required by the CPU to fully wake up.
|
|
|
|
*/
|
|
|
|
z_set_timeout_expiry(ticks -
|
2021-10-22 10:44:55 -07:00
|
|
|
k_us_to_ticks_ceil32(
|
|
|
|
z_power_states[id].exit_latency_us),
|
|
|
|
true);
|
2021-04-02 10:49:01 -07:00
|
|
|
}
|
|
|
|
|
2021-03-01 13:44:40 -08:00
|
|
|
#if CONFIG_PM_DEVICE
|
2021-11-03 10:36:19 -07:00
|
|
|
if ((z_power_states[id].state != PM_STATE_RUNTIME_IDLE) &&
|
|
|
|
(atomic_sub(&z_cpus_active, 1) == 1)) {
|
2021-03-01 13:44:40 -08:00
|
|
|
if (pm_suspend_devices()) {
|
2021-11-03 22:19:48 -07:00
|
|
|
pm_resume_devices();
|
|
|
|
z_power_states[id].state = PM_STATE_ACTIVE;
|
|
|
|
(void)atomic_add(&z_cpus_active, 1);
|
2021-11-03 10:36:19 -07:00
|
|
|
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
|
2021-11-26 19:38:22 -08:00
|
|
|
z_power_states[id].state);
|
2021-11-14 11:28:41 -08:00
|
|
|
ret = false;
|
|
|
|
goto end;
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
2019-01-16 14:49:16 +01:00
|
|
|
}
|
2021-03-01 13:44:40 -08:00
|
|
|
#endif
|
2021-03-11 15:02:09 -08:00
|
|
|
/*
|
|
|
|
* 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();
|
2021-11-22 19:14:10 +01:00
|
|
|
pm_stats_start();
|
2020-09-07 12:23:30 -04:00
|
|
|
/* Enter power state */
|
2021-02-26 15:29:52 -08:00
|
|
|
pm_state_notify(true);
|
2021-11-19 18:23:03 -08:00
|
|
|
atomic_set_bit(z_post_ops_required, id);
|
2021-10-22 10:44:55 -07:00
|
|
|
pm_state_set(z_power_states[id]);
|
2021-11-22 19:14:10 +01:00
|
|
|
pm_stats_stop();
|
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
|
2021-11-03 10:36:19 -07:00
|
|
|
if (atomic_add(&z_cpus_active, 1) == 0) {
|
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
|
2021-10-22 10:44:55 -07:00
|
|
|
pm_stats_update(z_power_states[id].state);
|
2021-03-11 10:29:22 -08:00
|
|
|
pm_system_resume();
|
2021-03-24 14:05:03 +01:00
|
|
|
k_sched_unlock();
|
2021-10-22 10:44:55 -07:00
|
|
|
SYS_PORT_TRACING_FUNC_EXIT(pm, system_suspend, ticks,
|
|
|
|
z_power_states[id].state);
|
2021-11-14 11:28:41 -08:00
|
|
|
|
|
|
|
end:
|
|
|
|
atomic_clear_bit(z_power_states_forced, id);
|
|
|
|
return ret;
|
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;
|
|
|
|
}
|
2021-09-27 12:38:39 -07:00
|
|
|
|
2021-11-30 10:44:30 +01:00
|
|
|
struct pm_state_info pm_power_state_next_get(uint8_t cpu)
|
2021-09-27 12:38:39 -07:00
|
|
|
{
|
2021-10-29 16:11:16 -07:00
|
|
|
return z_power_states[cpu];
|
2021-09-27 12:38:39 -07:00
|
|
|
}
|