diff options
-rw-r--r-- | drivers/cpufreq/cpu-boost.c | 164 |
1 files changed, 157 insertions, 7 deletions
diff --git a/drivers/cpufreq/cpu-boost.c b/drivers/cpufreq/cpu-boost.c index d1fa3d0caff2..9846eb77d2ad 100644 --- a/drivers/cpufreq/cpu-boost.c +++ b/drivers/cpufreq/cpu-boost.c @@ -21,26 +21,43 @@ #include <linux/jiffies.h> #include <linux/kthread.h> #include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/time.h> struct cpu_sync { struct task_struct *thread; wait_queue_head_t sync_wq; struct delayed_work boost_rem; + struct delayed_work input_boost_rem; int cpu; spinlock_t lock; bool pending; int src_cpu; unsigned int boost_min; + unsigned int input_boost_min; }; static DEFINE_PER_CPU(struct cpu_sync, sync_info); -static struct workqueue_struct *boost_rem_wq; +static struct workqueue_struct *cpu_boost_wq; + +static struct work_struct input_boost_work; static unsigned int boost_ms; module_param(boost_ms, uint, 0644); static unsigned int sync_threshold; module_param(sync_threshold, uint, 0644); + +static unsigned int input_boost_freq; +module_param(input_boost_freq, uint, 0644); + +static unsigned int input_boost_ms = 40; +module_param(input_boost_ms, uint, 0644); + +static u64 last_input_time; +#define MIN_INPUT_INTERVAL (150 * USEC_PER_MSEC) + /* * The CPUFREQ_ADJUST notifier is used to override the current policy min to * make sure policy min >= boost_min. The cpufreq framework then does the job @@ -52,14 +69,18 @@ static int boost_adjust_notify(struct notifier_block *nb, unsigned long val, struct cpufreq_policy *policy = data; unsigned int cpu = policy->cpu; struct cpu_sync *s = &per_cpu(sync_info, cpu); - unsigned int min = s->boost_min; + unsigned int b_min = s->boost_min; + unsigned int ib_min = s->input_boost_min; + unsigned int min; if (val != CPUFREQ_ADJUST) return NOTIFY_OK; - if (min == 0) + if (!b_min && !ib_min) return NOTIFY_OK; + min = max(b_min, ib_min); + pr_debug("CPU%u policy min before boost: %u kHz\n", cpu, policy->min); pr_debug("CPU%u boost min: %u kHz\n", cpu, min); @@ -87,6 +108,17 @@ static void do_boost_rem(struct work_struct *work) cpufreq_update_policy(s->cpu); } +static void do_input_boost_rem(struct work_struct *work) +{ + struct cpu_sync *s = container_of(work, struct cpu_sync, + input_boost_rem.work); + + pr_debug("Removing input boost for CPU%d\n", s->cpu); + s->input_boost_min = 0; + /* Force policy re-evaluation to trigger adjust notifier. */ + cpufreq_update_policy(s->cpu); +} + static int boost_mig_sync_thread(void *data) { int dest_cpu = (int) data; @@ -133,7 +165,7 @@ static int boost_mig_sync_thread(void *data) /* Force policy re-evaluation to trigger adjust notifier. */ cpufreq_update_policy(dest_cpu); - queue_delayed_work_on(s->cpu, boost_rem_wq, + queue_delayed_work_on(s->cpu, cpu_boost_wq, &s->boost_rem, msecs_to_jiffies(boost_ms)); } @@ -163,29 +195,147 @@ static struct notifier_block boost_migration_nb = { .notifier_call = boost_migration_notify, }; +static void do_input_boost(struct work_struct *work) +{ + unsigned int i, ret; + struct cpu_sync *i_sync_info; + struct cpufreq_policy policy; + + for_each_online_cpu(i) { + + i_sync_info = &per_cpu(sync_info, i); + ret = cpufreq_get_policy(&policy, i); + if (ret) + continue; + if (policy.cur >= input_boost_freq) + continue; + + cancel_delayed_work_sync(&i_sync_info->input_boost_rem); + i_sync_info->input_boost_min = input_boost_freq; + cpufreq_update_policy(i); + queue_delayed_work_on(i_sync_info->cpu, cpu_boost_wq, + &i_sync_info->input_boost_rem, + msecs_to_jiffies(input_boost_ms)); + } +} + +static void cpuboost_input_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + u64 now; + + if (!input_boost_freq) + return; + + now = ktime_to_us(ktime_get()); + if (now - last_input_time < MIN_INPUT_INTERVAL) + return; + + if (work_pending(&input_boost_work)) + return; + + queue_work(cpu_boost_wq, &input_boost_work); + last_input_time = ktime_to_us(ktime_get()); +} + +static int cpuboost_input_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = "cpufreq"; + + error = input_register_handle(handle); + if (error) + goto err2; + + error = input_open_device(handle); + if (error) + goto err1; + + return 0; +err1: + input_unregister_handle(handle); +err2: + kfree(handle); + return error; +} + +static void cpuboost_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id cpuboost_ids[] = { + /* multi-touch touchscreen */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .evbit = { BIT_MASK(EV_ABS) }, + .absbit = { [BIT_WORD(ABS_MT_POSITION_X)] = + BIT_MASK(ABS_MT_POSITION_X) | + BIT_MASK(ABS_MT_POSITION_Y) }, + }, + /* touchpad */ + { + .flags = INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .absbit = { [BIT_WORD(ABS_X)] = + BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) }, + }, + /* Keypad */ + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { }, +}; + +static struct input_handler cpuboost_input_handler = { + .event = cpuboost_input_event, + .connect = cpuboost_input_connect, + .disconnect = cpuboost_input_disconnect, + .name = "cpu-boost", + .id_table = cpuboost_ids, +}; + static int cpu_boost_init(void) { - int cpu; + int cpu, ret; struct cpu_sync *s; cpufreq_register_notifier(&boost_adjust_nb, CPUFREQ_POLICY_NOTIFIER); - boost_rem_wq = alloc_workqueue("cpuboost_rem_wq", WQ_HIGHPRI, 0); - if (!boost_rem_wq) + cpu_boost_wq = alloc_workqueue("cpuboost_wq", WQ_HIGHPRI, 0); + if (!cpu_boost_wq) return -EFAULT; + INIT_WORK(&input_boost_work, do_input_boost); + for_each_possible_cpu(cpu) { s = &per_cpu(sync_info, cpu); s->cpu = cpu; init_waitqueue_head(&s->sync_wq); spin_lock_init(&s->lock); INIT_DELAYED_WORK(&s->boost_rem, do_boost_rem); + INIT_DELAYED_WORK(&s->input_boost_rem, do_input_boost_rem); s->thread = kthread_run(boost_mig_sync_thread, (void *)cpu, "boost_sync/%d", cpu); } atomic_notifier_chain_register(&migration_notifier_head, &boost_migration_nb); + ret = input_register_handler(&cpuboost_input_handler); return 0; } late_initcall(cpu_boost_init); |