diff mbox series

[V1,1/1] Input/misc: add support for Advantech software defined button

Message ID 20200227031721.17703-1-Andrea.Ho@advantech.com.tw (mailing list archive)
State New, archived
Headers show
Series [V1,1/1] Input/misc: add support for Advantech software defined button | expand

Commit Message

ycho1399@gmail.com Feb. 27, 2020, 3:15 a.m. UTC
From: "Andrea.Ho" <Andrea.Ho@advantech.com.tw>

Advantech sw_button is a ACPI event trigger button.

With this driver, we can report KEY_EVENTs on the
Advantech Tabletop Network Appliances products and it has been
tested in FWA1112VC.

Add the software define button support to report KEY_EVENTs by
different acts of pressing button (like double-click, long pressed
and tick) that cloud be get on user interface and trigger the
customized actions.

Signed-off-by: Andrea.Ho <Andrea.Ho@advantech.com.tw>
---
 ...define.patch.EXPERIMENTAL-checkpatch-fixes | 554 ++++++++++++++++++
 MAINTAINERS                                   |   5 +
 drivers/input/misc/Kconfig                    |  11 +
 drivers/input/misc/Makefile                   |   2 +-
 drivers/input/misc/adv_swbutton.c             | 473 +++++++++++++++
 5 files changed, 1044 insertions(+), 1 deletion(-)
 create mode 100644 0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes
 create mode 100644 drivers/input/misc/adv_swbutton.c

Comments

Greg KH Feb. 27, 2020, 7:22 a.m. UTC | #1
On Thu, Feb 27, 2020 at 03:15:31AM +0000, ycho1399@gmail.com wrote:
> From: "Andrea.Ho" <Andrea.Ho@advantech.com.tw>
> 
> Advantech sw_button is a ACPI event trigger button.
> 
> With this driver, we can report KEY_EVENTs on the
> Advantech Tabletop Network Appliances products and it has been
> tested in FWA1112VC.
> 
> Add the software define button support to report KEY_EVENTs by
> different acts of pressing button (like double-click, long pressed
> and tick) that cloud be get on user interface and trigger the
> customized actions.
> 
> Signed-off-by: Andrea.Ho <Andrea.Ho@advantech.com.tw>
> ---
>  ...define.patch.EXPERIMENTAL-checkpatch-fixes | 554 ++++++++++++++++++
>  MAINTAINERS                                   |   5 +
>  drivers/input/misc/Kconfig                    |  11 +
>  drivers/input/misc/Makefile                   |   2 +-
>  drivers/input/misc/adv_swbutton.c             | 473 +++++++++++++++
>  5 files changed, 1044 insertions(+), 1 deletion(-)
>  create mode 100644 0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes
>  create mode 100644 drivers/input/misc/adv_swbutton.c
> 
> diff --git a/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes b/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes
> new file mode 100644
> index 000000000000..45e49aee5b47
> --- /dev/null
> +++ b/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes

<snip>

