pm: device: De-couple device pm from system pm

PM_DEVICE is not attached to system managed device power management.
It is a very common use case targets with device runtime power
management that don't want system device power management enabled.

We introduce a new symbol (PM_DEVICE_SYSTEM_MANAGED) to explicit
control whether or not system device power management should be
globally enabled.

Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
This commit is contained in:
Flavio Ceolin 2024-01-16 15:17:02 -08:00 committed by Anas Nashif
commit 1c2e988021
5 changed files with 137 additions and 76 deletions

View file

@ -8,3 +8,4 @@ endif()
zephyr_sources_ifdef(CONFIG_PM_DEVICE device.c)
zephyr_sources_ifdef(CONFIG_PM_DEVICE_RUNTIME device_runtime.c)
zephyr_sources_ifdef(CONFIG_PM_DEVICE_SHELL pm_shell.c)
zephyr_sources_ifdef(CONFIG_PM_DEVICE_SYSTEM_MANAGED device_system_managed.c)

View file

@ -78,12 +78,11 @@ config PM_DEVICE
bool "Device Power Management"
help
This option enables the device power management interface. The
interface consists of hook functions implemented by device drivers
that get called by the power manager application when the system
is going to suspend state or resuming from suspend state. This allows
device drivers to do any necessary power management operations
like turning off device clocks and peripherals. The device drivers
may also save and restore states in these hook functions.
interface implemented by device drivers are called by the power
management subsystem. This allows device drivers to do any
necessary power management operations like turning off
device clocks and peripherals. Device drivers may also save
and restore states in these hook functions.
if PM_DEVICE
@ -137,6 +136,18 @@ config PM_DEVICE_SHELL
Enable the device power management shell, for triggering device power
management events through the shell interface.
config PM_DEVICE_SYSTEM_MANAGED
bool "System-Managed Device Power Management"
default y if !PM_DEVICE_RUNTIME_EXCLUSIVE
default y if !PM_DEVICE_RUNTIME
help
This option enables the system-managed device power
management. The power management subsystem will suspend
devices before entering a low power state. Conversely, after
the core wakes up from low power mode all suspended devices
are resumed.
endif # PM_DEVICE
endmenu

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2024 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL);
#define DT_PM_DEVICE_ENABLED(node_id) \
COND_CODE_1(DT_PROP(node_id, zephyr_pm_device_disabled), \
(), (1 +))
#define DT_PM_DEVICE_NEEDED \
(DT_FOREACH_STATUS_OKAY(zephyr_power_state, DT_PM_DEVICE_ENABLED) 0)
#if DT_PM_DEVICE_NEEDED
TYPE_SECTION_START_EXTERN(const struct device *, pm_device_slots);
/* Number of devices successfully suspended. */
static size_t num_susp;
bool 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;
/*
* Ignore uninitialized devices, busy devices, wake up sources, and
* devices with runtime PM enabled.
*/
if (!device_is_ready(dev) || pm_device_is_busy(dev) ||
pm_device_wakeup_is_enabled(dev) ||
pm_device_runtime_is_enabled(dev)) {
continue;
}
ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
/* 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 false;
}
TYPE_SECTION_START(pm_device_slots)[num_susp] = dev;
num_susp++;
}
return true;
}
void pm_resume_devices(void)
{
for (int i = (num_susp - 1); i >= 0; i--) {
pm_device_action_run(TYPE_SECTION_START(pm_device_slots)[i],
PM_DEVICE_ACTION_RESUME);
}
num_susp = 0;
}
#else /* !DT_PM_DEVICE_NEEDED */
void pm_resume_devices(void)
{
}
bool pm_suspend_devices(void)
{
return true;
}
#endif

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_
#define ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_
#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
bool pm_suspend_devices(void);
void pm_resume_devices(void);
#else
bool pm_resume_devices(void) { return true; }
void pm_suspend_devices(void) {}
#endif /* CONFIG_PM_DEVICE_SYSTEM_MANAGED */
#endif /* ZEPHYR_SUBSYS_PM_DEVICE_SYSTEM_MANAGED_H_ */

View file

@ -18,6 +18,7 @@
#include <zephyr/tracing/tracing.h>
#include "pm_stats.h"
#include "device_system_managed.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(pm, CONFIG_PM_LOG_LEVEL);
@ -42,72 +43,6 @@ static struct pm_state_info z_cpus_pm_forced_state[] = {
static struct k_spinlock pm_forced_state_lock;
static struct k_spinlock pm_notifier_lock;
#define DT_PM_DEVICE_ENABLED(node_id) \
COND_CODE_1(DT_PROP_OR(node_id, zephyr_pm_device_disabled, 0), \
(), (1 +))
#define DT_PM_DEVICE_NEEDED \
(DT_FOREACH_STATUS_OKAY(zephyr_power_state, DT_PM_DEVICE_ENABLED) 0)
#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
TYPE_SECTION_START_EXTERN(const struct device *, pm_device_slots);
#if !defined(CONFIG_PM_DEVICE_RUNTIME_EXCLUSIVE)
/* 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;
/*
* Ignore uninitialized devices, busy devices, wake up sources, and
* devices with runtime PM enabled.
*/
if (!device_is_ready(dev) || pm_device_is_busy(dev) ||
pm_device_wakeup_is_enabled(dev) ||
pm_device_runtime_is_enabled(dev)) {
continue;
}
ret = pm_device_action_run(dev, PM_DEVICE_ACTION_SUSPEND);
/* 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;
}
TYPE_SECTION_START(pm_device_slots)[num_susp] = dev;
num_susp++;
}
return 0;
}
static void pm_resume_devices(void)
{
for (int i = (num_susp - 1); i >= 0; i--) {
pm_device_action_run(TYPE_SECTION_START(pm_device_slots)[i],
PM_DEVICE_ACTION_RESUME);
}
num_susp = 0;
}
#endif /* defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED */
/*
* Function called to notify when the system is entering / exiting a
* power state
@ -150,9 +85,12 @@ void pm_system_resume(void)
* and it may schedule another thread.
*/
if (atomic_test_and_clear_bit(z_post_ops_required, id)) {
#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
if (atomic_add(&_cpus_active, 1) == 0) {
pm_resume_devices();
if ((z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) &&
!z_cpus_pm_state[id].pm_device_disabled) {
pm_resume_devices();
}
}
#endif
pm_state_exit_post_ops(z_cpus_pm_state[id].state, z_cpus_pm_state[id].substate_id);
@ -209,11 +147,11 @@ bool pm_system_suspend(int32_t ticks)
return false;
}
#if defined(CONFIG_PM_DEVICE) && DT_PM_DEVICE_NEEDED
#ifdef CONFIG_PM_DEVICE_SYSTEM_MANAGED
if (atomic_sub(&_cpus_active, 1) == 1) {
if ((z_cpus_pm_state[id].state != PM_STATE_RUNTIME_IDLE) &&
!z_cpus_pm_state[id].pm_device_disabled) {
if (pm_suspend_devices()) {
if (!pm_suspend_devices()) {
pm_resume_devices();
z_cpus_pm_state[id].state = PM_STATE_ACTIVE;
(void)atomic_add(&_cpus_active, 1);