diff options
Diffstat (limited to 'drivers/base/power/runtime.c')
| -rw-r--r-- | drivers/base/power/runtime.c | 157 | 
1 files changed, 137 insertions, 20 deletions
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 8c78443bca8..541f821d4ea 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -250,6 +250,9 @@ static int rpm_idle(struct device *dev, int rpmflags)  	else  		callback = NULL; +	if (!callback && dev->driver && dev->driver->pm) +		callback = dev->driver->pm->runtime_idle; +  	if (callback)  		__rpm_callback(callback, dev); @@ -279,6 +282,47 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)  	return retval != -EACCES ? retval : -EIO;  } +struct rpm_qos_data { +	ktime_t time_now; +	s64 constraint_ns; +}; + +/** + * rpm_update_qos_constraint - Update a given PM QoS constraint data. + * @dev: Device whose timing data to use. + * @data: PM QoS constraint data to update. + * + * Use the suspend timing data of @dev to update PM QoS constraint data pointed + * to by @data. + */ +static int rpm_update_qos_constraint(struct device *dev, void *data) +{ +	struct rpm_qos_data *qos = data; +	unsigned long flags; +	s64 delta_ns; +	int ret = 0; + +	spin_lock_irqsave(&dev->power.lock, flags); + +	if (dev->power.max_time_suspended_ns < 0) +		goto out; + +	delta_ns = dev->power.max_time_suspended_ns - +		ktime_to_ns(ktime_sub(qos->time_now, dev->power.suspend_time)); +	if (delta_ns <= 0) { +		ret = -EBUSY; +		goto out; +	} + +	if (qos->constraint_ns > delta_ns || qos->constraint_ns == 0) +		qos->constraint_ns = delta_ns; + + out: +	spin_unlock_irqrestore(&dev->power.lock, flags); + +	return ret; +} +  /**   * rpm_suspend - Carry out runtime suspend of given device.   * @dev: Device to suspend. @@ -305,6 +349,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)  {  	int (*callback)(struct device *);  	struct device *parent = NULL; +	struct rpm_qos_data qos;  	int retval;  	trace_rpm_suspend(dev, rpmflags); @@ -400,8 +445,38 @@ static int rpm_suspend(struct device *dev, int rpmflags)  		goto out;  	} +	qos.constraint_ns = __dev_pm_qos_read_value(dev); +	if (qos.constraint_ns < 0) { +		/* Negative constraint means "never suspend". */ +		retval = -EPERM; +		goto out; +	} +	qos.constraint_ns *= NSEC_PER_USEC; +	qos.time_now = ktime_get(); +  	__update_runtime_status(dev, RPM_SUSPENDING); +	if (!dev->power.ignore_children) { +		if (dev->power.irq_safe) +			spin_unlock(&dev->power.lock); +		else +			spin_unlock_irq(&dev->power.lock); + +		retval = device_for_each_child(dev, &qos, +					       rpm_update_qos_constraint); + +		if (dev->power.irq_safe) +			spin_lock(&dev->power.lock); +		else +			spin_lock_irq(&dev->power.lock); + +		if (retval) +			goto fail; +	} + +	dev->power.suspend_time = qos.time_now; +	dev->power.max_time_suspended_ns = qos.constraint_ns ? : -1; +  	if (dev->pm_domain)  		callback = dev->pm_domain->ops.runtime_suspend;  	else if (dev->type && dev->type->pm) @@ -413,28 +488,13 @@ static int rpm_suspend(struct device *dev, int rpmflags)  	else  		callback = NULL; +	if (!callback && dev->driver && dev->driver->pm) +		callback = dev->driver->pm->runtime_suspend; +  	retval = rpm_callback(callback, dev); -	if (retval) { -		__update_runtime_status(dev, RPM_ACTIVE); -		dev->power.deferred_resume = false; -		if (retval == -EAGAIN || retval == -EBUSY) { -			dev->power.runtime_error = 0; +	if (retval) +		goto fail; -			/* -			 * If the callback routine failed an autosuspend, and -			 * if the last_busy time has been updated so that there -			 * is a new autosuspend expiration time, automatically -			 * reschedule another autosuspend. -			 */ -			if ((rpmflags & RPM_AUTO) && -			    pm_runtime_autosuspend_expiration(dev) != 0) -				goto repeat; -		} else { -			pm_runtime_cancel_pending(dev); -		} -		wake_up_all(&dev->power.wait_queue); -		goto out; -	}   no_callback:  	__update_runtime_status(dev, RPM_SUSPENDED);  	pm_runtime_deactivate_timer(dev); @@ -466,6 +526,29 @@ static int rpm_suspend(struct device *dev, int rpmflags)  	trace_rpm_return_int(dev, _THIS_IP_, retval);  	return retval; + + fail: +	__update_runtime_status(dev, RPM_ACTIVE); +	dev->power.suspend_time = ktime_set(0, 0); +	dev->power.max_time_suspended_ns = -1; +	dev->power.deferred_resume = false; +	if (retval == -EAGAIN || retval == -EBUSY) { +		dev->power.runtime_error = 0; + +		/* +		 * If the callback routine failed an autosuspend, and +		 * if the last_busy time has been updated so that there +		 * is a new autosuspend expiration time, automatically +		 * reschedule another autosuspend. +		 */ +		if ((rpmflags & RPM_AUTO) && +		    pm_runtime_autosuspend_expiration(dev) != 0) +			goto repeat; +	} else { +		pm_runtime_cancel_pending(dev); +	} +	wake_up_all(&dev->power.wait_queue); +	goto out;  }  /** @@ -620,6 +703,9 @@ static int rpm_resume(struct device *dev, int rpmflags)  	if (dev->power.no_callbacks)  		goto no_callback;	/* Assume success. */ +	dev->power.suspend_time = ktime_set(0, 0); +	dev->power.max_time_suspended_ns = -1; +  	__update_runtime_status(dev, RPM_RESUMING);  	if (dev->pm_domain) @@ -633,6 +719,9 @@ static int rpm_resume(struct device *dev, int rpmflags)  	else  		callback = NULL; +	if (!callback && dev->driver && dev->driver->pm) +		callback = dev->driver->pm->runtime_resume; +  	retval = rpm_callback(callback, dev);  	if (retval) {  		__update_runtime_status(dev, RPM_SUSPENDED); @@ -1279,6 +1368,9 @@ void pm_runtime_init(struct device *dev)  	setup_timer(&dev->power.suspend_timer, pm_suspend_timer_fn,  			(unsigned long)dev); +	dev->power.suspend_time = ktime_set(0, 0); +	dev->power.max_time_suspended_ns = -1; +  	init_waitqueue_head(&dev->power.wait_queue);  } @@ -1296,3 +1388,28 @@ void pm_runtime_remove(struct device *dev)  	if (dev->power.irq_safe && dev->parent)  		pm_runtime_put_sync(dev->parent);  } + +/** + * pm_runtime_update_max_time_suspended - Update device's suspend time data. + * @dev: Device to handle. + * @delta_ns: Value to subtract from the device's max_time_suspended_ns field. + * + * Update the device's power.max_time_suspended_ns field by subtracting + * @delta_ns from it.  The resulting value of power.max_time_suspended_ns is + * never negative. + */ +void pm_runtime_update_max_time_suspended(struct device *dev, s64 delta_ns) +{ +	unsigned long flags; + +	spin_lock_irqsave(&dev->power.lock, flags); + +	if (delta_ns > 0 && dev->power.max_time_suspended_ns > 0) { +		if (dev->power.max_time_suspended_ns > delta_ns) +			dev->power.max_time_suspended_ns -= delta_ns; +		else +			dev->power.max_time_suspended_ns = 0; +	} + +	spin_unlock_irqrestore(&dev->power.lock, flags); +}  |