input: add keycombo, a general key combo driver.

Keycombo lets you provide a key up and key down function, and an
optional time delay for key down. The driver will call the key
down function after the specified key combo has been held for the
speicified time delay. After you release the combo, if the key down
has happened, it calls key up.

Change-Id: I6a9a94e96a8f58fadd908fd1dc7944b9102a089f
Signed-off-by: Daniel Rosenberg <drosen@google.com>
Git-commit: 491e319ddc9388350826ef2333f5cbe27ace7f1c
Git-repo: https://android.googlesource.com/kernel/common.git
Signed-off-by: Ian Maund <imaund@codeaurora.org>
This commit is contained in:
Daniel Rosenberg 2014-05-07 16:52:10 -07:00 committed by Ian Maund
parent 379327269c
commit 1cc4bcfcbc
4 changed files with 308 additions and 0 deletions

View file

@ -183,6 +183,15 @@ config INPUT_KEYRESET
To compile this driver as a module, choose M here: the
module will be called keyreset.
config INPUT_KEYCOMBO
tristate "Key combo"
depends on INPUT
---help---
Say Y here if you want to take action when some keys are pressed;
To compile this driver as a module, choose M here: the
module will be called keycombo.
comment "Input Device Drivers"
source "drivers/input/keyboard/Kconfig"

View file

@ -26,3 +26,5 @@ obj-$(CONFIG_INPUT_MISC) += misc/
obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o
obj-$(CONFIG_INPUT_KEYRESET) += keyreset.o
obj-$(CONFIG_INPUT_KEYCOMBO) += keycombo.o

261
drivers/input/keycombo.c Normal file
View file

