android_kernel_oneplus_msm8998/drivers/input/misc/tri_state_key.c
codeworkx 061081b187 tri_state_key: import driver from msm8996
input: tri-state-key: Fix trivial code style issue in IRQ handler

Change-Id: Ie1e9396cf4674586a68dbf606a1d51fbffeaaca4
Signed-off-by: Sultanxda <sultanxda@gmail.com>

input: tri-state-key: Use ffz() instead of find_first_zero_bit()

find_first_zero_bit() is intended for large bitmaps; ffz() is much faster
when only a single word needs to be searched through.

Change-Id: Ib81b742805489947164af96bb8603f6732d64e8b
Suggested-by: Jason A. Donenfeld <Jason@zx2c4.com>
Signed-off-by: Sultanxda <sultanxda@gmail.com>

input: tri-state-key: Clean up some code styling

Use a macro to store the number of states, reduce the curr_state member in
the struct to uint8_t (since it only needs to contain 3 bits), and use
find_first_zero_bit() instead of a loop to find the first zero bit.

Change-Id: I35f369dc30186ab95033a4d27b550a07e4dc2dd8
Suggested-by: Joel Porquet <joel@porquet.org>
Signed-off-by: Sultanxda <sultanxda@gmail.com>

input: tri-state-key: Rewrite and optimize

A driver for such a simple device shouldn't be so ugly. Clean it up and
optimize it in the process.

Summary of changes:
-Remove unneeded switch device
-Remove unused/unnecessary struct members
-Remove module references (this driver will always be built in)
-Utilize fixed loops and clever logic to eliminate significant amounts of
 code duplication
-Remove unused pinctrl code
-Use a threaded interrupt handler
-Process interrupts directly in the threaded interrupt handler (which runs
 with realtime priority) rather than a worker
-Read the initial switch state upon init (so userspace gets the correct
 switch state upon boot)
-Refactor code wherever possible to make it cleaner

Note that although the procfs naming scheme is non-standard (i.e.
"keyCode_top" instead of "keycode_top"), the old naming scheme is retained
in order to maintain compatibility with userspace.

Change-Id: Ic2de6ddeb223aa7669d61c186be0b57a15e1488b
Signed-off-by: Sultanxda <sultanxda@gmail.com>
2019-03-10 10:57:09 +01:00

344 lines
7.6 KiB
C

