PM / devfreq: Add timeout feature for cpufreq governor

Some devfreq devices might need their frequency to be set only for a short
time after the CPU frequency changes.

For such devices, add a "timeout" tunable that determines for how many
milliseconds the governor sets the device frequency based on the CPU
frequency. After "timeout" milliseconds from the CPU frequency change, the
governor will set the device frequency back to its minimum value.

Change-Id: I17fc972c0b03fab781864ce735013710c2df4647
Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
This commit is contained in:
Saravana Kannan 2014-09-30 18:48:21 -07:00 committed by David Keitel
parent 18f0cc27ec
commit adb7181f83

View file

@ -46,23 +46,96 @@ struct devfreq_node {
struct list_head list; struct list_head list;
struct freq_map **map; struct freq_map **map;
struct freq_map *common_map; struct freq_map *common_map;
unsigned int timeout;
struct delayed_work dwork;
bool drop;
unsigned long prev_tgt;
}; };
static LIST_HEAD(devfreq_list); static LIST_HEAD(devfreq_list);
static DEFINE_MUTEX(state_lock); static DEFINE_MUTEX(state_lock);
#define show_attr(name) \
static ssize_t show_##name(struct device *dev, \
struct device_attribute *attr, char *buf) \
{ \
struct devfreq *df = to_devfreq(dev); \
struct devfreq_node *n = df->data; \
return snprintf(buf, PAGE_SIZE, "%u\n", n->name); \
}
#define store_attr(name, _min, _max) \
static ssize_t store_##name(struct device *dev, \
struct device_attribute *attr, const char *buf, \
size_t count) \
{ \
struct devfreq *df = to_devfreq(dev); \
struct devfreq_node *n = df->data; \
int ret; \
unsigned int val; \
ret = sscanf(buf, "%u", &val); \
if (ret != 1) \
return -EINVAL; \
val = max(val, _min); \
val = min(val, _max); \
n->name = val; \
return count; \
}
#define gov_attr(__attr, min, max) \
show_attr(__attr) \
store_attr(__attr, min, max) \
static DEVICE_ATTR(__attr, 0644, show_##__attr, store_##__attr)
static int update_node(struct devfreq_node *node)
{
int ret;
struct devfreq *df = node->df;
if (!df)
return 0;
cancel_delayed_work_sync(&node->dwork);
mutex_lock(&df->lock);
node->drop = false;
ret = update_devfreq(df);
if (ret) {
dev_err(df->dev.parent, "Unable to update frequency\n");
goto out;
}
if (!node->timeout)
goto out;
if (df->previous_freq <= df->min_freq)
goto out;
schedule_delayed_work(&node->dwork,
msecs_to_jiffies(node->timeout));
out:
mutex_unlock(&df->lock);
return ret;
}
static void update_all_devfreqs(void) static void update_all_devfreqs(void)
{ {
struct devfreq_node *node; struct devfreq_node *node;
list_for_each_entry(node, &devfreq_list, list) { list_for_each_entry(node, &devfreq_list, list) {
update_node(node);
}
}
static void do_timeout(struct work_struct *work)
{
struct devfreq_node *node = container_of(to_delayed_work(work),
struct devfreq_node, dwork);
struct devfreq *df = node->df; struct devfreq *df = node->df;
if (!node->df)
continue;
mutex_lock(&df->lock); mutex_lock(&df->lock);
node->drop = true;
update_devfreq(df); update_devfreq(df);
mutex_unlock(&df->lock); mutex_unlock(&df->lock);
}
} }
static struct devfreq_node *find_devfreq_node(struct device *dev) static struct devfreq_node *find_devfreq_node(struct device *dev)
@ -300,10 +373,21 @@ static int devfreq_cpufreq_get_freq(struct devfreq *df,
return -ENODEV; return -ENODEV;
} }
if (node->drop) {
*freq = 0;
return 0;
}
for_each_possible_cpu(cpu) for_each_possible_cpu(cpu)
tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu)); tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
if (node->timeout && tgt_freq < node->prev_tgt)
*freq = 0;
else
*freq = tgt_freq; *freq = tgt_freq;
node->prev_tgt = tgt_freq;
return 0; return 0;
} }
@ -362,8 +446,11 @@ static ssize_t show_map(struct device *dev, struct device_attribute *attr,
} }
static DEVICE_ATTR(freq_map, 0444, show_map, NULL); static DEVICE_ATTR(freq_map, 0444, show_map, NULL);
gov_attr(timeout, 0U, 100U);
static struct attribute *dev_attr[] = { static struct attribute *dev_attr[] = {
&dev_attr_freq_map.attr, &dev_attr_freq_map.attr,
&dev_attr_timeout.attr,
NULL, NULL,
}; };
@ -402,17 +489,16 @@ static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
node->dev = devfreq->dev.parent; node->dev = devfreq->dev.parent;
list_add_tail(&node->list, &devfreq_list); list_add_tail(&node->list, &devfreq_list);
} }
INIT_DELAYED_WORK(&node->dwork, do_timeout);
node->df = devfreq; node->df = devfreq;
node->orig_data = devfreq->data; node->orig_data = devfreq->data;
devfreq->data = node; devfreq->data = node;
mutex_lock(&devfreq->lock); ret = update_node(node);
ret = update_devfreq(devfreq); if (ret)
mutex_unlock(&devfreq->lock);
if (ret) {
pr_err("Freq update failed!\n");
goto update_fail; goto update_fail;
}
mutex_unlock(&state_lock); mutex_unlock(&state_lock);
return 0; return 0;
@ -434,6 +520,8 @@ static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
{ {
struct devfreq_node *node = devfreq->data; struct devfreq_node *node = devfreq->data;
cancel_delayed_work_sync(&node->dwork);
mutex_lock(&state_lock); mutex_lock(&state_lock);
devfreq->data = node->orig_data; devfreq->data = node->orig_data;
if (node->map || node->common_map) { if (node->map || node->common_map) {