blob: 0090bf6cec7c9969880b1749bbb1477529ac94a5 [file] [log] [blame]
/*
* linux/drivers/exynos/soc/samsung/exynos-hotplug_governor.c
*
* Copyright (c) 2015 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/kthread.h>
#include <linux/slab.h>
#include <linux/pm_qos.h>
#include <linux/sched.h>
#include <soc/samsung/exynos-cpu_hotplug.h>
#define CREATE_TRACE_POINTS
#include <trace/events/hotplug_governor.h>
#define DEFAULT_DUAL_CHANGE_MS (15) /* 15 ms */
#define DEFAULT_BOOT_ENABLE_MS (0) /* boot delay is not applied */
#define RETRY_BOOT_ENABLE_MS (100) /* 100 ms */
enum hpgov_event {
HPGOV_SLACK_TIMER_EXPIRED = 1, /* slack timer expired */
HPGOV_BIG_MODE_UPDATED = 2, /* dual/quad mode updated */
};
struct hpgov_attrib {
struct kobj_attribute enabled;
struct kobj_attribute dual_change_ms;
struct attribute_group attrib_group;
};
struct hpgov_data {
enum hpgov_event event;
int req_cpu_max;
int req_cpu_min;
};
struct {
int enabled;
int cur_cpu_max;
int cur_cpu_min;
long use_fast_hp;
int dual_change_ms;
struct hpgov_attrib attrib;
struct mutex attrib_lock;
struct task_struct *task;
struct task_struct *hptask;
struct hrtimer slack_timer;
ktime_t slack_start_time;
struct irq_work update_irq_work;
struct irq_work start_slack_timer_irq_work;
struct hpgov_data data;
int hp_state;
wait_queue_head_t wait_q;
wait_queue_head_t wait_hpq;
int boost_cnt;
} exynos_hpgov;
static struct pm_qos_request hpgov_max_pm_qos;
static struct pm_qos_request hpgov_min_pm_qos;
static DEFINE_SPINLOCK(hpgov_lock);
static DEFINE_SPINLOCK(hpgov_boost_cnt_lock);
enum {
HP_STATE_WAITING = 0, /* waiting for cpumask update */
HP_STATE_SCHEDULED = 1, /* hotplugging is scheduled */
HP_STATE_IN_PROGRESS = 2, /* in the process of hotplugging */
};
enum {
BIG_NORMAL_MODE,
BIG_BOOST_MODE,
};
static void start_slack_timer(void)
{
if (!exynos_hpgov.enabled)
return;
hrtimer_cancel(&exynos_hpgov.slack_timer);
hrtimer_start(&exynos_hpgov.slack_timer,
ktime_add(exynos_hpgov.slack_start_time, ktime_set(0,
exynos_hpgov.dual_change_ms * NSEC_PER_MSEC)),
HRTIMER_MODE_PINNED);
}
static inline int slack_timer_is_queued(void)
{
return hrtimer_is_queued(&exynos_hpgov.slack_timer);
}
static enum hrtimer_restart exynos_hpgov_slack_timer(struct hrtimer *timer)
{
unsigned long flags;
if (exynos_hpgov.boost_cnt)
goto out;
spin_lock_irqsave(&hpgov_lock, flags);
exynos_hpgov.data.event = HPGOV_SLACK_TIMER_EXPIRED;
exynos_hpgov.data.req_cpu_min = 8;
spin_unlock_irqrestore(&hpgov_lock, flags);
wake_up(&exynos_hpgov.wait_q);
out:
return HRTIMER_NORESTART;
}
static void exynos_hpgov_big_mode_update(int big_mode)
{
unsigned long flags;
if (!exynos_hpgov.enabled)
return;
spin_lock_irqsave(&hpgov_lock, flags);
exynos_hpgov.data.event = HPGOV_BIG_MODE_UPDATED;
if (big_mode == BIG_NORMAL_MODE)
exynos_hpgov.data.req_cpu_min = 8;
else
exynos_hpgov.data.req_cpu_min = 5;
spin_unlock_irqrestore(&hpgov_lock, flags);
irq_work_queue(&exynos_hpgov.update_irq_work);
}
static void exynos_hpgov_irq_work(struct irq_work *irq_work)
{
wake_up(&exynos_hpgov.wait_q);
}
static void exynos_hpgov_slack_timer_start(void)
{
if (!exynos_hpgov.enabled)
return;
irq_work_queue(&exynos_hpgov.start_slack_timer_irq_work);
}
static void slack_timer_irq_work(struct irq_work *irq_work)
{
start_slack_timer();
}
void inc_boost_req_count(void)
{
unsigned long flags;
int boost_cnt;
spin_lock_irqsave(&hpgov_boost_cnt_lock, flags);
boost_cnt = ++exynos_hpgov.boost_cnt;
if (boost_cnt == 1)
exynos_hpgov_big_mode_update(BIG_BOOST_MODE);
spin_unlock_irqrestore(&hpgov_boost_cnt_lock, flags);
}
void dec_boost_req_count(bool delayed_boost)
{
unsigned long flags;
int boost_cnt;
spin_lock_irqsave(&hpgov_boost_cnt_lock, flags);
if (delayed_boost)
exynos_hpgov.slack_start_time = ktime_get();
boost_cnt = --exynos_hpgov.boost_cnt;
if (boost_cnt == 0) {
if (delayed_boost || ktime_before(ktime_get(),
ktime_add_ms(exynos_hpgov.slack_start_time,
exynos_hpgov.dual_change_ms - 1)))
exynos_hpgov_slack_timer_start();
else
exynos_hpgov_big_mode_update(BIG_NORMAL_MODE);
}
spin_unlock_irqrestore(&hpgov_boost_cnt_lock, flags);
}
static int exynos_hpgov_update_governor(enum hpgov_event event, int req_cpu_max, int req_cpu_min)
{
int ret = 0;
unsigned long flags;
spin_lock_irqsave(&hpgov_lock, flags);
switch(event) {
case HPGOV_BIG_MODE_UPDATED:
case HPGOV_SLACK_TIMER_EXPIRED:
exynos_hpgov.use_fast_hp = FAST_HP;
default:
break;
}
if (req_cpu_max == exynos_hpgov.cur_cpu_max)
req_cpu_max = 0;
if (req_cpu_min == exynos_hpgov.cur_cpu_min)
req_cpu_min = 0;
if (!req_cpu_max && !req_cpu_min) {
spin_unlock_irqrestore(&hpgov_lock, flags);
return ret;
}
trace_exynos_hpgov_governor_update(event, req_cpu_max, req_cpu_min);
if (req_cpu_max)
exynos_hpgov.cur_cpu_max = req_cpu_max;
if (req_cpu_min)
exynos_hpgov.cur_cpu_min = req_cpu_min;
spin_unlock_irqrestore(&hpgov_lock, flags);
exynos_hpgov.hp_state = HP_STATE_SCHEDULED;
wake_up(&exynos_hpgov.wait_hpq);
return ret;
}
static int exynos_hpgov_do_update_governor(void *data)
{
struct hpgov_data *pdata = (struct hpgov_data *)data;
unsigned long flags;
enum hpgov_event event;
int req_cpu_max;
int req_cpu_min;
while (1) {
wait_event(exynos_hpgov.wait_q, pdata->event || kthread_should_stop());
if (kthread_should_stop())
break;
spin_lock_irqsave(&hpgov_lock, flags);
event = pdata->event;
req_cpu_max = pdata->req_cpu_max;
req_cpu_min = pdata->req_cpu_min;
pdata->event = 0;
pdata->req_cpu_max = 0;
pdata->req_cpu_min = 0;
spin_unlock_irqrestore(&hpgov_lock, flags);
exynos_hpgov_update_governor(event, req_cpu_max, req_cpu_min);
}
return 0;
}
static int exynos_hpgov_do_hotplug(void *data)
{
int *event = (int *)data;
unsigned long flags;
int cpu_max;
int cpu_min;
long use_fast_hp;
int last_max = 0;
int last_min = 0;
while (1) {
wait_event(exynos_hpgov.wait_hpq, *event || kthread_should_stop());
if (kthread_should_stop())
break;
restart:
exynos_hpgov.hp_state = HP_STATE_IN_PROGRESS;
spin_lock_irqsave(&hpgov_lock, flags);
cpu_max = exynos_hpgov.cur_cpu_max;
cpu_min = exynos_hpgov.cur_cpu_min;
use_fast_hp = exynos_hpgov.use_fast_hp;
spin_unlock_irqrestore(&hpgov_lock, flags);
if (cpu_max != last_max) {
pm_qos_update_request_param(&hpgov_max_pm_qos,
cpu_max, (void *)&use_fast_hp);
last_max = cpu_max;
}
if (cpu_min != last_min) {
pm_qos_update_request_param(&hpgov_min_pm_qos,
cpu_min, (void *)&use_fast_hp);
last_min = cpu_min;
}
exynos_hpgov.hp_state = HP_STATE_WAITING;
if (last_max != exynos_hpgov.cur_cpu_max ||
last_min != exynos_hpgov.cur_cpu_min)
goto restart;
}
return 0;
}
static int exynos_hpgov_set_enabled(int enable)
{
int ret = 0;
static int last_enable = 0;
enable = (enable > 0) ? 1 : 0;
if (last_enable == enable)
return ret;
last_enable = enable;
if (enable) {
exynos_hpgov.task = kthread_create(exynos_hpgov_do_update_governor,
&exynos_hpgov.data, "exynos_hpgov");
if (IS_ERR(exynos_hpgov.task))
return -EFAULT;
set_user_nice(exynos_hpgov.task, MIN_NICE);
kthread_bind(exynos_hpgov.task, 2);
wake_up_process(exynos_hpgov.task);
exynos_hpgov.hptask = kthread_create(exynos_hpgov_do_hotplug,
&exynos_hpgov.hp_state, "exynos_hp");
if (IS_ERR(exynos_hpgov.hptask)) {
kthread_stop(exynos_hpgov.task);
return -EFAULT;
}
set_user_nice(exynos_hpgov.hptask, MIN_NICE);
kthread_bind(exynos_hpgov.hptask, 2);
wake_up_process(exynos_hpgov.hptask);
exynos_hpgov.enabled = 1;
smp_wmb();
start_slack_timer();
} else {
kthread_stop(exynos_hpgov.hptask);
kthread_stop(exynos_hpgov.task);
exynos_hpgov.enabled = 0;
smp_wmb();
pm_qos_update_request(&hpgov_max_pm_qos, PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE);
pm_qos_update_request(&hpgov_min_pm_qos, PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE);
exynos_hpgov.cur_cpu_max = PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE;
exynos_hpgov.cur_cpu_min = PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE;
}
return ret;
}
int hpgov_default_level(void)
{
return 1;
}
static int exynos_hpgov_set_dual_change_ms(int val)
{
if (!(val >= 0))
return -EINVAL;
exynos_hpgov.dual_change_ms = val;
return 0;
}
#define HPGOV_PARAM(_name, _param) \
static ssize_t exynos_hpgov_attr_##_name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return snprintf(buf, PAGE_SIZE, "%d\n", _param); \
} \
static ssize_t exynos_hpgov_attr_##_name##_store(struct kobject *kobj, \
struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
int ret = 0; \
int val; \
int old_val; \
mutex_lock(&exynos_hpgov.attrib_lock); \
ret = kstrtoint(buf, 10, &val); \
if (ret) { \
pr_err("Invalid input %s for %s %d\n", \
buf, __stringify(_name), ret);\
return 0; \
} \
old_val = _param; \
ret = exynos_hpgov_set_##_name(val); \
if (ret) { \
pr_err("Error %d returned when setting param %s to %d\n",\
ret, __stringify(_name), val); \
_param = old_val; \
} \
mutex_unlock(&exynos_hpgov.attrib_lock); \
return count; \
}
#define HPGOV_RW_ATTRIB(i, _name) \
exynos_hpgov.attrib._name.attr.name = __stringify(_name); \
exynos_hpgov.attrib._name.attr.mode = S_IRUGO | S_IWUSR; \
exynos_hpgov.attrib._name.show = exynos_hpgov_attr_##_name##_show; \
exynos_hpgov.attrib._name.store = exynos_hpgov_attr_##_name##_store; \
exynos_hpgov.attrib.attrib_group.attrs[i] = &exynos_hpgov.attrib._name.attr;
HPGOV_PARAM(enabled, exynos_hpgov.enabled);
HPGOV_PARAM(dual_change_ms, exynos_hpgov.dual_change_ms);
static void hpgov_boot_enable(struct work_struct *work);
static DECLARE_DELAYED_WORK(hpgov_boot_work, hpgov_boot_enable);
static void hpgov_boot_enable(struct work_struct *work)
{
if (exynos_hpgov_set_enabled(1))
schedule_delayed_work_on(0, &hpgov_boot_work, msecs_to_jiffies(RETRY_BOOT_ENABLE_MS));
}
static int __init exynos_hpgov_init(void)
{
int ret = 0;
const int attr_count = 5;
int i_attr = attr_count;
hrtimer_init(&exynos_hpgov.slack_timer, CLOCK_MONOTONIC,
HRTIMER_MODE_PINNED);
exynos_hpgov.slack_timer.function = exynos_hpgov_slack_timer;
mutex_init(&exynos_hpgov.attrib_lock);
init_waitqueue_head(&exynos_hpgov.wait_q);
init_waitqueue_head(&exynos_hpgov.wait_hpq);
init_irq_work(&exynos_hpgov.update_irq_work, exynos_hpgov_irq_work);
init_irq_work(&exynos_hpgov.start_slack_timer_irq_work, slack_timer_irq_work);
exynos_hpgov.attrib.attrib_group.attrs =
kzalloc((attr_count + 1) * sizeof(struct attribute *), GFP_KERNEL);
if (!exynos_hpgov.attrib.attrib_group.attrs) {
ret = -ENOMEM;
goto done;
}
HPGOV_RW_ATTRIB(attr_count - (i_attr--), enabled);
HPGOV_RW_ATTRIB(attr_count - (i_attr--), dual_change_ms);
exynos_hpgov.attrib.attrib_group.name = "governor";
ret = sysfs_create_group(exynos_cpu_hotplug_kobj(), &exynos_hpgov.attrib.attrib_group);
if (ret)
pr_err("Unable to create sysfs objects :%d\n", ret);
exynos_hpgov.dual_change_ms = DEFAULT_DUAL_CHANGE_MS;
exynos_hpgov.cur_cpu_max = PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE;
exynos_hpgov.cur_cpu_min = PM_QOS_CPU_ONLINE_MIN_DEFAULT_VALUE;
pm_qos_add_request(&hpgov_max_pm_qos, PM_QOS_CPU_ONLINE_MAX, PM_QOS_CPU_ONLINE_MAX_DEFAULT_VALUE);
pm_qos_add_request(&hpgov_min_pm_qos, PM_QOS_CPU_ONLINE_MIN, PM_QOS_CPU_ONLINE_MIN_DEFAULT_VALUE);
schedule_delayed_work_on(0, &hpgov_boot_work, msecs_to_jiffies(DEFAULT_BOOT_ENABLE_MS));
done:
return ret;
}
late_initcall(exynos_hpgov_init);