I doubt you ment to commit the patch itself into the patch :(
Joe Perches Feb. 27, 2020, 8:11 a.m. UTC | #2
On Thu, 2020-02-27 at 03:15 +0000, ycho1399@gmail.com wrote:
> From: "Andrea.Ho" <Andrea.Ho@advantech.com.tw>
> 
> Advantech sw_button is a ACPI event trigger button.
> 
> With this driver, we can report KEY_EVENTs on the
> Advantech Tabletop Network Appliances products and it has been
> tested in FWA1112VC.
> 
> Add the software define button support to report KEY_EVENTs by
> different acts of pressing button (like double-click, long pressed
> and tick) that cloud be get on user interface and trigger the
> customized actions.
[]
> diff --git a/drivers/input/misc/adv_swbutton.c b/drivers/input/misc/adv_swbutton.c
> new file mode 100644

mostly trivia:

> +/*
> + * Switch two elements in array.
> + *
> + * @param xp, yp The array elements need to swap.
> + */
> +void array_swap(unsigned int *xp, unsigned int *yp)
> +{
> +	int temp = *xp;
> +	*xp = *yp;
> +	*yp = temp;
> +}

kernel.h has swap

> +/*
> + * Sorting an array in ascending order
> + *
> + * @param arr The array for sorting.
> + * @param n The array size
> + */
> +void sort_asc(unsigned int arr[], int n)
> +{
> +	int i, j, min_idx;
> +
> +	for (i = 0; i < n - 1; i++) {
> +		min_idx = i;
> +		for (j = i + 1; j < n; j++)
> +			if (arr[j] < arr[min_idx])
> +				min_idx = j;
> +
> +		array_swap(&arr[min_idx], &arr[i]);
> +	}
> +}

sort.h has a generic sort too

> +
> +/*
> + * initial software button timer to check tick or double click
> + *
> + * @param btn Struct of acpi_button that should be required.
> + */ 
> +static void swbtn_init_timer(struct acpi_button *btn)
> +{
> +	pr_info(PREFIX "swbtn timer start\n");

Many of these printks should be removed and ftrace used
when necessary.

> +static int acpi_button_add(struct acpi_device *device)
> +{
> +	struct acpi_button *button;
> +	struct input_dev *input;
> +	const char *hid = acpi_device_hid(device);
> +	char *name, *class;
> +	int error, i;
> +
> +	pr_info(PREFIX "%s\n",  __func__);
> +	button = kzalloc(sizeof(*button), GFP_KERNEL);
> +	if (!button) {
> +		pr_err(PREFIX "alloc acpi_button failed\n");

alloc failure messages aren't really necessary
as a dump_stack() is already done on failure.

[]

> +		for (i = (!swbtn_cfg.dclick_enabled);
> +		     i < (swbtn_cfg.lkey_number + 2); i++) {
> +			pr_info(PREFIX "%d. Enabled keycode[0x%x]\n",
> +				i, swbtn_keycodes[i]);

Is it really useful to print all enabled keycodes?
Pavel Machek Feb. 27, 2020, 8:56 a.m. UTC | #3
On Thu 2020-02-27 03:15:31, ycho1399@gmail.com wrote:
> From: "Andrea.Ho" <Andrea.Ho@advantech.com.tw>
> 
> Advantech sw_button is a ACPI event trigger button.
> 
> With this driver, we can report KEY_EVENTs on the
> Advantech Tabletop Network Appliances products and it has been
> tested in FWA1112VC.
> 
> Add the software define button support to report KEY_EVENTs by
> different acts of pressing button (like double-click, long pressed
> and tick) that cloud be get on user interface and trigger the
> customized actions.

I'm not sure why you Cc half of the world. What does Dave Miller have
to do with this. Please be more careful (and drop me from the list).

Plus, I'm pretty sure you should not be detecting double clicks (etc)
in the driver.

Best regards,
									Pavel
diff mbox series

Patch

diff --git a/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes b/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes
new file mode 100644
index 000000000000..45e49aee5b47
--- /dev/null
+++ b/0001-Input-misc-add-support-for-Advantech-software-define.patch.EXPERIMENTAL-checkpatch-fixes
@@ -0,0 +1,553 @@ 
+From b5375ec981bf5b434731ac59fa1471dce79cce26 Mon Sep 17 00:00:00 2001
+From: "Andrea.Ho" <Andrea.Ho@advantech.com.tw>
+Date: Tue, 25 Feb 2020 03:52:38 +0000
+Subject: [V1,1/1] Input/misc: add support for Advantech software defined
+ button
+
+Advantech sw_button is a ACPI event trigger button.
+
+With this driver, we can report KEY_EVENTs on the
+Advantech Tabletop Network Appliances products and it has been
+tested in FWA1112VC.
+
+Add the software define button support to report KEY_EVENTs by
+different acts of pressing button (like double-click, long pressed
+and tick) that cloud be get on user interface and trigger the
+customized actions.
+
+Signed-off-by: Andrea.Ho <Andrea.Ho@advantech.com.tw>
+---
+ MAINTAINERS                       |   5 +
+ drivers/input/misc/Kconfig        |  11 +
+ drivers/input/misc/Makefile       |   2 +-
+ drivers/input/misc/adv_swbutton.c | 473 ++++++++++++++++++++++++++++++
+ 4 files changed, 490 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/input/misc/adv_swbutton.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 8982c6e013b3..d68db02fa280 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -543,6 +543,11 @@ S:	Maintained
+ F:	Documentation/scsi/advansys.txt
+ F:	drivers/scsi/advansys.c
+
++ADVANTECH SWBTN DRIVER
++M:	Andrea Ho <Andrea.Ho@advantech.com.tw>
++S:	Maintained
++F:	drivers/input/misc/adv_swbutton.c
++
+ ADXL34X THREE-AXIS DIGITAL ACCELEROMETER DRIVER (ADXL345/ADXL346)
+ M:	Michael Hennerich <michael.hennerich@analog.com>
+ W:	http://wiki.analog.com/ADXL345
+diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
+index 7e2e658d551c..3c9350bdd7ae 100644
+--- a/drivers/input/misc/Kconfig
++++ b/drivers/input/misc/Kconfig
+@@ -879,4 +879,15 @@ config INPUT_STPMIC1_ONKEY
+	  To compile this driver as a module, choose M here: the
+	  module will be called stpmic1_onkey.
+
++config INPUT_ADV_SWBUTTON
++        tristate "Advantech ACPI Software button Driver"
++        depends on X86 && ACPI
++        help
++          Say Y here to enable support for Advantech software defined
++          button feature. More information can be fount at
++          <http://www.advantech.com.tw/products/>
++
++          To compile this driver as a module, choose M here. The module will
++          be called adv_swbutton.
++
+ endif
+diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
+index 8fd187f314bd..a5ceb98f18f6 100644
+--- a/drivers/input/misc/Makefile
++++ b/drivers/input/misc/Makefile
+@@ -85,4 +85,4 @@ obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
+ obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)	+= xen-kbdfront.o
+ obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
+ obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)	+= ideapad_slidebar.o
+-
++obj-$(CONFIG_INPUT_ADV_SWBUTTON)    += adv_swbutton.o
+diff --git a/drivers/input/misc/adv_swbutton.c b/drivers/input/misc/adv_swbutton.c
+new file mode 100644
+index 000000000000..7e4db67780dc
+--- /dev/null
++++ b/drivers/input/misc/adv_swbutton.c
+@@ -0,0 +1,473 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ *  adv_swbutton.c - Software Button Interface Driver.
++ *
++ *  (C) Copyright 2020 Advantech Corporation, Inc
++ *
++ *  Based on soc_button_array.c:
++ *
++ *  {C} Copyright 2014 Intel Corporation
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; either version 2 of the License, or
++ *  (at your option) any later version.
++ *
++ *  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/version.h>
++#include <linux/types.h>
++#include <linux/proc_fs.h>
++#include <linux/seq_file.h>
++#include <linux/input.h>
++#include <linux/slab.h>
++#include <linux/acpi.h>
++#include <linux/ktime.h>
++#include <linux/moduleparam.h>
++#include <acpi/button.h>
++#include <acpi/acpi_bus.h>
++#include <acpi/acpi_drivers.h>
++
++#define PREFIX "[AHC] ACPI: "
++#define DRIVER_VERSION "v1.0.0"
++
++#define ACPI_BUTTON_CLASS                   "button"
++#define ACPI_BUTTON_FILE_INFO               "info"
++#define ACPI_BUTTON_FILE_STATE              "state"
++#define ACPI_BUTTON_TYPE_UNKNOWN            0x00
++
++#define ACPI_BUTTON_SUBCLASS_SOFTWARE       "software"
++#define ACPI_BUTTON_HID_SWBTN               "AHC0310"
++#define ACPI_BUTTON_DEVICE_NAME_SOFTWARE    "Software Button"
++#define ACPI_BUTTON_TYPE_SOFTWARE           0x07
++
++#define ACPI_BUTTON_NOTIFY_SWBTN_RELEASE    0x86
++#define ACPI_BUTTON_NOTIFY_SWBTN_PRESSED    0x85
++
++#define SWBTN_DOUBLE_TRIGGER_DELAY          130
++#define SWBTN_TRIGGER_DELAY                 350
++#define SWBTN_MAX_PKEYS                     8
++#define SWBTN_DEF_PKEYS                     2
++
++#define _COMPONENT                          ACPI_BUTTON_COMPONENT
++
++ACPI_MODULE_NAME("swbutton");
++
++MODULE_VERSION(DRIVER_VERSION);
++MODULE_AUTHOR("Andrea Ho");
++MODULE_DESCRIPTION("Advantech ACPI SW Button Driver");
++MODULE_LICENSE("GPL");
++
++/* Defined keycodes */
++static short swbtn_keycodes[]  = {
++	BTN_TRIGGER_HAPPY2, /* double click */
++	BTN_TRIGGER_HAPPY,  /* tick */
++	BTN_TRIGGER_HAPPY3, BTN_TRIGGER_HAPPY4, /* long press */
++	BTN_TRIGGER_HAPPY5, BTN_TRIGGER_HAPPY6, BTN_TRIGGER_HAPPY7,
++	BTN_TRIGGER_HAPPY8, BTN_TRIGGER_HAPPY9, BTN_TRIGGER_HAPPY10
++};
++
++struct swbtn_config {
++	bool dclick_enabled;
++	int lkey_number;
++	unsigned int pressed_interval[SWBTN_MAX_PKEYS];
++	unsigned int tolerance;
++	bool open_interval;
++};
++
++static struct swbtn_config swbtn_cfg = {
++	.dclick_enabled = true,
++	.lkey_number = SWBTN_DEF_PKEYS,
++	.pressed_interval = {3000, 8000},
++	.tolerance = 800,
++	.open_interval = true
++};
++
++module_param_array_named(press_interval, swbtn_cfg.pressed_interval, uint,
++			 &swbtn_cfg.lkey_number, 0444);
++MODULE_PARM_DESC(press_interval,
++		 "A list of long press interval in ms. If no need, fill 0.");
++
++module_param_named(tolerance, swbtn_cfg.tolerance, uint, 0444);
++MODULE_PARM_DESC(tolerance,
++		 "The torlerance of press interval in ms. Default is 800ms.");
++
++module_param_named(open_interval, swbtn_cfg.open_interval, bool, 0444);
++MODULE_PARM_DESC(open_interval,
++		 "To let the last interval is the open interval.");
++
++module_param_named(enable_dclick, swbtn_cfg.dclick_enabled, bool, 0444);
++MODULE_PARM_DESC(enable_dclick,
++		 "To enable/disable double click event. Default is enabled.");
++
++static const struct acpi_device_id button_device_ids[] = {
++	{ACPI_BUTTON_HID_SWBTN, 0},
++	{"", 0},
++};
++MODULE_DEVICE_TABLE(acpi, button_device_ids);
++
++static int acpi_button_add(struct acpi_device *device);
++static int acpi_button_remove(struct acpi_device *device);
++static void acpi_button_notify(struct acpi_device *device, u32 event);
++
++static struct acpi_driver acpi_button_driver = {
++	.name = ACPI_BUTTON_DEVICE_NAME_SOFTWARE,
++	.class = ACPI_BUTTON_CLASS,
++	.owner = THIS_MODULE,
++	.ids = button_device_ids,
++	.ops = {
++		.add = acpi_button_add,
++		.remove = acpi_button_remove,
++		.notify = acpi_button_notify,
++	},
++};
++
++struct acpi_button {
++	unsigned int type;
++	struct input_dev *input;
++	char phys[32];                  /* for input device */
++	unsigned long pushed;
++	int last_state;
++	ktime_t last_time;
++	bool doubleclick;
++
++	/* defined timer_list struct */
++	struct timer_list swbtn_trigger_timer;
++};
++
++/*
++ * trigger software button event while timeout
++ *
++ */
++static void swbtn_trigger(struct timer_list *tdata)
++{
++	struct acpi_button *btn = from_timer(btn, tdata, swbtn_trigger_timer);
++
++	struct input_dev *input;
++	int keycode;
++
++	input = btn->input;
++
++	keycode = btn->last_state;
++	input_report_key(input, keycode, 1);
++	input_sync(input);
++
++	input_report_key(input, keycode, 0);
++	input_sync(input);
++}
++
++/*
++ * Switch two elements in array.
++ *
++ * @param xp, yp The array elements need to swap.
++ */
++void array_swap(unsigned int *xp, unsigned int *yp)
++{
++	int temp = *xp;
++	*xp = *yp;
++	*yp = temp;
++}
++
++/*
++ * Sorting an array in ascending order
++ *
++ * @param arr The array for sorting.
++ * @param n The array size
++ */
++void sort_asc(unsigned int arr[], int n)
++{
++	int i, j, min_idx;
++
++	for (i = 0; i < n - 1; i++) {
++		min_idx = i;
++		for (j = i + 1; j < n; j++)
++			if (arr[j] < arr[min_idx])
++				min_idx = j;
++
++		array_swap(&arr[min_idx], &arr[i]);
++	}
++}
++
++/*
++ * initial software button timer to check tick or double click
++ *
++ * @param btn Struct of acpi_button that should be required.
++ */
++static void swbtn_init_timer(struct acpi_button *btn)
++{
++	pr_info(PREFIX "swbtn timer start\n");
++
++	timer_setup(&btn->swbtn_trigger_timer, swbtn_trigger, 0);
++
++	btn->swbtn_trigger_timer.expires =
++		jiffies + SWBTN_DOUBLE_TRIGGER_DELAY;
++	add_timer(&btn->swbtn_trigger_timer);
++}
++
++/*-------------------------------------------------------------------------
++ *                               Driver Interface
++ *--------------------------------------------------------------------------
++ */
++static void acpi_button_notify(struct acpi_device *device, u32 event)
++{
++	struct acpi_button *button = acpi_driver_data(device);
++	struct input_dev *input;
++
++	int i, keycode, BTN_KEYCODE, lkey_number = swbtn_cfg.lkey_number;
++	ktime_t calltime, delta, lasttime, l_time;
++	unsigned long long duration;
++
++	pr_debug(PREFIX "%s, event:0x%x\n", __func__, event);
++
++	switch (event) {
++	case ACPI_BUTTON_NOTIFY_SWBTN_RELEASE:
++		del_timer(&button->swbtn_trigger_timer);
++
++		if (button->last_state != KEY_DOWN)
++			return;
++
++		input = button->input;
++
++		calltime = ktime_get();
++		lasttime = button->last_time;
++		button->last_time = calltime;
++
++		if (ktime_to_ns(lasttime) == 0)
++			lasttime = calltime;
++
++		delta = ktime_sub(calltime, lasttime); //ns
++		duration = (unsigned long long)
++			(ktime_to_ns(delta) >> 10) >> 10; //ms
++		pr_debug(PREFIX "duration time %llu ms\n", duration);
++
++		BTN_KEYCODE = BTN_TRIGGER_HAPPY;
++		if (button->doubleclick && duration < SWBTN_TRIGGER_DELAY) {
++			pr_debug(PREFIX "double click %llu s\n",
++				 duration >> 10);
++
++			BTN_KEYCODE = BTN_TRIGGER_HAPPY2;
++		} else if (duration >= 0 && duration < SWBTN_TRIGGER_DELAY) {
++			pr_debug(PREFIX "click %llu s\n", duration >> 10);
++
++			button->last_state = BTN_TRIGGER_HAPPY;
++			swbtn_init_timer(button);
++		} else {
++			for (i = 0; i < lkey_number; i++) {
++				unsigned int p_intval =
++					swbtn_cfg.pressed_interval[i];
++				unsigned int diff = swbtn_cfg.tolerance;
++				int j = i + 1;
++
++				if (p_intval < diff ||
++				    p_intval < SWBTN_TRIGGER_DELAY)
++					break;
++				if ((j) < lkey_number) {
++					unsigned int n_intval =
++						swbtn_cfg.pressed_interval[j];
++
++					if ((p_intval + diff) >
++					    (n_intval - diff))
++						diff = (n_intval
++							- p_intval) / 2;
++				}
++
++				pr_debug(PREFIX "pressed_interval: %lu ms\n",
++					 p_intval);
++
++				if ((swbtn_cfg.open_interval &&
++				     j == lkey_number &&
++				     duration > (p_intval - diff)) ||
++				    (duration > (p_intval - diff) &&
++				     duration < (p_intval + diff))) {
++					pr_debug(PREFIX "long pressed %llu s\n",
++						 duration >> 10);
++
++					BTN_KEYCODE = swbtn_keycodes[i + 2];
++					break;
++				}
++			}
++		}
++
++		if (!button->doubleclick &&
++		    (duration >= 0 &&
++		     duration < SWBTN_TRIGGER_DELAY))
++			return;
++
++		keycode = test_bit(BTN_KEYCODE, input->keybit) ?
++				BTN_KEYCODE : KEY_UNKNOWN;
++		pr_debug(PREFIX "released keycode: 0x%x", keycode);
++
++		button->last_state = keycode;
++		button->doubleclick = false;
++
++		input_report_key(input, keycode, 1);
++		input_sync(input);
++
++		input_report_key(input, keycode, 0);
++		input_sync(input);
++
++	break;
++	case ACPI_BUTTON_NOTIFY_SWBTN_PRESSED:
++		l_time = ktime_to_ns(button->last_time);
++
++		input = button->input;
++
++		calltime = ktime_get();
++		lasttime = l_time == 0 ? calltime : button->last_time;
++
++		delta = ktime_sub(calltime, lasttime); //ns
++		duration = (unsigned long long)
++			(ktime_to_ns(delta) >> 10) >> 10; //ms
++
++		button->doubleclick = (button->last_state ==
++				       BTN_TRIGGER_HAPPY &&
++				       duration > 0 &&
++				       duration < SWBTN_DOUBLE_TRIGGER_DELAY);
++
++		button->last_time = calltime;
++		button->last_state = KEY_DOWN;
++
++		pr_debug(PREFIX "pressed software button, duration %llu ms",
++			 duration);
++		pr_debug(PREFIX " is double click: %s\n",
++			 (button->doubleclick) ? "true" : "false");
++
++	break;
++	default:
++		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
++				  "Unsupported event [0x%x]\n", event));
++	break;
++	}
++}
++
++static int __init acpi_button_init(void)
++{
++	int result;
++
++	pr_info(PREFIX "acpi button init!");
++
++	result = acpi_bus_register_driver(&acpi_button_driver);
++	if (result < 0) {
++		pr_err(PREFIX "register acpi button driver failed");
++		return -ENODEV;
++	}
++	return 0;
++}
++
++static void __exit acpi_button_exit(void)
++{
++	pr_info(PREFIX "%s\n", __func__);
++	acpi_bus_unregister_driver(&acpi_button_driver);
++}
++
++static int acpi_button_add(struct acpi_device *device)
++{
++	struct acpi_button *button;
++	struct input_dev *input;
++	const char *hid = acpi_device_hid(device);
++	char *name, *class;
++	int error, i;
++
++	pr_info(PREFIX "%s\n",  __func__);
++	button = kzalloc(sizeof(*button), GFP_KERNEL);
++	if (!button) {
++		pr_err(PREFIX "alloc acpi_button failed\n");
++		return -ENOMEM;
++	}
++
++	device->driver_data = button;
++
++	button->input = input_allocate_device();
++	input = button->input;
++	if (!input) {
++		error = -ENOMEM;
++		pr_err(PREFIX "allocat input device failed!\n");
++		goto err_free_button;
++	}
++
++	name = acpi_device_name(device);
++	class = acpi_device_class(device);
++
++	pr_info(PREFIX "device name[%s]\n", name);
++
++	if (!strcmp(hid, ACPI_BUTTON_HID_SWBTN)) {
++		button->type = ACPI_BUTTON_TYPE_SOFTWARE;
++		button->last_time = ktime_set(0, 0);
++		button->last_state = KEY_UNKNOWN;
++		strcpy(name, ACPI_BUTTON_DEVICE_NAME_SOFTWARE);
++		sprintf(class, "%s/%s", ACPI_BUTTON_CLASS,
++			ACPI_BUTTON_SUBCLASS_SOFTWARE);
++	} else {
++		pr_err(PREFIX "Unsupported hid [%s]\n", hid);
++		error = -ENODEV;
++		goto err_free_input;
++	}
++
++	snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid);
++
++	input->name = name;
++	input->phys = button->phys;
++	input->id.bustype = BUS_HOST;
++	input->id.product = button->type;
++	input->dev.parent = &device->dev;
++
++	pr_info(PREFIX "ACPI_BUTTON_TYPE_SOFTWARE: [0x%x]",
++		ACPI_BUTTON_TYPE_SOFTWARE);
++
++	switch (button->type) {
++	case ACPI_BUTTON_TYPE_SOFTWARE:
++		set_bit(EV_KEY, input->evbit);
++		set_bit(EV_REP, input->evbit);
++
++		if (swbtn_cfg.lkey_number == 1 &&
++		    swbtn_cfg.pressed_interval[0] == 0)
++			swbtn_cfg.lkey_number = 0;
++
++		for (i = (!swbtn_cfg.dclick_enabled);
++		     i < (swbtn_cfg.lkey_number + 2); i++) {
++			pr_info(PREFIX "%d. Enabled keycode[0x%x]\n",
++				i, swbtn_keycodes[i]);
++			input_set_capability(input, EV_KEY, swbtn_keycodes[i]);
++		}
++	break;
++	}
++
++	sort_asc(swbtn_cfg.pressed_interval, swbtn_cfg.lkey_number);
++
++	input_set_drvdata(input, device);
++	error = input_register_device(input);
++	if (error)
++		goto err_free_input;
++
++	device_init_wakeup(&device->dev, true);
++
++	pr_info(PREFIX "%s [%s]\n", name, acpi_device_bid(device));
++	return 0;
++
++err_free_input:
++	input_free_device(input);
++err_free_button:
++	kfree(button);
++	return error;
++}
++
++static int acpi_button_remove(struct acpi_device *device)
++{
++	struct acpi_button *button = acpi_driver_data(device);
++
++	pr_info(PREFIX "acpi_button_remove");
++
++	input_unregister_device(button->input);
++	kfree(button);
++	return 0;
++}
++
++module_init(acpi_button_init);
++module_exit(acpi_button_exit);
+--
+2.17.1
diff --git a/MAINTAINERS b/MAINTAINERS
index 8982c6e013b3..821c5cacf553 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -543,6 +543,11 @@  S:	Maintained
 F:	Documentation/scsi/advansys.txt
 F:	drivers/scsi/advansys.c
 
