blob: 3e1ee4b816f9ea86cec9dcd1a535aa484f245a33 [file] [log] [blame]
/*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <kernel.h>
#include <device.h>
#include <misc/__assert.h>
#define LOG_LEVEL CONFIG_SYS_PM_LOG_LEVEL /* From power module Kconfig */
#include <logging/log.h>
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);
}