From: "hj2.im" <hj2.im@xxxxxxxxxxx> Date: Thu, 29 Oct 2020 22:11:24 +0900 Subject: [PATCH v2] input: add 2 kind of switch We need support to various accessories on the device, some switch does not exist in switch list. So added switch for the following purpose. SW_COVER_ATTACHED is for the checking the cover attached or not on the device. SW_EXT_PEN_ATTACHED is for the checking the external pen attached or not on the device Signed-off-by: Hyungjae Im <hj2.im@xxxxxxxxxxx> --- drivers/input/Kconfig | 20 ++ drivers/input/Makefile | 3 + drivers/input/cover_detect.c | 242 ++++++++++++++++++++++++ drivers/input/ext_pen_detect.c | 243 +++++++++++++++++++++++++ include/linux/mod_devicetable.h | 2 +- include/uapi/linux/input-event-codes.h | 4 +- 6 files changed, 512 insertions(+), 2 deletions(-) create mode 100644 drivers/input/cover_detect.c create mode 100644 drivers/input/ext_pen_detect.c diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 1efd3154b68d..df902f4a549e 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -185,6 +185,26 @@ config INPUT_APMPOWER To compile this driver as a module, choose M here: the module will be called apm-power. +config COVER_DETECT + tristate "Enable cover attach detection" + default n + help + Say Y here to enable cover attach detection + and send a event when cover is attached/detached. + Active gpio state is low and active event value is 0. + + If unsure, say N. + +config EXT_PEN_DETECT + tristate "Enable external pen attach detection" + default n + help + Say Y here to enable external pen attach detection + and send a event when external pen is attached/detached. + Active gpio state is low and active event value is 0. + + If unsure, say N. + comment "Input Device Drivers" source "drivers/input/keyboard/Kconfig" diff --git a/drivers/input/Makefile b/drivers/input/Makefile index e35650930371..31ee1f2d2e21 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -29,3 +29,6 @@ obj-$(CONFIG_INPUT_MISC) += misc/ obj-$(CONFIG_INPUT_APMPOWER) += apm-power.o obj-$(CONFIG_RMI4_CORE) += rmi4/ + +obj-$(CONFIG_COVER_DETECT) += cover_detect.o +obj-$(CONFIG_EXT_PEN_DETECT) += ext_pen_detect.o diff --git a/drivers/input/cover_detect.c b/drivers/input/cover_detect.c new file mode 100644 index 000000000000..4d3d68c616ec --- /dev/null +++ b/drivers/input/cover_detect.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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/module.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/wakelock.h> + +struct cover_detect_drvdata { + struct input_dev *input; + struct delayed_work cover_detect_dwork; + struct wakeup_source *ws; + int gpio_cover_detect; + int irq_cover_detect; +}; + +static void cover_detect_work(struct work_struct *work) +{ + struct cover_detect_drvdata *ddata = + container_of(work, struct cover_detect_drvdata, + cover_detect_dwork.work); + bool cover_status; + + cover_status = gpio_get_value(ddata->gpio_cover_detect); + + input_report_switch(ddata->input, + SW_COVER_ATTACHED, cover_status); + input_sync(ddata->input); +} + +static void __cover_detect(struct cover_detect_drvdata *ddata, + bool cover_status) +{ + cancel_delayed_work_sync(&ddata->cover_detect_dwork); + if (cover_status) { + __pm_wakeup_event(ddata->ws, jiffies_to_msecs(HZ / 20)); + schedule_delayed_work(&ddata->cover_detect_dwork, HZ * 1 / 100); + } else { + __pm_relax(ddata->ws); + schedule_delayed_work(&ddata->cover_detect_dwork, 0); + } +} + +static irqreturn_t cover_detect_irq(int irq, void *dev_id) +{ + bool cover_status; + struct cover_detect_drvdata *ddata = dev_id; + + cover_status = gpio_get_value(ddata->gpio_cover_detect); + + __cover_detect(ddata, cover_status); + + return IRQ_HANDLED; +} + +static int cover_detect_open(struct input_dev *input) +{ + struct cover_detect_drvdata *ddata = input_get_drvdata(input); + /* update the current status */ + schedule_delayed_work(&ddata->cover_detect_dwork, HZ / 2); + /* Report current state of buttons that are connected to GPIOs */ + input_sync(input); + + return 0; +} + +static void cover_detect_close(struct input_dev *input) +{ +} + +static void init_cover_detect_irq(struct input_dev *input) +{ + struct cover_detect_drvdata *ddata = input_get_drvdata(input); + + int ret = 0; + int irq = ddata->irq_cover_detect; + + ret = request_threaded_irq( + irq, NULL, + cover_detect_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "cover_detect", ddata); + if (ret < 0) + pr_err("keys: failed to request cover detect irq %d gpio %d\n", + irq, ddata->gpio_cover_detect); +} + +#ifdef CONFIG_OF +static int of_cover_detect_data_parsing_dt(struct device *dev, + struct cover_detect_drvdata *ddata) +{ + struct device_node *np = dev->of_node; + int gpio; + enum of_gpio_flags flags; + + gpio = of_get_named_gpio_flags(np, "gpio_cover_detect", 0, &flags); + ddata->gpio_cover_detect = gpio; + + ddata->irq_cover_detect = gpio_to_irq(gpio); + + return 0; +} +#endif + +static int cover_detect_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cover_detect_drvdata *ddata; + struct input_dev *input; + int error; + int wakeup = 0; + + ddata = kzalloc(sizeof(struct cover_detect_drvdata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + +#ifdef CONFIG_OF + if (dev->of_node) { + error = of_cover_detect_data_parsing_dt(dev, ddata); + if (error < 0) { + dev_err(dev, "fail to get the devicetree, error: %d\n", + error); + goto fail1; + } + } +#endif + + input = input_allocate_device(); + if (!input) { + dev_err(dev, "failed to allocate state\n"); + error = -ENOMEM; + goto fail1; + } + + ddata->input = input; + + ddata->ws = wakeup_source_register(NULL, "cover_detect"); + + platform_set_drvdata(pdev, ddata); + input_set_drvdata(input, ddata); + + input->name = "cover_detect"; + input->phys = "cover_detect"; + input->dev.parent = &pdev->dev; + + input->evbit[0] |= BIT_MASK(EV_SW); + input_set_capability(input, EV_SW, SW_COVER_ATTACHED); + + input->open = cover_detect_open; + input->close = cover_detect_close; + + /* Enable auto repeat feature of Linux input subsystem */ + __set_bit(EV_REP, input->evbit); + + INIT_DELAYED_WORK(&ddata->cover_detect_dwork, cover_detect_work); + + init_cover_detect_irq(input); + + if (ddata->gpio_cover_detect != 0) { + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + goto fail1; + } + } + + device_init_wakeup(&pdev->dev, wakeup); + + return 0; + + fail1: + kfree(ddata); + + return error; +} + +static int cover_detect_remove(struct platform_device *pdev) +{ + struct cover_detect_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + + device_init_wakeup(&pdev->dev, 0); + + input_unregister_device(input); + + wakeup_source_unregister(ddata->ws); + + kfree(ddata); + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id cover_detect_dt_ids[] = { + { .compatible = "cover_detect" }, + { }, +}; +MODULE_DEVICE_TABLE(of, cover_detect_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver cover_detect_device_driver = { + .probe = cover_detect_probe, + .remove = cover_detect_remove, + .driver = { + .name = "cover_detect", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = cover_detect_dt_ids, +#endif /* CONFIG_OF */ + } +}; + +static int __init cover_detect_init(void) +{ + return platform_driver_register(&cover_detect_device_driver); +} + +static void __exit cover_detect_exit(void) +{ + platform_driver_unregister(&cover_detect_device_driver); +} + +module_init(cover_detect_init); +module_exit(cover_detect_exit); diff --git a/drivers/input/ext_pen_detect.c b/drivers/input/ext_pen_detect.c new file mode 100644 index 000000000000..3595b967718b --- /dev/null +++ b/drivers/input/ext_pen_detect.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co. Ltd. All Rights Reserved. + * + * 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/module.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/wakelock.h> + +struct ext_pen_detect_drvdata { + struct input_dev *input; + struct delayed_work ext_pen_detect_dwork; + struct wakeup_source *ws; + int gpio_ext_pen_detect; + int irq_ext_pen_detect; +}; + +static void ext_pen_detect_work(struct work_struct *work) +{ + struct ext_pen_detect_drvdata *ddata = + container_of(work, struct ext_pen_detect_drvdata, + ext_pen_detect_dwork.work); + bool ext_pen_status; + + ext_pen_status = gpio_get_value(ddata->gpio_ext_pen_detect); + + input_report_switch(ddata->input, + SW_EXT_PEN_ATTACHED, ext_pen_status); + input_sync(ddata->input); +} + +static void __ext_pen_detect(struct ext_pen_detect_drvdata *ddata, + bool ext_pen_status) +{ + cancel_delayed_work_sync(&ddata->ext_pen_detect_dwork); + if (ext_pen_status) { + __pm_wakeup_event(ddata->ws, jiffies_to_msecs(HZ / 20)); + schedule_delayed_work(&ddata->ext_pen_detect_dwork, + HZ * 1 / 100); + } else { + __pm_relax(ddata->ws); + schedule_delayed_work(&ddata->ext_pen_detect_dwork, 0); + } +} + +static irqreturn_t ext_pen_detect_irq(int irq, void *dev_id) +{ + bool ext_pen_status; + struct ext_pen_detect_drvdata *ddata = dev_id; + + ext_pen_status = gpio_get_value(ddata->gpio_ext_pen_detect); + + __ext_pen_detect(ddata, ext_pen_status); + + return IRQ_HANDLED; +} + +static int ext_pen_detect_open(struct input_dev *input) +{ + struct ext_pen_detect_drvdata *ddata = input_get_drvdata(input); + /* update the current status */ + schedule_delayed_work(&ddata->ext_pen_detect_dwork, HZ / 2); + /* Report current state of buttons that are connected to GPIOs */ + input_sync(input); + + return 0; +} + +static void ext_pen_detect_close(struct input_dev *input) +{ +} + +static void init_ext_pen_detect_irq(struct input_dev *input) +{ + struct ext_pen_detect_drvdata *ddata = input_get_drvdata(input); + + int ret = 0; + int irq = ddata->irq_ext_pen_detect; + + ret = request_threaded_irq( + irq, NULL, + ext_pen_detect_irq, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "ext_pen_detect", ddata); + if (ret < 0) + pr_err("keys: failed to request ext_pen detect irq %d gpio %d\n", + irq, ddata->gpio_ext_pen_detect); +} + +#ifdef CONFIG_OF +static int of_ext_pen_detect_data_parsing_dt(struct device *dev, + struct ext_pen_detect_drvdata *ddata) +{ + struct device_node *np = dev->of_node; + int gpio; + enum of_gpio_flags flags; + + gpio = of_get_named_gpio_flags(np, "gpio_ext_pen_detect", 0, &flags); + ddata->gpio_ext_pen_detect = gpio; + + ddata->irq_ext_pen_detect = gpio_to_irq(gpio); + + return 0; +} +#endif + +static int ext_pen_detect_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ext_pen_detect_drvdata *ddata; + struct input_dev *input; + int error; + int wakeup = 0; + + ddata = kzalloc(sizeof(struct ext_pen_detect_drvdata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + +#ifdef CONFIG_OF + if (dev->of_node) { + error = of_ext_pen_detect_data_parsing_dt(dev, ddata); + if (error < 0) { + dev_err(dev, "fail to get the devicetree, error: %d\n", + error); + goto fail1; + } + } +#endif + + input = input_allocate_device(); + if (!input) { + dev_err(dev, "failed to allocate state\n"); + error = -ENOMEM; + goto fail1; + } + + ddata->input = input; + + ddata->ws = wakeup_source_register(NULL, "ext_pen_detect"); + + platform_set_drvdata(pdev, ddata); + input_set_drvdata(input, ddata); + + input->name = "ext_pen_detect"; + input->phys = "ext_pen_detect"; + input->dev.parent = &pdev->dev; + + input->evbit[0] |= BIT_MASK(EV_SW); + input_set_capability(input, EV_SW, SW_EXT_PEN_ATTACHED); + + input->open = ext_pen_detect_open; + input->close = ext_pen_detect_close; + + /* Enable auto repeat feature of Linux input subsystem */ + __set_bit(EV_REP, input->evbit); + + INIT_DELAYED_WORK(&ddata->ext_pen_detect_dwork, ext_pen_detect_work); + + init_ext_pen_detect_irq(input); + + if (ddata->gpio_ext_pen_detect != 0) { + error = input_register_device(input); + if (error) { + dev_err(dev, "Unable to register input device, error: %d\n", + error); + goto fail1; + } + } + + device_init_wakeup(&pdev->dev, wakeup); + + return 0; + + fail1: + kfree(ddata); + + return error; +} + +static int ext_pen_detect_remove(struct platform_device *pdev) +{ + struct ext_pen_detect_drvdata *ddata = platform_get_drvdata(pdev); + struct input_dev *input = ddata->input; + + device_init_wakeup(&pdev->dev, 0); + + input_unregister_device(input); + + wakeup_source_unregister(ddata->ws); + + kfree(ddata); + + return 0; +} + +#if defined(CONFIG_OF) +static const struct of_device_id ext_pen_detect_dt_ids[] = { + { .compatible = "ext_pen_detect" }, + { }, +}; +MODULE_DEVICE_TABLE(of, ext_pen_detect_dt_ids); +#endif /* CONFIG_OF */ + +static struct platform_driver ext_pen_detect_device_driver = { + .probe = ext_pen_detect_probe, + .remove = ext_pen_detect_remove, + .driver = { + .name = "ext_pen_detect", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = ext_pen_detect_dt_ids, +#endif /* CONFIG_OF */ + } +}; + +static int __init ext_pen_detect_init(void) +{ + return platform_driver_register(&ext_pen_detect_device_driver); +} + +static void __exit ext_pen_detect_exit(void) +{ + platform_driver_unregister(&ext_pen_detect_device_driver); +} + +module_init(ext_pen_detect_init); +module_exit(ext_pen_detect_exit); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 5b08a473cdba..897f5a3e7721 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -320,7 +320,7 @@ struct pcmcia_device_id { #define INPUT_DEVICE_ID_LED_MAX 0x0f #define INPUT_DEVICE_ID_SND_MAX 0x07 #define INPUT_DEVICE_ID_FF_MAX 0x7f -#define INPUT_DEVICE_ID_SW_MAX 0x10 +#define INPUT_DEVICE_ID_SW_MAX 0x12 #define INPUT_DEVICE_ID_PROP_MAX 0x1f #define INPUT_DEVICE_ID_MATCH_BUS 1 diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 0c2e27d28e0a..8ca2acee1f92 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -889,7 +889,9 @@ #define SW_MUTE_DEVICE 0x0e /* set = device disabled */ #define SW_PEN_INSERTED 0x0f /* set = pen inserted */ #define SW_MACHINE_COVER 0x10 /* set = cover closed */ -#define SW_MAX 0x10 +#define SW_COVER_ATTACHED 0x11 /* set = cover attached */ +#define SW_EXT_PEN_ATTACHED 0x12 /* set = external pen attached */ +#define SW_MAX 0x12 #define SW_CNT (SW_MAX+1) /* -- 2.17.1