subsys: power: Add OS managed Power Management framework

Add support for OS managed Power Management framework for Zephyr
under 'subsys/power'. This framework takes care of implementing
the _sys_soc_suspend/_sys_soc_resume API's, a PM policy based on
SoC Low Power residencies and also provides necessary API's to
do devices suspend and resume.

Also add necessary changes to support the existing Application
managed Power Management framework.

Signed-off-by: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
This commit is contained in:
Ramakrishna Pallala 2018-08-16 10:39:40 +05:30 committed by Anas Nashif
commit 2ad647857c
9 changed files with 496 additions and 0 deletions

View file

@ -14,6 +14,29 @@ menuconfig SYS_POWER_MANAGEMENT
timer is due to expire.
if SYS_POWER_MANAGEMENT
choice POWER_MANAGEMENT_CONTROL
prompt "Power Management Control"
default PM_CONTROL_APP
help
Select the Application managed or OS managed power saving
mechanism.
config PM_CONTROL_APP
bool
prompt "Handled at Application level"
help
This option enbles the Application to handle all the Power
Management flows for the platform.
config PM_CONTROL_OS
bool
prompt "Handle at OS level"
help
This option allows the OS to handle all the Power
Management flows for the platform.
endchoice # POWER_MANAGEMENT_CONTROL
config SYS_POWER_LOW_POWER_STATE
bool "Low power state"
depends on SYS_POWER_LOW_POWER_STATE_SUPPORTED

View file

@ -14,3 +14,4 @@ add_subdirectory_ifdef(CONFIG_USB usb)
add_subdirectory(random)
add_subdirectory(storage)
add_subdirectory_ifdef(CONFIG_SETTINGS settings)
add_subdirectory_ifdef(CONFIG_PM_CONTROL_OS power)

View file

@ -34,3 +34,5 @@ source "subsys/storage/Kconfig"
source "subsys/settings/Kconfig"
source "subsys/app_memory/Kconfig"
source "subsys/power/Kconfig"

View file

@ -0,0 +1,5 @@
zephyr_sources(
power.c
policy.c
device.c
)

88
subsys/power/Kconfig Normal file
View file

@ -0,0 +1,88 @@
if PM_CONTROL_OS
menu "OS Power Management"
if SYS_POWER_LOW_POWER_STATE
config PM_CONTROL_OS_LPS
bool "Platform supports LPS"
help
Select this option if SoC support LPS state.
if PM_CONTROL_OS_LPS
config PM_LPS_MIN_RES
int "LPS minimum residency"
default 5
help
Minimum residency in ticks to enter LPS state.
endif
config PM_CONTROL_OS_LPS_1
bool "Platform supports LPS_1"
help
Select this option if SoC support LPS_1 state.
if PM_CONTROL_OS_LPS_1
config PM_LPS_1_MIN_RES
int "LPS_1 minimum residency"
default 10
help
Minimum residency in ticks to enter LPS_1 state.
endif
config PM_CONTROL_OS_LPS_2
bool "Platform supports LPS_2"
help
Select this option if SoC support LPS_2 state.
if PM_CONTROL_OS_LPS_2
config PM_LPS_2_MIN_RES
int "LPS_2 minimum residency"
default 30
help
Minimum residency in ticks to enter LPS_2 state.
endif
endif # SYS_POWER_LOW_POWER_STATE
if SYS_POWER_DEEP_SLEEP
config PM_CONTROL_OS_DEEP_SLEEP
bool "Platform supports DEEP_SLEEP"
help
Select this option if SoC support DEEP_SLEEP state.
if PM_CONTROL_OS_DEEP_SLEEP
config PM_DEEP_SLEEP_MIN_RES
int "DEEP_SLEEP minimum residency"
default 60
help
Minimum residency in ticks to enter DEEP_SLEEP state.
endif
config PM_CONTROL_OS_DEEP_SLEEP_1
bool "Platform supports DEEP_SLEEP_1"
help
Select this option if SoC support DEEP_SLEEP_1 state.
if PM_CONTROL_OS_DEEP_SLEEP_1
config PM_DEEP_SLEEP_1_MIN_RES
int "DEEP_SLEEP_1 minimum residency"
default 90
help
Minimum residency in ticks to enter DEEP_SLEEP_1 state.
endif
config PM_CONTROL_OS_DEEP_SLEEP_2
bool "Platform supports DEEP_SLEEP_2"
help
Select this option if SoC support DEEP_SLEEP_2 state.
if PM_CONTROL_OS_DEEP_SLEEP_2
config PM_DEEP_SLEEP_2_MIN_RES
int "DEEP_SLEEP_2 minimum residency"
default 120
help
Minimum residency in ticks to enter DEEP_SLEEP_2 state.
endif
endif # PM_CONTROL_OS_DEEP_SLEEP
endmenu
endif # PM_CONTROL_OS

