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>
This commit is contained in:
codeworkx 2019-03-10 10:57:09 +01:00
parent feee2d2a6f
commit 061081b187

View file

@ -1,385 +1,344 @@
/*
* drivers/input/misc/tri_state_key.c
*
* 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.
* 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/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/ioport.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/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/switch.h>
#include <linux/workqueue.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#define DRIVER_NAME "tri-state-key"
#define NR_STATES (3)
#include <linux/regulator/consumer.h>
#include <linux/timer.h>
#define DRV_NAME "tri-state-key"
/*
* Original tri-state modes designed by OnePlus
*/
typedef enum {
MODE_UNKNOWN,
MODE_MUTE,
MODE_DO_NOT_DISTURB,
MODE_NORMAL,
} tri_mode_t;
/*
* Target key codes sending to OPPO's Keyhandler
* see KeyHandler.java in device/oppo/common/keyhandler
*/
#define KEY_MODE_TOTAL_SILENCE 600
#define KEY_MODE_ALARMS_ONLY 601
#define KEY_MODE_PRIORITY_ONLY 602
#define KEY_MODE_NONE 603
static int current_mode = MODE_UNKNOWN;
/*
* Default mapping between OP's sti-state switch and OPPO's key codes
* see Constants.java in device/oppo/common/configpanel
*/
static int keyCode_slider_top = KEY_MODE_ALARMS_ONLY;
static int keyCode_slider_middle = KEY_MODE_PRIORITY_ONLY;
static int keyCode_slider_bottom = KEY_MODE_NONE;
struct switch_dev_data {
int irq_key1;
int irq_key2;
int irq_key3;
int key1_gpio; // Mute
int key2_gpio; // Do Not Disturb
int key3_gpio; // Normal
struct work_struct work;
struct device *dev;
struct input_dev *input;
struct switch_dev sdev;
struct timer_list s_timer;
struct pinctrl *key_pinctrl;
struct pinctrl_state *set_state;
enum key_vals {
KEYCODE_TOTAL_SILENCE = 600,
KEYCODE_MODE_ALARMS_ONLY,
KEYCODE_MODE_PRIORITY_ONLY,
KEYCODE_MODE_NONE,
KEYCODE_MODE_MAX
};
static struct switch_dev_data *switch_data;
static const char *const proc_names[] = {
"keyCode_top", "keyCode_middle", "keyCode_bottom"
};
static DEFINE_MUTEX(sem);
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 void send_input(int keyCode)
static struct tristate_data *tdata_g;
static void send_input(struct tristate_data *t, int keycode)
{
input_report_key(switch_data->input, keyCode, 1);
input_sync(switch_data->input);
input_report_key(switch_data->input, keyCode, 0);
input_sync(switch_data->input);
input_report_key(t->input, keycode, 1);
input_sync(t->input);
input_report_key(t->input, keycode, 0);
input_sync(t->input);
}
static void switch_dev_work(struct work_struct *work)
static void tristate_process_state(struct tristate_data *t)
{
int key1, key2, key3;
int mode = MODE_UNKNOWN;
int keyCode;
unsigned long key_state = 0;
int i;
mutex_lock(&sem);
/* 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;
key1 = gpio_get_value(switch_data->key1_gpio);
key2 = gpio_get_value(switch_data->key2_gpio);
key3 = gpio_get_value(switch_data->key3_gpio);
/* Spurious interrupt; at least one of the pins should be zero */
if (key_state == 0x7)
return;
if (key1 == 0) {
mode = MODE_MUTE;
keyCode = keyCode_slider_top;
} else if (key2 == 0) {
mode = MODE_DO_NOT_DISTURB;
keyCode = keyCode_slider_middle;
} else if (key3 == 0) {
mode = MODE_NORMAL;
keyCode = keyCode_slider_bottom;
}
/* Redundant interrupt */
if (key_state == t->curr_state)
return;
if (current_mode != mode && mode != MODE_UNKNOWN) {
current_mode = mode;
switch_set_state(&switch_data->sdev, current_mode);
send_input(keyCode);
printk(DRV_NAME " changed to mode: %d\n", switch_data->sdev.state);
}
t->curr_state = key_state;
mutex_unlock(&sem);
/*
* 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]);
}
irqreturn_t switch_dev_interrupt(int irq, void *_dev)
static irqreturn_t tristate_irq_handler(int irq, void *dev_id)
{
schedule_work(&switch_data->work);
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 void timer_handle(unsigned long arg)
static int keycode_get_idx(const char *filename)
{
schedule_work(&switch_data->work);
int i;
for (i = 0; i < NR_STATES; i++) {
if (!strcmp(proc_names[i], filename))
break;
}
return i;
}
#ifdef CONFIG_OF
static int switch_dev_get_devtree_pdata(struct device *dev)
static int keycode_show(struct seq_file *seq, void *offset)
{
struct device_node *node;
struct tristate_data *t = tdata_g;
int idx = (int)(long)seq->private;
node = dev->of_node;
if (!node)
return -EINVAL;
switch_data->key1_gpio = of_get_named_gpio(node, "tristate,gpio_key1", 0);
if (!gpio_is_valid(switch_data->key1_gpio))
return -EINVAL;
switch_data->key2_gpio = of_get_named_gpio(node, "tristate,gpio_key2", 0);
if (!gpio_is_valid(switch_data->key2_gpio))
return -EINVAL;
switch_data->key3_gpio = of_get_named_gpio(node, "tristate,gpio_key3", 0);
if (!gpio_is_valid(switch_data->key3_gpio))
return -EINVAL;
return 0;
}
#else
static inline int
switch_dev_get_devtree_pdata(struct device *dev)
{
return 0;
}
#endif
#define KEYCODE_FOPS(WHICH)\
static int keyCode_##WHICH##_show(struct seq_file *seq, void *offset)\
{\
seq_printf(seq, "%d\n", keyCode_slider_##WHICH);\
return 0;\
}\
static ssize_t keyCode_##WHICH##_write(struct file *file,\
const char __user *page, size_t t, loff_t *lo)\
{\
int data;\
char buf[10];\
if (copy_from_user(buf, page, t)) {\
dev_err(switch_data->dev, "read proc input error.\n");\
return t;\
}\
if (sscanf(buf, "%d", &data) != 1)\
return t;\
if (data < 600 || data > 603)\
return t;\
keyCode_slider_##WHICH = data;\
if (current_mode == 1)\
send_input(keyCode_slider_##WHICH);\
return t;\
}\
static int keyCode_##WHICH##_open(struct inode *inode, struct file *file)\
{\
return single_open(file, keyCode_##WHICH##_show, inode->i_private);\
}\
const struct file_operations proc_keyCode_##WHICH = {\
.owner = THIS_MODULE,\
.open = keyCode_##WHICH##_open,\
.read = seq_read,\
.write = keyCode_##WHICH##_write,\
.llseek = seq_lseek,\
.release = single_release,\
};
KEYCODE_FOPS(top);
KEYCODE_FOPS(middle);
KEYCODE_FOPS(bottom);
#define REGISTER_IRQ_FOR(KEY)\
switch_data->irq_##KEY = gpio_to_irq(switch_data->KEY##_gpio);\
if (switch_data->irq_##KEY <= 0) {\
dev_err(dev, "%s: irq number is not specified, irq #= %d, int pin=%d\n",\
__func__, switch_data->irq_##KEY, switch_data->KEY##_gpio);\
goto err_detect_irq_num_failed;\
} else {\
error = gpio_request(switch_data->KEY##_gpio, "tristate_" #KEY "-int");\
if (error < 0) {\
dev_err(dev, "%s: gpio_request, err=%d\n", __func__, error);\
goto err_request_gpio;\
}\
error = gpio_direction_input(switch_data->KEY##_gpio);\
if (error < 0) {\
dev_err(dev, "%s: gpio_direction_input, err=%d\n", __func__, error);\
goto err_set_gpio_input;\
}\
error = request_irq(switch_data->irq_##KEY, switch_dev_interrupt,\
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, "tristate_" #KEY, switch_data);\
if (error) {\
dev_err(dev, "%s: request_irq, err=%d\n", __func__, error);\
switch_data->irq_##KEY = -EINVAL;\
goto err_request_irq;\
}\
}
static int tristate_dev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct proc_dir_entry *procdir;
int error = 0;
switch_data = kzalloc(sizeof(struct switch_dev_data), GFP_KERNEL);
switch_data->dev = dev;
switch_data->input = input_allocate_device();
// init input device
switch_data->input->name = DRV_NAME;
switch_data->input->dev.parent = &pdev->dev;
set_bit(EV_KEY, switch_data->input->evbit);
set_bit(KEY_MODE_TOTAL_SILENCE, switch_data->input->keybit);
set_bit(KEY_MODE_ALARMS_ONLY, switch_data->input->keybit);
set_bit(KEY_MODE_PRIORITY_ONLY, switch_data->input->keybit);
set_bit(KEY_MODE_NONE, switch_data->input->keybit);
input_set_drvdata(switch_data->input, switch_data);
error = input_register_device(switch_data->input);
if (error) {
dev_err(dev, "Failed to register input device\n");
goto err_input_device_register;
}
// init pinctrl
switch_data->key_pinctrl = devm_pinctrl_get(switch_data->dev);
if (IS_ERR_OR_NULL(switch_data->key_pinctrl)) {
dev_err(dev, "Failed to get pinctrl\n");
goto err_switch_dev_register;
}
switch_data->set_state = pinctrl_lookup_state(switch_data->key_pinctrl,
"pmx_tri_state_key_active");
if (IS_ERR_OR_NULL(switch_data->set_state)) {
dev_err(dev, "Failed to lookup_state\n");
goto err_switch_dev_register;
}
pinctrl_select_state(switch_data->key_pinctrl, switch_data->set_state);
// parse gpios from dt
error = switch_dev_get_devtree_pdata(dev);
if (error) {
dev_err(dev, "parse device tree fail!!!\n");
goto err_switch_dev_register;
}
// irqs and work, timer stuffs
REGISTER_IRQ_FOR(key1);
REGISTER_IRQ_FOR(key2);
REGISTER_IRQ_FOR(key3);
INIT_WORK(&switch_data->work, switch_dev_work);
init_timer(&switch_data->s_timer);
switch_data->s_timer.function = &timer_handle;
switch_data->s_timer.expires = jiffies + 5*HZ;
add_timer(&switch_data->s_timer);
enable_irq_wake(switch_data->irq_key1);
enable_irq_wake(switch_data->irq_key2);
enable_irq_wake(switch_data->irq_key3);
// init switch device
switch_data->sdev.name = DRV_NAME;
error = switch_dev_register(&switch_data->sdev);
if (error < 0) {
dev_err(dev, "Failed to register switch dev\n");
goto err_request_gpio;
}
// init proc fs
procdir = proc_mkdir("tri-state-key", NULL);
proc_create_data("keyCode_top", 0666, procdir,
&proc_keyCode_top, NULL);
proc_create_data("keyCode_middle", 0666, procdir,
&proc_keyCode_middle, NULL);
proc_create_data("keyCode_bottom", 0666, procdir,
&proc_keyCode_bottom, NULL);
return 0;
err_request_gpio:
switch_dev_unregister(&switch_data->sdev);
err_request_irq:
err_detect_irq_num_failed:
err_set_gpio_input:
gpio_free(switch_data->key1_gpio);
gpio_free(switch_data->key2_gpio);
gpio_free(switch_data->key3_gpio);
err_switch_dev_register:
kfree(switch_data);
err_input_device_register:
input_unregister_device(switch_data->input);
input_free_device(switch_data->input);
dev_err(dev, "%s error: %d\n", __func__, error);
return error;
}
static int tristate_dev_remove(struct platform_device *pdev)
{
cancel_work_sync(&switch_data->work);
gpio_free(switch_data->key1_gpio);
gpio_free(switch_data->key2_gpio);
gpio_free(switch_data->key3_gpio);
switch_dev_unregister(&switch_data->sdev);
kfree(switch_data);
seq_printf(seq, "%d\n", t->key_codes[idx]);
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id tristate_dev_of_match[] = {
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", },
{ },
};
MODULE_DEVICE_TABLE(of, tristate_dev_of_match);
#endif
static struct platform_driver tristate_dev_driver = {
.probe = tristate_dev_probe,
.remove = tristate_dev_remove,
static struct platform_driver tristate_driver = {
.probe = tristate_probe,
.driver = {
.name = DRV_NAME,
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(tristate_dev_of_match),
.of_match_table = tristate_of_match,
},
};
module_platform_driver(tristate_dev_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("switch Profiles by this triple key driver");
static int __init tristate_init(void)
{
return platform_driver_register(&tristate_driver);
}
device_initcall(tristate_init);