aboutsummaryrefslogtreecommitdiff
path: root/drivers/devfreq/devfreq.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/devfreq/devfreq.c')
-rw-r--r--drivers/devfreq/devfreq.c243
1 files changed, 180 insertions, 63 deletions
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index 141413067b5c..6b6991f0e873 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -29,6 +29,9 @@
#include <linux/of.h>
#include "governor.h"
+#define CREATE_TRACE_POINTS
+#include <trace/events/devfreq.h>
+
static struct class *devfreq_class;
/*
@@ -228,7 +231,7 @@ static struct devfreq_governor *find_devfreq_governor(const char *name)
* if is not found. This can happen when both drivers (the governor driver
* and the driver that call devfreq_add_device) are built as modules.
* devfreq_list_lock should be held by the caller. Returns the matched
- * governor's pointer.
+ * governor's pointer or an error pointer.
*/
static struct devfreq_governor *try_then_request_governor(const char *name)
{
@@ -254,7 +257,7 @@ static struct devfreq_governor *try_then_request_governor(const char *name)
/* Restore previous state before return */
mutex_lock(&devfreq_list_lock);
if (err)
- return NULL;
+ return ERR_PTR(err);
governor = find_devfreq_governor(name);
}
@@ -285,6 +288,44 @@ static int devfreq_notify_transition(struct devfreq *devfreq,
return 0;
}
+static int devfreq_set_target(struct devfreq *devfreq, unsigned long new_freq,
+ u32 flags)
+{
+ struct devfreq_freqs freqs;
+ unsigned long cur_freq;
+ int err = 0;
+
+ if (devfreq->profile->get_cur_freq)
+ devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
+ else
+ cur_freq = devfreq->previous_freq;
+
+ freqs.old = cur_freq;
+ freqs.new = new_freq;
+ devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
+
+ err = devfreq->profile->target(devfreq->dev.parent, &new_freq, flags);
+ if (err) {
+ freqs.new = cur_freq;
+ devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
+ return err;
+ }
+
+ freqs.new = new_freq;
+ devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
+
+ if (devfreq_update_status(devfreq, new_freq))
+ dev_err(&devfreq->dev,
+ "Couldn't update frequency transition information.\n");
+
+ devfreq->previous_freq = new_freq;
+
+ if (devfreq->suspend_freq)
+ devfreq->resume_freq = cur_freq;
+
+ return err;
+}
+
/* Load monitoring helper functions for governors use */
/**
@@ -296,8 +337,7 @@ static int devfreq_notify_transition(struct devfreq *devfreq,
*/
int update_devfreq(struct devfreq *devfreq)
{
- struct devfreq_freqs freqs;
- unsigned long freq, cur_freq, min_freq, max_freq;
+ unsigned long freq, min_freq, max_freq;
int err = 0;
u32 flags = 0;
@@ -333,31 +373,8 @@ int update_devfreq(struct devfreq *devfreq)
flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
}
- if (devfreq->profile->get_cur_freq)
- devfreq->profile->get_cur_freq(devfreq->dev.parent, &cur_freq);
- else
- cur_freq = devfreq->previous_freq;
-
- freqs.old = cur_freq;
- freqs.new = freq;
- devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
-
- err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
- if (err) {
- freqs.new = cur_freq;
- devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
- return err;
- }
-
- freqs.new = freq;
- devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
+ return devfreq_set_target(devfreq, freq, flags);
- if (devfreq_update_status(devfreq, freq))
- dev_err(&devfreq->dev,
- "Couldn't update frequency transition information.\n");
-
- devfreq->previous_freq = freq;
- return err;
}
EXPORT_SYMBOL(update_devfreq);
@@ -380,6 +397,8 @@ static void devfreq_monitor(struct work_struct *work)
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
mutex_unlock(&devfreq->lock);
+
+ trace_devfreq_monitor(devfreq);
}
/**
@@ -514,7 +533,7 @@ void devfreq_interval_update(struct devfreq *devfreq, unsigned int *delay)
mutex_lock(&devfreq->lock);
if (!devfreq->stop_polling)
queue_delayed_work(devfreq_wq, &devfreq->work,
- msecs_to_jiffies(devfreq->profile->polling_ms));
+ msecs_to_jiffies(devfreq->profile->polling_ms));
}
out:
mutex_unlock(&devfreq->lock);
@@ -523,7 +542,7 @@ EXPORT_SYMBOL(devfreq_interval_update);
/**
* devfreq_notifier_call() - Notify that the device frequency requirements
- * has been changed out of devfreq framework.
+ * has been changed out of devfreq framework.
* @nb: the notifier_block (supposed to be devfreq->nb)
* @type: not used
* @devp: not used
@@ -637,7 +656,7 @@ struct devfreq *devfreq_add_device(struct device *dev,
mutex_unlock(&devfreq->lock);
err = set_freq_table(devfreq);
if (err < 0)
- goto err_out;
+ goto err_dev;
mutex_lock(&devfreq->lock);
}
@@ -657,6 +676,9 @@ struct devfreq *devfreq_add_device(struct device *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);
+
dev_set_name(&devfreq->dev, "devfreq%d",
atomic_inc_return(&devfreq_no));
err = device_register(&devfreq->dev);
@@ -666,16 +688,27 @@ struct devfreq *devfreq_add_device(struct device *dev,
goto err_out;
}
- devfreq->trans_table =
- devm_kzalloc(&devfreq->dev,
- array3_size(sizeof(unsigned int),
- devfreq->profile->max_state,
- devfreq->profile->max_state),
- GFP_KERNEL);
+ devfreq->trans_table = devm_kzalloc(&devfreq->dev,
+ array3_size(sizeof(unsigned int),
+ devfreq->profile->max_state,
+ devfreq->profile->max_state),
+ GFP_KERNEL);
+ if (!devfreq->trans_table) {
+ mutex_unlock(&devfreq->lock);
+ err = -ENOMEM;
+ goto err_devfreq;
+ }
+
devfreq->time_in_state = devm_kcalloc(&devfreq->dev,
- devfreq->profile->max_state,
- sizeof(unsigned long),
- GFP_KERNEL);
+ devfreq->profile->max_state,
+ sizeof(unsigned long),
+ GFP_KERNEL);
+ if (!devfreq->time_in_state) {
+ mutex_unlock(&devfreq->lock);
+ err = -ENOMEM;
+ goto err_devfreq;
+ }
+
devfreq->last_stat_updated = jiffies;
srcu_init_notifier_head(&devfreq->transition_notifier_list);
@@ -709,7 +742,7 @@ struct devfreq *devfreq_add_device(struct device *dev,
err_init:
mutex_unlock(&devfreq_list_lock);
-
+err_devfreq:
devfreq_remove_device(devfreq);
devfreq = NULL;
err_dev:
@@ -857,14 +890,28 @@ EXPORT_SYMBOL(devm_devfreq_remove_device);
*/
int devfreq_suspend_device(struct devfreq *devfreq)
{
+ int ret;
+
if (!devfreq)
return -EINVAL;
- if (!devfreq->governor)
+ if (atomic_inc_return(&devfreq->suspend_count) > 1)
return 0;
- return devfreq->governor->event_handler(devfreq,
- DEVFREQ_GOV_SUSPEND, NULL);
+ if (devfreq->governor) {
+ ret = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_SUSPEND, NULL);
+ if (ret)
+ return ret;
+ }
+
+ if (devfreq->suspend_freq) {
+ ret = devfreq_set_target(devfreq, devfreq->suspend_freq, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
EXPORT_SYMBOL(devfreq_suspend_device);
@@ -878,18 +925,76 @@ EXPORT_SYMBOL(devfreq_suspend_device);
*/
int devfreq_resume_device(struct devfreq *devfreq)
{
+ int ret;
+
if (!devfreq)
return -EINVAL;
- if (!devfreq->governor)
+ if (atomic_dec_return(&devfreq->suspend_count) >= 1)
return 0;
- return devfreq->governor->event_handler(devfreq,
- DEVFREQ_GOV_RESUME, NULL);
+ if (devfreq->resume_freq) {
+ ret = devfreq_set_target(devfreq, devfreq->resume_freq, 0);
+ if (ret)
+ return ret;
+ }
+
+ if (devfreq->governor) {
+ ret = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_RESUME, NULL);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
EXPORT_SYMBOL(devfreq_resume_device);
/**
+ * devfreq_suspend() - Suspend devfreq governors and devices
+ *
+ * Called during system wide Suspend/Hibernate cycles for suspending governors
+ * and devices preserving the state for resume. On some platforms the devfreq
+ * device must have precise state (frequency) after resume in order to provide
+ * fully operating setup.
+ */
+void devfreq_suspend(void)
+{
+ struct devfreq *devfreq;
+ int ret;
+
+ mutex_lock(&devfreq_list_lock);
+ list_for_each_entry(devfreq, &devfreq_list, node) {
+ ret = devfreq_suspend_device(devfreq);
+ if (ret)
+ dev_err(&devfreq->dev,
+ "failed to suspend devfreq device\n");
+ }
+ mutex_unlock(&devfreq_list_lock);
+}
+
+/**
+ * devfreq_resume() - Resume devfreq governors and devices
+ *
+ * Called during system wide Suspend/Hibernate cycle for resuming governors and
+ * devices that are suspended with devfreq_suspend().
+ */
+void devfreq_resume(void)
+{
+ struct devfreq *devfreq;
+ int ret;
+
+ mutex_lock(&devfreq_list_lock);
+ list_for_each_entry(devfreq, &devfreq_list, node) {
+ ret = devfreq_resume_device(devfreq);
+ if (ret)
+ dev_warn(&devfreq->dev,
+ "failed to resume devfreq device\n");
+ }
+ mutex_unlock(&devfreq_list_lock);
+}
+
+/**
* devfreq_add_governor() - Add devfreq governor
* @governor: the devfreq governor to be added
*/
@@ -1024,7 +1129,7 @@ static ssize_t governor_store(struct device *dev, struct device_attribute *attr,
struct devfreq *df = to_devfreq(dev);
int ret;
char str_governor[DEVFREQ_NAME_LEN + 1];
- struct devfreq_governor *governor;
+ const struct devfreq_governor *governor, *prev_governor;
ret = sscanf(buf, "%" __stringify(DEVFREQ_NAME_LEN) "s", str_governor);
if (ret != 1)
@@ -1053,12 +1158,24 @@ static ssize_t governor_store(struct device *dev, struct device_attribute *attr,
goto out;
}
}
+ prev_governor = df->governor;
df->governor = governor;
strncpy(df->governor_name, governor->name, DEVFREQ_NAME_LEN);
ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL);
- if (ret)
+ if (ret) {
dev_warn(dev, "%s: Governor %s not started(%d)\n",
__func__, df->governor->name, ret);
+ df->governor = prev_governor;
+ strncpy(df->governor_name, prev_governor->name,
+ DEVFREQ_NAME_LEN);
+ ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL);
+ if (ret) {
+ dev_err(dev,
+ "%s: reverting to Governor %s failed (%d)\n",
+ __func__, df->governor_name, ret);
+ df->governor = NULL;
+ }
+ }
out:
mutex_unlock(&devfreq_list_lock);
@@ -1083,7 +1200,7 @@ static ssize_t available_governors_show(struct device *d,
*/
if (df->governor->immutable) {
count = scnprintf(&buf[count], DEVFREQ_NAME_LEN,
- "%s ", df->governor_name);
+ "%s ", df->governor_name);
/*
* The devfreq device shows the registered governor except for
* immutable governors such as passive governor .
@@ -1396,8 +1513,8 @@ EXPORT_SYMBOL(devfreq_recommended_opp);
/**
* devfreq_register_opp_notifier() - Helper function to get devfreq notified
- * for any changes in the OPP availability
- * changes
+ * for any changes in the OPP availability
+ * changes
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
*/
@@ -1409,8 +1526,8 @@ EXPORT_SYMBOL(devfreq_register_opp_notifier);
/**
* devfreq_unregister_opp_notifier() - Helper function to stop getting devfreq
- * notified for any changes in the OPP
- * availability changes anymore.
+ * notified for any changes in the OPP
+ * availability changes anymore.
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
*
@@ -1429,8 +1546,8 @@ static void devm_devfreq_opp_release(struct device *dev, void *res)
}
/**
- * devm_ devfreq_register_opp_notifier()
- * - Resource-managed devfreq_register_opp_notifier()
+ * devm_devfreq_register_opp_notifier() - Resource-managed
+ * devfreq_register_opp_notifier()
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
*/
@@ -1458,8 +1575,8 @@ int devm_devfreq_register_opp_notifier(struct device *dev,
EXPORT_SYMBOL(devm_devfreq_register_opp_notifier);
/**
- * devm_devfreq_unregister_opp_notifier()
- * - Resource-managed devfreq_unregister_opp_notifier()
+ * devm_devfreq_unregister_opp_notifier() - Resource-managed
+ * devfreq_unregister_opp_notifier()
* @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object.
*/
@@ -1478,8 +1595,8 @@ EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
int devfreq_register_notifier(struct devfreq *devfreq,
- struct notifier_block *nb,
- unsigned int list)
+ struct notifier_block *nb,
+ unsigned int list)
{
int ret = 0;
@@ -1585,9 +1702,9 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier);
* @list: DEVFREQ_TRANSITION_NOTIFIER.
*/
void devm_devfreq_unregister_notifier(struct device *dev,
- struct devfreq *devfreq,
- struct notifier_block *nb,
- unsigned int list)
+ struct devfreq *devfreq,
+ struct notifier_block *nb,
+ unsigned int list)
{
WARN_ON(devres_release(dev, devm_devfreq_notifier_release,
devm_devfreq_dev_match, devfreq));