| /* |
| * 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); |
| } |