aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/devfreq
diff options
context:
space:
mode:
authorLeonard Crestez <leonard.crestez@nxp.com>2019-12-05 12:05:07 +0200
committerChanwoo Choi <cw00.choi@samsung.com>2019-12-09 12:19:16 +0900
commit27dbc542f651ed09de910f274b32634904103774 (patch)
treea74b9e5196e4c460a8d5ddc130011d84e9d9bae4 /drivers/devfreq
parentPM / devfreq: Add PM QoS support (diff)
downloadlinux-dev-27dbc542f651ed09de910f274b32634904103774.tar.xz
linux-dev-27dbc542f651ed09de910f274b32634904103774.zip
PM / devfreq: Use PM QoS for sysfs min/max_freq
Switch the handling of min_freq and max_freq from sysfs to use the dev_pm_qos_request interface. Since PM QoS handles frequencies as kHz this change reduces the precision of min_freq and max_freq. This shouldn't introduce problems because frequencies which are not an integer number of kHz are likely not an integer number of Hz either. Try to ensure compatibility by rounding min values down and rounding max values up. Signed-off-by: Leonard Crestez <leonard.crestez@nxp.com> Acked-by: Chanwoo Choi <cw00.choi@samsung.com> Reviewed-by: Matthias Kaehlcke <mka@chromium.org> Tested-by: Matthias Kaehlcke <mka@chromium.org> [cw00.choi: Return -EAGAIN instead of -EINVAL if dev_pm_qos is inactive] Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Diffstat (limited to 'drivers/devfreq')
-rw-r--r--drivers/devfreq/devfreq.c76
1 files changed, 59 insertions, 17 deletions
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index f6e272085448..57f6944d65a6 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -141,10 +141,6 @@ static void get_freq_range(struct devfreq *devfreq,
*max_freq = min(*max_freq,
(unsigned long)HZ_PER_KHZ * qos_max_freq);
- /* Apply constraints from sysfs */
- *min_freq = max(*min_freq, devfreq->min_freq);
- *max_freq = min(*max_freq, devfreq->max_freq);
-
/* Apply constraints from OPP interface */
*min_freq = max(*min_freq, devfreq->scaling_min_freq);
*max_freq = min(*max_freq, devfreq->scaling_max_freq);
@@ -705,6 +701,19 @@ static void devfreq_dev_release(struct device *dev)
dev_warn(dev->parent,
"Failed to remove min_freq notifier: %d\n", err);
+ if (dev_pm_qos_request_active(&devfreq->user_max_freq_req)) {
+ err = dev_pm_qos_remove_request(&devfreq->user_max_freq_req);
+ if (err)
+ dev_warn(dev->parent,
+ "Failed to remove max_freq request: %d\n", err);
+ }
+ if (dev_pm_qos_request_active(&devfreq->user_min_freq_req)) {
+ err = dev_pm_qos_remove_request(&devfreq->user_min_freq_req);
+ if (err)
+ dev_warn(dev->parent,
+ "Failed to remove min_freq request: %d\n", err);
+ }
+
if (devfreq->profile->exit)
devfreq->profile->exit(devfreq->dev.parent);
@@ -778,7 +787,6 @@ struct devfreq *devfreq_add_device(struct device *dev,
err = -EINVAL;
goto err_dev;
}
- devfreq->min_freq = devfreq->scaling_min_freq;
devfreq->scaling_max_freq = find_available_max_freq(devfreq);
if (!devfreq->scaling_max_freq) {
@@ -786,7 +794,6 @@ struct devfreq *devfreq_add_device(struct device *dev,
err = -EINVAL;
goto err_dev;
}
- devfreq->max_freq = devfreq->scaling_max_freq;
devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev);
atomic_set(&devfreq->suspend_count, 0);
@@ -827,6 +834,16 @@ struct devfreq *devfreq_add_device(struct device *dev,
mutex_unlock(&devfreq->lock);
+ err = dev_pm_qos_add_request(dev, &devfreq->user_min_freq_req,
+ DEV_PM_QOS_MIN_FREQUENCY, 0);
+ if (err < 0)
+ goto err_devfreq;
+ err = dev_pm_qos_add_request(dev, &devfreq->user_max_freq_req,
+ DEV_PM_QOS_MAX_FREQUENCY,
+ PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
+ if (err < 0)
+ goto err_devfreq;
+
devfreq->nb_min.notifier_call = qos_min_notifier_call;
err = dev_pm_qos_add_notifier(devfreq->dev.parent, &devfreq->nb_min,
DEV_PM_QOS_MIN_FREQUENCY);
@@ -1412,14 +1429,22 @@ static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
unsigned long value;
int ret;
+ /*
+ * Protect against theoretical sysfs writes between
+ * device_add and dev_pm_qos_add_request
+ */
+ if (!dev_pm_qos_request_active(&df->user_min_freq_req))
+ return -EAGAIN;
+
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
return -EINVAL;
- mutex_lock(&df->lock);
- df->min_freq = value;
- update_devfreq(df);
- mutex_unlock(&df->lock);
+ /* Round down to kHz for PM QoS */
+ ret = dev_pm_qos_update_request(&df->user_min_freq_req,
+ value / HZ_PER_KHZ);
+ if (ret < 0)
+ return ret;
return count;
}
@@ -1444,18 +1469,35 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
unsigned long value;
int ret;
+ /*
+ * Protect against theoretical sysfs writes between
+ * device_add and dev_pm_qos_add_request
+ */
+ if (!dev_pm_qos_request_active(&df->user_max_freq_req))
+ return -EINVAL;
+
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
return -EINVAL;
- mutex_lock(&df->lock);
-
- if (!value)
- value = ULONG_MAX;
+ /*
+ * PM QoS frequencies are in kHz so we need to convert. Convert by
+ * rounding upwards so that the acceptable interval never shrinks.
+ *
+ * For example if the user writes "666666666" to sysfs this value will
+ * be converted to 666667 kHz and back to 666667000 Hz before an OPP
+ * lookup, this ensures that an OPP of 666666666Hz is still accepted.
+ *
+ * A value of zero means "no limit".
+ */
+ if (value)
+ value = DIV_ROUND_UP(value, HZ_PER_KHZ);
+ else
+ value = PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE;
- df->max_freq = value;
- update_devfreq(df);
- mutex_unlock(&df->lock);
+ ret = dev_pm_qos_update_request(&df->user_max_freq_req, value);
+ if (ret < 0)
+ return ret;
return count;
}