/*
* drivers/base/power/runtime.c - Helper functions for device run-time PM
*
* Copyright (c) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
*
* This file is released under the GPLv2.
*/
#include <linux/sched.h>
#include <linux/pm_runtime.h>
#include <linux/jiffies.h>
static int __pm_runtime_resume(struct device *dev, bool from_wq);
static int __pm_request_idle(struct device *dev);
static int __pm_request_resume(struct device *dev);
/**
* pm_runtime_deactivate_timer - Deactivate given device's suspend timer.
* @dev: Device to handle.
*/
static void pm_runtime_deactivate_timer(struct device *dev)
{
if (dev->power.timer_expires > 0) {
del_timer(&dev->power.suspend_timer);
dev->power.timer_expires = 0;
}
}
/**
* pm_runtime_cancel_pending - Deactivate suspend timer and cancel requests.
* @dev: Device to handle.
*/
static void pm_runtime_cancel_pending(struct device *dev)
{
pm_runtime_deactivate_timer(dev);
/*
* In case there's a request pending, make sure its work function will
* return without doing anything.
*/
dev->power.request = RPM_REQ_NONE;
}
/**
* __pm_runtime_idle - Notify device bus type if the device can be suspended.
* @dev: Device to notify the bus type about.
*
* This function must be called under dev->power.lock with interrupts disabled.
*/
static int __pm_runtime_idle(struct device *dev)
__releases(&dev->power.lock) __acquires(&dev->power.lock)
{
int retval = 0;
dev_dbg(dev, "__pm_runtime_idle()!\n");
if (dev->power.runtime_error)
retval = -EINVAL;
else if (dev->power.idle_notification)
retval = -EINPROGRESS;
else if (atomic_read(&dev->power.usage_count) > 0
|| dev->power.disable_depth > 0
|| dev->power.runtime_status != RPM_ACTIVE)
retval = -EAGAIN;
else if (!pm_children_suspended(dev))
retval = -EBUSY;
if (retval)
goto out;
if (dev->power.request_pending) {
/*
* If an idle notification request is pending, cancel it. Any
* other pending request takes precedence over us.
*/
if (dev->power.request == RPM_REQ_IDLE) {
dev->power.request = RPM_REQ_NONE;
} else if (dev->power.request != RPM_REQ_NONE) {
retval = -EAGAIN;
goto out;
}
}
dev->power.idle_notification = true;
if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_idle) {
spin_unlock_irq(&dev->power.lock);
dev->bus->pm->runtime_idle(dev);
spin_lock_irq(&dev->power.lock);
}
dev->power.idle_notification = false;
wake_up_all(&dev->power.wait_queue);
out:
dev_dbg(dev, "__pm_runtime_idle() returns %d!\n", retval);
return retval;
}
/**
* pm_runtime_idle - Notify device bus type if the device can be suspended.
* @dev: Device to notify the bus type about.
*/
int pm_runtime_idle(struct device *dev)
{
int retval;
spin_lock_irq(&dev->power.lock);
retval = __pm_runtime_idle(dev);
spin_unlock_irq(&dev->power.lock);
return retval;
}
EXPORT_SYMBOL_GPL(pm_runtime_idle);
/**
* __pm_runtime_suspend - Carry out run-time suspend of given device.
* @dev: Device to suspend.
* @from_wq: If set, the function has been called via pm_wq.
*
* Check if the device can be suspended and run the ->runtime_suspend() callback
* provided by its bus type. If another suspend has been started earlier, wait
* for it to finish. If an idle notification or suspend request is pending or
* scheduled, cancel it.
*
* This function must be called under dev->power.lock with interrupts disabled.
*/
int __pm_runtime_suspend(struct device *dev, bool from_wq)
__releases(&dev->power.lock) __acquires(&dev->power.lock)
{
struct device *parent = NULL;
bool notify = false;
int retval = 0;
dev_dbg(dev, "__pm_runtime_suspend()%s!\n",
from_wq ? " from workqueue" : "");
repeat:
if (dev->power.runtime_error) {
retval = -EINVAL;
goto out;
}
/* Pending resume requests take precedence over us. */
if (dev->power.request_pending
&& dev->power.request == RPM_REQ_RESUME) {
retval = -EAGAIN;
goto out;
}
/* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);
if (dev->power.runtime_status == RPM_SUSPENDED)
retval =