@ -0,0 +1,261 @@
/* drivers/input/keycombo.c
*
* Copyright (C) 2014 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <linux/input.h>
#include <linux/keycombo.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/sched.h>
#include <linux/slab.h>
struct keycombo_state {
struct input_handler input_handler;
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long upbit[BITS_TO_LONGS(KEY_CNT)];
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
spinlock_t lock;
struct workqueue_struct *wq;
int key_down_target;
int key_down;
int key_up;
struct delayed_work key_down_work;
int delay;
struct work_struct key_up_work;
void (*key_up_fn)(void *);
void (*key_down_fn)(void *);
void *priv;
int key_is_down;
struct wakeup_source combo_held_wake_source;
struct wakeup_source combo_up_wake_source;
};
static void do_key_down(struct work_struct *work)
{
struct delayed_work *dwork = container_of(work, struct delayed_work,
work);
struct keycombo_state *state = container_of(dwork,
struct keycombo_state, key_down_work);
if (state->key_down_fn)
state->key_down_fn(state->priv);
}
static void do_key_up(struct work_struct *work)
{
struct keycombo_state *state = container_of(work, struct keycombo_state,
key_up_work);
if (state->key_up_fn)
state->key_up_fn(state->priv);
__pm_relax(&state->combo_up_wake_source);
}
static void keycombo_event(struct input_handle *handle, unsigned int type,
unsigned int code, int value)
{
unsigned long flags;
struct keycombo_state *state = handle->private;
if (type != EV_KEY)
return;
if (code >= KEY_MAX)
return;
if (!test_bit(code, state->keybit))
return;
spin_lock_irqsave(&state->lock, flags);
if (!test_bit(code, state->key) == !value)
goto done;
__change_bit(code, state->key);
if (test_bit(code, state->upbit)) {
if (value)
state->key_up++;
else
state->key_up--;
} else {
if (value)
state->key_down++;
else
state->key_down--;
}
if (state->key_down == state->key_down_target && state->key_up == 0) {
__pm_stay_awake(&state->combo_held_wake_source);
state->key_is_down = 1;
if (queue_delayed_work(state->wq, &state->key_down_work,
state->delay))
pr_debug("Key down work already queued!");
} else if (state->key_is_down) {
if (!cancel_delayed_work(&state->key_down_work)) {
__pm_stay_awake(&state->combo_up_wake_source);
queue_work(state->wq, &state->key_up_work);
}
__pm_relax(&state->combo_held_wake_source);
state->key_is_down = 0;
}
done:
spin_unlock_irqrestore(&state->lock, flags);
}
static int keycombo_connect(struct input_handler *handler,
struct input_dev *dev,
const struct input_device_id *id)
{
int i;
int ret;
struct input_handle *handle;
struct keycombo_state *state =
container_of(handler, struct keycombo_state, input_handler);
for (i = 0; i < KEY_MAX; i++) {
if (test_bit(i, state->keybit) && test_bit(i, dev->keybit))
break;
}
if (i == KEY_MAX)
return -ENODEV;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return -ENOMEM;
handle->dev = dev;
handle->handler = handler;
handle->name = KEYCOMBO_NAME;
handle->private = state;
ret = input_register_handle(handle);
if (ret)
goto err_input_register_handle;
ret = input_open_device(handle);
if (ret)
goto err_input_open_device;
return 0;
err_input_open_device:
input_unregister_handle(handle);
err_input_register_handle:
kfree(handle);
return ret;
}
static void keycombo_disconnect(struct input_handle *handle)
{
input_close_device(handle);
input_unregister_handle(handle);
kfree(handle);
}
static const struct input_device_id keycombo_ids[] = {
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT,
.evbit = { BIT_MASK(EV_KEY) },
},
{ },
};
MODULE_DEVICE_TABLE(input, keycombo_ids);
static int keycombo_probe(struct platform_device *pdev)
{
int ret;
int key, *keyp;
struct keycombo_state *state;
struct keycombo_platform_data *pdata = pdev->dev.platform_data;
if (!pdata)
return -EINVAL;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
spin_lock_init(&state->lock);
keyp = pdata->keys_down;
while ((key = *keyp++)) {
if (key >= KEY_MAX)
continue;
state->key_down_target++;
__set_bit(key, state->keybit);
}
if (pdata->keys_up) {
keyp = pdata->keys_up;
while ((key = *keyp++)) {
if (key >= KEY_MAX)
continue;
__set_bit(key, state->keybit);
__set_bit(key, state->upbit);
}
}
state->wq = alloc_ordered_workqueue("keycombo", 0);
if (!state->wq)
return -ENOMEM;
state->priv = pdata->priv;
if (pdata->key_down_fn)
state->key_down_fn = pdata->key_down_fn;
INIT_DELAYED_WORK(&state->key_down_work, do_key_down);
if (pdata->key_up_fn)
state->key_up_fn = pdata->key_up_fn;
INIT_WORK(&state->key_up_work, do_key_up);
wakeup_source_init(&state->combo_held_wake_source, "key combo");
wakeup_source_init(&state->combo_up_wake_source, "key combo up");
state->delay = msecs_to_jiffies(pdata->key_down_delay);
state->input_handler.event = keycombo_event;
state->input_handler.connect = keycombo_connect;
state->input_handler.disconnect = keycombo_disconnect;
state->input_handler.name = KEYCOMBO_NAME;
state->input_handler.id_table = keycombo_ids;
ret = input_register_handler(&state->input_handler);
if (ret) {
kfree(state);
return ret;
}
platform_set_drvdata(pdev, state);
return 0;
}
int keycombo_remove(struct platform_device *pdev)
{
struct keycombo_state *state = platform_get_drvdata(pdev);
input_unregister_handler(&state->input_handler);
destroy_workqueue(state->wq);
kfree(state);
return 0;
}
struct platform_driver keycombo_driver = {
.driver.name = KEYCOMBO_NAME,
.probe = keycombo_probe,
.remove = keycombo_remove,
};
static int __init keycombo_init(void)
{
return platform_driver_register(&keycombo_driver);
}
static void __exit keycombo_exit(void)
{
return platform_driver_unregister(&keycombo_driver);
}
module_init(keycombo_init);
module_exit(keycombo_exit);

36
include/linux/keycombo.h Normal file
View file

@ -0,0 +1,36 @@
/*
* include/linux/keycombo.h - platform data structure for keycombo driver
*
* Copyright (C) 2014 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef _LINUX_KEYCOMBO_H
#define _LINUX_KEYCOMBO_H
#define KEYCOMBO_NAME "keycombo"
/*
* if key_down_fn and key_up_fn are both present, you are guaranteed that
* key_down_fn will return before key_up_fn is called, and that key_up_fn
* is called iff key_down_fn is called.
*/
struct keycombo_platform_data {
void (*key_down_fn)(void *);
void (*key_up_fn)(void *);
void *priv;
int key_down_delay; /* Time in ms */
int *keys_up;
int keys_down[]; /* 0 terminated */
};
#endif /* _LINUX_KEYCOMBO_H */