+ADVANTECH SWBTN DRIVER
+M:	Andrea Ho <Andrea.Ho@advantech.com.tw>
+S:	Maintained
+F:	drivers/input/misc/adv_swbutton.c
+
 ADXL34X THREE-AXIS DIGITAL ACCELEROMETER DRIVER (ADXL345/ADXL346)
 M:	Michael Hennerich <michael.hennerich@analog.com>
 W:	http://wiki.analog.com/ADXL345
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 7e2e658d551c..3c9350bdd7ae 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -879,4 +879,15 @@  config INPUT_STPMIC1_ONKEY
 	  To compile this driver as a module, choose M here: the
 	  module will be called stpmic1_onkey.
 
+config INPUT_ADV_SWBUTTON
+        tristate "Advantech ACPI Software button Driver"
+        depends on X86 && ACPI
+        help
+          Say Y here to enable support for Advantech software defined
+          button feature. More information can be fount at
+          <http://www.advantech.com.tw/products/>
+
+          To compile this driver as a module, choose M here. The module will
+          be called adv_swbutton.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 8fd187f314bd..a5ceb98f18f6 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -85,4 +85,4 @@  obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
 obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)	+= xen-kbdfront.o
 obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
 obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)	+= ideapad_slidebar.o
-
+obj-$(CONFIG_INPUT_ADV_SWBUTTON)    += adv_swbutton.o
diff --git a/drivers/input/misc/adv_swbutton.c b/drivers/input/misc/adv_swbutton.c
new file mode 100644
index 000000000000..7e4db67780dc
--- /dev/null
+++ b/drivers/input/misc/adv_swbutton.c
@@ -0,0 +1,473 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  adv_swbutton.c - Software Button Interface Driver.
+ *
+ *  (C) Copyright 2020 Advantech Corporation, Inc
+ *
+ *  Based on soc_button_array.c:
+ *
+ *  {C} Copyright 2014 Intel Corporation
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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/version.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/ktime.h>
+#include <linux/moduleparam.h>
+#include <acpi/button.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+#define PREFIX "[AHC] ACPI: "
+#define DRIVER_VERSION "v1.0.0"
+
+#define ACPI_BUTTON_CLASS                   "button"
+#define ACPI_BUTTON_FILE_INFO               "info"
+#define ACPI_BUTTON_FILE_STATE              "state"
+#define ACPI_BUTTON_TYPE_UNKNOWN            0x00
+
+#define ACPI_BUTTON_SUBCLASS_SOFTWARE       "software"
+#define ACPI_BUTTON_HID_SWBTN               "AHC0310"
+#define ACPI_BUTTON_DEVICE_NAME_SOFTWARE    "Software Button"
+#define ACPI_BUTTON_TYPE_SOFTWARE           0x07
+
+#define ACPI_BUTTON_NOTIFY_SWBTN_RELEASE    0x86
+#define ACPI_BUTTON_NOTIFY_SWBTN_PRESSED    0x85
+
+#define SWBTN_DOUBLE_TRIGGER_DELAY          130
+#define SWBTN_TRIGGER_DELAY                 350
+#define SWBTN_MAX_PKEYS                     8
+#define SWBTN_DEF_PKEYS                     2
+
+#define _COMPONENT                          ACPI_BUTTON_COMPONENT
+
+ACPI_MODULE_NAME("swbutton");
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_AUTHOR("Andrea Ho");
+MODULE_DESCRIPTION("Advantech ACPI SW Button Driver");
+MODULE_LICENSE("GPL");
+
+/* Defined keycodes */
+static short swbtn_keycodes[]  = {
+	BTN_TRIGGER_HAPPY2, /* double click */
+	BTN_TRIGGER_HAPPY,  /* tick */
+	BTN_TRIGGER_HAPPY3, BTN_TRIGGER_HAPPY4, /* long press */
+	BTN_TRIGGER_HAPPY5, BTN_TRIGGER_HAPPY6, BTN_TRIGGER_HAPPY7,
+	BTN_TRIGGER_HAPPY8, BTN_TRIGGER_HAPPY9, BTN_TRIGGER_HAPPY10
+};
+
+struct swbtn_config {
+	bool dclick_enabled;
+	int lkey_number;
+	unsigned int pressed_interval[SWBTN_MAX_PKEYS];
+	unsigned int tolerance;
+	bool open_interval;
+};
+
+static struct swbtn_config swbtn_cfg = {
+	.dclick_enabled = true,
+	.lkey_number = SWBTN_DEF_PKEYS,
+	.pressed_interval = {3000, 8000},
+	.tolerance = 800,
+	.open_interval = true
+};
+
+module_param_array_named(press_interval, swbtn_cfg.pressed_interval, uint,
+			 &swbtn_cfg.lkey_number, 0444);
+MODULE_PARM_DESC(press_interval,
+		 "A list of long press interval in ms. If no need, fill 0.");
+
+module_param_named(tolerance, swbtn_cfg.tolerance, uint, 0444);
+MODULE_PARM_DESC(tolerance,
+		 "The torlerance of press interval in ms. Default is 800ms.");
+
+module_param_named(open_interval, swbtn_cfg.open_interval, bool, 0444);
+MODULE_PARM_DESC(open_interval,
+		 "To let the last interval is the open interval.");
+
+module_param_named(enable_dclick, swbtn_cfg.dclick_enabled, bool, 0444);
+MODULE_PARM_DESC(enable_dclick,
+		 "To enable/disable double click event. Default is enabled.");
+
+static const struct acpi_device_id button_device_ids[] = {
+	{ACPI_BUTTON_HID_SWBTN, 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, button_device_ids);
+
+static int acpi_button_add(struct acpi_device *device);
+static int acpi_button_remove(struct acpi_device *device);
+static void acpi_button_notify(struct acpi_device *device, u32 event);
+
+static struct acpi_driver acpi_button_driver = {
+	.name = ACPI_BUTTON_DEVICE_NAME_SOFTWARE,
+	.class = ACPI_BUTTON_CLASS,
+	.owner = THIS_MODULE,
+	.ids = button_device_ids,
+	.ops = {
+		.add = acpi_button_add,
+		.remove = acpi_button_remove,
+		.notify = acpi_button_notify,
+	},
+};
+
+struct acpi_button {
+	unsigned int type;
+	struct input_dev *input;
+	char phys[32];                  /* for input device */
+	unsigned long pushed;
+	int last_state;
+	ktime_t last_time;
+	bool doubleclick;
+
+	/* defined timer_list struct */
+	struct timer_list swbtn_trigger_timer;
+};
+
+/*
+ * trigger software button event while timeout
+ *
+ */
+static void swbtn_trigger(struct timer_list *tdata)
+{
+	struct acpi_button *btn = from_timer(btn, tdata, swbtn_trigger_timer);
+
+	struct input_dev *input;
+	int keycode;
+
+	input = btn->input;
+
+	keycode = btn->last_state;
+	input_report_key(input, keycode, 1);
+	input_sync(input);
+
+	input_report_key(input, keycode, 0);
+	input_sync(input);
+}
+
+/*
+ * Switch two elements in array.
+ *
+ * @param xp, yp The array elements need to swap.
+ */
+void array_swap(unsigned int *xp, unsigned int *yp)
+{
+	int temp = *xp;
+	*xp = *yp;
+	*yp = temp;
+}
+
+/*
+ * Sorting an array in ascending order
+ *
+ * @param arr The array for sorting.
+ * @param n The array size
+ */
+void sort_asc(unsigned int arr[], int n)
+{
+	int i, j, min_idx;
+
+	for (i = 0; i < n - 1; i++) {
+		min_idx = i;
+		for (j = i + 1; j < n; j++)
+			if (arr[j] < arr[min_idx])
+				min_idx = j;
+
+		array_swap(&arr[min_idx], &arr[i]);
+	}
+}
+
+/*
+ * initial software button timer to check tick or double click
+ *
+ * @param btn Struct of acpi_button that should be required.
+ */
+static void swbtn_init_timer(struct acpi_button *btn)
+{
+	pr_info(PREFIX "swbtn timer start\n");
+
+	timer_setup(&btn->swbtn_trigger_timer, swbtn_trigger, 0);
+
+	btn->swbtn_trigger_timer.expires =
+		jiffies + SWBTN_DOUBLE_TRIGGER_DELAY;
+	add_timer(&btn->swbtn_trigger_timer);
+}
+
+/*-------------------------------------------------------------------------
+ *                               Driver Interface
+ *--------------------------------------------------------------------------
+ */
+static void acpi_button_notify(struct acpi_device *device, u32 event)
+{
+	struct acpi_button *button = acpi_driver_data(device);
+	struct input_dev *input;
+
+	int i, keycode, BTN_KEYCODE, lkey_number = swbtn_cfg.lkey_number;
+	ktime_t calltime, delta, lasttime, l_time;
+	unsigned long long duration;
+
+	pr_debug(PREFIX "%s, event:0x%x\n", __func__, event);
+
+	switch (event) {
+	case ACPI_BUTTON_NOTIFY_SWBTN_RELEASE:
+		del_timer(&button->swbtn_trigger_timer);
+
+		if (button->last_state != KEY_DOWN)
+			return;
+
+		input = button->input;
+
+		calltime = ktime_get();
+		lasttime = button->last_time;
+		button->last_time = calltime;
+
+		if (ktime_to_ns(lasttime) == 0)
+			lasttime = calltime;
+
+		delta = ktime_sub(calltime, lasttime); //ns
+		duration = (unsigned long long)
+			(ktime_to_ns(delta) >> 10) >> 10; //ms
+		pr_debug(PREFIX "duration time %llu ms\n", duration);
+
+		BTN_KEYCODE = BTN_TRIGGER_HAPPY;
+		if (button->doubleclick && duration < SWBTN_TRIGGER_DELAY) {
+			pr_debug(PREFIX "double click %llu s\n",
+				 duration >> 10);
+
+			BTN_KEYCODE = BTN_TRIGGER_HAPPY2;
+		} else if (duration >= 0 && duration < SWBTN_TRIGGER_DELAY) {
+			pr_debug(PREFIX "click %llu s\n", duration >> 10);
+
+			button->last_state = BTN_TRIGGER_HAPPY;
+			swbtn_init_timer(button);
+		} else {
+			for (i = 0; i < lkey_number; i++) {
+				unsigned int p_intval =
+					swbtn_cfg.pressed_interval[i];
+				unsigned int diff = swbtn_cfg.tolerance;
+				int j = i + 1;
+
+				if (p_intval < diff ||
+				    p_intval < SWBTN_TRIGGER_DELAY)
+					break;
+				if ((j) < lkey_number) {
+					unsigned int n_intval =
+						swbtn_cfg.pressed_interval[j];
+
+					if ((p_intval + diff) >
+					    (n_intval - diff))
+						diff = (n_intval
+							- p_intval) / 2;
+				}
+
+				pr_debug(PREFIX "pressed_interval: %lu ms\n",
+					 p_intval);
+
+				if ((swbtn_cfg.open_interval &&
+				     j == lkey_number &&
+				     duration > (p_intval - diff)) ||
+				    (duration > (p_intval - diff) &&
+				     duration < (p_intval + diff))) {
+					pr_debug(PREFIX "long pressed %llu s\n",
+						 duration >> 10);
+
+					BTN_KEYCODE = swbtn_keycodes[i + 2];
+					break;
+				}
+			}
+		}
+
+		if (!button->doubleclick &&
+		    (duration >= 0 &&
+		     duration < SWBTN_TRIGGER_DELAY))
+			return;
+
+		keycode = test_bit(BTN_KEYCODE, input->keybit) ?
+				BTN_KEYCODE : KEY_UNKNOWN;
+		pr_debug(PREFIX "released keycode: 0x%x", keycode);
+
+		button->last_state = keycode;
+		button->doubleclick = false;
+
+		input_report_key(input, keycode, 1);
+		input_sync(input);
+
+		input_report_key(input, keycode, 0);
+		input_sync(input);
+
+	break;
+	case ACPI_BUTTON_NOTIFY_SWBTN_PRESSED:
+		l_time = ktime_to_ns(button->last_time);
+
+		input = button->input;
+
+		calltime = ktime_get();
+		lasttime = l_time == 0 ? calltime : button->last_time;
+
+		delta = ktime_sub(calltime, lasttime); //ns
+		duration = (unsigned long long)
+			(ktime_to_ns(delta) >> 10) >> 10; //ms
+
+		button->doubleclick = (button->last_state ==
+				       BTN_TRIGGER_HAPPY &&
+				       duration > 0 &&
+				       duration < SWBTN_DOUBLE_TRIGGER_DELAY);
+
+		button->last_time = calltime;
+		button->last_state = KEY_DOWN;
+
+		pr_debug(PREFIX "pressed software button, duration %llu ms",
+			 duration);
+		pr_debug(PREFIX " is double click: %s\n",
+			 (button->doubleclick) ? "true" : "false");
+
+	break;
+	default:
+		ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+				  "Unsupported event [0x%x]\n", event));
+	break;
+	}
+}
+
+static int __init acpi_button_init(void)
+{
+	int result;
+
+	pr_info(PREFIX "acpi button init!");
+
+	result = acpi_bus_register_driver(&acpi_button_driver);
+	if (result < 0) {
+		pr_err(PREFIX "register acpi button driver failed");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void __exit acpi_button_exit(void)
+{
+	pr_info(PREFIX "%s\n", __func__);
+	acpi_bus_unregister_driver(&acpi_button_driver);
+}
+
+static int acpi_button_add(struct acpi_device *device)
+{
+	struct acpi_button *button;
+	struct input_dev *input;
+	const char *hid = acpi_device_hid(device);
+	char *name, *class;
+	int error, i;
+
+	pr_info(PREFIX "%s\n",  __func__);
+	button = kzalloc(sizeof(*button), GFP_KERNEL);
+	if (!button) {
+		pr_err(PREFIX "alloc acpi_button failed\n");
+		return -ENOMEM;
+	}
+
+	device->driver_data = button;
+
+	button->input = input_allocate_device();
+	input = button->input;
+	if (!input) {
+		error = -ENOMEM;
+		pr_err(PREFIX "allocat input device failed!\n");
+		goto err_free_button;
+	}
+
+	name = acpi_device_name(device);
+	class = acpi_device_class(device);
+
+	pr_info(PREFIX "device name[%s]\n", name);
+
+	if (!strcmp(hid, ACPI_BUTTON_HID_SWBTN)) {
+		button->type = ACPI_BUTTON_TYPE_SOFTWARE;
+		button->last_time = ktime_set(0, 0);
+		button->last_state = KEY_UNKNOWN;
+		strcpy(name, ACPI_BUTTON_DEVICE_NAME_SOFTWARE);
+		sprintf(class, "%s/%s", ACPI_BUTTON_CLASS,
+			ACPI_BUTTON_SUBCLASS_SOFTWARE);
+	} else {
+		pr_err(PREFIX "Unsupported hid [%s]\n", hid);
+		error = -ENODEV;
+		goto err_free_input;
+	}
+
+	snprintf(button->phys, sizeof(button->phys), "%s/button/input0", hid);
+
+	input->name = name;
+	input->phys = button->phys;
+	input->id.bustype = BUS_HOST;
+	input->id.product = button->type;
+	input->dev.parent = &device->dev;
+
+	pr_info(PREFIX "ACPI_BUTTON_TYPE_SOFTWARE: [0x%x]",
+		ACPI_BUTTON_TYPE_SOFTWARE);
+
+	switch (button->type) {
+	case ACPI_BUTTON_TYPE_SOFTWARE:
+		set_bit(EV_KEY, input->evbit);
+		set_bit(EV_REP, input->evbit);
+
+		if (swbtn_cfg.lkey_number == 1 &&
+		    swbtn_cfg.pressed_interval[0] == 0)
+			swbtn_cfg.lkey_number = 0;
+
+		for (i = (!swbtn_cfg.dclick_enabled);
+		     i < (swbtn_cfg.lkey_number + 2); i++) {
+			pr_info(PREFIX "%d. Enabled keycode[0x%x]\n",
+				i, swbtn_keycodes[i]);
+			input_set_capability(input, EV_KEY, swbtn_keycodes[i]);
+		}
+	break;
+	}
+
+	sort_asc(swbtn_cfg.pressed_interval, swbtn_cfg.lkey_number);
+
+	input_set_drvdata(input, device);
+	error = input_register_device(input);
+	if (error)
+		goto err_free_input;
+
+	device_init_wakeup(&device->dev, true);
+
+	pr_info(PREFIX "%s [%s]\n", name, acpi_device_bid(device));
+	return 0;
+
+err_free_input:
+	input_free_device(input);
+err_free_button:
+	kfree(button);
+	return error;
+}
+
+static int acpi_button_remove(struct acpi_device *device)
+{
+	struct acpi_button *button = acpi_driver_data(device);
+
+	pr_info(PREFIX "acpi_button_remove");
+
+	input_unregister_device(button->input);
+	kfree(button);
+	return 0;
+}
+
+module_init(acpi_button_init);
+module_exit(acpi_button_exit);