diff --git a/CODEOWNERS b/CODEOWNERS index d5bb205720efb7..e6135a842deb91 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -249,6 +249,7 @@ /subsys/net/lib/mqtt/ @jukkar @tbursztyka /subsys/net/lib/coap/ @rveerama1 /subsys/net/lib/sockets/ @jukkar @tbursztyka @pfalcon +/subsys/power/ @ramakrishnapallala @pizi-nordic /subsys/settings/ @nvlsianpu /subsys/shell/ @jarz-nordic @nordic-krch /subsys/storage/ @nvlsianpu diff --git a/doc/reference/power_management/index.rst b/doc/reference/power_management/index.rst index 689d3ac0a7c206..764f316d99155e 100644 --- a/doc/reference/power_management/index.rst +++ b/doc/reference/power_management/index.rst @@ -423,6 +423,92 @@ Check Busy Status of All Devices API Checks if any device is busy. The API returns 0 if no device in the system is busy. +Device Idle Power Management +**************************** + + +The Device Idle Power Management framework is a Active Power +Management mechanism which reduces the overall system Power consumtion +by suspending the devices which are idle or not being used while the +System is active or running. + +The framework uses device_set_power_state() API set the +device power state accordingly based on the usage count. + +The interfaces and APIs provided by the Device Idle PM are +designed to be generic and architecture independent. + +Device Idle Power Management API +================================ + +The Device Drivers use these APIs to perform device idle power management +operations on the devices. + +Enable Device Idle Power Management of a Device API +--------------------------------------------------- + +.. code-block:: c + + void device_pm_enable(struct device *dev); + +Enbles Idle Power Management of the device. + +Disable Device Idle Power Management of a Device API +---------------------------------------------------- + +.. code-block:: c + + void device_pm_disable(struct device *dev); + +Disables Idle Power Management of the device. + +Resume Device asynchronously API +-------------------------------- + +.. code-block:: c + + int device_pm_get(struct device *dev); + +Marks the device as being used. This API will asynchronously +bring the device to resume state. The API returns 0 on success. + +Resume Device synchronously API +------------------------------- + +.. code-block:: c + + int device_pm_get_sync(struct device *dev); + +Marks the device as being used. It will bring up or resume +the device if it is in suspended state based on the device +usage count. This call is blocked until the device PM state +is changed to active. The API returns 0 on success. + +Suspend Device asynchronously API +--------------------------------- + +.. code-block:: c + + int device_pm_put(struct device *dev); + +Marks the device as being released. This API asynchronously put +the device to suspend state if not already in suspend state. +The API returns 0 on success. + +Suspend Device synchronously API +-------------------------------- + +.. code-block:: c + + int device_pm_put_sync(struct device *dev); + +Marks the device as being released. It will put the device to +suspended state if is is in active state based on the device +usage count. This call is blocked until the device PM state +is changed to resume. The API returns 0 on success. This +call is blocked until the device is suspended. + + Power Management Configuration Flags ************************************ @@ -450,6 +536,10 @@ the following configuration flags. This flag is enabled if the SOC interface and the devices support device power management. +:code:`CONFIG_DEVICE_IDLE_PM` + + This flag enables the Device Idle Power Management. + API Reference ************* diff --git a/include/device.h b/include/device.h index 5c3b47c1652a4d..418251219ec03d 100644 --- a/include/device.h +++ b/include/device.h @@ -130,7 +130,9 @@ extern "C" { * @def DEVICE_DEFINE * * @brief Create device object and set it up for boot time initialization, - * with the option to device_pm_control. + * with the option to device_pm_control. In case of Device Idle Power + * Management is enabled, make sure the device is in suspended state after + * initialization. * * @copydetails DEVICE_AND_API_INIT * @param pm_control_fn Pointer to device_pm_control function. @@ -144,17 +146,29 @@ extern "C" { #else #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \ data, cfg_info, level, prio, api) \ + static struct device_pm _CONCAT(__pm_, dev_name) __used \ + = { \ + .usage = ATOMIC_INIT(0), \ + .lock = _K_SEM_INITIALIZER( \ + _CONCAT(__pm_, dev_name).lock, 1, 1), \ + .signal = K_POLL_SIGNAL_INITIALIZER( \ + _CONCAT(__pm_, dev_name).signal), \ + .event = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, \ + K_POLL_MODE_NOTIFY_ONLY, \ + &_CONCAT(__pm_, dev_name).signal), \ + }; \ static struct device_config _CONCAT(__config_, dev_name) __used \ __attribute__((__section__(".devconfig.init"))) = { \ .name = drv_name, .init = (init_fn), \ .device_pm_control = (pm_control_fn), \ + .pm = &_CONCAT(__pm_, dev_name), \ .config_info = (cfg_info) \ }; \ static struct device _CONCAT(__device_, dev_name) __used \ __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \ .config = &_CONCAT(__config_, dev_name), \ .driver_api = api, \ - .driver_data = data \ + .driver_data = data, \ } #endif @@ -211,6 +225,28 @@ struct device; typedef void (*device_pm_cb)(struct device *dev, int status, void *context, void *arg); +/** + * @brief Device PM info + * + * @param dev pointer to device structure + * @param lock lock to synchronize the get/put operations + * @param enable device pm enable flag + * @param usage device usage count + * @param fsm_state device idle internal power state + * @param event event object to listen to the sync request events + * @param signal signal to notify the Async API callers + */ +struct device_pm { + struct device *dev; + struct k_sem lock; + bool enable; + atomic_t usage; + atomic_t fsm_state; + struct k_work work; + struct k_poll_event event; + struct k_poll_signal signal; +}; + /** * @brief Static device information (In ROM) Per driver instance * @@ -224,6 +260,7 @@ struct device_config { #ifdef CONFIG_DEVICE_POWER_MANAGEMENT int (*device_pm_control)(struct device *device, u32_t command, void *context, device_pm_cb cb, void *arg); + struct device_pm *pm; #endif const void *config_info; }; @@ -387,8 +424,8 @@ int device_pm_control_nop(struct device *unused_device, * @param cb Callback function to notify device power status * @param arg Caller passed argument to callback function * - * @retval 0 If successful. - * @retval Errno Negative errno code if failure. + * @retval 0 If successful in queuing the request or changing the state. + * @retval Errno Negative errno code if failure. Callback will not be called. */ static inline int device_set_power_state(struct device *device, u32_t device_power_state, @@ -458,6 +495,109 @@ int device_any_busy_check(void); */ int device_busy_check(struct device *chk_dev); +#ifdef CONFIG_DEVICE_IDLE_PM + +/* Device PM FSM states */ +enum device_pm_fsm_state { + DEVICE_PM_FSM_STATE_ACTIVE = 1, + DEVICE_PM_FSM_STATE_SUSPENDED, + DEVICE_PM_FSM_STATE_SUSPENDING, + DEVICE_PM_FSM_STATE_RESUMING, +}; + +/** + * @brief Enable device idle PM + * + * Called by a device driver to enable device idle power management. + * The device might be asynchronously suspended if Idle PM is enabled + * when the device is not use. + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + */ +void device_pm_enable(struct device *dev); + +/** + * @brief Disable device idle PM + * + * Called by a device driver to disable device idle power management. + * The device might be asynchronously resumed if Idle PM is disabled + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + */ +void device_pm_disable(struct device *dev); + +/** + * @brief Call device resume asynchronously based on usage count + * + * Called by a device driver to mark the device as being used. + * This API will asynchronously bring the device to resume state + * if it not already in active state. + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + * @retval 0 If successfully queued the Async request. If queued, + * the caller need to wait on the poll event linked to device + * pm signal mechanism to know the comepletion of resume operation. + * @retval Errno Negative errno code if failure. + */ +int device_pm_get(struct device *dev); + +/** + * @brief Call device resume synchronously based on usage count + * + * Called by a device driver to mark the device as being used. It + * will bring up or resume the device if it is in suspended state + * based on the device usage count. This call is blocked until the + * device PM state is changed to resume. + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + * @retval 0 If successful. + * @retval Errno Negative errno code if failure. + */ +int device_pm_get_sync(struct device *dev); + +/** + * @brief Call device suspend asynchronously based on usage count + * + * Called by a device driver to mark the device as being released. + * This API asynchronously put the device to suspend state if + * it not already in suspended state. + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + * @retval 0 If successfully queued the Async request. If queued, + * the caller need to wait on the poll event linked to device pm + * signal mechanism to know the comepletion of suspend operation. + * @retval Errno Negative errno code if failure. + */ +int device_pm_put(struct device *dev); + +/** + * @brief Call device suspend synchronously based on usage count + * + * Called by a device driver to mark the device as being released. It + * will put the device to suspended state if is is in active state + * based on the device usage count. This call is blocked until the + * device PM state is changed to resume. + * + * @param dev Pointer to device structure of the specific device driver + * the caller is interested in. + * @retval 0 If successful. + * @retval Errno Negative errno code if failure. + */ +int device_pm_put_sync(struct device *dev); +#else +static inline void device_pm_enable(struct device *dev) { } +static inline void device_pm_disable(struct device *dev) { } +static inline int device_pm_get(struct device *dev) { return 0; } +static inline int device_pm_get_sync(struct device *dev) { return 0; } +static inline int device_pm_put(struct device *dev) { return 0; } +static inline int device_pm_put_sync(struct device *dev) { return 0; } +#endif + #endif /** diff --git a/subsys/power/CMakeLists.txt b/subsys/power/CMakeLists.txt index d7c1b8be310e9a..f3e8c3b48f31ed 100644 --- a/subsys/power/CMakeLists.txt +++ b/subsys/power/CMakeLists.txt @@ -1,5 +1,6 @@ zephyr_sources_ifdef(CONFIG_SYS_POWER_MANAGEMENT power.c) zephyr_sources_ifdef(CONFIG_DEVICE_POWER_MANAGEMENT device.c) zephyr_sources_ifdef(CONFIG_SYS_PM_STATE_LOCK pm_ctrl.c) +zephyr_sources_ifdef(CONFIG_DEVICE_IDLE_PM device_pm.c) zephyr_sources_if_kconfig(reboot.c) add_subdirectory(policy) diff --git a/subsys/power/Kconfig b/subsys/power/Kconfig index 9cc3248a754bb2..7391a19c776046 100644 --- a/subsys/power/Kconfig +++ b/subsys/power/Kconfig @@ -11,6 +11,16 @@ config SYS_PM_DEBUG help Enable System Power Management debugging hooks. +config DEVICE_IDLE_PM + bool "Enable device Idle Power Management" + depends on DEVICE_POWER_MANAGEMENT + select POLL + help + Enable device Idle Power Management to save power. + With device Idle PM enabled, devices can be suspended or + resumed based on the device usage even while the CPU or + system is running. + source "subsys/power/policy/Kconfig" module = SYS_PM diff --git a/subsys/power/device_pm.c b/subsys/power/device_pm.c new file mode 100644 index 00000000000000..3cd7ce40953c20 --- /dev/null +++ b/subsys/power/device_pm.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_PM_LOG_LEVEL /* From power module Kconfig */ +#include +LOG_MODULE_DECLARE(power); + +/* Device PM request type */ +#define DEVICE_PM_SYNC (0 << 0) +#define DEVICE_PM_ASYNC (1 << 0) + +static void device_pm_callback(struct device *dev, + int retval, void *context, void *arg) +{ + __ASSERT(retval == 0, "Device set power state failed"); + + /* Set the fsm_state */ + if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) { + atomic_set(&dev->config->pm->fsm_state, + DEVICE_PM_FSM_STATE_ACTIVE); + } else { + atomic_set(&dev->config->pm->fsm_state, + DEVICE_PM_FSM_STATE_SUSPENDED); + } + + k_work_submit(&dev->config->pm->work); +} + +static void pm_work_handler(struct k_work *work) +{ + struct device_pm *pm = CONTAINER_OF(work, + struct device_pm, work); + struct device *dev = pm->dev; + int ret = 0; + u8_t pm_state; + + switch (atomic_get(&dev->config->pm->fsm_state)) { + case DEVICE_PM_FSM_STATE_ACTIVE: + if ((atomic_get(&dev->config->pm->usage) == 0) && + dev->config->pm->enable) { + atomic_set(&dev->config->pm->fsm_state, + DEVICE_PM_FSM_STATE_SUSPENDING); + ret = device_set_power_state(dev, + DEVICE_PM_SUSPEND_STATE, + device_pm_callback, NULL); + } else { + pm_state = DEVICE_PM_ACTIVE_STATE; + goto fsm_out; + } + break; + case DEVICE_PM_FSM_STATE_SUSPENDED: + if ((atomic_get(&dev->config->pm->usage) > 0) || + !dev->config->pm->enable) { + atomic_set(&dev->config->pm->fsm_state, + DEVICE_PM_FSM_STATE_RESUMING); + ret = device_set_power_state(dev, + DEVICE_PM_ACTIVE_STATE, + device_pm_callback, NULL); + } else { + pm_state = DEVICE_PM_SUSPEND_STATE; + goto fsm_out; + } + break; + case DEVICE_PM_FSM_STATE_SUSPENDING: + case DEVICE_PM_FSM_STATE_RESUMING: + /* Do nothing: We are waiting for device_pm_callback() */ + break; + default: + LOG_ERR("Invalid FSM state!!\n"); + } + + __ASSERT(ret == 0, "Set Power state error"); + + return; + +fsm_out: + k_poll_signal_raise(&dev->config->pm->signal, pm_state); +} + +static int device_pm_request(struct device *dev, + u32_t target_state, u32_t pm_flags) +{ + int result, signaled = 0; + + __ASSERT((target_state == DEVICE_PM_ACTIVE_STATE) || + (target_state == DEVICE_PM_SUSPEND_STATE), + "Invalid device PM state requested"); + + if (target_state == DEVICE_PM_ACTIVE_STATE) { + if (atomic_inc(&dev->config->pm->usage) < 0) { + return 0; + } + } else { + if (atomic_dec(&dev->config->pm->usage) > 1) { + return 0; + } + } + + k_work_submit(&dev->config->pm->work); + + /* Return in case of Async request */ + if (pm_flags & DEVICE_PM_ASYNC) { + return 0; + } + + /* Incase of Sync request wait for completion event */ + do { + (void)k_poll(&dev->config->pm->event, 1, K_FOREVER); + k_poll_signal_check(&dev->config->pm->signal, + &signaled, &result); + } while (!signaled); + + dev->config->pm->event.state = K_POLL_STATE_NOT_READY; + k_poll_signal_reset(&dev->config->pm->signal); + + + return result == target_state ? 0 : -EIO; +} + +int device_pm_get(struct device *dev) +{ + return device_pm_request(dev, + DEVICE_PM_ACTIVE_STATE, DEVICE_PM_ASYNC); +} + +int device_pm_get_sync(struct device *dev) +{ + return device_pm_request(dev, DEVICE_PM_ACTIVE_STATE, 0); +} + +int device_pm_put(struct device *dev) +{ + return device_pm_request(dev, + DEVICE_PM_SUSPEND_STATE, DEVICE_PM_ASYNC); +} + +int device_pm_put_sync(struct device *dev) +{ + return device_pm_request(dev, DEVICE_PM_SUSPEND_STATE, 0); +} + +void device_pm_enable(struct device *dev) +{ + k_sem_take(&dev->config->pm->lock, K_FOREVER); + dev->config->pm->enable = true; + + /* During the driver init, device can set the + * PM state accordingly. For later cases we need + * to check the usage and set the device PM state. + */ + if (!dev->config->pm->dev) { + dev->config->pm->dev = dev; + atomic_set(&dev->config->pm->fsm_state, + DEVICE_PM_FSM_STATE_SUSPENDED); + k_work_init(&dev->config->pm->work, pm_work_handler); + } else { + k_work_submit(&dev->config->pm->work); + } + k_sem_give(&dev->config->pm->lock); +} + +void device_pm_disable(struct device *dev) +{ + k_sem_take(&dev->config->pm->lock, K_FOREVER); + dev->config->pm->enable = false; + /* Bring up the device before disabling the Idle PM */ + k_work_submit(&dev->config->pm->work); + k_sem_give(&dev->config->pm->lock); +}