/*
* drivers/input/misc/tri_state_key.c
*
* Copyright (C) 2018, Sultanxda <sultanxda@gmail.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 and
* only version 2 as published by the Free Software Foundation.
*
* 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/gpio.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define DRIVER_NAME "tri-state-key"
#define NR_STATES (3)
enum key_vals {
KEYCODE_TOTAL_SILENCE = 600,
KEYCODE_MODE_ALARMS_ONLY,
KEYCODE_MODE_PRIORITY_ONLY,
KEYCODE_MODE_NONE,
KEYCODE_MODE_MAX
};
static const char *const proc_names[] = {
"keyCode_top", "keyCode_middle", "keyCode_bottom"
};
struct tristate_data {
struct device *dev;
struct input_dev *input;
struct mutex irq_lock;
enum key_vals key_codes[NR_STATES];
int key_gpios[NR_STATES];
uint8_t curr_state;
};
static struct tristate_data *tdata_g;
static void send_input(struct tristate_data *t, int keycode)
{
input_report_key(t->input, keycode, 1);
input_sync(t->input);
input_report_key(t->input, keycode, 0);
input_sync(t->input);
}
static void tristate_process_state(struct tristate_data *t)
{
unsigned long key_state = 0;
int i;
/* Store the GPIO values of the keys as a bitmap */
for (i = 0; i < NR_STATES; i++)
key_state |= !!gpio_get_value(t->key_gpios[i]) << i;
/* Spurious interrupt; at least one of the pins should be zero */
if (key_state == 0x7)
return;
/* Redundant interrupt */
if (key_state == t->curr_state)
return;
t->curr_state = key_state;
/*
* Find the first bit set to zero; this is the current position. Bit 0
* corresponds to the top position, bit 1 corresponds to the middle
* position, and bit 2 corresponds to the bottom position.
*/
i = ffz(key_state);
send_input(t, t->key_codes[i]);
}
static irqreturn_t tristate_irq_handler(int irq, void *dev_id)
{
struct tristate_data *t = dev_id;
/* This handler is shared between three interrupt lines */
mutex_lock(&t->irq_lock);
tristate_process_state(t);
mutex_unlock(&t->irq_lock);
return IRQ_HANDLED;
}
static int keycode_get_idx(const char *filename)
{
int i;
for (i = 0; i < NR_STATES; i++) {
if (!strcmp(proc_names[i], filename))
break;
}
return i;
}
static int keycode_show(struct seq_file *seq, void *offset)
{
struct tristate_data *t = tdata_g;
int idx = (int)(long)seq->private;
seq_printf(seq, "%d\n", t->key_codes[idx]);
return 0;
}
static ssize_t tristate_keycode_write(struct file *file,
const char __user *page, size_t count, loff_t *offset)
{
struct tristate_data *t = tdata_g;
char buf[sizeof("600")];
int data, idx;
memset(buf, 0, sizeof(buf));
if (copy_from_user(buf, page, min_t(int, count, 3))) {
dev_err(t->dev, "Failed to read procfs input\n");
return count;
}
if (kstrtoint(buf, 10, &data) < 0)
return count;
if (data < KEYCODE_TOTAL_SILENCE || data >= KEYCODE_MODE_MAX)
return count;
idx = keycode_get_idx(file->f_path.dentry->d_iname);
mutex_lock(&t->irq_lock);
t->key_codes[idx] = data;
if (!(t->curr_state & BIT(idx)))
send_input(t, data);
mutex_unlock(&t->irq_lock);
return count;
}
static int tristate_keycode_open(struct inode *inode, struct file *file)
{
int idx = keycode_get_idx(file->f_path.dentry->d_iname);
/* Pass the index to keycode_show */
return single_open(file, keycode_show, (void *)(long)idx);
}
static const struct file_operations tristate_keycode_proc = {
.owner = THIS_MODULE,
.open = tristate_keycode_open,
.read = seq_read,
.write = tristate_keycode_write,
.llseek = seq_lseek,
.release = single_release,
};
static int tristate_parse_dt(struct tristate_data *t)
{
struct device_node *np;
char prop_name[sizeof("tristate,gpio_key#")];
int i;
np = t->dev->of_node;
if (!np)
return -EINVAL;
for (i = 0; i < NR_STATES; i++) {
sprintf(prop_name, "tristate,gpio_key%d", i + 1);
t->key_gpios[i] = of_get_named_gpio(np, prop_name, 0);
if (!gpio_is_valid(t->key_gpios[i])) {
dev_err(t->dev, "Invalid %s property\n", prop_name);
return -EINVAL;
}
}
return 0;
}
static int tristate_register_irqs(struct tristate_data *t)
{
char label[sizeof("tristate_key#-int")];
int i, j, key_irqs[NR_STATES], ret;
/* Get the IRQ numbers corresponding to the GPIOs */
for (i = 0; i < NR_STATES; i++) {
key_irqs[i] = gpio_to_irq(t->key_gpios[i]);
if (key_irqs[i] < 0) {
dev_err(t->dev, "Invalid IRQ (%d) for GPIO%d\n",
key_irqs[i], i + 1);
return -EINVAL;
}
}
for (i = 0; i < NR_STATES; i++) {
sprintf(label, "tristate_key%d-int", i + 1);
ret = gpio_request(t->key_gpios[i], label);
if (ret < 0) {
dev_err(t->dev, "Failed to request GPIO%d, ret: %d\n",
i + 1, ret);
goto free_gpios;
}
ret = gpio_direction_input(t->key_gpios[i]);
if (ret < 0) {
dev_err(t->dev, "Failed to set GPIO%d dir, ret: %d\n",
i + 1, ret);
goto free_gpios;
}
sprintf(label, "tristate_key%d", i + 1);
ret = request_threaded_irq(key_irqs[i], NULL,
tristate_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, label, t);
if (ret < 0) {
dev_err(t->dev, "Failed to register %s IRQ, ret: %d\n",
label, ret);
goto free_irqs;
}
}
for (i = 0; i < NR_STATES; i++)
enable_irq_wake(key_irqs[i]);
return 0;
free_irqs:
for (j = i; j--;)
free_irq(key_irqs[j], t);
i = NR_STATES;
free_gpios:
for (j = i; j--;)
gpio_free(t->key_gpios[j]);
return -EINVAL;
}
static void tristate_create_procfs(void)
{
struct proc_dir_entry *proc_dir;
int i;
proc_dir = proc_mkdir("tri-state-key", NULL);
if (!proc_dir)
return;
for (i = 0; i < NR_STATES; i++)
proc_create_data(proc_names[i], 0644, proc_dir,
&tristate_keycode_proc, NULL);
}
static int tristate_probe(struct platform_device *pdev)
{
struct tristate_data *t;
int i, ret;
t = kzalloc(sizeof(*t), GFP_KERNEL);
if (!t)
return -ENOMEM;
t->dev = &pdev->dev;
tdata_g = t;
ret = tristate_parse_dt(t);
if (ret)
goto free_t;
t->input = input_allocate_device();
if (!t->input) {
dev_err(t->dev, "Failed to allocate input device\n");
ret = -ENOMEM;
goto free_t;
}
t->input->name = DRIVER_NAME;
t->input->dev.parent = t->dev;
input_set_drvdata(t->input, t);
set_bit(EV_KEY, t->input->evbit);
set_bit(KEYCODE_TOTAL_SILENCE, t->input->keybit);
set_bit(KEYCODE_MODE_ALARMS_ONLY, t->input->keybit);
set_bit(KEYCODE_MODE_PRIORITY_ONLY, t->input->keybit);
set_bit(KEYCODE_MODE_NONE, t->input->keybit);
ret = input_register_device(t->input);
if (ret)
goto free_input;
/* Initialize with default keycodes */
for (i = 0; i < NR_STATES; i++)
t->key_codes[i] = KEYCODE_TOTAL_SILENCE + i;
/* Process initial state */
tristate_process_state(t);
mutex_init(&t->irq_lock);
ret = tristate_register_irqs(t);
if (ret)
goto input_unregister;
tristate_create_procfs();
return 0;
input_unregister:
input_unregister_device(t->input);
free_input:
input_free_device(t->input);
free_t:
kfree(t);
return ret;
}
static const struct of_device_id tristate_of_match[] = {
{ .compatible = "oneplus,tri-state-key", },
{ },
};
static struct platform_driver tristate_driver = {
.probe = tristate_probe,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = tristate_of_match,
},
};
static int __init tristate_init(void)
{
return platform_driver_register(&tristate_driver);
}
device_initcall(tristate_init);