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 <string.h>
|
|
|
|
#include <device.h>
|
2021-06-11 09:22:33 -07:00
|
|
|
#include <pm/policy.h>
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2020-09-01 18:31:40 -04:00
|
|
|
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */
|
2018-08-16 10:39:40 +05:30
|
|
|
#include <logging/log.h>
|
2018-08-09 08:48:18 -05:00
|
|
|
LOG_MODULE_DECLARE(power);
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-05-26 16:36:49 -07:00
|
|
|
extern const struct device __device_start[];
|
|
|
|
extern const struct device __device_end[];
|
2020-03-14 15:51:31 -05:00
|
|
|
|
2021-05-31 15:24:34 +02:00
|
|
|
#if defined(CONFIG_PM)
|
2021-05-27 21:00:22 +02:00
|
|
|
extern const struct device *__pm_device_slots_start[];
|
2020-03-14 15:51:31 -05:00
|
|
|
|
|
|
|
/* Number of devices successfully suspended. */
|
2021-05-26 16:54:41 -07:00
|
|
|
static size_t num_susp;
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-06-03 19:06:53 +02:00
|
|
|
static bool should_suspend(const struct device *dev, enum pm_device_state state)
|
2021-03-31 11:20:22 -07:00
|
|
|
{
|
|
|
|
int rc;
|
2021-06-03 19:06:53 +02:00
|
|
|
enum pm_device_state current_state;
|
2021-03-31 11:20:22 -07:00
|
|
|
|
2021-07-29 11:07:27 +02:00
|
|
|
if (pm_device_is_busy(dev) != 0) {
|
2021-05-05 14:59:06 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-31 11:20:22 -07:00
|
|
|
rc = pm_device_state_get(dev, ¤t_state);
|
2021-05-26 16:36:49 -07:00
|
|
|
if ((rc == -ENOSYS) || (rc != 0)) {
|
2021-03-31 11:20:22 -07:00
|
|
|
LOG_DBG("Was not possible to get device %s state: %d",
|
|
|
|
dev->name, rc);
|
2021-05-26 16:36:49 -07:00
|
|
|
return false;
|
2021-03-31 11:20:22 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the device is currently powered off or the request was
|
|
|
|
* to go to the same state, just ignore it.
|
|
|
|
*/
|
2021-05-07 14:18:57 -07:00
|
|
|
if ((current_state == PM_DEVICE_STATE_OFF) ||
|
2021-03-31 11:20:22 -07:00
|
|
|
(current_state == state)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
static int _pm_devices(uint32_t state)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
2021-05-26 16:36:49 -07:00
|
|
|
const struct device *dev;
|
2020-03-14 15:51:31 -05:00
|
|
|
num_susp = 0;
|
|
|
|
|
2021-05-26 16:36:49 -07:00
|
|
|
for (dev = (__device_end - 1); dev > __device_start; dev--) {
|
2021-03-31 11:20:22 -07:00
|
|
|
bool suspend;
|
2020-03-14 15:51:31 -05:00
|
|
|
int rc;
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-03-31 11:20:22 -07:00
|
|
|
suspend = should_suspend(dev, state);
|
|
|
|
if (suspend) {
|
|
|
|
/*
|
|
|
|
* Don't bother the device if it is currently
|
|
|
|
* in the right state.
|
|
|
|
*/
|
2021-06-04 12:30:35 +02:00
|
|
|
rc = pm_device_state_set(dev, state);
|
2021-05-12 15:34:24 +02:00
|
|
|
if ((rc != -ENOSYS) && (rc != 0)) {
|
2021-03-31 11:20:22 -07:00
|
|
|
LOG_DBG("%s did not enter %s state: %d",
|
|
|
|
dev->name, pm_device_state_str(state),
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2021-05-27 21:00:22 +02:00
|
|
|
__pm_device_slots_start[num_susp] = dev;
|
2021-05-26 16:36:49 -07:00
|
|
|
num_susp++;
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
int pm_suspend_devices(void)
|
2020-01-04 13:18:08 +08:00
|
|
|
{
|
2021-07-02 18:09:07 +02:00
|
|
|
return _pm_devices(PM_DEVICE_STATE_SUSPENDED);
|
2020-01-21 22:53:48 +01:00
|
|
|
}
|
2020-01-04 13:18:08 +08:00
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
int pm_low_power_devices(void)
|
2020-01-21 22:53:48 +01:00
|
|
|
{
|
2021-05-07 14:18:57 -07:00
|
|
|
return _pm_devices(PM_DEVICE_STATE_LOW_POWER);
|
2020-01-04 13:18:08 +08:00
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
int pm_force_suspend_devices(void)
|
2018-10-09 11:08:39 +05:30
|
|
|
{
|
2021-05-07 14:18:57 -07:00
|
|
|
return _pm_devices(PM_DEVICE_STATE_FORCE_SUSPEND);
|
2018-10-09 11:08:39 +05:30
|
|
|
}
|
|
|
|
|
2020-09-01 21:46:30 -04:00
|
|
|
void pm_resume_devices(void)
|
2018-08-16 10:39:40 +05:30
|
|
|
{
|
2021-05-26 16:54:41 -07:00
|
|
|
size_t i;
|
2018-08-16 10:39:40 +05:30
|
|
|
|
2021-05-26 16:36:49 -07:00
|
|
|
for (i = 0; i < num_susp; i++) {
|
2021-05-27 21:00:22 +02:00
|
|
|
pm_device_state_set(__pm_device_slots_start[i],
|
2021-06-04 12:30:35 +02:00
|
|
|
PM_DEVICE_STATE_ACTIVE);
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
|
|
|
|
2021-05-26 16:36:49 -07:00
|
|
|
num_susp = 0;
|
2018-08-16 10:39:40 +05:30
|
|
|
}
|
2020-09-01 18:31:40 -04:00
|
|
|
#endif /* defined(CONFIG_PM) */
|
2021-01-25 15:08:48 +01:00
|
|
|
|
2021-06-03 19:06:53 +02:00
|
|
|
const char *pm_device_state_str(enum pm_device_state state)
|
2021-01-25 15:08:48 +01:00
|
|
|
{
|
|
|
|
switch (state) {
|
2021-05-07 14:18:57 -07:00
|
|
|
case PM_DEVICE_STATE_ACTIVE:
|
2021-01-25 15:08:48 +01:00
|
|
|
return "active";
|
2021-05-07 14:18:57 -07:00
|
|
|
case PM_DEVICE_STATE_LOW_POWER:
|
2021-01-25 15:08:48 +01:00
|
|
|
return "low power";
|
2021-07-02 18:09:07 +02:00
|
|
|
case PM_DEVICE_STATE_SUSPENDED:
|
|
|
|
return "suspended";
|
2021-05-07 14:18:57 -07:00
|
|
|
case PM_DEVICE_STATE_FORCE_SUSPEND:
|
2021-01-25 15:08:48 +01:00
|
|
|
return "force suspend";
|
2021-05-07 14:18:57 -07:00
|
|
|
case PM_DEVICE_STATE_OFF:
|
2021-01-25 15:08:48 +01:00
|
|
|
return "off";
|
|
|
|
default:
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
2021-05-03 18:12:17 +02:00
|
|
|
|
2021-06-03 19:06:53 +02:00
|
|
|
int pm_device_state_set(const struct device *dev,
|
2021-06-10 09:41:18 +02:00
|
|
|
enum pm_device_state state)
|
2021-05-03 18:12:17 +02:00
|
|
|
{
|
2021-06-04 17:41:39 +02:00
|
|
|
int ret;
|
|
|
|
|
2021-05-03 18:12:17 +02:00
|
|
|
if (dev->pm_control == NULL) {
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
2021-07-02 12:52:18 +02:00
|
|
|
switch (state) {
|
2021-07-02 18:09:07 +02:00
|
|
|
case PM_DEVICE_STATE_SUSPENDED:
|
|
|
|
if ((dev->pm->state == PM_DEVICE_STATE_SUSPENDED) ||
|
2021-07-02 12:52:18 +02:00
|
|
|
(dev->pm->state == PM_DEVICE_STATE_SUSPENDING)) {
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PM_DEVICE_STATE_ACTIVE:
|
|
|
|
if ((dev->pm->state == PM_DEVICE_STATE_ACTIVE) ||
|
|
|
|
(dev->pm->state == PM_DEVICE_STATE_RESUMING)) {
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PM_DEVICE_STATE_FORCE_SUSPEND:
|
|
|
|
__fallthrough;
|
|
|
|
case PM_DEVICE_STATE_LOW_POWER:
|
|
|
|
__fallthrough;
|
|
|
|
case PM_DEVICE_STATE_OFF:
|
|
|
|
if (dev->pm->state == state) {
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
2021-06-10 09:41:18 +02:00
|
|
|
ret = dev->pm_control(dev, state);
|
2021-06-04 17:41:39 +02:00
|
|
|
if (ret < 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-06-10 09:41:18 +02:00
|
|
|
dev->pm->state = state;
|
2021-06-04 17:41:39 +02:00
|
|
|
|
|
|
|
return 0;
|
2021-05-03 18:12:17 +02:00
|
|
|
}
|
|
|
|
|
2021-06-03 19:06:53 +02:00
|
|
|
int pm_device_state_get(const struct device *dev,
|
2021-06-04 17:41:39 +02:00
|
|
|
enum pm_device_state *state)
|
2021-05-03 18:12:17 +02:00
|
|
|
{
|
|
|
|
if (dev->pm_control == NULL) {
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
2021-06-04 17:41:39 +02:00
|
|
|
*state = dev->pm->state;
|
|
|
|
|
|
|
|
return 0;
|
2021-05-03 18:12:17 +02:00
|
|
|
}
|
2021-05-31 15:24:34 +02:00
|
|
|
|
2021-07-29 11:07:27 +02:00
|
|
|
bool pm_device_is_any_busy(void)
|
2021-05-31 15:24:34 +02:00
|
|
|
{
|
|
|
|
const struct device *dev = __device_start;
|
|
|
|
|
|
|
|
while (dev < __device_end) {
|
2021-07-14 11:05:34 +02:00
|
|
|
if (atomic_test_bit(dev->pm->flags, PM_DEVICE_FLAG_BUSY)) {
|
2021-07-29 11:07:27 +02:00
|
|
|
return true;
|
2021-05-31 15:24:34 +02:00
|
|
|
}
|
|
|
|
++dev;
|
|
|
|
}
|
|
|
|
|
2021-07-29 11:07:27 +02:00
|
|
|
return false;
|
2021-05-31 15:24:34 +02:00
|
|
|
}
|
|
|
|
|
2021-07-29 11:07:27 +02:00
|
|
|
bool pm_device_is_busy(const struct device *dev)
|
2021-05-31 15:24:34 +02:00
|
|
|
{
|
2021-07-29 11:07:27 +02:00
|
|
|
return atomic_test_bit(dev->pm->flags, PM_DEVICE_FLAG_BUSY);
|
2021-05-31 15:24:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void pm_device_busy_set(const struct device *dev)
|
|
|
|
{
|
2021-07-14 11:05:34 +02:00
|
|
|
atomic_set_bit(dev->pm->flags, PM_DEVICE_FLAG_BUSY);
|
2021-05-31 15:24:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void pm_device_busy_clear(const struct device *dev)
|
|
|
|
{
|
2021-07-14 11:05:34 +02:00
|
|
|
atomic_clear_bit(dev->pm->flags, PM_DEVICE_FLAG_BUSY);
|
2021-05-31 15:24:34 +02:00
|
|
|
}
|