We need support to various accessories on the device,
some requiring switch does not exist in switch list.
So added switch for the following purpose.

SW_EXT_PEN_ATTACHED is for the checking the external pen
attached or not on the device. We also added driver
that uses such event.

Signed-off-by: Hyungjae Im <hj2...@samsung.com>
---
 drivers/input/Kconfig                  |  12 ++
 drivers/input/Makefile                 |   1 +
 drivers/input/ext_pen_detect.c         | 237 +++++++++++++++++++++++++
 include/linux/mod_devicetable.h        |   2 +-
 include/uapi/linux/input-event-codes.h |   3 +-
 5 files changed, 253 insertions(+), 2 deletions(-)
 create mode 100644 drivers/input/ext_pen_detect.c

diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index ba5e7444c547..5d6d15c8f7e7 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -197,6 +197,18 @@ config INPUT_COVER_DETECT
          To compile this driver as a module, choose M here: the
          module will be called cover_detect.
 
+config INPUT_EXT_PEN_DETECT
+       tristate "Enable external pen attach detection"
+       help
+         Say Y here to enable external pen 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.
+
+         To compile this driver as a module, choose M here: the
+         module will be called ext_pen_detect.
+
 comment "Input Device Drivers"
 
 source "drivers/input/keyboard/Kconfig"
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index fc8dd9091821..0ccf02e34557 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -31,3 +31,4 @@ obj-$(CONFIG_INPUT_APMPOWER)  += apm-power.o
 obj-$(CONFIG_RMI4_CORE)                += rmi4/
 
 obj-$(CONFIG_INPUT_COVER_DETECT)+= cover_detect.o
+obj-$(CONFIG_INPUT_EXT_PEN_DETECT)+= ext_pen_detect.o
diff --git a/drivers/input/ext_pen_detect.c b/drivers/input/ext_pen_detect.c
new file mode 100644
index 000000000000..9a0d106e49f8
--- /dev/null
+++ b/drivers/input/ext_pen_detect.c
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support detection pen attachment externally on device
+ *
+ * Copyright (C) 2020 Samsung Electronics Co. Ltd. All Rights Reserved.
+ *
+ */
+
+#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 2e6ae686555b..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         0x11
+#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 32e27732161c..8ca2acee1f92 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -890,7 +890,8 @@
 #define SW_PEN_INSERTED                0x0f  /* set = pen inserted */
 #define SW_MACHINE_COVER       0x10  /* set = cover closed */
 #define SW_COVER_ATTACHED      0x11  /* set = cover attached */
-#define SW_MAX                 0x11
+#define SW_EXT_PEN_ATTACHED    0x12  /* set = external pen attached */
+#define SW_MAX                 0x12
 #define SW_CNT                 (SW_MAX+1)
 
 /*
-- 
2.17.1

Reply via email to