diff options
| -rw-r--r-- | drivers/base/power/qos.c | 24 | ||||
| -rw-r--r-- | drivers/base/power/runtime.c | 148 | ||||
| -rw-r--r-- | include/linux/pm.h | 2 | ||||
| -rw-r--r-- | include/linux/pm_qos.h | 3 | ||||
| -rw-r--r-- | include/linux/pm_runtime.h | 5 | 
5 files changed, 154 insertions, 28 deletions
diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 86de6c50fc4..03f4bd069ca 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -47,21 +47,29 @@ static DEFINE_MUTEX(dev_pm_qos_mtx);  static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);  /** - * dev_pm_qos_read_value - Get PM QoS constraint for a given device. + * __dev_pm_qos_read_value - Get PM QoS constraint for a given device. + * @dev: Device to get the PM QoS constraint value for. + * + * This routine must be called with dev->power.lock held. + */ +s32 __dev_pm_qos_read_value(struct device *dev) +{ +	struct pm_qos_constraints *c = dev->power.constraints; + +	return c ? pm_qos_read_value(c) : 0; +} + +/** + * dev_pm_qos_read_value - Get PM QoS constraint for a given device (locked).   * @dev: Device to get the PM QoS constraint value for.   */  s32 dev_pm_qos_read_value(struct device *dev)  { -	struct pm_qos_constraints *c;  	unsigned long flags; -	s32 ret = 0; +	s32 ret;  	spin_lock_irqsave(&dev->power.lock, flags); - -	c = dev->power.constraints; -	if (c) -		ret = pm_qos_read_value(c); - +	ret = __dev_pm_qos_read_value(dev);  	spin_unlock_irqrestore(&dev->power.lock, flags);  	return ret; diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 8c78443bca8..068f7ed1f00 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -279,6 +279,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 +346,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 +442,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) @@ -414,27 +486,9 @@ static int rpm_suspend(struct device *dev, int rpmflags)  		callback = NULL;  	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 +520,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 +697,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) @@ -1279,6 +1359,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 +1379,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); +} diff --git a/include/linux/pm.h b/include/linux/pm.h index 3f3ed83a9aa..a7676efa683 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -521,6 +521,8 @@ struct dev_pm_info {  	unsigned long		active_jiffies;  	unsigned long		suspended_jiffies;  	unsigned long		accounting_timestamp; +	ktime_t			suspend_time; +	s64			max_time_suspended_ns;  #endif  	struct pm_subsys_data	*subsys_data;  /* Owned by the subsystem. */  	struct pm_qos_constraints *constraints; diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 83b0ea302a8..775a3236343 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -78,6 +78,7 @@ int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier);  int pm_qos_request_active(struct pm_qos_request *req);  s32 pm_qos_read_value(struct pm_qos_constraints *c); +s32 __dev_pm_qos_read_value(struct device *dev);  s32 dev_pm_qos_read_value(struct device *dev);  int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,  			   s32 value); @@ -119,6 +120,8 @@ static inline int pm_qos_request_active(struct pm_qos_request *req)  static inline s32 pm_qos_read_value(struct pm_qos_constraints *c)  			{ return 0; } +static inline s32 __dev_pm_qos_read_value(struct device *dev) +			{ return 0; }  static inline s32 dev_pm_qos_read_value(struct device *dev)  			{ return 0; }  static inline int dev_pm_qos_add_request(struct device *dev, diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index d3085e72a0e..609daae7a01 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -45,6 +45,8 @@ extern void pm_runtime_irq_safe(struct device *dev);  extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);  extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);  extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev); +extern void pm_runtime_update_max_time_suspended(struct device *dev, +						 s64 delta_ns);  static inline bool pm_children_suspended(struct device *dev)  { @@ -148,6 +150,9 @@ static inline void pm_runtime_set_autosuspend_delay(struct device *dev,  static inline unsigned long pm_runtime_autosuspend_expiration(  				struct device *dev) { return 0; } +static inline void pm_runtime_update_max_time_suspended(struct device *dev, +							s64 delta_ns) {} +  #endif /* !CONFIG_PM_RUNTIME */  static inline int pm_runtime_idle(struct device *dev)  |