/* * drivers/input/input-cfboost.c * * Copyright (c) 2012-2013 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include #include #include #include #include #include #include #define CREATE_TRACE_POINTS #include /* This module listens to input events and sets a temporary frequency * floor upon input event detection. This is based on changes to * cpufreq ondemand governor by: * * Tero Kristo * Brian Steuer * David Ng * * at git://codeaurora.org/kernel/msm.git tree, commits: * * 2a6181bc76c6ce46ca0fa8e547be42acd534cf0e * 1cca8861d8fda4e05f6b0c59c60003345c15454d * 96a9aeb02bf5b3fbbef47e44460750eb275e9f1b * b600449501cf15928440f87eff86b1f32d14214e * 88a65c7ae04632ffee11f9fc628d7ab017c06b83 */ MODULE_AUTHOR("Antti P Miettinen "); MODULE_DESCRIPTION("Input event CPU frequency booster"); MODULE_LICENSE("GPL v2"); static struct pm_qos_request freq_req, core_req; static struct work_struct boost; static struct delayed_work unboost; static unsigned int boost_freq; /* kHz */ static int boost_freq_set(const char *arg, const struct kernel_param *kp) { unsigned int old_boost = boost_freq; int ret = param_set_uint(arg, kp); if (ret == 0 && old_boost && !boost_freq) pm_qos_update_request(&freq_req, PM_QOS_DEFAULT_VALUE); return ret; } static int boost_freq_get(char *buffer, const struct kernel_param *kp) { return param_get_uint(buffer, kp); } static struct kernel_param_ops boost_freq_ops = { .set = boost_freq_set, .get = boost_freq_get, }; module_param_cb(boost_freq, &boost_freq_ops, &boost_freq, 0644); static unsigned long boost_time = 500; /* ms */ module_param(boost_time, ulong, 0644); static struct workqueue_struct *cfb_wq; static void cfb_boost(struct work_struct *w) { trace_input_cfboost_params("boost_params", boost_freq, boost_time); cancel_delayed_work_sync(&unboost); pm_qos_update_request(&core_req, 1); pm_qos_update_request(&freq_req, boost_freq); queue_delayed_work(cfb_wq, &unboost, msecs_to_jiffies(boost_time)); } static void cfb_unboost(struct work_struct *w) { pm_qos_update_request(&freq_req, PM_QOS_DEFAULT_VALUE); pm_qos_update_request(&core_req, PM_QOS_DEFAULT_VALUE); } static void cfb_input_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { trace_input_cfboost_event("event", type, code, value); if (!work_pending(&boost)) queue_work(cfb_wq, &boost); } static int cfb_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 = "icfboost"; 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 cfb_input_disconnect(struct input_handle *handle) { input_close_device(handle); input_unregister_handle(handle); kfree(handle); } /* XXX make configurable */ static const struct input_device_id cfb_ids[] = { { /* touch screen */ .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_ABS) }, .keybit = {[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, }, { /* mouse */ .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_REL) }, .keybit = {[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_MOUSE) }, }, /* keypad */ { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = {[BIT_WORD(KEY_HOME)] = BIT_MASK(KEY_HOME) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = {[BIT_WORD(KEY_VOLUMEUP)] = BIT_MASK(KEY_VOLUMEUP) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = {[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER) }, }, /* joystick */ { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT_MASK(EV_ABS) }, .absbit = { BIT_MASK(ABS_X) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT_MASK(EV_ABS) }, .absbit = { BIT_MASK(ABS_WHEEL) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT_MASK(EV_ABS) }, .absbit = { BIT_MASK(ABS_THROTTLE) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = {[BIT_WORD(BTN_JOYSTICK)] = BIT_MASK(BTN_JOYSTICK) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = { [BIT_WORD(BTN_GAMEPAD)] = BIT_MASK(BTN_GAMEPAD) }, }, { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_KEY) }, .keybit = { [BIT_WORD(BTN_TRIGGER_HAPPY)] = BIT_MASK(BTN_TRIGGER_HAPPY) }, }, /* stylus */ { .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT_MASK(EV_ABS) }, .keybit = {[BIT_WORD(BTN_TOOL_RUBBER)] = BIT_MASK(BTN_TOOL_RUBBER) }, }, /* terminating entry */ { }, }; static struct input_handler cfb_input_handler = { .event = cfb_input_event, .connect = cfb_input_connect, .disconnect = cfb_input_disconnect, .name = "icfboost", .id_table = cfb_ids, }; static int __init cfboost_init(void) { int ret; cfb_wq = create_workqueue("icfb-wq"); if (!cfb_wq) return -ENOMEM; INIT_WORK(&boost, cfb_boost); INIT_DELAYED_WORK(&unboost, cfb_unboost); ret = input_register_handler(&cfb_input_handler); if (ret) { destroy_workqueue(cfb_wq); return ret; } pm_qos_add_request(&core_req, PM_QOS_MIN_ONLINE_CPUS, PM_QOS_DEFAULT_VALUE); pm_qos_add_request(&freq_req, PM_QOS_CPU_FREQ_MIN, PM_QOS_DEFAULT_VALUE); return 0; } static void __exit cfboost_exit(void) { /* stop input events */ input_unregister_handler(&cfb_input_handler); /* cancel pending work requests */ cancel_work_sync(&boost); cancel_delayed_work_sync(&unboost); /* clean up */ destroy_workqueue(cfb_wq); pm_qos_remove_request(&freq_req); pm_qos_remove_request(&core_req); } module_init(cfboost_init); module_exit(cfboost_exit);