diff --git a/kernel/Kconfig.power_mgmt b/kernel/Kconfig.power_mgmt index 73baa7be807..8e6c15e5483 100644 --- a/kernel/Kconfig.power_mgmt +++ b/kernel/Kconfig.power_mgmt @@ -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 diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 3e71bac6f69..50b74b66975 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -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) diff --git a/subsys/Kconfig b/subsys/Kconfig index 0630d7d6751..f3deb779855 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -34,3 +34,5 @@ source "subsys/storage/Kconfig" source "subsys/settings/Kconfig" source "subsys/app_memory/Kconfig" + +source "subsys/power/Kconfig" diff --git a/subsys/power/CMakeLists.txt b/subsys/power/CMakeLists.txt new file mode 100644 index 00000000000..fc6ed85e682 --- /dev/null +++ b/subsys/power/CMakeLists.txt @@ -0,0 +1,5 @@ +zephyr_sources( + power.c + policy.c + device.c + ) diff --git a/subsys/power/Kconfig b/subsys/power/Kconfig new file mode 100644 index 00000000000..d88499e5b2a --- /dev/null +++ b/subsys/power/Kconfig @@ -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 diff --git a/subsys/power/device.c b/subsys/power/device.c new file mode 100644 index 00000000000..2f411183544 --- /dev/null +++ b/subsys/power/device.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2018 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "pm_policy.h" + +#define LOG_MODULE_NAME power +#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */ +#include +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; + } + } +} diff --git a/subsys/power/pm_policy.h b/subsys/power/pm_policy.h new file mode 100644 index 00000000000..0fb5a99d039 --- /dev/null +++ b/subsys/power/pm_policy.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _PM_POLICY_H_ +#define _PM_POLICY_H_ + +#include +#include + +#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_ */ diff --git a/subsys/power/policy.c b/subsys/power/policy.c new file mode 100644 index 00000000000..ef204e1dde5 --- /dev/null +++ b/subsys/power/policy.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "pm_policy.h" + +#define LOG_MODULE_NAME power +#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */ +#include +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; +} diff --git a/subsys/power/power.c b/subsys/power/power.c new file mode 100644 index 00000000000..fa039836a88 --- /dev/null +++ b/subsys/power/power.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "pm_policy.h" + +#define LOG_MODULE_NAME power +#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */ +#include +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);