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:
parent
18f0cc27ec
commit
adb7181f83
1 changed files with 102 additions and 14 deletions
|
@ -46,25 +46,98 @@ 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) {
|
||||||
struct devfreq *df = node->df;
|
update_node(node);
|
||||||
if (!node->df)
|
|
||||||
continue;
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
update_devfreq(df);
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
mutex_lock(&df->lock);
|
||||||
|
node->drop = true;
|
||||||
|
update_devfreq(df);
|
||||||
|
mutex_unlock(&df->lock);
|
||||||
|
}
|
||||||
|
|
||||||
static struct devfreq_node *find_devfreq_node(struct device *dev)
|
static struct devfreq_node *find_devfreq_node(struct device *dev)
|
||||||
{
|
{
|
||||||
struct devfreq_node *node;
|
struct devfreq_node *node;
|
||||||
|
@ -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));
|
||||||
|
|
||||||
*freq = tgt_freq;
|
if (node->timeout && tgt_freq < node->prev_tgt)
|
||||||
|
*freq = 0;
|
||||||
|
else
|
||||||
|
*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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue