diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-devices-power | 31 | ||||
| -rw-r--r-- | drivers/base/power/power.h | 6 | ||||
| -rw-r--r-- | drivers/base/power/qos.c | 168 | ||||
| -rw-r--r-- | drivers/base/power/sysfs.c | 94 | ||||
| -rw-r--r-- | include/linux/pm.h | 1 | ||||
| -rw-r--r-- | include/linux/pm_qos.h | 26 | 
6 files changed, 278 insertions, 48 deletions
diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power index 45000f0db4d..7fc2997b23a 100644 --- a/Documentation/ABI/testing/sysfs-devices-power +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -204,3 +204,34 @@ Description:  		This attribute has no effect on system-wide suspend/resume and  		hibernation. + +What:		/sys/devices/.../power/pm_qos_no_power_off +Date:		September 2012 +Contact:	Rafael J. Wysocki <rjw@sisk.pl> +Description: +		The /sys/devices/.../power/pm_qos_no_power_off attribute +		is used for manipulating the PM QoS "no power off" flag.  If +		set, this flag indicates to the kernel that power should not +		be removed entirely from the device. + +		Not all drivers support this attribute.  If it isn't supported, +		it is not present. + +		This attribute has no effect on system-wide suspend/resume and +		hibernation. + +What:		/sys/devices/.../power/pm_qos_remote_wakeup +Date:		September 2012 +Contact:	Rafael J. Wysocki <rjw@sisk.pl> +Description: +		The /sys/devices/.../power/pm_qos_remote_wakeup attribute +		is used for manipulating the PM QoS "remote wakeup required" +		flag.  If set, this flag indicates to the kernel that the +		device is a source of user events that have to be signaled from +		its low-power states. + +		Not all drivers support this attribute.  If it isn't supported, +		it is not present. + +		This attribute has no effect on system-wide suspend/resume and +		hibernation. diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index 0dbfdf4419a..b16686a0a5a 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -93,8 +93,10 @@ extern void dpm_sysfs_remove(struct device *dev);  extern void rpm_sysfs_remove(struct device *dev);  extern int wakeup_sysfs_add(struct device *dev);  extern void wakeup_sysfs_remove(struct device *dev); -extern int pm_qos_sysfs_add(struct device *dev); -extern void pm_qos_sysfs_remove(struct device *dev); +extern int pm_qos_sysfs_add_latency(struct device *dev); +extern void pm_qos_sysfs_remove_latency(struct device *dev); +extern int pm_qos_sysfs_add_flags(struct device *dev); +extern void pm_qos_sysfs_remove_flags(struct device *dev);  #else /* CONFIG_PM */ diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 3c66f75d14b..167834dcc82 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -40,6 +40,7 @@  #include <linux/device.h>  #include <linux/mutex.h>  #include <linux/export.h> +#include <linux/pm_runtime.h>  #include "power.h" @@ -322,6 +323,37 @@ int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,  EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);  /** + * __dev_pm_qos_update_request - Modify an existing device PM QoS request. + * @req : PM QoS request to modify. + * @new_value: New value to request. + */ +static int __dev_pm_qos_update_request(struct dev_pm_qos_request *req, +				       s32 new_value) +{ +	s32 curr_value; +	int ret = 0; + +	if (!req->dev->power.qos) +		return -ENODEV; + +	switch(req->type) { +	case DEV_PM_QOS_LATENCY: +		curr_value = req->data.pnode.prio; +		break; +	case DEV_PM_QOS_FLAGS: +		curr_value = req->data.flr.flags; +		break; +	default: +		return -EINVAL; +	} + +	if (curr_value != new_value) +		ret = apply_constraint(req, PM_QOS_UPDATE_REQ, new_value); + +	return ret; +} + +/**   * dev_pm_qos_update_request - modifies an existing qos request   * @req : handle to list element holding a dev_pm_qos request to use   * @new_value: defines the qos request @@ -336,11 +368,9 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);   * -EINVAL in case of wrong parameters, -ENODEV if the device has been   * removed from the system   */ -int dev_pm_qos_update_request(struct dev_pm_qos_request *req, -			      s32 new_value) +int dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value)  { -	s32 curr_value; -	int ret = 0; +	int ret;  	if (!req) /*guard against callers passing in null */  		return -EINVAL; @@ -350,29 +380,9 @@ int dev_pm_qos_update_request(struct dev_pm_qos_request *req,  		return -EINVAL;  	mutex_lock(&dev_pm_qos_mtx); - -	if (!req->dev->power.qos) { -		ret = -ENODEV; -		goto out; -	} - -	switch(req->type) { -	case DEV_PM_QOS_LATENCY: -		curr_value = req->data.pnode.prio; -		break; -	case DEV_PM_QOS_FLAGS: -		curr_value = req->data.flr.flags; -		break; -	default: -		ret = -EINVAL; -		goto out; -	} - -	if (curr_value != new_value) -		ret = apply_constraint(req, PM_QOS_UPDATE_REQ, new_value); - - out: +	__dev_pm_qos_update_request(req, new_value);  	mutex_unlock(&dev_pm_qos_mtx); +  	return ret;  }  EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); @@ -533,10 +543,19 @@ int dev_pm_qos_add_ancestor_request(struct device *dev,  EXPORT_SYMBOL_GPL(dev_pm_qos_add_ancestor_request);  #ifdef CONFIG_PM_RUNTIME -static void __dev_pm_qos_drop_user_request(struct device *dev) +static void __dev_pm_qos_drop_user_request(struct device *dev, +					   enum dev_pm_qos_req_type type)  { -	dev_pm_qos_remove_request(dev->power.pq_req); -	dev->power.pq_req = NULL; +	switch(type) { +	case DEV_PM_QOS_LATENCY: +		dev_pm_qos_remove_request(dev->power.qos->latency_req); +		dev->power.qos->latency_req = NULL; +		break; +	case DEV_PM_QOS_FLAGS: +		dev_pm_qos_remove_request(dev->power.qos->flags_req); +		dev->power.qos->flags_req = NULL; +		break; +	}  }  /** @@ -552,7 +571,7 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)  	if (!device_is_registered(dev) || value < 0)  		return -EINVAL; -	if (dev->power.pq_req) +	if (dev->power.qos && dev->power.qos->latency_req)  		return -EEXIST;  	req = kzalloc(sizeof(*req), GFP_KERNEL); @@ -563,10 +582,10 @@ int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)  	if (ret < 0)  		return ret; -	dev->power.pq_req = req; -	ret = pm_qos_sysfs_add(dev); +	dev->power.qos->latency_req = req; +	ret = pm_qos_sysfs_add_latency(dev);  	if (ret) -		__dev_pm_qos_drop_user_request(dev); +		__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_LATENCY);  	return ret;  } @@ -578,10 +597,87 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_expose_latency_limit);   */  void dev_pm_qos_hide_latency_limit(struct device *dev)  { -	if (dev->power.pq_req) { -		pm_qos_sysfs_remove(dev); -		__dev_pm_qos_drop_user_request(dev); +	if (dev->power.qos && dev->power.qos->latency_req) { +		pm_qos_sysfs_remove_latency(dev); +		__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_LATENCY);  	}  }  EXPORT_SYMBOL_GPL(dev_pm_qos_hide_latency_limit); + +/** + * dev_pm_qos_expose_flags - Expose PM QoS flags of a device to user space. + * @dev: Device whose PM QoS flags are to be exposed to user space. + * @val: Initial values of the flags. + */ +int dev_pm_qos_expose_flags(struct device *dev, s32 val) +{ +	struct dev_pm_qos_request *req; +	int ret; + +	if (!device_is_registered(dev)) +		return -EINVAL; + +	if (dev->power.qos && dev->power.qos->flags_req) +		return -EEXIST; + +	req = kzalloc(sizeof(*req), GFP_KERNEL); +	if (!req) +		return -ENOMEM; + +	ret = dev_pm_qos_add_request(dev, req, DEV_PM_QOS_FLAGS, val); +	if (ret < 0) +		return ret; + +	dev->power.qos->flags_req = req; +	ret = pm_qos_sysfs_add_flags(dev); +	if (ret) +		__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS); + +	return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_expose_flags); + +/** + * dev_pm_qos_hide_flags - Hide PM QoS flags of a device from user space. + * @dev: Device whose PM QoS flags are to be hidden from user space. + */ +void dev_pm_qos_hide_flags(struct device *dev) +{ +	if (dev->power.qos && dev->power.qos->flags_req) { +		pm_qos_sysfs_remove_flags(dev); +		__dev_pm_qos_drop_user_request(dev, DEV_PM_QOS_FLAGS); +	} +} +EXPORT_SYMBOL_GPL(dev_pm_qos_hide_flags); + +/** + * dev_pm_qos_update_flags - Update PM QoS flags request owned by user space. + * @dev: Device to update the PM QoS flags request for. + * @mask: Flags to set/clear. + * @set: Whether to set or clear the flags (true means set). + */ +int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set) +{ +	s32 value; +	int ret; + +	if (!dev->power.qos || !dev->power.qos->flags_req) +		return -EINVAL; + +	pm_runtime_get_sync(dev); +	mutex_lock(&dev_pm_qos_mtx); + +	value = dev_pm_qos_requested_flags(dev); +	if (set) +		value |= mask; +	else +		value &= ~mask; + +	ret = __dev_pm_qos_update_request(dev->power.qos->flags_req, value); + +	mutex_unlock(&dev_pm_qos_mtx); +	pm_runtime_put(dev); + +	return ret; +}  #endif /* CONFIG_PM_RUNTIME */ diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 54c61ffa204..50d16e3cb0a 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -221,7 +221,7 @@ static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show,  static ssize_t pm_qos_latency_show(struct device *dev,  				   struct device_attribute *attr, char *buf)  { -	return sprintf(buf, "%d\n", dev->power.pq_req->data.pnode.prio); +	return sprintf(buf, "%d\n", dev_pm_qos_requested_latency(dev));  }  static ssize_t pm_qos_latency_store(struct device *dev, @@ -237,12 +237,66 @@ static ssize_t pm_qos_latency_store(struct device *dev,  	if (value < 0)  		return -EINVAL; -	ret = dev_pm_qos_update_request(dev->power.pq_req, value); +	ret = dev_pm_qos_update_request(dev->power.qos->latency_req, value);  	return ret < 0 ? ret : n;  }  static DEVICE_ATTR(pm_qos_resume_latency_us, 0644,  		   pm_qos_latency_show, pm_qos_latency_store); + +static ssize_t pm_qos_no_power_off_show(struct device *dev, +					struct device_attribute *attr, +					char *buf) +{ +	return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev) +					& PM_QOS_FLAG_NO_POWER_OFF)); +} + +static ssize_t pm_qos_no_power_off_store(struct device *dev, +					 struct device_attribute *attr, +					 const char *buf, size_t n) +{ +	int ret; + +	if (kstrtoint(buf, 0, &ret)) +		return -EINVAL; + +	if (ret != 0 && ret != 1) +		return -EINVAL; + +	ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_NO_POWER_OFF, ret); +	return ret < 0 ? ret : n; +} + +static DEVICE_ATTR(pm_qos_no_power_off, 0644, +		   pm_qos_no_power_off_show, pm_qos_no_power_off_store); + +static ssize_t pm_qos_remote_wakeup_show(struct device *dev, +					 struct device_attribute *attr, +					 char *buf) +{ +	return sprintf(buf, "%d\n", !!(dev_pm_qos_requested_flags(dev) +					& PM_QOS_FLAG_REMOTE_WAKEUP)); +} + +static ssize_t pm_qos_remote_wakeup_store(struct device *dev, +					  struct device_attribute *attr, +					  const char *buf, size_t n) +{ +	int ret; + +	if (kstrtoint(buf, 0, &ret)) +		return -EINVAL; + +	if (ret != 0 && ret != 1) +		return -EINVAL; + +	ret = dev_pm_qos_update_flags(dev, PM_QOS_FLAG_REMOTE_WAKEUP, ret); +	return ret < 0 ? ret : n; +} + +static DEVICE_ATTR(pm_qos_remote_wakeup, 0644, +		   pm_qos_remote_wakeup_show, pm_qos_remote_wakeup_store);  #endif /* CONFIG_PM_RUNTIME */  #ifdef CONFIG_PM_SLEEP @@ -564,15 +618,27 @@ static struct attribute_group pm_runtime_attr_group = {  	.attrs	= runtime_attrs,  }; -static struct attribute *pm_qos_attrs[] = { +static struct attribute *pm_qos_latency_attrs[] = {  #ifdef CONFIG_PM_RUNTIME  	&dev_attr_pm_qos_resume_latency_us.attr,  #endif /* CONFIG_PM_RUNTIME */  	NULL,  }; -static struct attribute_group pm_qos_attr_group = { +static struct attribute_group pm_qos_latency_attr_group = {  	.name	= power_group_name, -	.attrs	= pm_qos_attrs, +	.attrs	= pm_qos_latency_attrs, +}; + +static struct attribute *pm_qos_flags_attrs[] = { +#ifdef CONFIG_PM_RUNTIME +	&dev_attr_pm_qos_no_power_off.attr, +	&dev_attr_pm_qos_remote_wakeup.attr, +#endif /* CONFIG_PM_RUNTIME */ +	NULL, +}; +static struct attribute_group pm_qos_flags_attr_group = { +	.name	= power_group_name, +	.attrs	= pm_qos_flags_attrs,  };  int dpm_sysfs_add(struct device *dev) @@ -615,14 +681,24 @@ void wakeup_sysfs_remove(struct device *dev)  	sysfs_unmerge_group(&dev->kobj, &pm_wakeup_attr_group);  } -int pm_qos_sysfs_add(struct device *dev) +int pm_qos_sysfs_add_latency(struct device *dev) +{ +	return sysfs_merge_group(&dev->kobj, &pm_qos_latency_attr_group); +} + +void pm_qos_sysfs_remove_latency(struct device *dev) +{ +	sysfs_unmerge_group(&dev->kobj, &pm_qos_latency_attr_group); +} + +int pm_qos_sysfs_add_flags(struct device *dev)  { -	return sysfs_merge_group(&dev->kobj, &pm_qos_attr_group); +	return sysfs_merge_group(&dev->kobj, &pm_qos_flags_attr_group);  } -void pm_qos_sysfs_remove(struct device *dev) +void pm_qos_sysfs_remove_flags(struct device *dev)  { -	sysfs_unmerge_group(&dev->kobj, &pm_qos_attr_group); +	sysfs_unmerge_group(&dev->kobj, &pm_qos_flags_attr_group);  }  void rpm_sysfs_remove(struct device *dev) diff --git a/include/linux/pm.h b/include/linux/pm.h index 0ce6df94221..03d7bb14531 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -546,7 +546,6 @@ struct dev_pm_info {  	unsigned long		active_jiffies;  	unsigned long		suspended_jiffies;  	unsigned long		accounting_timestamp; -	struct dev_pm_qos_request *pq_req;  #endif  	struct pm_subsys_data	*subsys_data;  /* Owned by the subsystem. */  	struct dev_pm_qos	*qos; diff --git a/include/linux/pm_qos.h b/include/linux/pm_qos.h index 3af7d8573c2..5a95013905c 100644 --- a/include/linux/pm_qos.h +++ b/include/linux/pm_qos.h @@ -34,6 +34,9 @@ enum pm_qos_flags_status {  #define PM_QOS_NETWORK_THROUGHPUT_DEFAULT_VALUE	0  #define PM_QOS_DEV_LAT_DEFAULT_VALUE		0 +#define PM_QOS_FLAG_NO_POWER_OFF	(1 << 0) +#define PM_QOS_FLAG_REMOTE_WAKEUP	(1 << 1) +  struct pm_qos_request {  	struct plist_node node;  	int pm_qos_class; @@ -86,6 +89,8 @@ struct pm_qos_flags {  struct dev_pm_qos {  	struct pm_qos_constraints latency;  	struct pm_qos_flags flags; +	struct dev_pm_qos_request *latency_req; +	struct dev_pm_qos_request *flags_req;  };  /* Action requested to pm_qos_update_target */ @@ -187,10 +192,31 @@ static inline int dev_pm_qos_add_ancestor_request(struct device *dev,  #ifdef CONFIG_PM_RUNTIME  int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value);  void dev_pm_qos_hide_latency_limit(struct device *dev); +int dev_pm_qos_expose_flags(struct device *dev, s32 value); +void dev_pm_qos_hide_flags(struct device *dev); +int dev_pm_qos_update_flags(struct device *dev, s32 mask, bool set); + +static inline s32 dev_pm_qos_requested_latency(struct device *dev) +{ +	return dev->power.qos->latency_req->data.pnode.prio; +} + +static inline s32 dev_pm_qos_requested_flags(struct device *dev) +{ +	return dev->power.qos->flags_req->data.flr.flags; +}  #else  static inline int dev_pm_qos_expose_latency_limit(struct device *dev, s32 value)  			{ return 0; }  static inline void dev_pm_qos_hide_latency_limit(struct device *dev) {} +static inline int dev_pm_qos_expose_flags(struct device *dev, s32 value) +			{ return 0; } +static inline void dev_pm_qos_hide_flags(struct device *dev) {} +static inline int dev_pm_qos_update_flags(struct device *dev, s32 m, bool set) +			{ return 0; } + +static inline s32 dev_pm_qos_requested_latency(struct device *dev) { return 0; } +static inline s32 dev_pm_qos_requested_flags(struct device *dev) { return 0; }  #endif  #endif  |