115
subsys/power/device.c Normal file
View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <string.h>
#include <soc.h>
#include <device.h>
#include "pm_policy.h"
#define LOG_MODULE_NAME power
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */
#include <logging/log.h>
LOG_MODULE_DECLARE();
/*
* FIXME: Remove the conditional inclusion of
* core_devices array once we enble the capability
* to build the device list based on devices power
* and clock domain dependencies.
*/
#ifdef CONFIG_SOC_SERIES_NRF52X
#define MAX_PM_DEVICES 15
#define NUM_CORE_DEVICES 4
#define MAX_DEV_NAME_LEN 16
static const char core_devices[NUM_CORE_DEVICES][MAX_DEV_NAME_LEN] = {
"clk_k32src",
"clk_m16src",
"sys_clock",
"UART_0",
};
#else
#error "Add SoC's core devices list for PM"
#endif
/*
* Ordered list to store devices on which
* device power policies would be executed.
*/
static int device_ordered_list[MAX_PM_DEVICES];
static int device_retval[MAX_PM_DEVICES];
static struct device *pm_device_list;
static int device_count;
int sys_pm_suspend_devices(void)
{
for (int i = device_count - 1; i >= 0; i--) {
int idx = device_ordered_list[i];
/* TODO: Improve the logic by checking device status
* and set the device states accordingly.
*/
device_retval[i] = device_set_power_state(&pm_device_list[idx],
DEVICE_PM_SUSPEND_STATE);
if (device_retval[i]) {
LOG_ERR("%s suspend operation failed\n",
pm_device_list[idx].config->name);
return device_retval[i];
}
}
return 0;
}
void sys_pm_resume_devices(void)
{
int i;
for (i = 0; i < device_count; i++) {
if (!device_retval[i]) {
int idx = device_ordered_list[i];
device_set_power_state(&pm_device_list[idx],
DEVICE_PM_ACTIVE_STATE);
}
}
}
void sys_pm_create_device_list(void)
{
int count;
int i, j;
bool is_core_dev;
/*
* Create an ordered list of devices that will be suspended.
* Ordering should be done based on dependencies. Devices
* in the beginning of the list will be resumed first.
*/
device_list_get(&pm_device_list, &count);
/* Reserve for 32KHz, 16MHz, system clock, etc... */
device_count = NUM_CORE_DEVICES;
for (i = 0; (i < count) && (device_count < MAX_PM_DEVICES); i++) {
/* Check if the device is core device */
for (j = 0, is_core_dev = false; j < NUM_CORE_DEVICES; j++) {
if (!strcmp(pm_device_list[i].config->name,
&core_devices[j][0])) {
is_core_dev = true;
break;
}
}
if (is_core_dev) {
device_ordered_list[j] = i;
} else {
device_ordered_list[device_count++] = i;
}
}
}

57
subsys/power/pm_policy.h Normal file
View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _PM_POLICY_H_
#define _PM_POLICY_H_
#include <power.h>
#include <soc_power.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Function to create device PM list
*/
extern void sys_pm_create_device_list(void);
/**
* @brief Function to suspend the devices in PM device list
*/
extern int sys_pm_suspend_devices(void);
/**
* @brief Function to resume the devices in PM device list
*/
extern void sys_pm_resume_devices(void);
/**
* @brief Function to get the next PM state based on the ticks
*/
extern int sys_pm_policy_next_state(s32_t ticks, enum power_states *state);
/**
* @brief Application defined function for Lower Power entry
*
* Application defined function for doing any target specific operations
* for low power entry.
*/
extern void sys_pm_notify_lps_entry(enum power_states state);
/**
* @brief Application defined function for Lower Power exit
*
* Application defined function for doing any target specific operations
* for low power exit.
*/
extern void sys_pm_notify_lps_exit(enum power_states state);
#ifdef __cplusplus
}
#endif
#endif /* _PM_POLICY_H_ */

