blob: 9d4e269362c513aecf4804c3fbe45e495b221d28 [file] [log] [blame]
/*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/devfreq.h>
#include <linux/math64.h>
#include <linux/pm_qos.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/kthread.h>
#include <linux/pm_opp.h>
#include "governor.h"
static int devfreq_simple_interactive_notifier(struct notifier_block *nb, unsigned long val,
void *v)
{
struct devfreq_notifier_block *devfreq_nb;
devfreq_nb = container_of(nb, struct devfreq_notifier_block, nb);
mutex_lock(&devfreq_nb->df->lock);
update_devfreq(devfreq_nb->df);
mutex_unlock(&devfreq_nb->df->lock);
return NOTIFY_OK;
}
#ifdef CONFIG_EXYNOS_WD_DVFS
#define NEXTBUF(x, b) if (++(x) > &(b)[SIMPLE_LOAD_MAX - 1]) (x) = (b)
#define POSTBUF(x, b) ((x) = ((--(x) < (b)) ? \
&(b)[SIMPLE_LOAD_MAX - 1] : (x)))
static unsigned long update_load(struct devfreq_dev_status *stat,
struct devfreq_simple_interactive_data *data)
{
struct devfreq_simple_load *ptr;
unsigned int targetload;
unsigned int freq;
int i;
if (!stat->total_time)
return stat->current_frequency;
for (i = 0; i < data->ntarget_load - 1 &&
stat->current_frequency >= data->target_load[i + 1]; i += 2);
targetload = data->target_load[i];
/* if frequency is changed then reset the load */
if (!stat->current_frequency ||
stat->current_frequency != data->prev_freq) {
data->rear = data->front;
data->front->delta = 0;
data->total = 0;
data->busy = 0;
if (data->max_load >= targetload)
data->max_load = targetload;
else
data->max_load = 0;
data->max_spent = 0;
data->min_load = targetload;
}
ptr = data->front;
ptr->delta += stat->delta_time;
data->max_spent += stat->delta_time;
data->total += stat->total_time;
data->busy += stat->busy_time;
/* if too short time, then not counting */
if (ptr->delta > data->min_sample_time * NSEC_PER_MSEC) {
NEXTBUF(data->front, data->buffer);
data->front->delta = 0;
if (data->front == data->rear)
NEXTBUF(data->rear, data->buffer);
ptr->load = data->total ?
data->busy * 100 / data->total : 0;
data->busy = 0;
data->total = 0;
/* if ptr load is higher than pervious or too small load */
if (data->max_load <= ptr->load) {
data->min_load = ptr->load;
data->max_spent = 0;
data->max_load = ptr->load;
goto out;
} else if (ptr->load < data->min_load) {
data->min_load = ptr->load;
if (ptr->load < data->tolerance) {
data->max_load = ptr->load;
data->max_spent = 0;
return 0;
}
}
}
/* a new max load */
if (data->max_spent > data->hold_sample_time * NSEC_PER_MSEC) {
unsigned long long spent = 0;
/* if not valid data, then skip */
if (data->front == ptr) {
spent += ptr->delta;
POSTBUF(ptr, data->buffer);
}
data->max_load = ptr->load;
data->max_spent = spent;
/* if there is downtrend, then reflect current load */
if (ptr->load > data->min_load + data->tolerance) {
data->min_load = ptr->load;
spent += ptr->delta;
POSTBUF(ptr, data->buffer);
for (; spent < data->hold_sample_time *
NSEC_PER_MSEC && ptr != data->rear;
POSTBUF(ptr, data->buffer)) {
if (data->max_load < ptr->load) {
data->max_load = ptr->load;
data->max_spent = spent;
} else if (data->min_load > ptr->load) {
data->min_load = ptr->load;
}
spent += ptr->delta;
}
} else {
data->min_load = ptr->load;
}
}
out:
/* a few measurement */
if (data->max_load == targetload || data->total)
freq = stat->current_frequency;
else
freq = data->max_load * stat->current_frequency / targetload;
if (data->max_load > data->go_hispeed_load && data->hispeed_freq > freq)
freq = data->hispeed_freq;
return freq;
}
#endif
static int devfreq_simple_interactive_func(struct devfreq *df,
unsigned long *freq)
{
#ifdef CONFIG_EXYNOS_WD_DVFS
struct devfreq_dev_status *stat;
int err;
#endif
struct devfreq_simple_interactive_data *data = df->data;
unsigned long pm_qos_min = 0;
unsigned long pm_qos_max = INT_MAX;
int delay_check = 0;
int delay_time = 0;
int i = 0;
struct dev_pm_opp *limit_opp;
if (!data) {
pr_err("%s: failed to find governor data\n", __func__);
return -ENODATA;
}
if (!df->disabled_pm_qos) {
pm_qos_min = pm_qos_request(data->pm_qos_class);
if (data->pm_qos_class_max) {
pm_qos_max = pm_qos_request(data->pm_qos_class_max);
rcu_read_lock();
limit_opp = devfreq_recommended_opp(df->dev.parent, &pm_qos_max,
DEVFREQ_FLAG_LEAST_UPPER_BOUND);
rcu_read_unlock();
if (IS_ERR(limit_opp)) {
pr_err("%s: failed to limit by max frequency\n", __func__);
return PTR_ERR(limit_opp);
}
}
}
*freq = pm_qos_min;
#ifdef CONFIG_EXYNOS_WD_DVFS
stat = &df->last_status;
if (df->profile->get_dev_status) {
err = devfreq_update_stats(df);
if (err)
return err;
*freq = max(*freq, update_load(stat, data));
}
#endif
if (!data->use_delay_time)
goto out;
if (data->prev_freq != df->previous_freq) {
for (i = 0; i < data->ndelay_time - 1 &&
*freq >= data->delay_time[i + 1]; i += 2)
;
/* unit of delay time should be 10msec */
delay_check = data->delay_time[i] % DELAY_TIME_RANGE;
delay_time = delay_check ?
data->delay_time[i] - delay_check + DELAY_TIME_RANGE :
data->delay_time[i];
data->freq_timer.expires = data->changed_time +
msecs_to_jiffies(delay_time);
}
if (pm_qos_max > df->previous_freq && *freq < df->previous_freq &&
data->freq_timer.expires > jiffies) {
*freq = df->previous_freq;
if (!timer_pending(&data->freq_timer))
/* timer is bound to cpu0 */
add_timer_on(&data->freq_timer, BOUND_CPU_NUM);
goto out;
} else if (timer_pending(&data->freq_timer)) {
del_timer_sync(&data->freq_timer);
}
data->changed_time = jiffies;
out:
/*
* save current frequency and time
* to use when update_devfreq is called next
*/
data->prev_freq = df->previous_freq;
*freq = min(pm_qos_max, *freq);
#ifdef CONFIG_EXYNOS_WD_DVFS
if (df->profile->get_dev_status) {
unsigned long expires = jiffies;
mod_timer_pinned(&data->freq_timer, expires +
msecs_to_jiffies(data->min_sample_time * 2));
if (*freq > df->min_freq) {
/* timer is bound to cpu0 */
mod_timer_pinned(&data->freq_slack_timer, expires +
msecs_to_jiffies(data->hold_sample_time));
} else if (timer_pending(&data->freq_slack_timer)) {
del_timer(&data->freq_slack_timer);
}
}
#endif
return 0;
}
static int devfreq_change_freq_task(void *data)
{
struct devfreq *df = data;
while (!kthread_should_stop()) {
set_current_state(TASK_INTERRUPTIBLE);
schedule();
set_current_state(TASK_RUNNING);
mutex_lock(&df->lock);
update_devfreq(df);
mutex_unlock(&df->lock);
}
return 0;
}
/*timer callback function send a signal */
static void simple_interactive_timer(unsigned long data)
{
struct devfreq_simple_interactive_data *gov_data = (void *)data;
wake_up_process(gov_data->change_freq_task);
}
#ifdef CONFIG_EXYNOS_WD_DVFS
static void simple_interactive_nop_timer(unsigned long data)
{
}
#endif
static int devfreq_simple_interactive_register_notifier(struct devfreq *df)
{
int ret;
struct devfreq_simple_interactive_data *data = df->data;
if (!data)
return -EINVAL;
data->nb.df = df;
data->nb.nb.notifier_call = devfreq_simple_interactive_notifier;
ret = pm_qos_add_notifier(data->pm_qos_class, &data->nb.nb);
if (ret < 0)
goto err1;
if (data->pm_qos_class_max) {
data->nb_max.df = df;
data->nb_max.nb.notifier_call = devfreq_simple_interactive_notifier;
ret = pm_qos_add_notifier(data->pm_qos_class_max, &data->nb_max.nb);
if (ret < 0) {
pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb);
goto err2;
}
}
/* timer of governor for delay time initialize */
data->freq_timer.data = (unsigned long)data;
data->freq_timer.function = simple_interactive_timer;
#ifdef CONFIG_EXYNOS_WD_DVFS
init_timer_deferrable(&data->freq_timer);
data->freq_slack_timer.function = simple_interactive_nop_timer;
init_timer(&data->freq_slack_timer);
#else
init_timer(&data->freq_timer);
#endif
data->change_freq_task = kthread_create(devfreq_change_freq_task, df, "simpleinteractive");
if (IS_ERR(data->change_freq_task)) {
pr_err("%s: failed kthread_create for simpleinteractive governor\n", __func__);
ret = PTR_ERR(data->change_freq_task);
destroy_timer_on_stack(&data->freq_timer);
#ifdef CONFIG_EXYNOS_WD_DVFS
destroy_timer_on_stack(&data->freq_slack_timer);
#endif
pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb);
if (data->pm_qos_class_max)
pm_qos_remove_notifier(data->pm_qos_class_max, &data->nb_max.nb);
goto err2;
}
#ifdef CONFIG_EXYNOS_WD_DVFS
if (df->profile->get_dev_status) {
data->freq_timer.expires = jiffies +
msecs_to_jiffies(data->min_sample_time * 2);
add_timer(&data->freq_timer);
data->freq_slack_timer.expires = jiffies +
msecs_to_jiffies(data->hold_sample_time);
add_timer_on(&data->freq_slack_timer, BOUND_CPU_NUM);
}
#else
kthread_bind(data->change_freq_task, BOUND_CPU_NUM);
#endif
return 0;
err2:
kfree((void *)&data->nb_max.nb);
err1:
kfree((void *)&data->nb.nb);
return ret;
}
static int devfreq_simple_interactive_unregister_notifier(struct devfreq *df)
{
int ret;
struct devfreq_simple_interactive_data *data = df->data;
if (!data)
return -EINVAL;
if (data->pm_qos_class_max) {
ret = pm_qos_remove_notifier(data->pm_qos_class_max, &data->nb_max.nb);
if (ret < 0)
goto err;
}
ret = pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb);
#ifdef CONFIG_EXYNOS_WD_DVFS
destroy_timer_on_stack(&data->freq_slack_timer);
#endif
destroy_timer_on_stack(&data->freq_timer);
kthread_stop(data->change_freq_task);
err:
return ret;
}
static int devfreq_simple_interactive_handler(struct devfreq *devfreq,
unsigned int event, void *data)
{
int ret;
switch (event) {
case DEVFREQ_GOV_START:
ret = devfreq_simple_interactive_register_notifier(devfreq);
if (ret)
return ret;
break;
case DEVFREQ_GOV_STOP:
ret = devfreq_simple_interactive_unregister_notifier(devfreq);
if (ret)
return ret;
break;
default:
break;
}
return 0;
}
static struct devfreq_governor devfreq_simple_interactive = {
.name = "interactive",
.get_target_freq = devfreq_simple_interactive_func,
.event_handler = devfreq_simple_interactive_handler,
};
static int __init devfreq_simple_interactive_init(void)
{
return devfreq_add_governor(&devfreq_simple_interactive);
}
subsys_initcall(devfreq_simple_interactive_init);
static void __exit devfreq_simple_interactive_exit(void)
{
int ret;
ret = devfreq_remove_governor(&devfreq_simple_interactive);
if (ret)
pr_err("%s: failed remove governor %d\n", __func__, ret);
return;
}
module_exit(devfreq_simple_interactive_exit);
MODULE_LICENSE("GPL");