98
subsys/power/policy.c Normal file
View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <soc.h>
#include "pm_policy.h"
#define LOG_MODULE_NAME power
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */
#include <logging/log.h>
LOG_MODULE_DECLARE();
#ifdef CONFIG_TICKLESS_KERNEL
#define SECS_TO_TICKS CONFIG_TICKLESS_KERNEL_TIME_UNIT_IN_MICRO_SECS
#else
#define SECS_TO_TICKS CONFIG_SYS_CLOCK_TICKS_PER_SEC
#endif
#if !(defined(CONFIG_PM_CONTROL_OS_LPS) || \
defined(CONFIG_PM_CONTROL_OS_LPS_1) || \
defined(CONFIG_PM_CONTROL_OS_LPS_2) || \
defined(CONFIG_PM_CONTROL_OS_DEEP_SLEEP) || \
defined(CONFIG_PM_CONTROL_OS_DEEP_SLEEP_1) || \
defined(CONFIG_PM_CONTROL_OS_DEEP_SLEEP_2))
#error "Low Power states not selected by policy"
#endif
struct sys_soc_pm_policy {
enum power_states pm_state;
int sys_state;
int min_residency;
};
/* PM Policy based on SoC/Platform residency requirements */
static struct sys_soc_pm_policy pm_policy[] = {
#ifdef CONFIG_PM_CONTROL_OS_LPS
{SYS_POWER_STATE_CPU_LPS, SYS_PM_LOW_POWER_STATE,
CONFIG_PM_LPS_MIN_RES * SECS_TO_TICKS},
#endif
#ifdef CONFIG_PM_CONTROL_OS_LPS_1
{SYS_POWER_STATE_CPU_LPS_1, SYS_PM_LOW_POWER_STATE,
CONFIG_PM_LPS_1_MIN_RES * SECS_TO_TICKS},
#endif
#ifdef CONFIG_PM_CONTROL_OS_LPS_2
{SYS_POWER_STATE_CPU_LPS_2, SYS_PM_LOW_POWER_STATE,
CONFIG_PM_LPS_2_MIN_RES * SECS_TO_TICKS},
#endif
#ifdef CONFIG_PM_CONTROL_OS_DEEP_SLEEP
{SYS_POWER_STATE_DEEP_SLEEP, SYS_PM_DEEP_SLEEP,
CONFIG_PM_DEEP_SLEEP_MIN_RES * SECS_TO_TICKS},
#endif
#ifdef CONFIG_PM_CONTROL_OS_DEEP_SLEEP_1
{SYS_POWER_STATE_DEEP_SLEEP_1, SYS_PM_DEEP_SLEEP,
CONFIG_PM_DEEP_SLEEP_1_MIN_RES * SECS_TO_TICKS},
#endif
#ifdef CONFIG_PM_CONTROL_OS_DEEP_SLEEP_2
{SYS_POWER_STATE_DEEP_SLEEP_2, SYS_PM_DEEP_SLEEP,
CONFIG_PM_DEEP_SLEEP_2_MIN_RES * SECS_TO_TICKS},
#endif
};
int sys_pm_policy_next_state(s32_t ticks, enum power_states *pm_state)
{
int i;
if ((ticks != K_FOREVER) && (ticks < pm_policy[0].min_residency)) {
LOG_ERR("Not enough time for PM operations: %d\n", ticks);
return SYS_PM_NOT_HANDLED;
}
for (i = 0; i < (ARRAY_SIZE(pm_policy) - 1); i++) {
if ((ticks >= pm_policy[i].min_residency) &&
(ticks < pm_policy[i + 1].min_residency)) {
break;
}
}
if (!_sys_soc_is_valid_power_state(pm_policy[i].pm_state)) {
LOG_ERR("pm_state(%d) not supported by SoC\n",
pm_policy[i].pm_state);
return SYS_PM_NOT_HANDLED;
}
*pm_state = pm_policy[i].pm_state;
LOG_DBG("pm_state: %d, min_residency: %d, idx: %d\n",
*pm_state, pm_policy[i].min_residency, i);
return pm_policy[i].sys_state;
}

107
subsys/power/power.c Normal file
View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <init.h>
#include <string.h>
#include <soc.h>
#include "pm_policy.h"
#define LOG_MODULE_NAME power
#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */
#include <logging/log.h>
LOG_MODULE_REGISTER();
static int post_ops_done = 1;
static enum power_states pm_state;
int _sys_soc_suspend(s32_t ticks)
{
int sys_state;
post_ops_done = 0;
sys_state = sys_pm_policy_next_state(ticks, &pm_state);
switch (sys_state) {
case SYS_PM_LOW_POWER_STATE:
sys_pm_notify_lps_entry(pm_state);
/* Do CPU LPS operations */
_sys_soc_set_power_state(pm_state);
break;
case SYS_PM_DEEP_SLEEP:
/* Don't need pm idle exit event notification */
_sys_soc_pm_idle_exit_notification_disable();
sys_pm_notify_lps_entry(pm_state);
/* Save device states and turn off peripherals as necessary */
if (sys_pm_suspend_devices()) {
LOG_ERR("System level device suspend failed\n");
break;
}
/* Enter CPU deep sleep state */
_sys_soc_set_power_state(pm_state);
/* Turn on peripherals and restore device states as necessary */
sys_pm_resume_devices();
break;
default:
/* No PM operations */
LOG_DBG("\nNo PM operations done\n");
break;
}
if (sys_state != SYS_PM_NOT_HANDLED) {
/*
* Do any arch or soc specific post operations specific to the
* power state.
*/
if (!post_ops_done) {
post_ops_done = 1;
sys_pm_notify_lps_exit(pm_state);
_sys_soc_power_state_post_ops(pm_state);
}
}
return sys_state;
}
void _sys_soc_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 _sys_soc_pm_idle_exit_notification_disable() if this
* notification is not required.
*/
if (!post_ops_done) {
post_ops_done = 1;
sys_pm_notify_lps_exit(pm_state);
_sys_soc_power_state_post_ops(pm_state);
}
}
static int sys_pm_init(struct device *dev)
{
ARG_UNUSED(dev);
sys_pm_create_device_list();
return 0;
}
SYS_INIT(sys_pm_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);