Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH 1/2] mfd: cros: add "base attached" MKBP switch definition
From: Dmitry Torokhov @ 2018-10-01 21:39 UTC (permalink / raw)
  To: Jiri Kosina, Lee Jones
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Nicolas Boichat

This adds a "base attached" switch definition to the MKBP protocol that
is used by Whiskers driver to properly determine device state (clamshell
vs tablet mode).

Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
---

Lee, I was wondering if it woudl be OK for cros_ec_commands.h to be
merged through HID tree.


 include/linux/mfd/cros_ec_commands.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/linux/mfd/cros_ec_commands.h b/include/linux/mfd/cros_ec_commands.h
index 20ee71f10865..5fd0e429f472 100644
--- a/include/linux/mfd/cros_ec_commands.h
+++ b/include/linux/mfd/cros_ec_commands.h
@@ -2132,6 +2132,7 @@ struct ec_response_get_next_event_v1 {
 /* Switches */
 #define EC_MKBP_LID_OPEN	0
 #define EC_MKBP_TABLET_MODE	1
+#define EC_MKBP_BASE_ATTACHED	2
 
 /*****************************************************************************/
 /* Temperature sensor commands */
-- 
2.19.0.605.g01d371f741-goog

^ permalink raw reply related

* [PATCH 2/2] HID: google: add Whiskers driver to handle tablet mode properly
From: Dmitry Torokhov @ 2018-10-01 21:39 UTC (permalink / raw)
  To: Jiri Kosina, Lee Jones
  Cc: Benjamin Tissoires, linux-input, linux-kernel, Nicolas Boichat
In-Reply-To: <20181001213937.8774-1-dtor@chromium.org>

This adds dedicated "Whiskers" driver that hooks both into HID and EC to
produce proper SW_TABLET_SWITCH event from base presence bit from EC and
base state (folded/unfolded) coming from USB/HID.

Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
---
 drivers/hid/Kconfig               |   6 +
 drivers/hid/Makefile              |   1 +
 drivers/hid/hid-google-hammer.c   |   7 +-
 drivers/hid/hid-google-whiskers.c | 382 ++++++++++++++++++++++++++++++
 4 files changed, 392 insertions(+), 4 deletions(-)
 create mode 100644 drivers/hid/hid-google-whiskers.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e5ec47705fa2..0607f1c7129e 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -355,6 +355,12 @@ config HID_GOOGLE_HAMMER
 	---help---
 	Say Y here if you have a Google Hammer device.
 
+config HID_GOOGLE_WHISKERS
+	tristate "Google Whiskers Keyboard"
+	depends on HID_GOOGLE_HAMMER && MFD_CROS_EC
+	---help---
+	Say Y here if you have a Google Whiskers device.
+
 config HID_GT683R
 	tristate "MSI GT68xR LED support"
 	depends on LEDS_CLASS && USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index bd7ac53b75c5..241e583b6913 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_HID_EZKEY)		+= hid-ezkey.o
 obj-$(CONFIG_HID_GEMBIRD)	+= hid-gembird.o
 obj-$(CONFIG_HID_GFRM)		+= hid-gfrm.o
 obj-$(CONFIG_HID_GOOGLE_HAMMER)	+= hid-google-hammer.o
+obj-$(CONFIG_HID_GOOGLE_WHISKERS)	+= hid-google-whiskers.o
 obj-$(CONFIG_HID_GT683R)	+= hid-gt683r.o
 obj-$(CONFIG_HID_GYRATION)	+= hid-gyration.o
 obj-$(CONFIG_HID_HOLTEK)	+= hid-holtek-kbd.o
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
index 6bf4da7ad63a..81ddf9773df9 100644
--- a/drivers/hid/hid-google-hammer.c
+++ b/drivers/hid/hid-google-hammer.c
@@ -18,6 +18,7 @@
 #include <linux/module.h>
 
 #include "hid-ids.h"
+#include "hid-google-hammer.h"
 
 #define MAX_BRIGHTNESS 100
 
@@ -90,8 +91,7 @@ static int hammer_register_leds(struct hid_device *hdev)
 	return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
 }
 
-static int hammer_input_configured(struct hid_device *hdev,
-				   struct hid_input *hi)
+int hammer_input_configured(struct hid_device *hdev, struct hid_input *hi)
 {
 	struct list_head *report_list =
 		&hdev->report_enum[HID_OUTPUT_REPORT].report_list;
@@ -116,6 +116,7 @@ static int hammer_input_configured(struct hid_device *hdev,
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(hammer_input_configured);
 
 static const struct hid_device_id hammer_devices[] = {
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
@@ -124,8 +125,6 @@ static const struct hid_device_id hammer_devices[] = {
 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
-	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
-		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, hammer_devices);
diff --git a/drivers/hid/hid-google-whiskers.c b/drivers/hid/hid-google-whiskers.c
new file mode 100644
index 000000000000..ff3d2bfa1194
--- /dev/null
+++ b/drivers/hid/hid-google-whiskers.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// HID driver for Google Whiskers device.
+//
+// Copyright (c) 2018 Google Inc.
+
+#include <linux/acpi.h>
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_wakeup.h>
+#include <linux/usb.h>
+#include <asm/unaligned.h>
+
+#include "hid-ids.h"
+#include "hid-google-hammer.h"
+
+#define HID_UP_GOOGLEVENDOR	0xffd10000
+#define HID_VD_KBD_FOLDED	0x00000019
+#define WHISKERS_KBD_FOLDED	(HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
+
+struct whiskers_ec {
+	struct device *dev;	/* The platform device (EC) */
+	struct input_dev *input;
+	bool base_present;
+	struct notifier_block notifier;
+};
+
+static struct whiskers_ec whiskers_ec;
+static DEFINE_SPINLOCK(whiskers_ec_lock);
+static DEFINE_MUTEX(whiskers_ec_reglock);
+
+static bool whiskers_parse_base_state(const void *data)
+{
+	u32 switches = get_unaligned_le32(data);
+
+	return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
+}
+
+static int whiskers_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
+				  bool *state)
+{
+	struct ec_params_mkbp_info *params;
+	struct cros_ec_command *msg;
+	int ret;
+
+	msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
+		      GFP_KERNEL);
+	if (!msg)
+		return -ENOMEM;
+
+	msg->command = EC_CMD_MKBP_INFO;
+	msg->version = 1;
+	msg->outsize = sizeof(*params);
+	msg->insize = sizeof(u32);
+	params = (struct ec_params_mkbp_info *)msg->data;
+	params->info_type = get_state ?
+		EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
+	params->event_type = EC_MKBP_EVENT_SWITCH;
+
+	ret = cros_ec_cmd_xfer_status(ec_dev, msg);
+	if (ret >= 0) {
+		if (ret != sizeof(u32)) {
+			dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
+				 ret, sizeof(u32));
+			ret = -EPROTO;
+		} else {
+			*state = whiskers_parse_base_state(msg->data);
+			ret = 0;
+		}
+	}
+
+	kfree(msg);
+
+	return ret;
+}
+
+static int whiskers_ec_notify(struct notifier_block *nb,
+			      unsigned long queued_during_suspend,
+			      void *_notify)
+{
+	struct cros_ec_device *ec = _notify;
+	unsigned long flags;
+	bool base_present;
+
+	if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
+		base_present = whiskers_parse_base_state(
+					&ec->event_data.data.switches);
+		dev_dbg(whiskers_ec.dev,
+			"%s: base: %d\n", __func__, base_present);
+
+		if (device_may_wakeup(whiskers_ec.dev) ||
+		    !queued_during_suspend) {
+
+			pm_wakeup_event(whiskers_ec.dev, 0);
+
+			spin_lock_irqsave(&whiskers_ec_lock, flags);
+
+			/*
+			 * While input layer dedupes the events, we do not want
+			 * to disrupt the state reported by the base by
+			 * overriding it with state reported by the LID. Only
+			 * report changes, as we assume that on attach the base
+			 * is not folded.
+			 */
+			if (base_present != whiskers_ec.base_present) {
+				input_report_switch(whiskers_ec.input,
+						    SW_TABLET_MODE,
+						    !base_present);
+				input_sync(whiskers_ec.input);
+				whiskers_ec.base_present = base_present;
+			}
+
+			spin_unlock_irqrestore(&whiskers_ec_lock, flags);
+		}
+	}
+
+	return NOTIFY_OK;
+}
+
+static __maybe_unused int whiskers_ec_resume(struct device *dev)
+{
+	struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
+	bool base_present;
+	int error;
+
+	error = whiskers_ec_query_base(ec, true, &base_present);
+	if (error) {
+		dev_warn(dev, "failed to fetch base state on resume: %d\n",
+			 error);
+	} else {
+		spin_lock_irq(&whiskers_ec_lock);
+
+		whiskers_ec.base_present = base_present;
+
+		/*
+		 * Only report if base is disconnected. If base is connected,
+		 * it will resend its state on resume, and we'll update it
+		 * in whiskers_event().
+		 */
+		if (!whiskers_ec.base_present) {
+			input_report_switch(whiskers_ec.input,
+					    SW_TABLET_MODE, 1);
+			input_sync(whiskers_ec.input);
+		}
+
+		spin_unlock_irq(&whiskers_ec_lock);
+	}
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(whiskers_ec_pm_ops, NULL, whiskers_ec_resume);
+
+static void whiskers_ec_set_input(struct input_dev *input)
+{
+	/* Take the lock so whiskers_event does not race with us here */
+	spin_lock_irq(&whiskers_ec_lock);
+	whiskers_ec.input = input;
+	spin_unlock_irq(&whiskers_ec_lock);
+}
+
+static int __whiskers_ec_probe(struct platform_device *pdev)
+{
+	struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+	struct input_dev *input;
+	bool base_supported;
+	int error;
+
+	error = whiskers_ec_query_base(ec, false, &base_supported);
+	if (error)
+		return error;
+
+	if (!base_supported)
+		return -ENXIO;
+
+	input = devm_input_allocate_device(&pdev->dev);
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Whiskers Tablet Mode Switch";
+
+	input->id.bustype = BUS_HOST;
+	input->id.version = 1;
+	input->id.product = 0;
+
+	input_set_capability(input, EV_SW, SW_TABLET_MODE);
+
+	error = input_register_device(input);
+	if (error) {
+		dev_err(&pdev->dev, "cannot register input device: %d\n",
+			error);
+		return error;
+	}
+
+	/* Seed the state */
+	error = whiskers_ec_query_base(ec, true, &whiskers_ec.base_present);
+	if (error) {
+		dev_err(&pdev->dev, "cannot query base state: %d\n", error);
+		return error;
+	}
+
+	input_report_switch(input, SW_TABLET_MODE, !whiskers_ec.base_present);
+
+	whiskers_ec_set_input(input);
+
+	whiskers_ec.dev = &pdev->dev;
+	whiskers_ec.notifier.notifier_call = whiskers_ec_notify;
+	error = blocking_notifier_chain_register(&ec->event_notifier,
+						 &whiskers_ec.notifier);
+	if (error) {
+		dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
+		whiskers_ec_set_input(NULL);
+		return error;
+	}
+
+	device_init_wakeup(&pdev->dev, true);
+	return 0;
+}
+
+static int whiskers_ec_probe(struct platform_device *pdev)
+{
+	int retval;
+
+	mutex_lock(&whiskers_ec_reglock);
+
+	if (whiskers_ec.input) {
+		retval = -EBUSY;
+		goto out;
+	}
+
+	retval = __whiskers_ec_probe(pdev);
+
+out:
+	mutex_unlock(&whiskers_ec_reglock);
+	return retval;
+}
+
+static int whiskers_ec_remove(struct platform_device *pdev)
+{
+	struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+
+	mutex_lock(&whiskers_ec_reglock);
+
+	blocking_notifier_chain_unregister(&ec->event_notifier,
+					   &whiskers_ec.notifier);
+	whiskers_ec_set_input(NULL);
+
+	mutex_unlock(&whiskers_ec_reglock);
+	return 0;
+}
+
+static const struct acpi_device_id whiskers_ec_acpi_ids[] = {
+	{ "GOOG000B", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(acpi, whiskers_ec_acpi_ids);
+
+static struct platform_driver whiskers_ec_driver = {
+	.probe	= whiskers_ec_probe,
+	.remove	= whiskers_ec_remove,
+	.driver	= {
+		.name			= "whiskers_ec",
+		.acpi_match_table	= ACPI_PTR(whiskers_ec_acpi_ids),
+		.pm			= &whiskers_ec_pm_ops,
+	},
+};
+
+static int whiskers_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+				  struct hid_field *field,
+				  struct hid_usage *usage,
+				  unsigned long **bit, int *max)
+{
+	if (usage->hid == WHISKERS_KBD_FOLDED) {
+		/*
+		 * We do not want to have this usage mapped as it will get
+		 * mixed in with "base attached" signal and delivered over
+		 * separate input device for tablet switch mode.
+		 */
+
+		return -1;
+	}
+
+	return 0;
+}
+
+static int whiskers_event(struct hid_device *hid, struct hid_field *field,
+			  struct hid_usage *usage, __s32 value)
+{
+	unsigned long flags;
+
+	if (usage->hid == WHISKERS_KBD_FOLDED) {
+		spin_lock_irqsave(&whiskers_ec_lock, flags);
+
+		hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
+			whiskers_ec.base_present, value);
+
+		/*
+		 * We should not get event if base is detached, but in case
+		 * we happen to service HID and EC notifications out of order
+		 * let's still check the "base present" flag.
+		 */
+		if (whiskers_ec.input && whiskers_ec.base_present) {
+			input_report_switch(whiskers_ec.input,
+					    SW_TABLET_MODE, value);
+			input_sync(whiskers_ec.input);
+		}
+
+		spin_unlock_irqrestore(&whiskers_ec_lock, flags);
+		return 1; /* We handled this event */
+	}
+
+	return 0;
+}
+
+static int whiskers_probe(struct hid_device *hdev,
+			  const struct hid_device_id *id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	int ret;
+
+	/*
+	 * We always want to poll for, and handle tablet mode events, even when
+	 * nobody has opened the input device. This also prevents the hid core
+	 * from dropping early tablet mode events from the device.
+	 */
+	if (intf->cur_altsetting->desc.bInterfaceProtocol ==
+			USB_INTERFACE_PROTOCOL_KEYBOARD)
+		hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
+
+	ret = hid_parse(hdev);
+	if (!ret)
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+	return ret;
+}
+
+static const struct hid_device_id whiskers_hid_devices[] = {
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, whiskers_hid_devices);
+
+static struct hid_driver whiskers_hid_driver = {
+	.name			= "whiskers",
+	.id_table		= whiskers_hid_devices,
+	.probe			= whiskers_probe,
+	.input_configured	= hammer_input_configured,
+	.input_mapping		= whiskers_input_mapping,
+	.event			= whiskers_event,
+};
+
+static int __init whiskers_init(void)
+{
+	int error;
+
+	error = platform_driver_register(&whiskers_ec_driver);
+	if (error)
+		return error;
+
+	error = hid_register_driver(&whiskers_hid_driver);
+	if (error) {
+		platform_driver_unregister(&whiskers_ec_driver);
+		return error;
+	}
+
+	return 0;
+}
+module_init(whiskers_init);
+
+static void __exit whiskers_exit(void)
+{
+	hid_unregister_driver(&whiskers_hid_driver);
+	platform_driver_unregister(&whiskers_ec_driver);
+}
+module_exit(whiskers_exit);
+
+MODULE_LICENSE("GPL");
-- 
2.19.0.605.g01d371f741-goog

^ permalink raw reply related

* [PATCH] input: xilinx_ps2: Convert to using %pOFn instead of device_node.name
From: Rob Herring @ 2018-10-01 21:43 UTC (permalink / raw)
  To: linux-kernel; +Cc: Dmitry Torokhov, Michal Simek, linux-input, linux-arm-kernel
In-Reply-To: <20181001214351.13230-1-robh@kernel.org>

In preparation to remove the node name pointer from struct device_node,
convert printf users to use the %pOFn format specifier.

Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Cc: Michal Simek <michal.simek@xilinx.com>
Cc: linux-input@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Signed-off-by: Rob Herring <robh@kernel.org>
---
 drivers/input/serio/xilinx_ps2.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/input/serio/xilinx_ps2.c b/drivers/input/serio/xilinx_ps2.c
index 07de1b49293c..6615c02a08fd 100644
--- a/drivers/input/serio/xilinx_ps2.c
+++ b/drivers/input/serio/xilinx_ps2.c
@@ -245,7 +245,7 @@ static int xps2_of_probe(struct platform_device *ofdev)
 	unsigned int irq;
 	int error;
 
-	dev_info(dev, "Device Tree Probing \'%s\'\n", dev->of_node->name);
+	dev_info(dev, "Device Tree Probing \'%pOFn\'\n", dev->of_node);
 
 	/* Get iospace for the device */
 	error = of_address_to_resource(dev->of_node, 0, &r_mem);
-- 
2.17.1

^ permalink raw reply related

* Re: [PATCH] input: xilinx_ps2: Convert to using %pOFn instead of device_node.name
From: Dmitry Torokhov @ 2018-10-01 23:29 UTC (permalink / raw)
  To: Rob Herring; +Cc: linux-kernel, Michal Simek, linux-input, linux-arm-kernel
In-Reply-To: <20181001214351.13230-2-robh@kernel.org>

On Mon, Oct 01, 2018 at 04:43:49PM -0500, Rob Herring wrote:
> In preparation to remove the node name pointer from struct device_node,
> convert printf users to use the %pOFn format specifier.
> 
> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com>
> Cc: Michal Simek <michal.simek@xilinx.com>
> Cc: linux-input@vger.kernel.org
> Cc: linux-arm-kernel@lists.infradead.org
> Signed-off-by: Rob Herring <robh@kernel.org>

Applied, thank you.

> ---
>  drivers/input/serio/xilinx_ps2.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/input/serio/xilinx_ps2.c b/drivers/input/serio/xilinx_ps2.c
> index 07de1b49293c..6615c02a08fd 100644
> --- a/drivers/input/serio/xilinx_ps2.c
> +++ b/drivers/input/serio/xilinx_ps2.c
> @@ -245,7 +245,7 @@ static int xps2_of_probe(struct platform_device *ofdev)
>  	unsigned int irq;
>  	int error;
>  
> -	dev_info(dev, "Device Tree Probing \'%s\'\n", dev->of_node->name);
> +	dev_info(dev, "Device Tree Probing \'%pOFn\'\n", dev->of_node);
>  
>  	/* Get iospace for the device */
>  	error = of_address_to_resource(dev->of_node, 0, &r_mem);
> -- 
> 2.17.1
> 

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH 3/8] dt-bindings: marvell,mmp2: Add clock id for the SP clock
From: Stephen Boyd @ 2018-10-02  7:18 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
	Dmitry Torokhov, Michael Turquette, Lubomir Rintel
In-Reply-To: <20180910112654.42061-4-lkundrak@v3.sk>

Quoting Lubomir Rintel (2018-09-10 04:26:49)
> This is the clock for the "security processor" core.
> 
> Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
> ---

Acked-by: Stephen Boyd <sboyd@kernel.org>

^ permalink raw reply

* Re: [PATCH 4/8] clk: mmp2: add SP clock
From: Stephen Boyd @ 2018-10-02  7:18 UTC (permalink / raw)
  To: linux-kernel
  Cc: linux-clk, devicetree, linux-input, Rob Herring, Mark Rutland,
	Dmitry Torokhov, Michael Turquette, Lubomir Rintel
In-Reply-To: <20180910112654.42061-5-lkundrak@v3.sk>

Quoting Lubomir Rintel (2018-09-10 04:26:50)
> The "security processor", sometimes referred to as "wireless trusted
> module" or "generic encrypt unit" is a low-power core present on MMP2,
> that has nothing to do with security, wireless, trust or encryption.
> 
> On an OLPC machine it runs CForth and serves as a keyboard controller:
> http://dev.laptop.org/git/users/wmb/cforth/tree/src/app/arm-xo-1.75/ps2.fth
> 
> The register address was obtained from the OLPC kernel, since the
> datasheet seems to be the Marvell's most important business secret.
> 
> Signed-off-by: Lubomir Rintel <lkundrak@v3.sk>
> ---

Acked-by: Stephen Boyd <sboyd@kernel.org>

^ permalink raw reply

* Re: [PATCH 2/2] HID: google: add Whiskers driver to handle tablet mode properly
From: Benjamin Tissoires @ 2018-10-02  8:53 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Jiri Kosina, lee.jones, open list:HID CORE LAYER, lkml,
	Nicolas Boichat
In-Reply-To: <20181001213937.8774-2-dtor@chromium.org>

Hi Dmitry,

On Mon, Oct 1, 2018 at 11:39 PM Dmitry Torokhov <dtor@chromium.org> wrote:
>
> This adds dedicated "Whiskers" driver that hooks both into HID and EC to
> produce proper SW_TABLET_SWITCH event from base presence bit from EC and
> base state (folded/unfolded) coming from USB/HID.
>
> Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
> Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
> ---

I have a few comments, see below.

>  drivers/hid/Kconfig               |   6 +
>  drivers/hid/Makefile              |   1 +
>  drivers/hid/hid-google-hammer.c   |   7 +-
>  drivers/hid/hid-google-whiskers.c | 382 ++++++++++++++++++++++++++++++
>  4 files changed, 392 insertions(+), 4 deletions(-)
>  create mode 100644 drivers/hid/hid-google-whiskers.c
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index e5ec47705fa2..0607f1c7129e 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -355,6 +355,12 @@ config HID_GOOGLE_HAMMER
>         ---help---
>         Say Y here if you have a Google Hammer device.
>
> +config HID_GOOGLE_WHISKERS
> +       tristate "Google Whiskers Keyboard"
> +       depends on HID_GOOGLE_HAMMER && MFD_CROS_EC
> +       ---help---
> +       Say Y here if you have a Google Whiskers device.
> +
>  config HID_GT683R
>         tristate "MSI GT68xR LED support"
>         depends on LEDS_CLASS && USB_HID
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index bd7ac53b75c5..241e583b6913 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_HID_EZKEY)               += hid-ezkey.o
>  obj-$(CONFIG_HID_GEMBIRD)      += hid-gembird.o
>  obj-$(CONFIG_HID_GFRM)         += hid-gfrm.o
>  obj-$(CONFIG_HID_GOOGLE_HAMMER)        += hid-google-hammer.o
> +obj-$(CONFIG_HID_GOOGLE_WHISKERS)      += hid-google-whiskers.o
>  obj-$(CONFIG_HID_GT683R)       += hid-gt683r.o
>  obj-$(CONFIG_HID_GYRATION)     += hid-gyration.o
>  obj-$(CONFIG_HID_HOLTEK)       += hid-holtek-kbd.o
> diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
> index 6bf4da7ad63a..81ddf9773df9 100644
> --- a/drivers/hid/hid-google-hammer.c
> +++ b/drivers/hid/hid-google-hammer.c
> @@ -18,6 +18,7 @@
>  #include <linux/module.h>
>
>  #include "hid-ids.h"
> +#include "hid-google-hammer.h"

hid-google-hammer.h doesn't seem to be part of the patch (or in my
local tree at least).

>
>  #define MAX_BRIGHTNESS 100
>
> @@ -90,8 +91,7 @@ static int hammer_register_leds(struct hid_device *hdev)
>         return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
>  }
>
> -static int hammer_input_configured(struct hid_device *hdev,
> -                                  struct hid_input *hi)
> +int hammer_input_configured(struct hid_device *hdev, struct hid_input *hi)
>  {
>         struct list_head *report_list =
>                 &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
> @@ -116,6 +116,7 @@ static int hammer_input_configured(struct hid_device *hdev,
>
>         return 0;
>  }
> +EXPORT_SYMBOL_GPL(hammer_input_configured);

I am not sure there is a need to separate the driver in 2 for whiskers
if it still need the LED settings from hammer.

Given that the whiskers_ec struct will be populated independently,
having a common code path would not hurt hammer anyway.

>
>  static const struct hid_device_id hammer_devices[] = {
>         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> @@ -124,8 +125,6 @@ static const struct hid_device_id hammer_devices[] = {
>                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
>         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
>                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
> -       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> -                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
>         { }
>  };
>  MODULE_DEVICE_TABLE(hid, hammer_devices);
> diff --git a/drivers/hid/hid-google-whiskers.c b/drivers/hid/hid-google-whiskers.c
> new file mode 100644
> index 000000000000..ff3d2bfa1194
> --- /dev/null
> +++ b/drivers/hid/hid-google-whiskers.c
> @@ -0,0 +1,382 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +//
> +// HID driver for Google Whiskers device.
> +//
> +// Copyright (c) 2018 Google Inc.
> +
> +#include <linux/acpi.h>
> +#include <linux/hid.h>
> +#include <linux/leds.h>

doesn't seem to be used

> +#include <linux/mfd/cros_ec.h>
> +#include <linux/mfd/cros_ec_commands.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_wakeup.h>
> +#include <linux/usb.h>

This is usually not a good sign in HID drivers to rely on the low
level transport.
Basically, if you replay the device through uhid, this tends to break
heavily the kernel.

> +#include <asm/unaligned.h>
> +
> +#include "hid-ids.h"
> +#include "hid-google-hammer.h"
> +
> +#define HID_UP_GOOGLEVENDOR    0xffd10000
> +#define HID_VD_KBD_FOLDED      0x00000019
> +#define WHISKERS_KBD_FOLDED    (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)

Huh, this is weird there is no standard HID usage for this (not that
you can do anything about it).

> +
> +struct whiskers_ec {
> +       struct device *dev;     /* The platform device (EC) */
> +       struct input_dev *input;
> +       bool base_present;
> +       struct notifier_block notifier;
> +};
> +
> +static struct whiskers_ec whiskers_ec;
> +static DEFINE_SPINLOCK(whiskers_ec_lock);
> +static DEFINE_MUTEX(whiskers_ec_reglock);
> +
> +static bool whiskers_parse_base_state(const void *data)
> +{
> +       u32 switches = get_unaligned_le32(data);
> +
> +       return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
> +}
> +
> +static int whiskers_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
> +                                 bool *state)
> +{
> +       struct ec_params_mkbp_info *params;
> +       struct cros_ec_command *msg;
> +       int ret;
> +
> +       msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
> +                     GFP_KERNEL);
> +       if (!msg)
> +               return -ENOMEM;
> +
> +       msg->command = EC_CMD_MKBP_INFO;
> +       msg->version = 1;
> +       msg->outsize = sizeof(*params);
> +       msg->insize = sizeof(u32);
> +       params = (struct ec_params_mkbp_info *)msg->data;
> +       params->info_type = get_state ?
> +               EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
> +       params->event_type = EC_MKBP_EVENT_SWITCH;
> +
> +       ret = cros_ec_cmd_xfer_status(ec_dev, msg);
> +       if (ret >= 0) {
> +               if (ret != sizeof(u32)) {
> +                       dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
> +                                ret, sizeof(u32));
> +                       ret = -EPROTO;
> +               } else {
> +                       *state = whiskers_parse_base_state(msg->data);
> +                       ret = 0;
> +               }
> +       }
> +
> +       kfree(msg);
> +
> +       return ret;
> +}
> +
> +static int whiskers_ec_notify(struct notifier_block *nb,
> +                             unsigned long queued_during_suspend,
> +                             void *_notify)
> +{
> +       struct cros_ec_device *ec = _notify;
> +       unsigned long flags;
> +       bool base_present;
> +
> +       if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
> +               base_present = whiskers_parse_base_state(
> +                                       &ec->event_data.data.switches);
> +               dev_dbg(whiskers_ec.dev,
> +                       "%s: base: %d\n", __func__, base_present);
> +
> +               if (device_may_wakeup(whiskers_ec.dev) ||
> +                   !queued_during_suspend) {
> +
> +                       pm_wakeup_event(whiskers_ec.dev, 0);
> +
> +                       spin_lock_irqsave(&whiskers_ec_lock, flags);
> +
> +                       /*
> +                        * While input layer dedupes the events, we do not want
> +                        * to disrupt the state reported by the base by
> +                        * overriding it with state reported by the LID. Only
> +                        * report changes, as we assume that on attach the base
> +                        * is not folded.
> +                        */
> +                       if (base_present != whiskers_ec.base_present) {
> +                               input_report_switch(whiskers_ec.input,
> +                                                   SW_TABLET_MODE,
> +                                                   !base_present);
> +                               input_sync(whiskers_ec.input);
> +                               whiskers_ec.base_present = base_present;
> +                       }
> +
> +                       spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> +               }
> +       }
> +
> +       return NOTIFY_OK;
> +}
> +
> +static __maybe_unused int whiskers_ec_resume(struct device *dev)
> +{
> +       struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
> +       bool base_present;
> +       int error;
> +
> +       error = whiskers_ec_query_base(ec, true, &base_present);
> +       if (error) {
> +               dev_warn(dev, "failed to fetch base state on resume: %d\n",
> +                        error);
> +       } else {
> +               spin_lock_irq(&whiskers_ec_lock);
> +
> +               whiskers_ec.base_present = base_present;
> +
> +               /*
> +                * Only report if base is disconnected. If base is connected,
> +                * it will resend its state on resume, and we'll update it
> +                * in whiskers_event().
> +                */
> +               if (!whiskers_ec.base_present) {
> +                       input_report_switch(whiskers_ec.input,
> +                                           SW_TABLET_MODE, 1);
> +                       input_sync(whiskers_ec.input);
> +               }
> +
> +               spin_unlock_irq(&whiskers_ec_lock);
> +       }
> +
> +       return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(whiskers_ec_pm_ops, NULL, whiskers_ec_resume);
> +
> +static void whiskers_ec_set_input(struct input_dev *input)
> +{
> +       /* Take the lock so whiskers_event does not race with us here */
> +       spin_lock_irq(&whiskers_ec_lock);
> +       whiskers_ec.input = input;
> +       spin_unlock_irq(&whiskers_ec_lock);
> +}
> +
> +static int __whiskers_ec_probe(struct platform_device *pdev)
> +{
> +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> +       struct input_dev *input;
> +       bool base_supported;
> +       int error;
> +
> +       error = whiskers_ec_query_base(ec, false, &base_supported);
> +       if (error)
> +               return error;
> +
> +       if (!base_supported)
> +               return -ENXIO;
> +
> +       input = devm_input_allocate_device(&pdev->dev);
> +       if (!input)
> +               return -ENOMEM;
> +
> +       input->name = "Whiskers Tablet Mode Switch";
> +
> +       input->id.bustype = BUS_HOST;
> +       input->id.version = 1;
> +       input->id.product = 0;

I wonder if you should not set the vendor to USB_VENDOR_ID_GOOGLE and
the product to something better.

> +
> +       input_set_capability(input, EV_SW, SW_TABLET_MODE);
> +
> +       error = input_register_device(input);
> +       if (error) {
> +               dev_err(&pdev->dev, "cannot register input device: %d\n",
> +                       error);
> +               return error;
> +       }
> +
> +       /* Seed the state */
> +       error = whiskers_ec_query_base(ec, true, &whiskers_ec.base_present);
> +       if (error) {
> +               dev_err(&pdev->dev, "cannot query base state: %d\n", error);
> +               return error;
> +       }
> +
> +       input_report_switch(input, SW_TABLET_MODE, !whiskers_ec.base_present);
> +
> +       whiskers_ec_set_input(input);
> +
> +       whiskers_ec.dev = &pdev->dev;
> +       whiskers_ec.notifier.notifier_call = whiskers_ec_notify;
> +       error = blocking_notifier_chain_register(&ec->event_notifier,
> +                                                &whiskers_ec.notifier);
> +       if (error) {
> +               dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
> +               whiskers_ec_set_input(NULL);
> +               return error;
> +       }
> +
> +       device_init_wakeup(&pdev->dev, true);
> +       return 0;
> +}
> +
> +static int whiskers_ec_probe(struct platform_device *pdev)
> +{
> +       int retval;
> +
> +       mutex_lock(&whiskers_ec_reglock);
> +
> +       if (whiskers_ec.input) {
> +               retval = -EBUSY;
> +               goto out;
> +       }
> +
> +       retval = __whiskers_ec_probe(pdev);
> +
> +out:
> +       mutex_unlock(&whiskers_ec_reglock);
> +       return retval;
> +}
> +
> +static int whiskers_ec_remove(struct platform_device *pdev)
> +{
> +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> +
> +       mutex_lock(&whiskers_ec_reglock);
> +
> +       blocking_notifier_chain_unregister(&ec->event_notifier,
> +                                          &whiskers_ec.notifier);
> +       whiskers_ec_set_input(NULL);
> +
> +       mutex_unlock(&whiskers_ec_reglock);
> +       return 0;
> +}
> +
> +static const struct acpi_device_id whiskers_ec_acpi_ids[] = {
> +       { "GOOG000B", 0 },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(acpi, whiskers_ec_acpi_ids);
> +
> +static struct platform_driver whiskers_ec_driver = {
> +       .probe  = whiskers_ec_probe,
> +       .remove = whiskers_ec_remove,
> +       .driver = {
> +               .name                   = "whiskers_ec",
> +               .acpi_match_table       = ACPI_PTR(whiskers_ec_acpi_ids),
> +               .pm                     = &whiskers_ec_pm_ops,
> +       },
> +};
> +
> +static int whiskers_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> +                                 struct hid_field *field,
> +                                 struct hid_usage *usage,
> +                                 unsigned long **bit, int *max)
> +{
> +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> +               /*
> +                * We do not want to have this usage mapped as it will get
> +                * mixed in with "base attached" signal and delivered over
> +                * separate input device for tablet switch mode.
> +                */
> +
> +               return -1;
> +       }
> +
> +       return 0;
> +}
> +
> +static int whiskers_event(struct hid_device *hid, struct hid_field *field,
> +                         struct hid_usage *usage, __s32 value)
> +{
> +       unsigned long flags;
> +
> +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> +               spin_lock_irqsave(&whiskers_ec_lock, flags);
> +
> +               hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
> +                       whiskers_ec.base_present, value);
> +
> +               /*
> +                * We should not get event if base is detached, but in case
> +                * we happen to service HID and EC notifications out of order
> +                * let's still check the "base present" flag.
> +                */
> +               if (whiskers_ec.input && whiskers_ec.base_present) {
> +                       input_report_switch(whiskers_ec.input,
> +                                           SW_TABLET_MODE, value);
> +                       input_sync(whiskers_ec.input);
> +               }
> +
> +               spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> +               return 1; /* We handled this event */
> +       }
> +
> +       return 0;
> +}
> +
> +static int whiskers_probe(struct hid_device *hdev,
> +                         const struct hid_device_id *id)
> +{
> +       struct usb_interface *intf = to_usb_interface(hdev->dev.parent);

As mentioned earlier, this is prone to kernel oopses when using uhid.

> +       int ret;
> +
> +       /*
> +        * We always want to poll for, and handle tablet mode events, even when
> +        * nobody has opened the input device. This also prevents the hid core
> +        * from dropping early tablet mode events from the device.
> +        */
> +       if (intf->cur_altsetting->desc.bInterfaceProtocol ==
> +                       USB_INTERFACE_PROTOCOL_KEYBOARD)

Can't you rely on something embedded in the report descriptor instead?
This way you can drop the USB dependency.

> +               hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
> +
> +       ret = hid_parse(hdev);
> +       if (!ret)
> +               ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> +
> +       return ret;
> +}
> +
> +static const struct hid_device_id whiskers_hid_devices[] = {
> +       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> +                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
> +       { }
> +};
> +MODULE_DEVICE_TABLE(hid, whiskers_hid_devices);
> +
> +static struct hid_driver whiskers_hid_driver = {
> +       .name                   = "whiskers",
> +       .id_table               = whiskers_hid_devices,
> +       .probe                  = whiskers_probe,
> +       .input_configured       = hammer_input_configured,
> +       .input_mapping          = whiskers_input_mapping,
> +       .event                  = whiskers_event,
> +};
> +
> +static int __init whiskers_init(void)
> +{
> +       int error;
> +
> +       error = platform_driver_register(&whiskers_ec_driver);
> +       if (error)
> +               return error;
> +
> +       error = hid_register_driver(&whiskers_hid_driver);
> +       if (error) {
> +               platform_driver_unregister(&whiskers_ec_driver);
> +               return error;
> +       }
> +
> +       return 0;
> +}
> +module_init(whiskers_init);
> +
> +static void __exit whiskers_exit(void)
> +{
> +       hid_unregister_driver(&whiskers_hid_driver);
> +       platform_driver_unregister(&whiskers_ec_driver);
> +}
> +module_exit(whiskers_exit);
> +
> +MODULE_LICENSE("GPL");
> --
> 2.19.0.605.g01d371f741-goog
>

Cheers,
Benjamin

^ permalink raw reply

* Re: [PATCH 2/2] HID: google: add Whiskers driver to handle tablet mode properly
From: Dmitry Torokhov @ 2018-10-02 18:24 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Jiri Kosina, Lee Jones, open list:HID CORE LAYER, lkml, drinkcat
In-Reply-To: <CAO-hwJKASi3+aFac8OR5XqESCF8RgB_aWZySAiMf+GHNh-2b0g@mail.gmail.com>

Hi Benjamin,

On Tue, Oct 2, 2018 at 1:53 AM Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
>
> Hi Dmitry,
>
> On Mon, Oct 1, 2018 at 11:39 PM Dmitry Torokhov <dtor@chromium.org> wrote:
> >
> > This adds dedicated "Whiskers" driver that hooks both into HID and EC to
> > produce proper SW_TABLET_SWITCH event from base presence bit from EC and
> > base state (folded/unfolded) coming from USB/HID.
> >
> > Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
> > Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
> > ---
>
> I have a few comments, see below.
>
> >  drivers/hid/Kconfig               |   6 +
> >  drivers/hid/Makefile              |   1 +
> >  drivers/hid/hid-google-hammer.c   |   7 +-
> >  drivers/hid/hid-google-whiskers.c | 382 ++++++++++++++++++++++++++++++
> >  4 files changed, 392 insertions(+), 4 deletions(-)
> >  create mode 100644 drivers/hid/hid-google-whiskers.c
> >
> > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > index e5ec47705fa2..0607f1c7129e 100644
> > --- a/drivers/hid/Kconfig
> > +++ b/drivers/hid/Kconfig
> > @@ -355,6 +355,12 @@ config HID_GOOGLE_HAMMER
> >         ---help---
> >         Say Y here if you have a Google Hammer device.
> >
> > +config HID_GOOGLE_WHISKERS
> > +       tristate "Google Whiskers Keyboard"
> > +       depends on HID_GOOGLE_HAMMER && MFD_CROS_EC
> > +       ---help---
> > +       Say Y here if you have a Google Whiskers device.
> > +
> >  config HID_GT683R
> >         tristate "MSI GT68xR LED support"
> >         depends on LEDS_CLASS && USB_HID
> > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> > index bd7ac53b75c5..241e583b6913 100644
> > --- a/drivers/hid/Makefile
> > +++ b/drivers/hid/Makefile
> > @@ -47,6 +47,7 @@ obj-$(CONFIG_HID_EZKEY)               += hid-ezkey.o
> >  obj-$(CONFIG_HID_GEMBIRD)      += hid-gembird.o
> >  obj-$(CONFIG_HID_GFRM)         += hid-gfrm.o
> >  obj-$(CONFIG_HID_GOOGLE_HAMMER)        += hid-google-hammer.o
> > +obj-$(CONFIG_HID_GOOGLE_WHISKERS)      += hid-google-whiskers.o
> >  obj-$(CONFIG_HID_GT683R)       += hid-gt683r.o
> >  obj-$(CONFIG_HID_GYRATION)     += hid-gyration.o
> >  obj-$(CONFIG_HID_HOLTEK)       += hid-holtek-kbd.o
> > diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
> > index 6bf4da7ad63a..81ddf9773df9 100644
> > --- a/drivers/hid/hid-google-hammer.c
> > +++ b/drivers/hid/hid-google-hammer.c
> > @@ -18,6 +18,7 @@
> >  #include <linux/module.h>
> >
> >  #include "hid-ids.h"
> > +#include "hid-google-hammer.h"
>
> hid-google-hammer.h doesn't seem to be part of the patch (or in my
> local tree at least).

Ugh, my bad.

>
> >
> >  #define MAX_BRIGHTNESS 100
> >
> > @@ -90,8 +91,7 @@ static int hammer_register_leds(struct hid_device *hdev)
> >         return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
> >  }
> >
> > -static int hammer_input_configured(struct hid_device *hdev,
> > -                                  struct hid_input *hi)
> > +int hammer_input_configured(struct hid_device *hdev, struct hid_input *hi)
> >  {
> >         struct list_head *report_list =
> >                 &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
> > @@ -116,6 +116,7 @@ static int hammer_input_configured(struct hid_device *hdev,
> >
> >         return 0;
> >  }
> > +EXPORT_SYMBOL_GPL(hammer_input_configured);
>
> I am not sure there is a need to separate the driver in 2 for whiskers
> if it still need the LED settings from hammer.
>
> Given that the whiskers_ec struct will be populated independently,
> having a common code path would not hurt hammer anyway.

The idea is that we we have device with Hammer, we do not need to load
bunch of code that deals with properly supporting Whiskers (connecting
to the EC, creating a tablet switch device).

>
> >
> >  static const struct hid_device_id hammer_devices[] = {
> >         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > @@ -124,8 +125,6 @@ static const struct hid_device_id hammer_devices[] = {
> >                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
> >         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> >                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
> > -       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > -                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
> >         { }
> >  };
> >  MODULE_DEVICE_TABLE(hid, hammer_devices);
> > diff --git a/drivers/hid/hid-google-whiskers.c b/drivers/hid/hid-google-whiskers.c
> > new file mode 100644
> > index 000000000000..ff3d2bfa1194
> > --- /dev/null
> > +++ b/drivers/hid/hid-google-whiskers.c
> > @@ -0,0 +1,382 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +//
> > +// HID driver for Google Whiskers device.
> > +//
> > +// Copyright (c) 2018 Google Inc.
> > +
> > +#include <linux/acpi.h>
> > +#include <linux/hid.h>
> > +#include <linux/leds.h>
>
> doesn't seem to be used

Indeed, will drop.

>
> > +#include <linux/mfd/cros_ec.h>
> > +#include <linux/mfd/cros_ec_commands.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_wakeup.h>
> > +#include <linux/usb.h>
>
> This is usually not a good sign in HID drivers to rely on the low
> level transport.
> Basically, if you replay the device through uhid, this tends to break
> heavily the kernel.

The data parsing relies solely on HID, USB is here just to make sure
we bind to the right interface, as you can see below.

>
> > +#include <asm/unaligned.h>
> > +
> > +#include "hid-ids.h"
> > +#include "hid-google-hammer.h"
> > +
> > +#define HID_UP_GOOGLEVENDOR    0xffd10000
> > +#define HID_VD_KBD_FOLDED      0x00000019
> > +#define WHISKERS_KBD_FOLDED    (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
>
> Huh, this is weird there is no standard HID usage for this (not that
> you can do anything about it).

This is really specific to this device, I am not sure if it makes
sense to standardize it. I guess we'll see if we make more of these.

>
> > +
> > +struct whiskers_ec {
> > +       struct device *dev;     /* The platform device (EC) */
> > +       struct input_dev *input;
> > +       bool base_present;
> > +       struct notifier_block notifier;
> > +};
> > +
> > +static struct whiskers_ec whiskers_ec;
> > +static DEFINE_SPINLOCK(whiskers_ec_lock);
> > +static DEFINE_MUTEX(whiskers_ec_reglock);
> > +
> > +static bool whiskers_parse_base_state(const void *data)
> > +{
> > +       u32 switches = get_unaligned_le32(data);
> > +
> > +       return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
> > +}
> > +
> > +static int whiskers_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
> > +                                 bool *state)
> > +{
> > +       struct ec_params_mkbp_info *params;
> > +       struct cros_ec_command *msg;
> > +       int ret;
> > +
> > +       msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
> > +                     GFP_KERNEL);
> > +       if (!msg)
> > +               return -ENOMEM;
> > +
> > +       msg->command = EC_CMD_MKBP_INFO;
> > +       msg->version = 1;
> > +       msg->outsize = sizeof(*params);
> > +       msg->insize = sizeof(u32);
> > +       params = (struct ec_params_mkbp_info *)msg->data;
> > +       params->info_type = get_state ?
> > +               EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
> > +       params->event_type = EC_MKBP_EVENT_SWITCH;
> > +
> > +       ret = cros_ec_cmd_xfer_status(ec_dev, msg);
> > +       if (ret >= 0) {
> > +               if (ret != sizeof(u32)) {
> > +                       dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
> > +                                ret, sizeof(u32));
> > +                       ret = -EPROTO;
> > +               } else {
> > +                       *state = whiskers_parse_base_state(msg->data);
> > +                       ret = 0;
> > +               }
> > +       }
> > +
> > +       kfree(msg);
> > +
> > +       return ret;
> > +}
> > +
> > +static int whiskers_ec_notify(struct notifier_block *nb,
> > +                             unsigned long queued_during_suspend,
> > +                             void *_notify)
> > +{
> > +       struct cros_ec_device *ec = _notify;
> > +       unsigned long flags;
> > +       bool base_present;
> > +
> > +       if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
> > +               base_present = whiskers_parse_base_state(
> > +                                       &ec->event_data.data.switches);
> > +               dev_dbg(whiskers_ec.dev,
> > +                       "%s: base: %d\n", __func__, base_present);
> > +
> > +               if (device_may_wakeup(whiskers_ec.dev) ||
> > +                   !queued_during_suspend) {
> > +
> > +                       pm_wakeup_event(whiskers_ec.dev, 0);
> > +
> > +                       spin_lock_irqsave(&whiskers_ec_lock, flags);
> > +
> > +                       /*
> > +                        * While input layer dedupes the events, we do not want
> > +                        * to disrupt the state reported by the base by
> > +                        * overriding it with state reported by the LID. Only
> > +                        * report changes, as we assume that on attach the base
> > +                        * is not folded.
> > +                        */
> > +                       if (base_present != whiskers_ec.base_present) {
> > +                               input_report_switch(whiskers_ec.input,
> > +                                                   SW_TABLET_MODE,
> > +                                                   !base_present);
> > +                               input_sync(whiskers_ec.input);
> > +                               whiskers_ec.base_present = base_present;
> > +                       }
> > +
> > +                       spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> > +               }
> > +       }
> > +
> > +       return NOTIFY_OK;
> > +}
> > +
> > +static __maybe_unused int whiskers_ec_resume(struct device *dev)
> > +{
> > +       struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
> > +       bool base_present;
> > +       int error;
> > +
> > +       error = whiskers_ec_query_base(ec, true, &base_present);
> > +       if (error) {
> > +               dev_warn(dev, "failed to fetch base state on resume: %d\n",
> > +                        error);
> > +       } else {
> > +               spin_lock_irq(&whiskers_ec_lock);
> > +
> > +               whiskers_ec.base_present = base_present;
> > +
> > +               /*
> > +                * Only report if base is disconnected. If base is connected,
> > +                * it will resend its state on resume, and we'll update it
> > +                * in whiskers_event().
> > +                */
> > +               if (!whiskers_ec.base_present) {
> > +                       input_report_switch(whiskers_ec.input,
> > +                                           SW_TABLET_MODE, 1);
> > +                       input_sync(whiskers_ec.input);
> > +               }
> > +
> > +               spin_unlock_irq(&whiskers_ec_lock);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(whiskers_ec_pm_ops, NULL, whiskers_ec_resume);
> > +
> > +static void whiskers_ec_set_input(struct input_dev *input)
> > +{
> > +       /* Take the lock so whiskers_event does not race with us here */
> > +       spin_lock_irq(&whiskers_ec_lock);
> > +       whiskers_ec.input = input;
> > +       spin_unlock_irq(&whiskers_ec_lock);
> > +}
> > +
> > +static int __whiskers_ec_probe(struct platform_device *pdev)
> > +{
> > +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> > +       struct input_dev *input;
> > +       bool base_supported;
> > +       int error;
> > +
> > +       error = whiskers_ec_query_base(ec, false, &base_supported);
> > +       if (error)
> > +               return error;
> > +
> > +       if (!base_supported)
> > +               return -ENXIO;
> > +
> > +       input = devm_input_allocate_device(&pdev->dev);
> > +       if (!input)
> > +               return -ENOMEM;
> > +
> > +       input->name = "Whiskers Tablet Mode Switch";
> > +
> > +       input->id.bustype = BUS_HOST;
> > +       input->id.version = 1;
> > +       input->id.product = 0;
>
> I wonder if you should not set the vendor to USB_VENDOR_ID_GOOGLE and
> the product to something better.

This is not USB interface, but host one (coming from the EC), so
USB_VENDOR_ID_GOOGLE does not make sense here. I think I'll simply
remove version and product.

>
> > +
> > +       input_set_capability(input, EV_SW, SW_TABLET_MODE);
> > +
> > +       error = input_register_device(input);
> > +       if (error) {
> > +               dev_err(&pdev->dev, "cannot register input device: %d\n",
> > +                       error);
> > +               return error;
> > +       }
> > +
> > +       /* Seed the state */
> > +       error = whiskers_ec_query_base(ec, true, &whiskers_ec.base_present);
> > +       if (error) {
> > +               dev_err(&pdev->dev, "cannot query base state: %d\n", error);
> > +               return error;
> > +       }
> > +
> > +       input_report_switch(input, SW_TABLET_MODE, !whiskers_ec.base_present);
> > +
> > +       whiskers_ec_set_input(input);
> > +
> > +       whiskers_ec.dev = &pdev->dev;
> > +       whiskers_ec.notifier.notifier_call = whiskers_ec_notify;
> > +       error = blocking_notifier_chain_register(&ec->event_notifier,
> > +                                                &whiskers_ec.notifier);
> > +       if (error) {
> > +               dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
> > +               whiskers_ec_set_input(NULL);
> > +               return error;
> > +       }
> > +
> > +       device_init_wakeup(&pdev->dev, true);
> > +       return 0;
> > +}
> > +
> > +static int whiskers_ec_probe(struct platform_device *pdev)
> > +{
> > +       int retval;
> > +
> > +       mutex_lock(&whiskers_ec_reglock);
> > +
> > +       if (whiskers_ec.input) {
> > +               retval = -EBUSY;
> > +               goto out;
> > +       }
> > +
> > +       retval = __whiskers_ec_probe(pdev);
> > +
> > +out:
> > +       mutex_unlock(&whiskers_ec_reglock);
> > +       return retval;
> > +}
> > +
> > +static int whiskers_ec_remove(struct platform_device *pdev)
> > +{
> > +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> > +
> > +       mutex_lock(&whiskers_ec_reglock);
> > +
> > +       blocking_notifier_chain_unregister(&ec->event_notifier,
> > +                                          &whiskers_ec.notifier);
> > +       whiskers_ec_set_input(NULL);
> > +
> > +       mutex_unlock(&whiskers_ec_reglock);
> > +       return 0;
> > +}
> > +
> > +static const struct acpi_device_id whiskers_ec_acpi_ids[] = {
> > +       { "GOOG000B", 0 },
> > +       { }
> > +};
> > +MODULE_DEVICE_TABLE(acpi, whiskers_ec_acpi_ids);
> > +
> > +static struct platform_driver whiskers_ec_driver = {
> > +       .probe  = whiskers_ec_probe,
> > +       .remove = whiskers_ec_remove,
> > +       .driver = {
> > +               .name                   = "whiskers_ec",
> > +               .acpi_match_table       = ACPI_PTR(whiskers_ec_acpi_ids),
> > +               .pm                     = &whiskers_ec_pm_ops,
> > +       },
> > +};
> > +
> > +static int whiskers_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> > +                                 struct hid_field *field,
> > +                                 struct hid_usage *usage,
> > +                                 unsigned long **bit, int *max)
> > +{
> > +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> > +               /*
> > +                * We do not want to have this usage mapped as it will get
> > +                * mixed in with "base attached" signal and delivered over
> > +                * separate input device for tablet switch mode.
> > +                */
> > +
> > +               return -1;
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int whiskers_event(struct hid_device *hid, struct hid_field *field,
> > +                         struct hid_usage *usage, __s32 value)
> > +{
> > +       unsigned long flags;
> > +
> > +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> > +               spin_lock_irqsave(&whiskers_ec_lock, flags);
> > +
> > +               hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
> > +                       whiskers_ec.base_present, value);
> > +
> > +               /*
> > +                * We should not get event if base is detached, but in case
> > +                * we happen to service HID and EC notifications out of order
> > +                * let's still check the "base present" flag.
> > +                */
> > +               if (whiskers_ec.input && whiskers_ec.base_present) {
> > +                       input_report_switch(whiskers_ec.input,
> > +                                           SW_TABLET_MODE, value);
> > +                       input_sync(whiskers_ec.input);
> > +               }
> > +
> > +               spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> > +               return 1; /* We handled this event */
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static int whiskers_probe(struct hid_device *hdev,
> > +                         const struct hid_device_id *id)
> > +{
> > +       struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
>
> As mentioned earlier, this is prone to kernel oopses when using uhid.

Hmm, we have similar code in hid-google-hammer.c, I guess we need to
fix that there too.

>
> > +       int ret;
> > +
> > +       /*
> > +        * We always want to poll for, and handle tablet mode events, even when
> > +        * nobody has opened the input device. This also prevents the hid core
> > +        * from dropping early tablet mode events from the device.
> > +        */
> > +       if (intf->cur_altsetting->desc.bInterfaceProtocol ==
> > +                       USB_INTERFACE_PROTOCOL_KEYBOARD)
>
> Can't you rely on something embedded in the report descriptor instead?
> This way you can drop the USB dependency.

Something like:

static bool hammer_is_keyboard(...)
{

       rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
       list_for_each_entry(report, &rep_enum->report_list, list) {
               for (i = 0; i < report->maxfield; i++) {
                       if (report->field[i].application == HID_GD_KEYBOARD)
                               return true;
       }

       return false;
}

?

>
> > +               hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
> > +
> > +       ret = hid_parse(hdev);
> > +       if (!ret)
> > +               ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> > +
> > +       return ret;
> > +}
> > +
> > +static const struct hid_device_id whiskers_hid_devices[] = {
> > +       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > +                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
> > +       { }
> > +};
> > +MODULE_DEVICE_TABLE(hid, whiskers_hid_devices);
> > +
> > +static struct hid_driver whiskers_hid_driver = {
> > +       .name                   = "whiskers",
> > +       .id_table               = whiskers_hid_devices,
> > +       .probe                  = whiskers_probe,
> > +       .input_configured       = hammer_input_configured,
> > +       .input_mapping          = whiskers_input_mapping,
> > +       .event                  = whiskers_event,
> > +};
> > +
> > +static int __init whiskers_init(void)
> > +{
> > +       int error;
> > +
> > +       error = platform_driver_register(&whiskers_ec_driver);
> > +       if (error)
> > +               return error;
> > +
> > +       error = hid_register_driver(&whiskers_hid_driver);
> > +       if (error) {
> > +               platform_driver_unregister(&whiskers_ec_driver);
> > +               return error;
> > +       }
> > +
> > +       return 0;
> > +}
> > +module_init(whiskers_init);
> > +
> > +static void __exit whiskers_exit(void)
> > +{
> > +       hid_unregister_driver(&whiskers_hid_driver);
> > +       platform_driver_unregister(&whiskers_ec_driver);
> > +}
> > +module_exit(whiskers_exit);
> > +
> > +MODULE_LICENSE("GPL");
> > --
> > 2.19.0.605.g01d371f741-goog
> >
>

Thanks.

-- 
Dmitry

^ permalink raw reply

* Re: [PATCH] [v3] HID: add support for Apple Magic Trackpad 2
From: Sean O'Brien @ 2018-10-02 22:39 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: peter.hutterer, marek.wyborski, linux-kernel, Dmitry Torokhov,
	linux-input, rydberg, Jiri Kosina, claudio
In-Reply-To: <CAO-hwJJL7XsNR4iVAdxYUrmB+01aJiZ=2s1b_JemU-=YNDehMQ@mail.gmail.com>

On Mon, Oct 1, 2018 at 1:43 AM Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
>
> [adding Peter, for the libinput question]
>
> On Fri, Sep 21, 2018 at 1:13 AM Sean O'Brien <seobrien@chromium.org> wrote:
> >
> > USB device
> >         Vendor 05ac (Apple)
> >         Device 0265 (Magic Trackpad 2)
> > Bluetooth device
> >         Vendor 004c (Apple)
> >         Device 0265 (Magic Trackpad 2)
> >
> > Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting
> > the device in multi-touch mode.
> >
> > Signed-off-by: Claudio Mettler <claudio@ponyfleisch.ch>
> > Signed-off-by: Marek Wyborski <marek.wyborski@emwesoft.com>
> > Signed-off-by: Sean O'Brien <seobrien@chromium.org>
> > ---
> >
>
> a few nitpcks:
>
> >  drivers/hid/hid-ids.h        |   1 +
> >  drivers/hid/hid-magicmouse.c | 149 +++++++++++++++++++++++++++++++----
> >  2 files changed, 134 insertions(+), 16 deletions(-)
> >
> > diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> > index 5146ee029db4..bb0cd212c7cc 100644
> > --- a/drivers/hid/hid-ids.h
> > +++ b/drivers/hid/hid-ids.h
> > @@ -92,6 +92,7 @@
> >  #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE        0x0304
> >  #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
> >  #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD      0x030e
> > +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2     0x0265
> >  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI      0x020e
> >  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO       0x020f
> >  #define USB_DEVICE_ID_APPLE_GEYSER_ANSI        0x0214
> > diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
> > index b454c4386157..6a3a6c83e509 100644
> > --- a/drivers/hid/hid-magicmouse.c
> > +++ b/drivers/hid/hid-magicmouse.c
> > @@ -54,6 +54,8 @@ module_param(report_undeciphered, bool, 0644);
> >  MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
> >
> >  #define TRACKPAD_REPORT_ID 0x28
> > +#define TRACKPAD2_USB_REPORT_ID 0x02
> > +#define TRACKPAD2_BT_REPORT_ID 0x31
> >  #define MOUSE_REPORT_ID    0x29
> >  #define DOUBLE_REPORT_ID   0xf7
> >  /* These definitions are not precise, but they're close enough.  (Bits
> > @@ -91,6 +93,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
> >  #define TRACKPAD_RES_Y \
> >         ((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
> >
> > +#define TRACKPAD2_DIMENSION_X (float)16000
> > +#define TRACKPAD2_MIN_X -3678
> > +#define TRACKPAD2_MAX_X 3934
> > +#define TRACKPAD2_RES_X \
> > +       ((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100))
> > +#define TRACKPAD2_DIMENSION_Y (float)11490
> > +#define TRACKPAD2_MIN_Y -2478
> > +#define TRACKPAD2_MAX_Y 2587
> > +#define TRACKPAD2_RES_Y \
> > +       ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
> > +
> >  /**
> >   * struct magicmouse_sc - Tracks Magic Mouse-specific data.
> >   * @input: Input device through which we report events.
> > @@ -183,6 +196,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> >  {
> >         struct input_dev *input = msc->input;
> >         int id, x, y, size, orientation, touch_major, touch_minor, state, down;
> > +       int pressure = 0;
> >
> >         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
> >                 id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
> > @@ -194,6 +208,20 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> >                 touch_minor = tdata[4];
> >                 state = tdata[7] & TOUCH_STATE_MASK;
> >                 down = state != TOUCH_STATE_NONE;
> > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               id = tdata[8] & 0xf;
> > +               x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> > +               y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
> > +               size = tdata[6];
> > +               orientation = (tdata[8] >> 5) - 4;
> > +               touch_major = tdata[4];
> > +               touch_minor = tdata[5];
> > +               /* Add to pressure to prevent libraries such as libinput
> > +                * from ignoring low pressure touches
> > +                */
> > +               pressure = tdata[7] + 30;
>
> Peter, can you have a look?
>
> To me, while adding this threshold, you are basically fooling
> userspace, and we might want to come back to the raw values at some
> point.
> If there is a userspace problem, it has to be solved in userspace.
>

I'm fine with removing the offset.  I haven't personally tested using
libinput, but the chromium OS gesture detector I've been using can
handle pressure values of 0.

> > +               state = tdata[3] & 0xC0;
> > +               down = state == 0x80;
> >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> >                 id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
> >                 x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> > @@ -215,7 +243,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> >         /* If requested, emulate a scroll wheel by detecting small
> >          * vertical touch motions.
> >          */
> > -       if (emulate_scroll_wheel) {
> > +       if (emulate_scroll_wheel && (input->id.product !=
> > +                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
> >                 unsigned long now = jiffies;
> >                 int step_x = msc->touches[id].scroll_x - x;
> >                 int step_y = msc->touches[id].scroll_y - y;
> > @@ -269,10 +298,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> >                 input_report_abs(input, ABS_MT_POSITION_X, x);
> >                 input_report_abs(input, ABS_MT_POSITION_Y, y);
> >
> > +               if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
> > +                       input_report_abs(input, ABS_MT_PRESSURE, pressure);
> > +
> >                 if (report_undeciphered) {
> >                         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
> >                                 input_event(input, EV_MSC, MSC_RAW, tdata[7]);
> > -                       else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > +                       else if (input->id.product !=
> > +                                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
> >                                 input_event(input, EV_MSC, MSC_RAW, tdata[8]);
> >                 }
> >         }
> > @@ -287,6 +320,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> >
> >         switch (data[0]) {
> >         case TRACKPAD_REPORT_ID:
> > +       case TRACKPAD2_BT_REPORT_ID:
> >                 /* Expect four bytes of prefix, and N*9 bytes of touch data. */
> >                 if (size < 4 || ((size - 4) % 9) != 0)
> >                         return 0;
> > @@ -301,12 +335,22 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> >                         magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
> >
> >                 clicks = data[1];
> > +               break;
> > +       case TRACKPAD2_USB_REPORT_ID:
> > +               /* Expect twelve bytes of prefix and N*9 bytes of touch data. */
> > +               if (size < 12 || ((size - 12) % 9) != 0)
> > +                       return 0;
> > +               npoints = (size - 12) / 9;
> > +               if (npoints > 15) {
> > +                       hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n",
> > +                                       size);
> > +                       return 0;
> > +               }
> > +               msc->ntouches = 0;
> > +               for (ii = 0; ii < npoints; ii++)
> > +                       magicmouse_emit_touch(msc, ii, data + ii * 9 + 12);
> >
> > -               /* The following bits provide a device specific timestamp. They
> > -                * are unused here.
> > -                *
> > -                * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
> > -                */
>
> Can you keep this comment from the original version? If your touchpad
> doesn't have the timestamp bit, the old ones still have them, and
> having the decoded information might help in the future.
>

Will do.

> Cheers,
> Benjamin
>
> > +               clicks = data[1];
> >                 break;
> >         case MOUSE_REPORT_ID:
> >                 /* Expect six bytes of prefix, and N*8 bytes of touch data. */
> > @@ -352,6 +396,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> >                 magicmouse_emit_buttons(msc, clicks & 3);
> >                 input_report_rel(input, REL_X, x);
> >                 input_report_rel(input, REL_Y, y);
> > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               input_mt_sync_frame(input);
> > +               input_report_key(input, BTN_MOUSE, clicks & 1);
> >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> >                 input_report_key(input, BTN_MOUSE, clicks & 1);
> >                 input_mt_report_pointer_emulation(input, true);
> > @@ -364,6 +411,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> >  static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
> >  {
> >         int error;
> > +       int mt_flags = 0;
> >
> >         __set_bit(EV_KEY, input->evbit);
> >
> > @@ -380,6 +428,22 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> >                         __set_bit(REL_WHEEL, input->relbit);
> >                         __set_bit(REL_HWHEEL, input->relbit);
> >                 }
> > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               /* setting the device name to ensure the same driver settings
> > +                * get loaded, whether connected through bluetooth or USB
> > +                */
> > +               input->name = "Apple Inc. Magic Trackpad 2";
> > +
> > +               __clear_bit(EV_MSC, input->evbit);
> > +               __clear_bit(BTN_0, input->keybit);
> > +               __clear_bit(BTN_RIGHT, input->keybit);
> > +               __clear_bit(BTN_MIDDLE, input->keybit);
> > +               __set_bit(BTN_MOUSE, input->keybit);
> > +               __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> > +               __set_bit(BTN_TOOL_FINGER, input->keybit);
> > +
> > +               mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED |
> > +                               INPUT_MT_TRACK;
> >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> >                 /* input->keybit is initialized with incorrect button info
> >                  * for Magic Trackpad. There really is only one physical
> > @@ -402,14 +466,13 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> >
> >         __set_bit(EV_ABS, input->evbit);
> >
> > -       error = input_mt_init_slots(input, 16, 0);
> > +       error = input_mt_init_slots(input, 16, mt_flags);
> >         if (error)
> >                 return error;
> >         input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
> >                              4, 0);
> >         input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
> >                              4, 0);
> > -       input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> >
> >         /* Note: Touch Y position from the device is inverted relative
> >          * to how pointer motion is reported (and relative to how USB
> > @@ -418,6 +481,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> >          * inverse of the reported Y.
> >          */
> >         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
> > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> >                 input_set_abs_params(input, ABS_MT_POSITION_X,
> >                                      MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
> >                 input_set_abs_params(input, ABS_MT_POSITION_Y,
> > @@ -427,7 +491,25 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> >                                   MOUSE_RES_X);
> >                 input_abs_set_res(input, ABS_MT_POSITION_Y,
> >                                   MOUSE_RES_Y);
> > +       } else if (input->id.product ==  USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               input_set_abs_params(input, ABS_MT_PRESSURE, 0, 283, 0, 0);
> > +               input_set_abs_params(input, ABS_PRESSURE, 0, 283, 0, 0);
> > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
> > +               input_set_abs_params(input, ABS_X, TRACKPAD2_MIN_X,
> > +                                    TRACKPAD2_MAX_X, 0, 0);
> > +               input_set_abs_params(input, ABS_Y, TRACKPAD2_MIN_Y,
> > +                                    TRACKPAD2_MAX_Y, 0, 0);
> > +               input_set_abs_params(input, ABS_MT_POSITION_X,
> > +                                    TRACKPAD2_MIN_X, TRACKPAD2_MAX_X, 0, 0);
> > +               input_set_abs_params(input, ABS_MT_POSITION_Y,
> > +                                    TRACKPAD2_MIN_Y, TRACKPAD2_MAX_Y, 0, 0);
> > +
> > +               input_abs_set_res(input, ABS_X, TRACKPAD2_RES_X);
> > +               input_abs_set_res(input, ABS_Y, TRACKPAD2_RES_Y);
> > +               input_abs_set_res(input, ABS_MT_POSITION_X, TRACKPAD2_RES_X);
> > +               input_abs_set_res(input, ABS_MT_POSITION_Y, TRACKPAD2_RES_Y);
> >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> >                 input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
> >                                      TRACKPAD_MAX_X, 4, 0);
> >                 input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
> > @@ -447,7 +529,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> >
> >         input_set_events_per_packet(input, 60);
> >
> > -       if (report_undeciphered) {
> > +       if (report_undeciphered &&
> > +           input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> >                 __set_bit(EV_MSC, input->evbit);
> >                 __set_bit(MSC_RAW, input->mscbit);
> >         }
> > @@ -465,7 +548,8 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
> >                 msc->input = hi->input;
> >
> >         /* Magic Trackpad does not give relative data after switching to MT */
> > -       if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
> > +       if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
> > +            hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
> >             field->flags & HID_MAIN_ITEM_RELATIVE)
> >                 return -1;
> >
> > @@ -494,11 +578,20 @@ static int magicmouse_input_configured(struct hid_device *hdev,
> >  static int magicmouse_probe(struct hid_device *hdev,
> >         const struct hid_device_id *id)
> >  {
> > -       const u8 feature[] = { 0xd7, 0x01 };
> > +       const u8 *feature;
> > +       const u8 feature_mt[] = { 0xD7, 0x01 };
> > +       const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
> > +       const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
> >         u8 *buf;
> >         struct magicmouse_sc *msc;
> >         struct hid_report *report;
> >         int ret;
> > +       int feature_size;
> > +
> > +       if (id->vendor == USB_VENDOR_ID_APPLE &&
> > +           id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
> > +           hdev->type != HID_TYPE_USBMOUSE)
> > +               return 0;
> >
> >         msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
> >         if (msc == NULL) {
> > @@ -532,7 +625,14 @@ static int magicmouse_probe(struct hid_device *hdev,
> >         if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
> >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> >                         MOUSE_REPORT_ID, 0);
> > -       else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > +       else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               if (id->vendor == BT_VENDOR_ID_APPLE)
> > +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> > +                               TRACKPAD2_BT_REPORT_ID, 0);
> > +               else /* USB_VENDOR_ID_APPLE */
> > +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> > +                               TRACKPAD2_USB_REPORT_ID, 0);
> > +       } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> >                         TRACKPAD_REPORT_ID, 0);
> >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> > @@ -546,7 +646,20 @@ static int magicmouse_probe(struct hid_device *hdev,
> >         }
> >         report->size = 6;
> >
> > -       buf = kmemdup(feature, sizeof(feature), GFP_KERNEL);
> > +       if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > +               if (id->vendor == BT_VENDOR_ID_APPLE) {
> > +                       feature_size = sizeof(feature_mt_trackpad2_bt);
> > +                       feature = feature_mt_trackpad2_bt;
> > +               } else { /* USB_VENDOR_ID_APPLE */
> > +                       feature_size = sizeof(feature_mt_trackpad2_usb);
> > +                       feature = feature_mt_trackpad2_usb;
> > +               }
> > +       } else {
> > +               feature_size = sizeof(feature_mt);
> > +               feature = feature_mt;
> > +       }
> > +
> > +       buf = kmemdup(feature, feature_size, GFP_KERNEL);
> >         if (!buf) {
> >                 ret = -ENOMEM;
> >                 goto err_stop_hw;
> > @@ -560,10 +673,10 @@ static int magicmouse_probe(struct hid_device *hdev,
> >          * but there seems to be no other way of switching the mode.
> >          * Thus the super-ugly hacky success check below.
> >          */
> > -       ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature),
> > +       ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
> >                                 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> >         kfree(buf);
> > -       if (ret != -EIO && ret != sizeof(feature)) {
> > +       if (ret != -EIO && ret != feature_size) {
> >                 hid_err(hdev, "unable to request touch data (%d)\n", ret);
> >                 goto err_stop_hw;
> >         }
> > @@ -579,6 +692,10 @@ static const struct hid_device_id magic_mice[] = {
> >                 USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
> >         { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
> >                 USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
> > +       { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
> > +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
> > +       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
> > +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
> >         { }
> >  };
> >  MODULE_DEVICE_TABLE(hid, magic_mice);
> > --
> > 2.19.0.444.g18242da7ef-goog
> >

^ permalink raw reply

* [PATCH] [v4] HID: add support for Apple Magic Trackpad 2
From: Sean O'Brien @ 2018-10-02 22:53 UTC (permalink / raw)
  To: Benjamin Tissoires
  Cc: Sean O'Brien, Marek Wyborski, linux-kernel, Dmitry Torokhov,
	linux-input, Henrik Rydberg, Jiri Kosina, Claudio Mettler

USB device
        Vendor 05ac (Apple)
        Device 0265 (Magic Trackpad 2)
Bluetooth device
        Vendor 004c (Apple)
        Device 0265 (Magic Trackpad 2)

Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting
the device in multi-touch mode.

Signed-off-by: Claudio Mettler <claudio@ponyfleisch.ch>
Signed-off-by: Marek Wyborski <marek.wyborski@emwesoft.com>
Signed-off-by: Sean O'Brien <seobrien@chromium.org>
---

 drivers/hid/hid-ids.h        |   1 +
 drivers/hid/hid-magicmouse.c | 142 ++++++++++++++++++++++++++++++++---
 2 files changed, 132 insertions(+), 11 deletions(-)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 5146ee029db4..bb0cd212c7cc 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -92,6 +92,7 @@
 #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE	0x030d
 #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD	0x030e
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2	0x0265
 #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI	0x020e
 #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO	0x020f
 #define USB_DEVICE_ID_APPLE_GEYSER_ANSI	0x0214
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index b454c4386157..1d5ea678d268 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -54,6 +54,8 @@ module_param(report_undeciphered, bool, 0644);
 MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
 
 #define TRACKPAD_REPORT_ID 0x28
+#define TRACKPAD2_USB_REPORT_ID 0x02
+#define TRACKPAD2_BT_REPORT_ID 0x31
 #define MOUSE_REPORT_ID    0x29
 #define DOUBLE_REPORT_ID   0xf7
 /* These definitions are not precise, but they're close enough.  (Bits
@@ -91,6 +93,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define TRACKPAD_RES_Y \
 	((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
 
+#define TRACKPAD2_DIMENSION_X (float)16000
+#define TRACKPAD2_MIN_X -3678
+#define TRACKPAD2_MAX_X 3934
+#define TRACKPAD2_RES_X \
+	((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100))
+#define TRACKPAD2_DIMENSION_Y (float)11490
+#define TRACKPAD2_MIN_Y -2478
+#define TRACKPAD2_MAX_Y 2587
+#define TRACKPAD2_RES_Y \
+	((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
+
 /**
  * struct magicmouse_sc - Tracks Magic Mouse-specific data.
  * @input: Input device through which we report events.
@@ -183,6 +196,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
 {
 	struct input_dev *input = msc->input;
 	int id, x, y, size, orientation, touch_major, touch_minor, state, down;
+	int pressure = 0;
 
 	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
 		id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
@@ -194,6 +208,17 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
 		touch_minor = tdata[4];
 		state = tdata[7] & TOUCH_STATE_MASK;
 		down = state != TOUCH_STATE_NONE;
+	} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		id = tdata[8] & 0xf;
+		x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
+		y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
+		size = tdata[6];
+		orientation = (tdata[8] >> 5) - 4;
+		touch_major = tdata[4];
+		touch_minor = tdata[5];
+		pressure = tdata[7];
+		state = tdata[3] & 0xC0;
+		down = state == 0x80;
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
 		x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
@@ -215,7 +240,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
 	/* If requested, emulate a scroll wheel by detecting small
 	 * vertical touch motions.
 	 */
-	if (emulate_scroll_wheel) {
+	if (emulate_scroll_wheel && (input->id.product !=
+			USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
 		unsigned long now = jiffies;
 		int step_x = msc->touches[id].scroll_x - x;
 		int step_y = msc->touches[id].scroll_y - y;
@@ -269,10 +295,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
 		input_report_abs(input, ABS_MT_POSITION_X, x);
 		input_report_abs(input, ABS_MT_POSITION_Y, y);
 
+		if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
+			input_report_abs(input, ABS_MT_PRESSURE, pressure);
+
 		if (report_undeciphered) {
 			if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
 				input_event(input, EV_MSC, MSC_RAW, tdata[7]);
-			else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+			else if (input->id.product !=
+					USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
 				input_event(input, EV_MSC, MSC_RAW, tdata[8]);
 		}
 	}
@@ -287,6 +317,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
 
 	switch (data[0]) {
 	case TRACKPAD_REPORT_ID:
+	case TRACKPAD2_BT_REPORT_ID:
 		/* Expect four bytes of prefix, and N*9 bytes of touch data. */
 		if (size < 4 || ((size - 4) % 9) != 0)
 			return 0;
@@ -308,6 +339,22 @@ static int magicmouse_raw_event(struct hid_device *hdev,
 		 * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
 		 */
 		break;
+	case TRACKPAD2_USB_REPORT_ID:
+		/* Expect twelve bytes of prefix and N*9 bytes of touch data. */
+		if (size < 12 || ((size - 12) % 9) != 0)
+			return 0;
+		npoints = (size - 12) / 9;
+		if (npoints > 15) {
+			hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n",
+					size);
+			return 0;
+		}
+		msc->ntouches = 0;
+		for (ii = 0; ii < npoints; ii++)
+			magicmouse_emit_touch(msc, ii, data + ii * 9 + 12);
+
+		clicks = data[1];
+		break;
 	case MOUSE_REPORT_ID:
 		/* Expect six bytes of prefix, and N*8 bytes of touch data. */
 		if (size < 6 || ((size - 6) % 8) != 0)
@@ -352,6 +399,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
 		magicmouse_emit_buttons(msc, clicks & 3);
 		input_report_rel(input, REL_X, x);
 		input_report_rel(input, REL_Y, y);
+	} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		input_mt_sync_frame(input);
+		input_report_key(input, BTN_MOUSE, clicks & 1);
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		input_report_key(input, BTN_MOUSE, clicks & 1);
 		input_mt_report_pointer_emulation(input, true);
@@ -364,6 +414,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
 static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
 {
 	int error;
+	int mt_flags = 0;
 
 	__set_bit(EV_KEY, input->evbit);
 
@@ -380,6 +431,22 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 			__set_bit(REL_WHEEL, input->relbit);
 			__set_bit(REL_HWHEEL, input->relbit);
 		}
+	} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		/* setting the device name to ensure the same driver settings
+		 * get loaded, whether connected through bluetooth or USB
+		 */
+		input->name = "Apple Inc. Magic Trackpad 2";
+
+		__clear_bit(EV_MSC, input->evbit);
+		__clear_bit(BTN_0, input->keybit);
+		__clear_bit(BTN_RIGHT, input->keybit);
+		__clear_bit(BTN_MIDDLE, input->keybit);
+		__set_bit(BTN_MOUSE, input->keybit);
+		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+		__set_bit(BTN_TOOL_FINGER, input->keybit);
+
+		mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED |
+				INPUT_MT_TRACK;
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		/* input->keybit is initialized with incorrect button info
 		 * for Magic Trackpad. There really is only one physical
@@ -402,14 +469,13 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 
 	__set_bit(EV_ABS, input->evbit);
 
-	error = input_mt_init_slots(input, 16, 0);
+	error = input_mt_init_slots(input, 16, mt_flags);
 	if (error)
 		return error;
 	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
 			     4, 0);
 	input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
 			     4, 0);
-	input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
 
 	/* Note: Touch Y position from the device is inverted relative
 	 * to how pointer motion is reported (and relative to how USB
@@ -418,6 +484,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 	 * inverse of the reported Y.
 	 */
 	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+		input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
 		input_set_abs_params(input, ABS_MT_POSITION_X,
 				     MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
 		input_set_abs_params(input, ABS_MT_POSITION_Y,
@@ -427,7 +494,25 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 				  MOUSE_RES_X);
 		input_abs_set_res(input, ABS_MT_POSITION_Y,
 				  MOUSE_RES_Y);
+	} else if (input->id.product ==  USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0);
+		input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0);
+		input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
+		input_set_abs_params(input, ABS_X, TRACKPAD2_MIN_X,
+				     TRACKPAD2_MAX_X, 0, 0);
+		input_set_abs_params(input, ABS_Y, TRACKPAD2_MIN_Y,
+				     TRACKPAD2_MAX_Y, 0, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_X,
+				     TRACKPAD2_MIN_X, TRACKPAD2_MAX_X, 0, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_Y,
+				     TRACKPAD2_MIN_Y, TRACKPAD2_MAX_Y, 0, 0);
+
+		input_abs_set_res(input, ABS_X, TRACKPAD2_RES_X);
+		input_abs_set_res(input, ABS_Y, TRACKPAD2_RES_Y);
+		input_abs_set_res(input, ABS_MT_POSITION_X, TRACKPAD2_RES_X);
+		input_abs_set_res(input, ABS_MT_POSITION_Y, TRACKPAD2_RES_Y);
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
 		input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
 				     TRACKPAD_MAX_X, 4, 0);
 		input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
@@ -447,7 +532,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 
 	input_set_events_per_packet(input, 60);
 
-	if (report_undeciphered) {
+	if (report_undeciphered &&
+	    input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
 		__set_bit(EV_MSC, input->evbit);
 		__set_bit(MSC_RAW, input->mscbit);
 	}
@@ -465,7 +551,8 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
 		msc->input = hi->input;
 
 	/* Magic Trackpad does not give relative data after switching to MT */
-	if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
+	if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
+	     hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
 	    field->flags & HID_MAIN_ITEM_RELATIVE)
 		return -1;
 
@@ -494,11 +581,20 @@ static int magicmouse_input_configured(struct hid_device *hdev,
 static int magicmouse_probe(struct hid_device *hdev,
 	const struct hid_device_id *id)
 {
-	const u8 feature[] = { 0xd7, 0x01 };
+	const u8 *feature;
+	const u8 feature_mt[] = { 0xD7, 0x01 };
+	const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
+	const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
 	u8 *buf;
 	struct magicmouse_sc *msc;
 	struct hid_report *report;
 	int ret;
+	int feature_size;
+
+	if (id->vendor == USB_VENDOR_ID_APPLE &&
+	    id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
+	    hdev->type != HID_TYPE_USBMOUSE)
+		return 0;
 
 	msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
 	if (msc == NULL) {
@@ -532,7 +628,14 @@ static int magicmouse_probe(struct hid_device *hdev,
 	if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
 			MOUSE_REPORT_ID, 0);
-	else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+	else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		if (id->vendor == BT_VENDOR_ID_APPLE)
+			report = hid_register_report(hdev, HID_INPUT_REPORT,
+				TRACKPAD2_BT_REPORT_ID, 0);
+		else /* USB_VENDOR_ID_APPLE */
+			report = hid_register_report(hdev, HID_INPUT_REPORT,
+				TRACKPAD2_USB_REPORT_ID, 0);
+	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
 			TRACKPAD_REPORT_ID, 0);
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
@@ -546,7 +649,20 @@ static int magicmouse_probe(struct hid_device *hdev,
 	}
 	report->size = 6;
 
-	buf = kmemdup(feature, sizeof(feature), GFP_KERNEL);
+	if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		if (id->vendor == BT_VENDOR_ID_APPLE) {
+			feature_size = sizeof(feature_mt_trackpad2_bt);
+			feature = feature_mt_trackpad2_bt;
+		} else { /* USB_VENDOR_ID_APPLE */
+			feature_size = sizeof(feature_mt_trackpad2_usb);
+			feature = feature_mt_trackpad2_usb;
+		}
+	} else {
+		feature_size = sizeof(feature_mt);
+		feature = feature_mt;
+	}
+
+	buf = kmemdup(feature, feature_size, GFP_KERNEL);
 	if (!buf) {
 		ret = -ENOMEM;
 		goto err_stop_hw;
@@ -560,10 +676,10 @@ static int magicmouse_probe(struct hid_device *hdev,
 	 * but there seems to be no other way of switching the mode.
 	 * Thus the super-ugly hacky success check below.
 	 */
-	ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature),
+	ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
 				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
 	kfree(buf);
-	if (ret != -EIO && ret != sizeof(feature)) {
+	if (ret != -EIO && ret != feature_size) {
 		hid_err(hdev, "unable to request touch data (%d)\n", ret);
 		goto err_stop_hw;
 	}
@@ -579,6 +695,10 @@ static const struct hid_device_id magic_mice[] = {
 		USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
+	{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, magic_mice);
-- 
2.19.0.605.g01d371f741-goog

^ permalink raw reply related

* Re: [PATCH] [v3] HID: add support for Apple Magic Trackpad 2
From: Peter Hutterer @ 2018-10-03  0:04 UTC (permalink / raw)
  To: Sean O'Brien
  Cc: Benjamin Tissoires, peter.hutterer, marek.wyborski, linux-kernel,
	Dmitry Torokhov, linux-input, rydberg, Jiri Kosina, claudio
In-Reply-To: <CAOOzhkp6ChopAXxDFcbz1sT17EKTGysF5hdkMkM50LGdXcnyaQ@mail.gmail.com>

On Tue, Oct 02, 2018 at 03:39:31PM -0700, Sean O'Brien wrote:
> On Mon, Oct 1, 2018 at 1:43 AM Benjamin Tissoires
> <benjamin.tissoires@redhat.com> wrote:
> >
> > [adding Peter, for the libinput question]
> >
> > On Fri, Sep 21, 2018 at 1:13 AM Sean O'Brien <seobrien@chromium.org> wrote:
> > >
> > > USB device
> > >         Vendor 05ac (Apple)
> > >         Device 0265 (Magic Trackpad 2)
> > > Bluetooth device
> > >         Vendor 004c (Apple)
> > >         Device 0265 (Magic Trackpad 2)
> > >
> > > Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting
> > > the device in multi-touch mode.
> > >
> > > Signed-off-by: Claudio Mettler <claudio@ponyfleisch.ch>
> > > Signed-off-by: Marek Wyborski <marek.wyborski@emwesoft.com>
> > > Signed-off-by: Sean O'Brien <seobrien@chromium.org>
> > > ---
> > >
> >
> > a few nitpcks:
> >
> > >  drivers/hid/hid-ids.h        |   1 +
> > >  drivers/hid/hid-magicmouse.c | 149 +++++++++++++++++++++++++++++++----
> > >  2 files changed, 134 insertions(+), 16 deletions(-)
> > >
> > > diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> > > index 5146ee029db4..bb0cd212c7cc 100644
> > > --- a/drivers/hid/hid-ids.h
> > > +++ b/drivers/hid/hid-ids.h
> > > @@ -92,6 +92,7 @@
> > >  #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE        0x0304
> > >  #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
> > >  #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD      0x030e
> > > +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2     0x0265
> > >  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI      0x020e
> > >  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO       0x020f
> > >  #define USB_DEVICE_ID_APPLE_GEYSER_ANSI        0x0214
> > > diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
> > > index b454c4386157..6a3a6c83e509 100644
> > > --- a/drivers/hid/hid-magicmouse.c
> > > +++ b/drivers/hid/hid-magicmouse.c
> > > @@ -54,6 +54,8 @@ module_param(report_undeciphered, bool, 0644);
> > >  MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
> > >
> > >  #define TRACKPAD_REPORT_ID 0x28
> > > +#define TRACKPAD2_USB_REPORT_ID 0x02
> > > +#define TRACKPAD2_BT_REPORT_ID 0x31
> > >  #define MOUSE_REPORT_ID    0x29
> > >  #define DOUBLE_REPORT_ID   0xf7
> > >  /* These definitions are not precise, but they're close enough.  (Bits
> > > @@ -91,6 +93,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
> > >  #define TRACKPAD_RES_Y \
> > >         ((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
> > >
> > > +#define TRACKPAD2_DIMENSION_X (float)16000
> > > +#define TRACKPAD2_MIN_X -3678
> > > +#define TRACKPAD2_MAX_X 3934
> > > +#define TRACKPAD2_RES_X \
> > > +       ((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100))
> > > +#define TRACKPAD2_DIMENSION_Y (float)11490
> > > +#define TRACKPAD2_MIN_Y -2478
> > > +#define TRACKPAD2_MAX_Y 2587
> > > +#define TRACKPAD2_RES_Y \
> > > +       ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
> > > +
> > >  /**
> > >   * struct magicmouse_sc - Tracks Magic Mouse-specific data.
> > >   * @input: Input device through which we report events.
> > > @@ -183,6 +196,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> > >  {
> > >         struct input_dev *input = msc->input;
> > >         int id, x, y, size, orientation, touch_major, touch_minor, state, down;
> > > +       int pressure = 0;
> > >
> > >         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
> > >                 id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
> > > @@ -194,6 +208,20 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> > >                 touch_minor = tdata[4];
> > >                 state = tdata[7] & TOUCH_STATE_MASK;
> > >                 down = state != TOUCH_STATE_NONE;
> > > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               id = tdata[8] & 0xf;
> > > +               x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> > > +               y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
> > > +               size = tdata[6];
> > > +               orientation = (tdata[8] >> 5) - 4;
> > > +               touch_major = tdata[4];
> > > +               touch_minor = tdata[5];
> > > +               /* Add to pressure to prevent libraries such as libinput
> > > +                * from ignoring low pressure touches
> > > +                */
> > > +               pressure = tdata[7] + 30;
> >
> > Peter, can you have a look?
> >
> > To me, while adding this threshold, you are basically fooling
> > userspace, and we might want to come back to the raw values at some
> > point.
> > If there is a userspace problem, it has to be solved in userspace.
> >
> 
> I'm fine with removing the offset.  I haven't personally tested using
> libinput, but the chromium OS gesture detector I've been using can
> handle pressure values of 0.

libinput uses touch major/minor but only if a threshold is defined for
that device in the quirks files (matches on VID/PID/name/...).
https://gitlab.freedesktop.org/libinput/libinput/quirks/

No existing quirk will match for this device so libinput falls back to using
pressure instead. That too can be defined per-device in the quirks but for
this VID/PID it isn't so we fall back to the default thresholds guessed
based on the pressure range (12% of the range, that's where the 30 comes
from).

Adding a quirk for this device with the required major/minor threshold (or
pressure if that's more reliable) should be enough to make it work, it
definitely doesn't require a kernel workaround.

fwiw, testing against libinput is as simple as building the git repo with
meson and running:
    sudo ./builddir/libinput-debug-events 
or if you want some graphical debugging (requires gtk3-devel)
    sudo ./builddir/libinput-debug-gui 

Cheers,
   Peter


> 
> > > +               state = tdata[3] & 0xC0;
> > > +               down = state == 0x80;
> > >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > >                 id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
> > >                 x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> > > @@ -215,7 +243,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> > >         /* If requested, emulate a scroll wheel by detecting small
> > >          * vertical touch motions.
> > >          */
> > > -       if (emulate_scroll_wheel) {
> > > +       if (emulate_scroll_wheel && (input->id.product !=
> > > +                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
> > >                 unsigned long now = jiffies;
> > >                 int step_x = msc->touches[id].scroll_x - x;
> > >                 int step_y = msc->touches[id].scroll_y - y;
> > > @@ -269,10 +298,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
> > >                 input_report_abs(input, ABS_MT_POSITION_X, x);
> > >                 input_report_abs(input, ABS_MT_POSITION_Y, y);
> > >
> > > +               if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
> > > +                       input_report_abs(input, ABS_MT_PRESSURE, pressure);
> > > +
> > >                 if (report_undeciphered) {
> > >                         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
> > >                                 input_event(input, EV_MSC, MSC_RAW, tdata[7]);
> > > -                       else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > > +                       else if (input->id.product !=
> > > +                                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
> > >                                 input_event(input, EV_MSC, MSC_RAW, tdata[8]);
> > >                 }
> > >         }
> > > @@ -287,6 +320,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> > >
> > >         switch (data[0]) {
> > >         case TRACKPAD_REPORT_ID:
> > > +       case TRACKPAD2_BT_REPORT_ID:
> > >                 /* Expect four bytes of prefix, and N*9 bytes of touch data. */
> > >                 if (size < 4 || ((size - 4) % 9) != 0)
> > >                         return 0;
> > > @@ -301,12 +335,22 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> > >                         magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
> > >
> > >                 clicks = data[1];
> > > +               break;
> > > +       case TRACKPAD2_USB_REPORT_ID:
> > > +               /* Expect twelve bytes of prefix and N*9 bytes of touch data. */
> > > +               if (size < 12 || ((size - 12) % 9) != 0)
> > > +                       return 0;
> > > +               npoints = (size - 12) / 9;
> > > +               if (npoints > 15) {
> > > +                       hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n",
> > > +                                       size);
> > > +                       return 0;
> > > +               }
> > > +               msc->ntouches = 0;
> > > +               for (ii = 0; ii < npoints; ii++)
> > > +                       magicmouse_emit_touch(msc, ii, data + ii * 9 + 12);
> > >
> > > -               /* The following bits provide a device specific timestamp. They
> > > -                * are unused here.
> > > -                *
> > > -                * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
> > > -                */
> >
> > Can you keep this comment from the original version? If your touchpad
> > doesn't have the timestamp bit, the old ones still have them, and
> > having the decoded information might help in the future.
> >
> 
> Will do.
> 
> > Cheers,
> > Benjamin
> >
> > > +               clicks = data[1];
> > >                 break;
> > >         case MOUSE_REPORT_ID:
> > >                 /* Expect six bytes of prefix, and N*8 bytes of touch data. */
> > > @@ -352,6 +396,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> > >                 magicmouse_emit_buttons(msc, clicks & 3);
> > >                 input_report_rel(input, REL_X, x);
> > >                 input_report_rel(input, REL_Y, y);
> > > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               input_mt_sync_frame(input);
> > > +               input_report_key(input, BTN_MOUSE, clicks & 1);
> > >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > >                 input_report_key(input, BTN_MOUSE, clicks & 1);
> > >                 input_mt_report_pointer_emulation(input, true);
> > > @@ -364,6 +411,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
> > >  static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
> > >  {
> > >         int error;
> > > +       int mt_flags = 0;
> > >
> > >         __set_bit(EV_KEY, input->evbit);
> > >
> > > @@ -380,6 +428,22 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> > >                         __set_bit(REL_WHEEL, input->relbit);
> > >                         __set_bit(REL_HWHEEL, input->relbit);
> > >                 }
> > > +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               /* setting the device name to ensure the same driver settings
> > > +                * get loaded, whether connected through bluetooth or USB
> > > +                */
> > > +               input->name = "Apple Inc. Magic Trackpad 2";
> > > +
> > > +               __clear_bit(EV_MSC, input->evbit);
> > > +               __clear_bit(BTN_0, input->keybit);
> > > +               __clear_bit(BTN_RIGHT, input->keybit);
> > > +               __clear_bit(BTN_MIDDLE, input->keybit);
> > > +               __set_bit(BTN_MOUSE, input->keybit);
> > > +               __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> > > +               __set_bit(BTN_TOOL_FINGER, input->keybit);
> > > +
> > > +               mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED |
> > > +                               INPUT_MT_TRACK;
> > >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > >                 /* input->keybit is initialized with incorrect button info
> > >                  * for Magic Trackpad. There really is only one physical
> > > @@ -402,14 +466,13 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> > >
> > >         __set_bit(EV_ABS, input->evbit);
> > >
> > > -       error = input_mt_init_slots(input, 16, 0);
> > > +       error = input_mt_init_slots(input, 16, mt_flags);
> > >         if (error)
> > >                 return error;
> > >         input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
> > >                              4, 0);
> > >         input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
> > >                              4, 0);
> > > -       input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> > >
> > >         /* Note: Touch Y position from the device is inverted relative
> > >          * to how pointer motion is reported (and relative to how USB
> > > @@ -418,6 +481,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> > >          * inverse of the reported Y.
> > >          */
> > >         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
> > > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> > >                 input_set_abs_params(input, ABS_MT_POSITION_X,
> > >                                      MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
> > >                 input_set_abs_params(input, ABS_MT_POSITION_Y,
> > > @@ -427,7 +491,25 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> > >                                   MOUSE_RES_X);
> > >                 input_abs_set_res(input, ABS_MT_POSITION_Y,
> > >                                   MOUSE_RES_Y);
> > > +       } else if (input->id.product ==  USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               input_set_abs_params(input, ABS_MT_PRESSURE, 0, 283, 0, 0);
> > > +               input_set_abs_params(input, ABS_PRESSURE, 0, 283, 0, 0);
> > > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
> > > +               input_set_abs_params(input, ABS_X, TRACKPAD2_MIN_X,
> > > +                                    TRACKPAD2_MAX_X, 0, 0);
> > > +               input_set_abs_params(input, ABS_Y, TRACKPAD2_MIN_Y,
> > > +                                    TRACKPAD2_MAX_Y, 0, 0);
> > > +               input_set_abs_params(input, ABS_MT_POSITION_X,
> > > +                                    TRACKPAD2_MIN_X, TRACKPAD2_MAX_X, 0, 0);
> > > +               input_set_abs_params(input, ABS_MT_POSITION_Y,
> > > +                                    TRACKPAD2_MIN_Y, TRACKPAD2_MAX_Y, 0, 0);
> > > +
> > > +               input_abs_set_res(input, ABS_X, TRACKPAD2_RES_X);
> > > +               input_abs_set_res(input, ABS_Y, TRACKPAD2_RES_Y);
> > > +               input_abs_set_res(input, ABS_MT_POSITION_X, TRACKPAD2_RES_X);
> > > +               input_abs_set_res(input, ABS_MT_POSITION_Y, TRACKPAD2_RES_Y);
> > >         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > > +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
> > >                 input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
> > >                                      TRACKPAD_MAX_X, 4, 0);
> > >                 input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
> > > @@ -447,7 +529,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
> > >
> > >         input_set_events_per_packet(input, 60);
> > >
> > > -       if (report_undeciphered) {
> > > +       if (report_undeciphered &&
> > > +           input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > >                 __set_bit(EV_MSC, input->evbit);
> > >                 __set_bit(MSC_RAW, input->mscbit);
> > >         }
> > > @@ -465,7 +548,8 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
> > >                 msc->input = hi->input;
> > >
> > >         /* Magic Trackpad does not give relative data after switching to MT */
> > > -       if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
> > > +       if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
> > > +            hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
> > >             field->flags & HID_MAIN_ITEM_RELATIVE)
> > >                 return -1;
> > >
> > > @@ -494,11 +578,20 @@ static int magicmouse_input_configured(struct hid_device *hdev,
> > >  static int magicmouse_probe(struct hid_device *hdev,
> > >         const struct hid_device_id *id)
> > >  {
> > > -       const u8 feature[] = { 0xd7, 0x01 };
> > > +       const u8 *feature;
> > > +       const u8 feature_mt[] = { 0xD7, 0x01 };
> > > +       const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
> > > +       const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
> > >         u8 *buf;
> > >         struct magicmouse_sc *msc;
> > >         struct hid_report *report;
> > >         int ret;
> > > +       int feature_size;
> > > +
> > > +       if (id->vendor == USB_VENDOR_ID_APPLE &&
> > > +           id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
> > > +           hdev->type != HID_TYPE_USBMOUSE)
> > > +               return 0;
> > >
> > >         msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
> > >         if (msc == NULL) {
> > > @@ -532,7 +625,14 @@ static int magicmouse_probe(struct hid_device *hdev,
> > >         if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
> > >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> > >                         MOUSE_REPORT_ID, 0);
> > > -       else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > > +       else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               if (id->vendor == BT_VENDOR_ID_APPLE)
> > > +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> > > +                               TRACKPAD2_BT_REPORT_ID, 0);
> > > +               else /* USB_VENDOR_ID_APPLE */
> > > +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> > > +                               TRACKPAD2_USB_REPORT_ID, 0);
> > > +       } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> > >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> > >                         TRACKPAD_REPORT_ID, 0);
> > >                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> > > @@ -546,7 +646,20 @@ static int magicmouse_probe(struct hid_device *hdev,
> > >         }
> > >         report->size = 6;
> > >
> > > -       buf = kmemdup(feature, sizeof(feature), GFP_KERNEL);
> > > +       if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> > > +               if (id->vendor == BT_VENDOR_ID_APPLE) {
> > > +                       feature_size = sizeof(feature_mt_trackpad2_bt);
> > > +                       feature = feature_mt_trackpad2_bt;
> > > +               } else { /* USB_VENDOR_ID_APPLE */
> > > +                       feature_size = sizeof(feature_mt_trackpad2_usb);
> > > +                       feature = feature_mt_trackpad2_usb;
> > > +               }
> > > +       } else {
> > > +               feature_size = sizeof(feature_mt);
> > > +               feature = feature_mt;
> > > +       }
> > > +
> > > +       buf = kmemdup(feature, feature_size, GFP_KERNEL);
> > >         if (!buf) {
> > >                 ret = -ENOMEM;
> > >                 goto err_stop_hw;
> > > @@ -560,10 +673,10 @@ static int magicmouse_probe(struct hid_device *hdev,
> > >          * but there seems to be no other way of switching the mode.
> > >          * Thus the super-ugly hacky success check below.
> > >          */
> > > -       ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature),
> > > +       ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
> > >                                 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
> > >         kfree(buf);
> > > -       if (ret != -EIO && ret != sizeof(feature)) {
> > > +       if (ret != -EIO && ret != feature_size) {
> > >                 hid_err(hdev, "unable to request touch data (%d)\n", ret);
> > >                 goto err_stop_hw;
> > >         }
> > > @@ -579,6 +692,10 @@ static const struct hid_device_id magic_mice[] = {
> > >                 USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
> > >         { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
> > >                 USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
> > > +       { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
> > > +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
> > > +       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
> > > +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
> > >         { }
> > >  };
> > >  MODULE_DEVICE_TABLE(hid, magic_mice);
> > > --
> > > 2.19.0.444.g18242da7ef-goog
> > >

^ permalink raw reply

* Re: [PATCH] [v4] HID: add support for Apple Magic Trackpad 2
From: Benjamin Tissoires @ 2018-10-03  7:38 UTC (permalink / raw)
  To: seobrien
  Cc: marek.wyborski, lkml, Dmitry Torokhov, open list:HID CORE LAYER,
	Henrik Rydberg, Jiri Kosina, claudio
In-Reply-To: <20181002225337.7349-1-seobrien@chromium.org>

On Wed, Oct 3, 2018 at 12:53 AM Sean O'Brien <seobrien@chromium.org> wrote:
>
> USB device
>         Vendor 05ac (Apple)
>         Device 0265 (Magic Trackpad 2)
> Bluetooth device
>         Vendor 004c (Apple)
>         Device 0265 (Magic Trackpad 2)
>
> Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting
> the device in multi-touch mode.
>
> Signed-off-by: Claudio Mettler <claudio@ponyfleisch.ch>
> Signed-off-by: Marek Wyborski <marek.wyborski@emwesoft.com>
> Signed-off-by: Sean O'Brien <seobrien@chromium.org>
> ---

Thanks for the quick respin.

This version is now good and looks like the rest of the code (I wish
we had a better handling than just parsing everything like we do here,
sigh).

Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>

Cheers,
Benjamin

>
>  drivers/hid/hid-ids.h        |   1 +
>  drivers/hid/hid-magicmouse.c | 142 ++++++++++++++++++++++++++++++++---
>  2 files changed, 132 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 5146ee029db4..bb0cd212c7cc 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -92,6 +92,7 @@
>  #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE        0x0304
>  #define USB_DEVICE_ID_APPLE_MAGICMOUSE 0x030d
>  #define USB_DEVICE_ID_APPLE_MAGICTRACKPAD      0x030e
> +#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2     0x0265
>  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI      0x020e
>  #define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO       0x020f
>  #define USB_DEVICE_ID_APPLE_GEYSER_ANSI        0x0214
> diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
> index b454c4386157..1d5ea678d268 100644
> --- a/drivers/hid/hid-magicmouse.c
> +++ b/drivers/hid/hid-magicmouse.c
> @@ -54,6 +54,8 @@ module_param(report_undeciphered, bool, 0644);
>  MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
>
>  #define TRACKPAD_REPORT_ID 0x28
> +#define TRACKPAD2_USB_REPORT_ID 0x02
> +#define TRACKPAD2_BT_REPORT_ID 0x31
>  #define MOUSE_REPORT_ID    0x29
>  #define DOUBLE_REPORT_ID   0xf7
>  /* These definitions are not precise, but they're close enough.  (Bits
> @@ -91,6 +93,17 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
>  #define TRACKPAD_RES_Y \
>         ((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
>
> +#define TRACKPAD2_DIMENSION_X (float)16000
> +#define TRACKPAD2_MIN_X -3678
> +#define TRACKPAD2_MAX_X 3934
> +#define TRACKPAD2_RES_X \
> +       ((TRACKPAD2_MAX_X - TRACKPAD2_MIN_X) / (TRACKPAD2_DIMENSION_X / 100))
> +#define TRACKPAD2_DIMENSION_Y (float)11490
> +#define TRACKPAD2_MIN_Y -2478
> +#define TRACKPAD2_MAX_Y 2587
> +#define TRACKPAD2_RES_Y \
> +       ((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
> +
>  /**
>   * struct magicmouse_sc - Tracks Magic Mouse-specific data.
>   * @input: Input device through which we report events.
> @@ -183,6 +196,7 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
>  {
>         struct input_dev *input = msc->input;
>         int id, x, y, size, orientation, touch_major, touch_minor, state, down;
> +       int pressure = 0;
>
>         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
>                 id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
> @@ -194,6 +208,17 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
>                 touch_minor = tdata[4];
>                 state = tdata[7] & TOUCH_STATE_MASK;
>                 down = state != TOUCH_STATE_NONE;
> +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               id = tdata[8] & 0xf;
> +               x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> +               y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
> +               size = tdata[6];
> +               orientation = (tdata[8] >> 5) - 4;
> +               touch_major = tdata[4];
> +               touch_minor = tdata[5];
> +               pressure = tdata[7];
> +               state = tdata[3] & 0xC0;
> +               down = state == 0x80;
>         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
>                 id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
>                 x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
> @@ -215,7 +240,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
>         /* If requested, emulate a scroll wheel by detecting small
>          * vertical touch motions.
>          */
> -       if (emulate_scroll_wheel) {
> +       if (emulate_scroll_wheel && (input->id.product !=
> +                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
>                 unsigned long now = jiffies;
>                 int step_x = msc->touches[id].scroll_x - x;
>                 int step_y = msc->touches[id].scroll_y - y;
> @@ -269,10 +295,14 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
>                 input_report_abs(input, ABS_MT_POSITION_X, x);
>                 input_report_abs(input, ABS_MT_POSITION_Y, y);
>
> +               if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
> +                       input_report_abs(input, ABS_MT_PRESSURE, pressure);
> +
>                 if (report_undeciphered) {
>                         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
>                                 input_event(input, EV_MSC, MSC_RAW, tdata[7]);
> -                       else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> +                       else if (input->id.product !=
> +                                       USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
>                                 input_event(input, EV_MSC, MSC_RAW, tdata[8]);
>                 }
>         }
> @@ -287,6 +317,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
>
>         switch (data[0]) {
>         case TRACKPAD_REPORT_ID:
> +       case TRACKPAD2_BT_REPORT_ID:
>                 /* Expect four bytes of prefix, and N*9 bytes of touch data. */
>                 if (size < 4 || ((size - 4) % 9) != 0)
>                         return 0;
> @@ -308,6 +339,22 @@ static int magicmouse_raw_event(struct hid_device *hdev,
>                  * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
>                  */
>                 break;
> +       case TRACKPAD2_USB_REPORT_ID:
> +               /* Expect twelve bytes of prefix and N*9 bytes of touch data. */
> +               if (size < 12 || ((size - 12) % 9) != 0)
> +                       return 0;
> +               npoints = (size - 12) / 9;
> +               if (npoints > 15) {
> +                       hid_warn(hdev, "invalid size value (%d) for TRACKPAD2_USB_REPORT_ID\n",
> +                                       size);
> +                       return 0;
> +               }
> +               msc->ntouches = 0;
> +               for (ii = 0; ii < npoints; ii++)
> +                       magicmouse_emit_touch(msc, ii, data + ii * 9 + 12);
> +
> +               clicks = data[1];
> +               break;
>         case MOUSE_REPORT_ID:
>                 /* Expect six bytes of prefix, and N*8 bytes of touch data. */
>                 if (size < 6 || ((size - 6) % 8) != 0)
> @@ -352,6 +399,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
>                 magicmouse_emit_buttons(msc, clicks & 3);
>                 input_report_rel(input, REL_X, x);
>                 input_report_rel(input, REL_Y, y);
> +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               input_mt_sync_frame(input);
> +               input_report_key(input, BTN_MOUSE, clicks & 1);
>         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
>                 input_report_key(input, BTN_MOUSE, clicks & 1);
>                 input_mt_report_pointer_emulation(input, true);
> @@ -364,6 +414,7 @@ static int magicmouse_raw_event(struct hid_device *hdev,
>  static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
>  {
>         int error;
> +       int mt_flags = 0;
>
>         __set_bit(EV_KEY, input->evbit);
>
> @@ -380,6 +431,22 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
>                         __set_bit(REL_WHEEL, input->relbit);
>                         __set_bit(REL_HWHEEL, input->relbit);
>                 }
> +       } else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               /* setting the device name to ensure the same driver settings
> +                * get loaded, whether connected through bluetooth or USB
> +                */
> +               input->name = "Apple Inc. Magic Trackpad 2";
> +
> +               __clear_bit(EV_MSC, input->evbit);
> +               __clear_bit(BTN_0, input->keybit);
> +               __clear_bit(BTN_RIGHT, input->keybit);
> +               __clear_bit(BTN_MIDDLE, input->keybit);
> +               __set_bit(BTN_MOUSE, input->keybit);
> +               __set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
> +               __set_bit(BTN_TOOL_FINGER, input->keybit);
> +
> +               mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED |
> +                               INPUT_MT_TRACK;
>         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
>                 /* input->keybit is initialized with incorrect button info
>                  * for Magic Trackpad. There really is only one physical
> @@ -402,14 +469,13 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
>
>         __set_bit(EV_ABS, input->evbit);
>
> -       error = input_mt_init_slots(input, 16, 0);
> +       error = input_mt_init_slots(input, 16, mt_flags);
>         if (error)
>                 return error;
>         input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
>                              4, 0);
>         input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
>                              4, 0);
> -       input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
>
>         /* Note: Touch Y position from the device is inverted relative
>          * to how pointer motion is reported (and relative to how USB
> @@ -418,6 +484,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
>          * inverse of the reported Y.
>          */
>         if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
> +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
>                 input_set_abs_params(input, ABS_MT_POSITION_X,
>                                      MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
>                 input_set_abs_params(input, ABS_MT_POSITION_Y,
> @@ -427,7 +494,25 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
>                                   MOUSE_RES_X);
>                 input_abs_set_res(input, ABS_MT_POSITION_Y,
>                                   MOUSE_RES_Y);
> +       } else if (input->id.product ==  USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0);
> +               input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0);
> +               input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
> +               input_set_abs_params(input, ABS_X, TRACKPAD2_MIN_X,
> +                                    TRACKPAD2_MAX_X, 0, 0);
> +               input_set_abs_params(input, ABS_Y, TRACKPAD2_MIN_Y,
> +                                    TRACKPAD2_MAX_Y, 0, 0);
> +               input_set_abs_params(input, ABS_MT_POSITION_X,
> +                                    TRACKPAD2_MIN_X, TRACKPAD2_MAX_X, 0, 0);
> +               input_set_abs_params(input, ABS_MT_POSITION_Y,
> +                                    TRACKPAD2_MIN_Y, TRACKPAD2_MAX_Y, 0, 0);
> +
> +               input_abs_set_res(input, ABS_X, TRACKPAD2_RES_X);
> +               input_abs_set_res(input, ABS_Y, TRACKPAD2_RES_Y);
> +               input_abs_set_res(input, ABS_MT_POSITION_X, TRACKPAD2_RES_X);
> +               input_abs_set_res(input, ABS_MT_POSITION_Y, TRACKPAD2_RES_Y);
>         } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> +               input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
>                 input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
>                                      TRACKPAD_MAX_X, 4, 0);
>                 input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
> @@ -447,7 +532,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
>
>         input_set_events_per_packet(input, 60);
>
> -       if (report_undeciphered) {
> +       if (report_undeciphered &&
> +           input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
>                 __set_bit(EV_MSC, input->evbit);
>                 __set_bit(MSC_RAW, input->mscbit);
>         }
> @@ -465,7 +551,8 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
>                 msc->input = hi->input;
>
>         /* Magic Trackpad does not give relative data after switching to MT */
> -       if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
> +       if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
> +            hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
>             field->flags & HID_MAIN_ITEM_RELATIVE)
>                 return -1;
>
> @@ -494,11 +581,20 @@ static int magicmouse_input_configured(struct hid_device *hdev,
>  static int magicmouse_probe(struct hid_device *hdev,
>         const struct hid_device_id *id)
>  {
> -       const u8 feature[] = { 0xd7, 0x01 };
> +       const u8 *feature;
> +       const u8 feature_mt[] = { 0xD7, 0x01 };
> +       const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
> +       const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
>         u8 *buf;
>         struct magicmouse_sc *msc;
>         struct hid_report *report;
>         int ret;
> +       int feature_size;
> +
> +       if (id->vendor == USB_VENDOR_ID_APPLE &&
> +           id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
> +           hdev->type != HID_TYPE_USBMOUSE)
> +               return 0;
>
>         msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
>         if (msc == NULL) {
> @@ -532,7 +628,14 @@ static int magicmouse_probe(struct hid_device *hdev,
>         if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
>                 report = hid_register_report(hdev, HID_INPUT_REPORT,
>                         MOUSE_REPORT_ID, 0);
> -       else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
> +       else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               if (id->vendor == BT_VENDOR_ID_APPLE)
> +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> +                               TRACKPAD2_BT_REPORT_ID, 0);
> +               else /* USB_VENDOR_ID_APPLE */
> +                       report = hid_register_report(hdev, HID_INPUT_REPORT,
> +                               TRACKPAD2_USB_REPORT_ID, 0);
> +       } else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
>                 report = hid_register_report(hdev, HID_INPUT_REPORT,
>                         TRACKPAD_REPORT_ID, 0);
>                 report = hid_register_report(hdev, HID_INPUT_REPORT,
> @@ -546,7 +649,20 @@ static int magicmouse_probe(struct hid_device *hdev,
>         }
>         report->size = 6;
>
> -       buf = kmemdup(feature, sizeof(feature), GFP_KERNEL);
> +       if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
> +               if (id->vendor == BT_VENDOR_ID_APPLE) {
> +                       feature_size = sizeof(feature_mt_trackpad2_bt);
> +                       feature = feature_mt_trackpad2_bt;
> +               } else { /* USB_VENDOR_ID_APPLE */
> +                       feature_size = sizeof(feature_mt_trackpad2_usb);
> +                       feature = feature_mt_trackpad2_usb;
> +               }
> +       } else {
> +               feature_size = sizeof(feature_mt);
> +               feature = feature_mt;
> +       }
> +
> +       buf = kmemdup(feature, feature_size, GFP_KERNEL);
>         if (!buf) {
>                 ret = -ENOMEM;
>                 goto err_stop_hw;
> @@ -560,10 +676,10 @@ static int magicmouse_probe(struct hid_device *hdev,
>          * but there seems to be no other way of switching the mode.
>          * Thus the super-ugly hacky success check below.
>          */
> -       ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature),
> +       ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
>                                 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
>         kfree(buf);
> -       if (ret != -EIO && ret != sizeof(feature)) {
> +       if (ret != -EIO && ret != feature_size) {
>                 hid_err(hdev, "unable to request touch data (%d)\n", ret);
>                 goto err_stop_hw;
>         }
> @@ -579,6 +695,10 @@ static const struct hid_device_id magic_mice[] = {
>                 USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
>         { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
>                 USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
> +       { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
> +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
> +       { HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
> +               USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
>         { }
>  };
>  MODULE_DEVICE_TABLE(hid, magic_mice);
> --
> 2.19.0.605.g01d371f741-goog
>

^ permalink raw reply

* Re: [PATCH 2/2] HID: google: add Whiskers driver to handle tablet mode properly
From: Benjamin Tissoires @ 2018-10-03  7:53 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Jiri Kosina, lee.jones, open list:HID CORE LAYER, lkml,
	Nicolas Boichat
In-Reply-To: <CAE_wzQ-erV75NMwSVsrQM1FL3a6dveKKhm_+OQaoBO5v5JJyrw@mail.gmail.com>

On Tue, Oct 2, 2018 at 8:25 PM Dmitry Torokhov <dtor@chromium.org> wrote:
>
> Hi Benjamin,
>
> On Tue, Oct 2, 2018 at 1:53 AM Benjamin Tissoires
> <benjamin.tissoires@redhat.com> wrote:
> >
> > Hi Dmitry,
> >
> > On Mon, Oct 1, 2018 at 11:39 PM Dmitry Torokhov <dtor@chromium.org> wrote:
> > >
> > > This adds dedicated "Whiskers" driver that hooks both into HID and EC to
> > > produce proper SW_TABLET_SWITCH event from base presence bit from EC and
> > > base state (folded/unfolded) coming from USB/HID.
> > >
> > > Signed-off-by: Nicolas Boichat <drinkcat@chromium.org>
> > > Signed-off-by: Dmitry Torokhov <dtor@chromium.org>
> > > ---
> >
> > I have a few comments, see below.
> >
> > >  drivers/hid/Kconfig               |   6 +
> > >  drivers/hid/Makefile              |   1 +
> > >  drivers/hid/hid-google-hammer.c   |   7 +-
> > >  drivers/hid/hid-google-whiskers.c | 382 ++++++++++++++++++++++++++++++
> > >  4 files changed, 392 insertions(+), 4 deletions(-)
> > >  create mode 100644 drivers/hid/hid-google-whiskers.c
> > >
> > > diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> > > index e5ec47705fa2..0607f1c7129e 100644
> > > --- a/drivers/hid/Kconfig
> > > +++ b/drivers/hid/Kconfig
> > > @@ -355,6 +355,12 @@ config HID_GOOGLE_HAMMER
> > >         ---help---
> > >         Say Y here if you have a Google Hammer device.
> > >
> > > +config HID_GOOGLE_WHISKERS
> > > +       tristate "Google Whiskers Keyboard"
> > > +       depends on HID_GOOGLE_HAMMER && MFD_CROS_EC
> > > +       ---help---
> > > +       Say Y here if you have a Google Whiskers device.
> > > +
> > >  config HID_GT683R
> > >         tristate "MSI GT68xR LED support"
> > >         depends on LEDS_CLASS && USB_HID
> > > diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> > > index bd7ac53b75c5..241e583b6913 100644
> > > --- a/drivers/hid/Makefile
> > > +++ b/drivers/hid/Makefile
> > > @@ -47,6 +47,7 @@ obj-$(CONFIG_HID_EZKEY)               += hid-ezkey.o
> > >  obj-$(CONFIG_HID_GEMBIRD)      += hid-gembird.o
> > >  obj-$(CONFIG_HID_GFRM)         += hid-gfrm.o
> > >  obj-$(CONFIG_HID_GOOGLE_HAMMER)        += hid-google-hammer.o
> > > +obj-$(CONFIG_HID_GOOGLE_WHISKERS)      += hid-google-whiskers.o
> > >  obj-$(CONFIG_HID_GT683R)       += hid-gt683r.o
> > >  obj-$(CONFIG_HID_GYRATION)     += hid-gyration.o
> > >  obj-$(CONFIG_HID_HOLTEK)       += hid-holtek-kbd.o
> > > diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
> > > index 6bf4da7ad63a..81ddf9773df9 100644
> > > --- a/drivers/hid/hid-google-hammer.c
> > > +++ b/drivers/hid/hid-google-hammer.c
> > > @@ -18,6 +18,7 @@
> > >  #include <linux/module.h>
> > >
> > >  #include "hid-ids.h"
> > > +#include "hid-google-hammer.h"
> >
> > hid-google-hammer.h doesn't seem to be part of the patch (or in my
> > local tree at least).
>
> Ugh, my bad.
>
> >
> > >
> > >  #define MAX_BRIGHTNESS 100
> > >
> > > @@ -90,8 +91,7 @@ static int hammer_register_leds(struct hid_device *hdev)
> > >         return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
> > >  }
> > >
> > > -static int hammer_input_configured(struct hid_device *hdev,
> > > -                                  struct hid_input *hi)
> > > +int hammer_input_configured(struct hid_device *hdev, struct hid_input *hi)
> > >  {
> > >         struct list_head *report_list =
> > >                 &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
> > > @@ -116,6 +116,7 @@ static int hammer_input_configured(struct hid_device *hdev,
> > >
> > >         return 0;
> > >  }
> > > +EXPORT_SYMBOL_GPL(hammer_input_configured);
> >
> > I am not sure there is a need to separate the driver in 2 for whiskers
> > if it still need the LED settings from hammer.
> >
> > Given that the whiskers_ec struct will be populated independently,
> > having a common code path would not hurt hammer anyway.
>
> The idea is that we we have device with Hammer, we do not need to load
> bunch of code that deals with properly supporting Whiskers (connecting
> to the EC, creating a tablet switch device).

I understand this. However, the code is just loaded and will not be
actually used given that the ACPI PnP ID will not be present. So in
the end, you are saving a little bit of memory while making the whole
driver harder to understand as part of it is in an other place (and in
a different module).
Anyway, the policy we tend to have in the HID tree is to have one
quirked driver per vendor. It is not always the case, but that makes
things easier for users to know which HID driver they need to enable.

If you really think having a separate driver for whiskers is better, I
won't go too much against it.

>
> >
> > >
> > >  static const struct hid_device_id hammer_devices[] = {
> > >         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > > @@ -124,8 +125,6 @@ static const struct hid_device_id hammer_devices[] = {
> > >                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
> > >         { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > >                      USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
> > > -       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > > -                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
> > >         { }
> > >  };
> > >  MODULE_DEVICE_TABLE(hid, hammer_devices);
> > > diff --git a/drivers/hid/hid-google-whiskers.c b/drivers/hid/hid-google-whiskers.c
> > > new file mode 100644
> > > index 000000000000..ff3d2bfa1194
> > > --- /dev/null
> > > +++ b/drivers/hid/hid-google-whiskers.c
> > > @@ -0,0 +1,382 @@
> > > +// SPDX-License-Identifier: GPL-2.0+
> > > +//
> > > +// HID driver for Google Whiskers device.
> > > +//
> > > +// Copyright (c) 2018 Google Inc.
> > > +
> > > +#include <linux/acpi.h>
> > > +#include <linux/hid.h>
> > > +#include <linux/leds.h>
> >
> > doesn't seem to be used
>
> Indeed, will drop.
>
> >
> > > +#include <linux/mfd/cros_ec.h>
> > > +#include <linux/mfd/cros_ec_commands.h>
> > > +#include <linux/module.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/pm_wakeup.h>
> > > +#include <linux/usb.h>
> >
> > This is usually not a good sign in HID drivers to rely on the low
> > level transport.
> > Basically, if you replay the device through uhid, this tends to break
> > heavily the kernel.
>
> The data parsing relies solely on HID, USB is here just to make sure
> we bind to the right interface, as you can see below.

Yes, but this is indeed the problem :)
When you replay a device with uhid, you don't have access to the
interface, and this is where the kernel oops happends :)

>
> >
> > > +#include <asm/unaligned.h>
> > > +
> > > +#include "hid-ids.h"
> > > +#include "hid-google-hammer.h"
> > > +
> > > +#define HID_UP_GOOGLEVENDOR    0xffd10000
> > > +#define HID_VD_KBD_FOLDED      0x00000019
> > > +#define WHISKERS_KBD_FOLDED    (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
> >
> > Huh, this is weird there is no standard HID usage for this (not that
> > you can do anything about it).
>
> This is really specific to this device, I am not sure if it makes
> sense to standardize it. I guess we'll see if we make more of these.

Well, there is not much we can do apart joining the USB consortium and
pushing for a new HID standard usage.
So it was comment that doesn't need to be addressed right now :)

>
> >
> > > +
> > > +struct whiskers_ec {
> > > +       struct device *dev;     /* The platform device (EC) */
> > > +       struct input_dev *input;
> > > +       bool base_present;
> > > +       struct notifier_block notifier;
> > > +};
> > > +
> > > +static struct whiskers_ec whiskers_ec;
> > > +static DEFINE_SPINLOCK(whiskers_ec_lock);
> > > +static DEFINE_MUTEX(whiskers_ec_reglock);
> > > +
> > > +static bool whiskers_parse_base_state(const void *data)
> > > +{
> > > +       u32 switches = get_unaligned_le32(data);
> > > +
> > > +       return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
> > > +}
> > > +
> > > +static int whiskers_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
> > > +                                 bool *state)
> > > +{
> > > +       struct ec_params_mkbp_info *params;
> > > +       struct cros_ec_command *msg;
> > > +       int ret;
> > > +
> > > +       msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)),
> > > +                     GFP_KERNEL);
> > > +       if (!msg)
> > > +               return -ENOMEM;
> > > +
> > > +       msg->command = EC_CMD_MKBP_INFO;
> > > +       msg->version = 1;
> > > +       msg->outsize = sizeof(*params);
> > > +       msg->insize = sizeof(u32);
> > > +       params = (struct ec_params_mkbp_info *)msg->data;
> > > +       params->info_type = get_state ?
> > > +               EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
> > > +       params->event_type = EC_MKBP_EVENT_SWITCH;
> > > +
> > > +       ret = cros_ec_cmd_xfer_status(ec_dev, msg);
> > > +       if (ret >= 0) {
> > > +               if (ret != sizeof(u32)) {
> > > +                       dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
> > > +                                ret, sizeof(u32));
> > > +                       ret = -EPROTO;
> > > +               } else {
> > > +                       *state = whiskers_parse_base_state(msg->data);
> > > +                       ret = 0;
> > > +               }
> > > +       }
> > > +
> > > +       kfree(msg);
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static int whiskers_ec_notify(struct notifier_block *nb,
> > > +                             unsigned long queued_during_suspend,
> > > +                             void *_notify)
> > > +{
> > > +       struct cros_ec_device *ec = _notify;
> > > +       unsigned long flags;
> > > +       bool base_present;
> > > +
> > > +       if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
> > > +               base_present = whiskers_parse_base_state(
> > > +                                       &ec->event_data.data.switches);
> > > +               dev_dbg(whiskers_ec.dev,
> > > +                       "%s: base: %d\n", __func__, base_present);
> > > +
> > > +               if (device_may_wakeup(whiskers_ec.dev) ||
> > > +                   !queued_during_suspend) {
> > > +
> > > +                       pm_wakeup_event(whiskers_ec.dev, 0);
> > > +
> > > +                       spin_lock_irqsave(&whiskers_ec_lock, flags);
> > > +
> > > +                       /*
> > > +                        * While input layer dedupes the events, we do not want
> > > +                        * to disrupt the state reported by the base by
> > > +                        * overriding it with state reported by the LID. Only
> > > +                        * report changes, as we assume that on attach the base
> > > +                        * is not folded.
> > > +                        */
> > > +                       if (base_present != whiskers_ec.base_present) {
> > > +                               input_report_switch(whiskers_ec.input,
> > > +                                                   SW_TABLET_MODE,
> > > +                                                   !base_present);
> > > +                               input_sync(whiskers_ec.input);
> > > +                               whiskers_ec.base_present = base_present;
> > > +                       }
> > > +
> > > +                       spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> > > +               }
> > > +       }
> > > +
> > > +       return NOTIFY_OK;
> > > +}
> > > +
> > > +static __maybe_unused int whiskers_ec_resume(struct device *dev)
> > > +{
> > > +       struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
> > > +       bool base_present;
> > > +       int error;
> > > +
> > > +       error = whiskers_ec_query_base(ec, true, &base_present);
> > > +       if (error) {
> > > +               dev_warn(dev, "failed to fetch base state on resume: %d\n",
> > > +                        error);
> > > +       } else {
> > > +               spin_lock_irq(&whiskers_ec_lock);
> > > +
> > > +               whiskers_ec.base_present = base_present;
> > > +
> > > +               /*
> > > +                * Only report if base is disconnected. If base is connected,
> > > +                * it will resend its state on resume, and we'll update it
> > > +                * in whiskers_event().
> > > +                */
> > > +               if (!whiskers_ec.base_present) {
> > > +                       input_report_switch(whiskers_ec.input,
> > > +                                           SW_TABLET_MODE, 1);
> > > +                       input_sync(whiskers_ec.input);
> > > +               }
> > > +
> > > +               spin_unlock_irq(&whiskers_ec_lock);
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static SIMPLE_DEV_PM_OPS(whiskers_ec_pm_ops, NULL, whiskers_ec_resume);
> > > +
> > > +static void whiskers_ec_set_input(struct input_dev *input)
> > > +{
> > > +       /* Take the lock so whiskers_event does not race with us here */
> > > +       spin_lock_irq(&whiskers_ec_lock);
> > > +       whiskers_ec.input = input;
> > > +       spin_unlock_irq(&whiskers_ec_lock);
> > > +}
> > > +
> > > +static int __whiskers_ec_probe(struct platform_device *pdev)
> > > +{
> > > +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> > > +       struct input_dev *input;
> > > +       bool base_supported;
> > > +       int error;
> > > +
> > > +       error = whiskers_ec_query_base(ec, false, &base_supported);
> > > +       if (error)
> > > +               return error;
> > > +
> > > +       if (!base_supported)
> > > +               return -ENXIO;
> > > +
> > > +       input = devm_input_allocate_device(&pdev->dev);
> > > +       if (!input)
> > > +               return -ENOMEM;
> > > +
> > > +       input->name = "Whiskers Tablet Mode Switch";
> > > +
> > > +       input->id.bustype = BUS_HOST;
> > > +       input->id.version = 1;
> > > +       input->id.product = 0;
> >
> > I wonder if you should not set the vendor to USB_VENDOR_ID_GOOGLE and
> > the product to something better.
>
> This is not USB interface, but host one (coming from the EC), so
> USB_VENDOR_ID_GOOGLE does not make sense here. I think I'll simply
> remove version and product.

Actually, the USB ids are already reused by the I2C devices. It makes
things simpler for hardware makers to have only one place where the
list of companies is.
Maybe we should just drop the "USB_" prefix in hid-ids.h, as it
doesn't make much sense anymore.

And I'd still rather have a proper VID/PID that userspace can rely on,
because matching on the name is doable, but sometimes names change :/

>
> >
> > > +
> > > +       input_set_capability(input, EV_SW, SW_TABLET_MODE);
> > > +
> > > +       error = input_register_device(input);
> > > +       if (error) {
> > > +               dev_err(&pdev->dev, "cannot register input device: %d\n",
> > > +                       error);
> > > +               return error;
> > > +       }
> > > +
> > > +       /* Seed the state */
> > > +       error = whiskers_ec_query_base(ec, true, &whiskers_ec.base_present);
> > > +       if (error) {
> > > +               dev_err(&pdev->dev, "cannot query base state: %d\n", error);
> > > +               return error;
> > > +       }
> > > +
> > > +       input_report_switch(input, SW_TABLET_MODE, !whiskers_ec.base_present);
> > > +
> > > +       whiskers_ec_set_input(input);
> > > +
> > > +       whiskers_ec.dev = &pdev->dev;
> > > +       whiskers_ec.notifier.notifier_call = whiskers_ec_notify;
> > > +       error = blocking_notifier_chain_register(&ec->event_notifier,
> > > +                                                &whiskers_ec.notifier);
> > > +       if (error) {
> > > +               dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
> > > +               whiskers_ec_set_input(NULL);
> > > +               return error;
> > > +       }
> > > +
> > > +       device_init_wakeup(&pdev->dev, true);
> > > +       return 0;
> > > +}
> > > +
> > > +static int whiskers_ec_probe(struct platform_device *pdev)
> > > +{
> > > +       int retval;
> > > +
> > > +       mutex_lock(&whiskers_ec_reglock);
> > > +
> > > +       if (whiskers_ec.input) {
> > > +               retval = -EBUSY;
> > > +               goto out;
> > > +       }
> > > +
> > > +       retval = __whiskers_ec_probe(pdev);
> > > +
> > > +out:
> > > +       mutex_unlock(&whiskers_ec_reglock);
> > > +       return retval;
> > > +}
> > > +
> > > +static int whiskers_ec_remove(struct platform_device *pdev)
> > > +{
> > > +       struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
> > > +
> > > +       mutex_lock(&whiskers_ec_reglock);
> > > +
> > > +       blocking_notifier_chain_unregister(&ec->event_notifier,
> > > +                                          &whiskers_ec.notifier);
> > > +       whiskers_ec_set_input(NULL);
> > > +
> > > +       mutex_unlock(&whiskers_ec_reglock);
> > > +       return 0;
> > > +}
> > > +
> > > +static const struct acpi_device_id whiskers_ec_acpi_ids[] = {
> > > +       { "GOOG000B", 0 },
> > > +       { }
> > > +};
> > > +MODULE_DEVICE_TABLE(acpi, whiskers_ec_acpi_ids);
> > > +
> > > +static struct platform_driver whiskers_ec_driver = {
> > > +       .probe  = whiskers_ec_probe,
> > > +       .remove = whiskers_ec_remove,
> > > +       .driver = {
> > > +               .name                   = "whiskers_ec",
> > > +               .acpi_match_table       = ACPI_PTR(whiskers_ec_acpi_ids),
> > > +               .pm                     = &whiskers_ec_pm_ops,
> > > +       },
> > > +};
> > > +
> > > +static int whiskers_input_mapping(struct hid_device *hdev, struct hid_input *hi,
> > > +                                 struct hid_field *field,
> > > +                                 struct hid_usage *usage,
> > > +                                 unsigned long **bit, int *max)
> > > +{
> > > +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> > > +               /*
> > > +                * We do not want to have this usage mapped as it will get
> > > +                * mixed in with "base attached" signal and delivered over
> > > +                * separate input device for tablet switch mode.
> > > +                */
> > > +
> > > +               return -1;
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static int whiskers_event(struct hid_device *hid, struct hid_field *field,
> > > +                         struct hid_usage *usage, __s32 value)
> > > +{
> > > +       unsigned long flags;
> > > +
> > > +       if (usage->hid == WHISKERS_KBD_FOLDED) {
> > > +               spin_lock_irqsave(&whiskers_ec_lock, flags);
> > > +
> > > +               hid_dbg(hid, "%s: base: %d, folded: %d\n", __func__,
> > > +                       whiskers_ec.base_present, value);
> > > +
> > > +               /*
> > > +                * We should not get event if base is detached, but in case
> > > +                * we happen to service HID and EC notifications out of order
> > > +                * let's still check the "base present" flag.
> > > +                */
> > > +               if (whiskers_ec.input && whiskers_ec.base_present) {
> > > +                       input_report_switch(whiskers_ec.input,
> > > +                                           SW_TABLET_MODE, value);
> > > +                       input_sync(whiskers_ec.input);
> > > +               }
> > > +
> > > +               spin_unlock_irqrestore(&whiskers_ec_lock, flags);
> > > +               return 1; /* We handled this event */
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +
> > > +static int whiskers_probe(struct hid_device *hdev,
> > > +                         const struct hid_device_id *id)
> > > +{
> > > +       struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
> >
> > As mentioned earlier, this is prone to kernel oopses when using uhid.
>
> Hmm, we have similar code in hid-google-hammer.c, I guess we need to
> fix that there too.

If you can do that, I'll be happy :)

>
> >
> > > +       int ret;
> > > +
> > > +       /*
> > > +        * We always want to poll for, and handle tablet mode events, even when
> > > +        * nobody has opened the input device. This also prevents the hid core
> > > +        * from dropping early tablet mode events from the device.
> > > +        */
> > > +       if (intf->cur_altsetting->desc.bInterfaceProtocol ==
> > > +                       USB_INTERFACE_PROTOCOL_KEYBOARD)
> >
> > Can't you rely on something embedded in the report descriptor instead?
> > This way you can drop the USB dependency.
>
> Something like:
>
> static bool hammer_is_keyboard(...)
> {
>
>        rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
>        list_for_each_entry(report, &rep_enum->report_list, list) {
>                for (i = 0; i < report->maxfield; i++) {
>                        if (report->field[i].application == HID_GD_KEYBOARD)
>                                return true;
>        }
>
>        return false;
> }
>
> ?

Yep. However, you can simplify it a little bit since later HID
revisions as we now store the application in the report:
        rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
        list_for_each_entry(report, &rep_enum->report_list, list) {
               if (report->application == HID_GD_KEYBOARD)
                          return true;
        }
        return false;

(whitespace apart)
Hopefully, this will work for you.

>
> >
> > > +               hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
> > > +
> > > +       ret = hid_parse(hdev);
> > > +       if (!ret)
> > > +               ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
> > > +
> > > +       return ret;
> > > +}
> > > +
> > > +static const struct hid_device_id whiskers_hid_devices[] = {
> > > +       { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
> > > +                    USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
> > > +       { }
> > > +};
> > > +MODULE_DEVICE_TABLE(hid, whiskers_hid_devices);
> > > +
> > > +static struct hid_driver whiskers_hid_driver = {
> > > +       .name                   = "whiskers",
> > > +       .id_table               = whiskers_hid_devices,
> > > +       .probe                  = whiskers_probe,
> > > +       .input_configured       = hammer_input_configured,
> > > +       .input_mapping          = whiskers_input_mapping,
> > > +       .event                  = whiskers_event,
> > > +};
> > > +
> > > +static int __init whiskers_init(void)
> > > +{
> > > +       int error;
> > > +
> > > +       error = platform_driver_register(&whiskers_ec_driver);
> > > +       if (error)
> > > +               return error;
> > > +
> > > +       error = hid_register_driver(&whiskers_hid_driver);
> > > +       if (error) {
> > > +               platform_driver_unregister(&whiskers_ec_driver);
> > > +               return error;
> > > +       }
> > > +
> > > +       return 0;
> > > +}
> > > +module_init(whiskers_init);
> > > +
> > > +static void __exit whiskers_exit(void)
> > > +{
> > > +       hid_unregister_driver(&whiskers_hid_driver);
> > > +       platform_driver_unregister(&whiskers_ec_driver);
> > > +}
> > > +module_exit(whiskers_exit);
> > > +
> > > +MODULE_LICENSE("GPL");
> > > --
> > > 2.19.0.605.g01d371f741-goog
> > >
> >
>
> Thanks.

Cheers,
Benjamin

>
> --
> Dmitry

^ permalink raw reply

* Re: [PATCH] [v4] HID: add support for Apple Magic Trackpad 2
From: Jiri Kosina @ 2018-10-03  8:59 UTC (permalink / raw)
  To: Sean O'Brien
  Cc: Benjamin Tissoires, Marek Wyborski, linux-kernel, Dmitry Torokhov,
	linux-input, Henrik Rydberg, Claudio Mettler
In-Reply-To: <20181002225337.7349-1-seobrien@chromium.org>

On Tue, 2 Oct 2018, Sean O'Brien wrote:

> USB device
>         Vendor 05ac (Apple)
>         Device 0265 (Magic Trackpad 2)
> Bluetooth device
>         Vendor 004c (Apple)
>         Device 0265 (Magic Trackpad 2)
> 
> Add support for Apple Magic Trackpad 2 over USB and bluetooth, putting
> the device in multi-touch mode.

Applied, thanks.

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH] HID: i2c-hid: Add a small delay after powering on/off the device
From: Jiri Kosina @ 2018-10-03  9:10 UTC (permalink / raw)
  To: Kai-Heng Feng; +Cc: benjamin.tissoires, hdegoede, linux-input, linux-kernel
In-Reply-To: <20181001035313.21453-1-kai.heng.feng@canonical.com>

On Mon, 1 Oct 2018, Kai-Heng Feng wrote:

> Raydium touchpanel (2386:4B33) sometimes does not workin desktop session
> although it works in display manager.
> 
> During user logging, the display manager exits, close the HID device,
> then the device gets runtime suspended and powered off. The desktop
> session begins shortly after, opens the HID device, then the device gets
> runtime resumed and powered on.
> 
> If the trasition from display manager to desktop sesesion is fast, the
> touchpanel cannot switch from powered off to powered on in short
> timeframe. So add a small delay to workaround the issue.
> 
> Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
> ---
>  drivers/hid/i2c-hid/i2c-hid.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
> index f3076659361a..ff5682cc1bce 100644
> --- a/drivers/hid/i2c-hid/i2c-hid.c
> +++ b/drivers/hid/i2c-hid/i2c-hid.c
> @@ -409,6 +409,8 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
>  
>  	if (ret)
>  		dev_err(&client->dev, "failed to change power setting.\n");
> +	else
> +		msleep(20);

In case there really is no other way around it, at least please add 
comment explaining why this ugly msleep() is there. Thanks,

-- 
Jiri Kosina
SUSE Labs

^ permalink raw reply

* Re: [PATCH] HID: i2c-hid: Add a small delay after powering on/off the device
From: Kai Heng Feng @ 2018-10-03  9:21 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: Benjamin Tissoires, Hans de Goede, linux-input, linux-kernel
In-Reply-To: <nycvar.YFH.7.76.1810031109200.14430@cbobk.fhfr.pm>



> On Oct 3, 2018, at 5:10 PM, Jiri Kosina <jikos@kernel.org> wrote:
> 
> On Mon, 1 Oct 2018, Kai-Heng Feng wrote:
> 
>> Raydium touchpanel (2386:4B33) sometimes does not workin desktop session
>> although it works in display manager.
>> 
>> During user logging, the display manager exits, close the HID device,
>> then the device gets runtime suspended and powered off. The desktop
>> session begins shortly after, opens the HID device, then the device gets
>> runtime resumed and powered on.
>> 
>> If the trasition from display manager to desktop sesesion is fast, the
>> touchpanel cannot switch from powered off to powered on in short
>> timeframe. So add a small delay to workaround the issue.
>> 
>> Signed-off-by: Kai-Heng Feng <kai.heng.feng@canonical.com>
>> ---
>> drivers/hid/i2c-hid/i2c-hid.c | 2 ++
>> 1 file changed, 2 insertions(+)
>> 
>> diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
>> index f3076659361a..ff5682cc1bce 100644
>> --- a/drivers/hid/i2c-hid/i2c-hid.c
>> +++ b/drivers/hid/i2c-hid/i2c-hid.c
>> @@ -409,6 +409,8 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state)
>> 
>> 	if (ret)
>> 		dev_err(&client->dev, "failed to change power setting.\n");
>> +	else
>> +		msleep(20);
> 
> In case there really is no other way around it, at least please add 
> comment explaining why this ugly msleep() is there. Thanks,

I am working on a better approach for this issue, I’ll send a v2 soon.

Kai-Heng

> 
> -- 
> Jiri Kosina
> SUSE Labs
> 

^ permalink raw reply

* [PATCH 0/3] HID: debug: fix the ring buffer implementation
From: Vladis Dronov @ 2018-10-03 17:19 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel

This patchset is fixing some aspects of the ring buffer implementation in
drivers/hid/hid-debug.c. This implementation has certain problem points:

- it may stuck in an infinite loop
- it may return corrupted data
- a reader and a writer are not protected by spinlocks, which can lead to
  the corrupted data

The suggested patchset is a new ring buffer implementation which overwrites
the oldest data in case of an overflow. One can verify the suggested ring
buffer implementation by fuzzing it with modified kernel and fuzzer-reader
at: https://gist.github.com/nefigtut/33d56e3870b67493cc867344aed2a062

Vladis Dronov (3):
  HID: debug: avoid infinite loop and corrupting data
  HID: debug: provide reader-writer locking for the ring buffer
  HID: debug: fix ring buffer implementation

 drivers/hid/hid-debug.c   | 201 ++++++++++++++++++++++++++------------
 include/linux/hid-debug.h |   1 +
 2 files changed, 142 insertions(+), 60 deletions(-)

-- 
2.19.0

^ permalink raw reply

* [PATCH 1/3] HID: debug: avoid infinite loop and corrupting data
From: Vladis Dronov @ 2018-10-03 17:19 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Vladis Dronov
In-Reply-To: <20181003171936.11271-1-vdronov@redhat.com>

hid_debug_events_read() does not properly handle the case when tail < head
and count < HID_DEBUG_BUFSIZE - head and returns corrupted data. Also,
after commit 717adfdaf147 ("HID: debug: check length before
copy_to_user()") it can enter an infinite loop if called with count == 0.
Fix this by properly handling this case and adding a check.

Also, the function has "while (ret == 0)" loop which is not needed, remove
it.

Signed-off-by: Vladis Dronov <vdronov@redhat.com>
---
 drivers/hid/hid-debug.c | 109 ++++++++++++++++++++++++----------------
 1 file changed, 65 insertions(+), 44 deletions(-)

diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index b48100236df8..20580871b0ec 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -722,7 +722,7 @@ void hid_dump_input(struct hid_device *hdev, struct hid_usage *usage, __s32 valu
 	hid_debug_event(hdev, buf);
 
 	kfree(buf);
-        wake_up_interruptible(&hdev->debug_wait);
+	wake_up_interruptible(&hdev->debug_wait);
 
 }
 EXPORT_SYMBOL_GPL(hid_dump_input);
@@ -1112,73 +1112,95 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 	int ret = 0, len;
 	DECLARE_WAITQUEUE(wait, current);
 
-	mutex_lock(&list->read_mutex);
-	while (ret == 0) {
-		if (list->head == list->tail) {
-			add_wait_queue(&list->hdev->debug_wait, &wait);
-			set_current_state(TASK_INTERRUPTIBLE);
+	if (!count)
+		return 0;
 
-			while (list->head == list->tail) {
-				if (file->f_flags & O_NONBLOCK) {
-					ret = -EAGAIN;
-					break;
-				}
-				if (signal_pending(current)) {
-					ret = -ERESTARTSYS;
-					break;
-				}
+	mutex_lock(&list->read_mutex);
+	if (list->head == list->tail) {
+		add_wait_queue(&list->hdev->debug_wait, &wait);
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		while (list->head == list->tail) {
+			if (file->f_flags & O_NONBLOCK) {
+				ret = -EAGAIN;
+				break;
+			}
 
-				if (!list->hdev || !list->hdev->debug) {
-					ret = -EIO;
-					set_current_state(TASK_RUNNING);
-					goto out;
-				}
+			if (signal_pending(current)) {
+				ret = -ERESTARTSYS;
+				break;
+			}
 
-				/* allow O_NONBLOCK from other threads */
-				mutex_unlock(&list->read_mutex);
-				schedule();
-				mutex_lock(&list->read_mutex);
-				set_current_state(TASK_INTERRUPTIBLE);
+			if (!list->hdev || !list->hdev->debug) {
+				ret = -EIO;
+				set_current_state(TASK_RUNNING);
+				goto out;
 			}
 
-			set_current_state(TASK_RUNNING);
-			remove_wait_queue(&list->hdev->debug_wait, &wait);
+			/* allow O_NONBLOCK from other threads */
+			mutex_unlock(&list->read_mutex);
+			schedule();
+			mutex_lock(&list->read_mutex);
+			set_current_state(TASK_INTERRUPTIBLE);
 		}
 
-		if (ret)
-			goto out;
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&list->hdev->debug_wait, &wait);
+	}
+
+	if (ret)
+		goto out;
 
-		/* pass the ringbuffer contents to userspace */
+	/* pass the ringbuffer content to userspace */
 copy_rest:
-		if (list->tail == list->head)
-			goto out;
-		if (list->tail > list->head) {
-			len = list->tail - list->head;
-			if (len > count)
-				len = count;
+	if (list->tail == list->head)
+		goto out;
+
+	/* the data from the head to the tail in the buffer is linear */
+	if (list->tail > list->head) {
+		len = list->tail - list->head;
+		if (len > count)
+			len = count;
 
-			if (copy_to_user(buffer + ret, &list->hid_debug_buf[list->head], len)) {
+		if (copy_to_user(buffer + ret,
+		      &list->hid_debug_buf[list->head], len)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		ret += len;
+		list->head += len;
+	} else {
+		len = HID_DEBUG_BUFSIZE - list->head;
+
+		/* the data is split in 2 pieces: from the head to the end of
+		 * the ring buffer and from the start of the ring buffer to the
+		 *  tail. check if we need to copy out only the part between the
+		 * head and the end.
+		 */
+		if (len > count) {
+			len = count;
+
+			if (copy_to_user(buffer,
+			      &list->hid_debug_buf[list->head], len)) {
 				ret = -EFAULT;
 				goto out;
 			}
 			ret += len;
 			list->head += len;
 		} else {
-			len = HID_DEBUG_BUFSIZE - list->head;
-			if (len > count)
-				len = count;
-
-			if (copy_to_user(buffer, &list->hid_debug_buf[list->head], len)) {
+			if (copy_to_user(buffer,
+			      &list->hid_debug_buf[list->head], len)) {
 				ret = -EFAULT;
 				goto out;
 			}
 			list->head = 0;
 			ret += len;
 			count -= len;
+
+			/* check for a corner case when len == count */
 			if (count > 0)
 				goto copy_rest;
 		}
-
 	}
 out:
 	mutex_unlock(&list->read_mutex);
@@ -1256,4 +1278,3 @@ void hid_debug_exit(void)
 {
 	debugfs_remove_recursive(hid_debug_root);
 }
-
-- 
2.19.0

^ permalink raw reply related

* [PATCH 2/3] HID: debug: provide reader-writer locking for the ring buffer
From: Vladis Dronov @ 2018-10-03 17:19 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Vladis Dronov
In-Reply-To: <20181003171936.11271-1-vdronov@redhat.com>

hdev->debug_list->hid_debug_buf is not protected from concurrent updates
from the writer, hid_debug_event() and reads by the reader,
hid_debug_events_read(). Fix this by adding per-list-element spinlock.
Also introduce a temporary buffer tempbuf so copy_to_user() is not called
from under a spinlock.

Signed-off-by: Vladis Dronov <vdronov@redhat.com>
---
 drivers/hid/hid-debug.c   | 47 +++++++++++++++++++++++----------------
 include/linux/hid-debug.h |  1 +
 2 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 20580871b0ec..e827784baf1a 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -663,15 +663,17 @@ void hid_debug_event(struct hid_device *hdev, char *buf)
 {
 	unsigned i;
 	struct hid_debug_list *list;
-	unsigned long flags;
+	unsigned long flags, flags2;
 
 	spin_lock_irqsave(&hdev->debug_list_lock, flags);
 	list_for_each_entry(list, &hdev->debug_list, node) {
+		spin_lock_irqsave(&list->list_lock, flags2);
 		for (i = 0; buf[i]; i++)
 			list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] =
 				buf[i];
 		list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE;
-        }
+		spin_unlock_irqrestore(&list->list_lock, flags2);
+	}
 	spin_unlock_irqrestore(&hdev->debug_list_lock, flags);
 
 	wake_up_interruptible(&hdev->debug_wait);
@@ -1095,6 +1097,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
 	}
 	list->hdev = (struct hid_device *) inode->i_private;
 	file->private_data = list;
+	spin_lock_init(&list->list_lock);
 	mutex_init(&list->read_mutex);
 
 	spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
@@ -1109,6 +1112,8 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 		size_t count, loff_t *ppos)
 {
 	struct hid_debug_list *list = file->private_data;
+	char *tmpbuf;
+	unsigned long flags;
 	int ret = 0, len;
 	DECLARE_WAITQUEUE(wait, current);
 
@@ -1151,10 +1156,18 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 	if (ret)
 		goto out;
 
-	/* pass the ringbuffer content to userspace */
+	tmpbuf = kmalloc(min_t(size_t, count, HID_DEBUG_BUFSIZE),
+			 GFP_KERNEL|__GFP_NOWARN);
+	if (!tmpbuf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/* lock and copy the ringbuffer content to tmpbuf */
+	spin_lock_irqsave(&list->list_lock, flags);
 copy_rest:
 	if (list->tail == list->head)
-		goto out;
+		goto out_unlock;
 
 	/* the data from the head to the tail in the buffer is linear */
 	if (list->tail > list->head) {
@@ -1162,11 +1174,7 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 		if (len > count)
 			len = count;
 
-		if (copy_to_user(buffer + ret,
-		      &list->hid_debug_buf[list->head], len)) {
-			ret = -EFAULT;
-			goto out;
-		}
+		memcpy(tmpbuf + ret, &list->hid_debug_buf[list->head], len);
 		ret += len;
 		list->head += len;
 	} else {
@@ -1180,19 +1188,11 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 		if (len > count) {
 			len = count;
 
-			if (copy_to_user(buffer,
-			      &list->hid_debug_buf[list->head], len)) {
-				ret = -EFAULT;
-				goto out;
-			}
+			memcpy(tmpbuf, &list->hid_debug_buf[list->head], len);
 			ret += len;
 			list->head += len;
 		} else {
-			if (copy_to_user(buffer,
-			      &list->hid_debug_buf[list->head], len)) {
-				ret = -EFAULT;
-				goto out;
-			}
+			memcpy(tmpbuf, &list->hid_debug_buf[list->head], len);
 			list->head = 0;
 			ret += len;
 			count -= len;
@@ -1202,6 +1202,15 @@ static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
 				goto copy_rest;
 		}
 	}
+
+out_unlock:
+	spin_unlock_irqrestore(&list->list_lock, flags);
+
+	/* copy out tmpbuf content to userspace */
+	if (ret && copy_to_user(buffer, tmpbuf, ret))
+		ret = -EFAULT;
+	kfree(tmpbuf);
+
 out:
 	mutex_unlock(&list->read_mutex);
 	return ret;
diff --git a/include/linux/hid-debug.h b/include/linux/hid-debug.h
index 8663f216c563..f58665651cb5 100644
--- a/include/linux/hid-debug.h
+++ b/include/linux/hid-debug.h
@@ -45,6 +45,7 @@ struct hid_debug_list {
 	struct fasync_struct *fasync;
 	struct hid_device *hdev;
 	struct list_head node;
+	spinlock_t list_lock;
 	struct mutex read_mutex;
 };
 
-- 
2.19.0

^ permalink raw reply related

* [PATCH 3/3] HID: debug: fix the ring buffer writer implementation
From: Vladis Dronov @ 2018-10-03 17:19 UTC (permalink / raw)
  To: Jiri Kosina, Benjamin Tissoires, linux-input, linux-kernel; +Cc: Vladis Dronov
In-Reply-To: <20181003171936.11271-1-vdronov@redhat.com>

ring buffer implementation hid_debug_event() is strange allowing a lost
of data. it does not move head pointer on append and uses per-byte for()
loop. fix this by introducing a new ring buffer implementation, which
overwrites the oldest data in case of the ring buffer overflow. it uses
some calculations for the buffer pointers but only 2 or less memcpy()
calls.

Signed-off-by: Vladis Dronov <vdronov@redhat.com>
---
 drivers/hid/hid-debug.c | 66 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 58 insertions(+), 8 deletions(-)

diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index de640e4132c7..63247ac4a9ce 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -661,20 +661,70 @@ EXPORT_SYMBOL_GPL(hid_dump_device);
 /* enqueue string to 'events' ring buffer */
 void hid_debug_event(struct hid_device *hdev, char *buf)
 {
-	unsigned i;
 	struct hid_debug_list *list;
-	unsigned long flags, flags2;
+	unsigned long flags1, flags2;
+	unsigned int part1, part2;
+	size_t len;
 
-	spin_lock_irqsave(&hdev->debug_list_lock, flags);
+	if (!buf || !buf[0])
+		return;
+
+	len = strlen(buf);
+
+	spin_lock_irqsave(&hdev->debug_list_lock, flags1);
 	list_for_each_entry(list, &hdev->debug_list, node) {
 		spin_lock_irqsave(&list->list_lock, flags2);
-		for (i = 0; buf[i]; i++)
-			list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] =
-				buf[i];
-		list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE;
+
+		/* len >= BUFSIZE, copy the last HID_DEBUG_BUFSIZE-1 bytes */
+		if (len > HID_DEBUG_BUFSIZE - 1) {
+			memcpy(list->hid_debug_buf,
+				buf + len - (HID_DEBUG_BUFSIZE - 1),
+				HID_DEBUG_BUFSIZE - 1);
+
+			list->head = 0;
+			list->tail = HID_DEBUG_BUFSIZE - 1;
+		} else {
+			/* append data to the ring buffer */
+			part1 = min_t(size_t, HID_DEBUG_BUFSIZE - list->tail,
+					len);
+			part2 = len - part1;
+
+			memcpy(list->hid_debug_buf + list->tail, buf, part1);
+
+			/* check if head must be moved as the ring is full */
+			if ((list->tail < list->head) &&
+			    (list->tail + part1 >= list->head)) {
+				list->head = list->tail + part1 + 1;
+
+				if (list->head >= HID_DEBUG_BUFSIZE)
+					list->head -= HID_DEBUG_BUFSIZE;
+			}
+
+			/* set tail after the end of appended data */
+			list->tail = list->tail + part1;
+			if (list->tail == HID_DEBUG_BUFSIZE) {
+				list->tail = 0;
+				if (list->head == 0)
+					list->head = 1;
+			}
+
+			/* append the 2nd part of data to the ring */
+			if (part2 > 0) {
+				memcpy(list->hid_debug_buf, buf + part1, part2);
+
+				/* check if head must be moved again as
+				 * the ring is full
+				 */
+				if (list->tail + part2 >= list->head)
+					list->head = list->tail + part2 + 1;
+
+				list->tail = list->tail + part2;
+			}
+		}
+
 		spin_unlock_irqrestore(&list->list_lock, flags2);
 	}
-	spin_unlock_irqrestore(&hdev->debug_list_lock, flags);
+	spin_unlock_irqrestore(&hdev->debug_list_lock, flags1);
 
 	wake_up_interruptible(&hdev->debug_wait);
 }
-- 
2.19.0

^ permalink raw reply related

* Re: [PATCH V2] hid: hid-core: Fix a sleep-in-atomic-context bug in __hid_request()
From: Jia-Ju Bai @ 2018-10-04  3:14 UTC (permalink / raw)
  To: Jiri Kosina; +Cc: benjamin.tissoires, linux-input, linux-kernel
In-Reply-To: <nycvar.YFH.7.76.1809292118240.15880@cbobk.fhfr.pm>



On 2018/9/30 3:20, Jiri Kosina wrote:
> On Sat, 29 Sep 2018, Jia-Ju Bai wrote:
>
>>>> picolcd_send_and_wait (acquire a spinlock)
>>>>     hid_hw_request
>>>>       __hid_request
>>>>         hid_alloc_report_buf(GFP_KERNEL)
>>>>
>>>> picolcd_reset (acquire a spinlock)
>>>>     hid_hw_request
>>>>       __hid_request
>>>>         hid_alloc_report_buf(GFP_KERNEL)
>>>>
>>>> lg4ff_play (acquire a spinlock)
>>>>     hid_hw_request
>>>>       __hid_request
>>>>         hid_alloc_report_buf(GFP_KERNEL)
>>>>
>>>> lg4ff_set_autocenter_ffex (acquire a spinlock)
>>>>     hid_hw_request
>>>>       __hid_request
>>>>         hid_alloc_report_buf(GFP_KERNEL)
>>> Hm, so it's always drivers calling out into core in atomic context. So
>>> either we take this, and put our bets on being able to allocate the buffer
>>> without sleeping,
>> In my opinion, I prefer this way.
> Why? Forcing all the report buffer to be limited to be non-sleeping
> allocations just because of two drivers, looks like an overkill, and
> actually calls for more issues (as GFP_ATOMIC is of course in principle
> less likely to succeed).
>

Okay, I thought that using GFP_ATOMIC is the simplest way to fix these bugs.
But I check the Linux kernel code again, and find that hid_hw_request() 
are called at many places.
So changing this function may affect many drivers.
I agree to only change the two drivers, and explicitly anotate 
__hid_request() with might_sleep().


Best wishes,
Jia-Ju Bai

^ permalink raw reply

* [PATCH V7 0/3]  da7280: haptic driver submission
From: Roy Im @ 2018-10-04  6:45 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
	linux-kernel

This patch adds support for the Dialog DA7280 Haptic driver IC.

In this patch set the following is provided:

[PATCH V2 1/3] MAINTAINERS file update for DA7280
[PATCH V2 2/3] DA7280 DT Binding
[PATCH V2 3/3] DA7280 Driver

This patch applies against linux-next and v4.19-rc6

Thank you,
Roy Im, Dialog Semiconductor Ltd.

Roy Im (3):
  MAINTAINERS: da7280 updates to the Dialog Semiconductor search terms
  dt-bindings: input: Add document bindings for DA7280
  Input: new da7280 haptic driver

 .../devicetree/bindings/input/dlg,da7280.txt       |  105 ++
 MAINTAINERS                                        |    2 +
 drivers/input/misc/Kconfig                         |   13 +
 drivers/input/misc/Makefile                        |    1 +
 drivers/input/misc/da7280.c                        | 1393 ++++++++++++++++++++
 drivers/input/misc/da7280.h                        |  412 ++++++
 6 files changed, 1926 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/dlg,da7280.txt
 create mode 100644 drivers/input/misc/da7280.c
 create mode 100644 drivers/input/misc/da7280.h

-- 
end-of-patch for PATCH V7

^ permalink raw reply

* [PATCH V7 2/3] dt-bindings: input: Add document bindings for DA7280
From: Roy Im @ 2018-10-04  6:45 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
	linux-kernel
In-Reply-To: <cover.1538635541.git.Roy.Im@diasemi.com>


Add device tree binding information for DA7280 haptic driver.
Example bindings for DA7280 are added.

Reviewed-by: Rob Herring <robh@kernel.org>.

Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>

---
v7: No changes.
v6: No changes.
v5: Updated descriptions and fixed errors.
v4: Fixed commit message, properties.
v3: Fixed subject format.
v2: No changes


 .../devicetree/bindings/input/dlg,da7280.txt       |  105 ++++++++++++++++++++
 1 file changed, 105 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/dlg,da7280.txt

diff --git a/Documentation/devicetree/bindings/input/dlg,da7280.txt b/Documentation/devicetree/bindings/input/dlg,da7280.txt
new file mode 100644
index 0000000..a25a12f
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/dlg,da7280.txt
@@ -0,0 +1,105 @@
+Dialog Semiconductor DA7280 Haptics bindings
+
+Required properties:
+- compatible: Should be "dlg,da7280".
+- reg: Specifies the I2C slave address.
+
+- interrupt-parent : Specifies the phandle of the interrupt controller to
+  which the IRQs from DA7280 are delivered to.
+
+- dlg,actuator-type: Set Actuator type. it should be one of:
+  "LRA" - Linear Resonance Actuator type.
+  "ERM-bar" - Bar type Eccentric Rotating Mass.
+  "ERM-coin" - Coin type Eccentric Rotating Mass.
+
+- dlg,op-mode: Haptic operation mode.
+  Possible values:
+	1 - Direct register override(DRO) mode triggered by i2c(default),
+	2 - PWM data source mode controlled by PWM duty,
+	3 - Register triggered waveform memory(RTWM) mode, the pattern
+	    assigned to the PS_SEQ_ID played as much times as PS_SEQ_LOOP,
+	4 - Edge triggered waveform memory(ETWM) mode, external GPI(N)
+	    control are required to enable/disable and it needs to keep
+	    device enabled by sending magnitude (X > 0),
+	    the pattern is assigned to the GPI(N)_SEQUENCE_ID below.
+	For more details, please see the datasheet.
+
+- dlg,nom-microvolt: Nominal actuator voltage rating.
+  Valid values: 0 - 6000000.
+- dlg,abs-max-microvolt: Absolute actuator maximum voltage rating.
+  Valid values: 0 - 6000000.
+- dlg,imax-microamp: Actuator max current rating.
+  Valid values: 0 - 252000.
+  Default: 130000.
+- dlg,impd-micro-ohms: the impedance of the actuator in micro ohms.
+  Valid values: 0 - 1500000000.
+
+Optional properties:
+- pwms : phandle to the physical PWM(Pulse Width Modulation) device.
+  PWM properties should be named "pwms". And number of cell is different
+  for each pwm device.
+  (See Documentation/devicetree/bindings/pwm/pwm.txt
+   for further information relating to pwm properties)
+
+- dlg,ps-seq-id: the PS_SEQ_ID(pattern ID in waveform memory inside chip)
+  to play back when RTWM-MODE is enabled.
+  Valid range: 0 - 15.
+- dlg,ps-seq-loop: the PS_SEQ_LOOP, Number of times the pre-stored sequence
+  pointed to by PS_SEQ_ID or GPI(N)_SEQUENCE_ID is repeated.
+  Valid range: 0 - 15.
+- dlg,gpiN-seq-id: the GPI(N)_SEQUENCE_ID, pattern to play
+  when gpi0 is triggered, 'N' must be 0 - 2.
+  Valid range: 0 - 15.
+- dlg,gpiN-mode: the pattern mode which can select either
+  "Single-pattern" or "Multi-pattern", 'N' must be 0 - 2.
+- dlg,gpiN-polarity: gpiN polarity which can be chosen among
+  "Rising-edge", "Falling-edge" and "Both-edge",
+  'N' must be 0 - 2
+  Haptic will work by this edge option in case of ETWM mode.
+
+- dlg,resonant-freq-hz: use in case of LRA.
+  the frequency range: 50 - 300.
+  Default: 205.
+
+- dlg,bemf-sens-enable: Enable for internal loop computations.
+- dlg,freq-track-enable: Enable for resonant frequency tracking.
+- dlg,acc-enable: Enable for active acceleration.
+- dlg,rapid-stop-enable: Enable for rapid stop.
+- dlg,amp-pid-enable: Enable for the amplitude PID.
+- dlg,mem-array: Customized waveform memory(patterns) data downloaded to
+  the device during initialization. This is an array of 100 values(u8).
+
+For further information, see device datasheet.
+
+======
+
+Example:
+
+	haptics: da7280-haptics@4a {
+		compatible = "dlg,da7280";
+		reg = <0x4a>;
+		interrupt-parent = <&gpio6>;
+		interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
+		dlg,actuator-type = "LRA";
+		dlg,op-mode = <1>;
+		dlg,nom-microvolt = <2000000>;
+		dlg,abs-max-microvolt = <2000000>;
+		dlg,imax-microamp = <170000>;
+		dlg,resonant-freq-hz = <180>;
+		dlg,impd-micro-ohms = <10500000>;
+		dlg,freq-track-enable;
+		dlg,rapid-stop-enable;
+		dlg,mem-array = <
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 		  0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+		>;
+
+	};
-- 
end-of-patch for PATCH V7

^ permalink raw reply related

* [PATCH V7 1/3] MAINTAINERS: da7280 updates to the Dialog Semiconductor search terms
From: Roy Im @ 2018-10-04  6:45 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
	linux-kernel
In-Reply-To: <cover.1538635541.git.Roy.Im@diasemi.com>


This patch adds the da7280 bindings doc and driver to the Dialog
Semiconductor support list.

Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>

---
v7: No changes.
v6: No changes.
v5: No changes.
v4: No changes.
v3: No changes.
v2: No changes.


 MAINTAINERS |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index b22e7fd..1af587f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4313,6 +4313,7 @@ S:	Supported
 F:	Documentation/hwmon/da90??
 F:	Documentation/devicetree/bindings/mfd/da90*.txt
 F:	Documentation/devicetree/bindings/input/da90??-onkey.txt
+F:	Documentation/devicetree/bindings/input/dlg,da72??.txt
 F:	Documentation/devicetree/bindings/thermal/da90??-thermal.txt
 F:	Documentation/devicetree/bindings/regulator/da92*.txt
 F:	Documentation/devicetree/bindings/watchdog/da90??-wdt.txt
@@ -4321,6 +4322,7 @@ F:	drivers/gpio/gpio-da90??.c
 F:	drivers/hwmon/da90??-hwmon.c
 F:	drivers/iio/adc/da91??-*.c
 F:	drivers/input/misc/da90??_onkey.c
+F:	drivers/input/misc/da72??.[ch]
 F:	drivers/input/touchscreen/da9052_tsi.c
 F:	drivers/leds/leds-da90??.c
 F:	drivers/mfd/da903x.c
-- 
end-of-patch for PATCH V7

^ permalink raw reply related

* [PATCH V7 3/3] Input: new da7280 haptic driver
From: Roy Im @ 2018-10-04  6:45 UTC (permalink / raw)
  To: Dmitry Torokhov, Rob Herring
  Cc: Mark Rutland, Support Opensource, devicetree, linux-input,
	linux-kernel
In-Reply-To: <cover.1538635541.git.Roy.Im@diasemi.com>


Adds support for the Dialog DA7280 LRA/ERM Haptic Driver with
multiple mode and integrated waveform memory and wideband support.
It communicates via an I2C bus to the device.

Signed-off-by: Roy Im <roy.im.opensource@diasemi.com>

---
v7: 
	- Added more attributes to handle one value per file.
	- Replaced and updated the dt-related code and functions called.
	- Fixed error/functions.
	- Rebased to v4.19-rc6.
v6: No changes.
v5: Fixed errors in Kconfig file.
v4: Updated code as dt-bindings are changed.
v3: No changes.
v2: Fixed kbuild error/warning


 drivers/input/misc/Kconfig  |   13 +
 drivers/input/misc/Makefile |    1 +
 drivers/input/misc/da7280.c | 1393 +++++++++++++++++++++++++++++++++++++++++++
 drivers/input/misc/da7280.h |  412 +++++++++++++
 4 files changed, 1819 insertions(+)
 create mode 100644 drivers/input/misc/da7280.c
 create mode 100644 drivers/input/misc/da7280.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index ca59a2b..751cac6 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -851,4 +851,17 @@ config INPUT_SC27XX_VIBRA
 	  To compile this driver as a module, choose M here. The module will
 	  be called sc27xx_vibra.
 
+config INPUT_DA7280_HAPTICS
+	tristate "Dialog Semiconductor DA7280 haptics support"
+	depends on INPUT && I2C
+	select INPUT_FF_MEMLESS
+	select REGMAP_I2C
+	help
+	  Say Y to enable support for the Dialog DA7280 haptics driver.
+	  The haptics can be controlled by i2c communication,
+	  or by PWM input, or by GPI.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called da7280.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9d0f9d1..d941348 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_INPUT_CMA3000)		+= cma3000_d0x.o
 obj-$(CONFIG_INPUT_CMA3000_I2C)		+= cma3000_d0x_i2c.o
 obj-$(CONFIG_INPUT_COBALT_BTNS)		+= cobalt_btns.o
 obj-$(CONFIG_INPUT_CPCAP_PWRBUTTON)	+= cpcap-pwrbutton.o
+obj-$(CONFIG_INPUT_DA7280_HAPTICS)	+= da7280.o
 obj-$(CONFIG_INPUT_DA9052_ONKEY)	+= da9052_onkey.o
 obj-$(CONFIG_INPUT_DA9055_ONKEY)	+= da9055_onkey.o
 obj-$(CONFIG_INPUT_DA9063_ONKEY)	+= da9063_onkey.o
diff --git a/drivers/input/misc/da7280.c b/drivers/input/misc/da7280.c
new file mode 100644
index 0000000..b87bf7c
--- /dev/null
+++ b/drivers/input/misc/da7280.c
@@ -0,0 +1,1393 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * DA7280 Haptic device driver
+ *
+ * Copyright (c) 2018 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include "da7280.h"
+
+/* uV unit for voltage rate */
+#define DA7280_VOLTAGE_RATE_MAX		6000000
+#define DA7280_VOLTAGE_RATE_STEP	23400
+#define DA7280_NOMMAX_DFT		0x6B
+#define DA7280_ABSMAX_DFT		0x78
+
+#define DA7280_IMPD_MAX			1500000000
+#define DA7280_IMPD_DEFAULT		22000000
+
+#define DA7280_IMAX_DEFAULT		0x0E
+/* uA unit step and limit for IMAX*/
+#define DA7280_IMAX_STEP		7200
+#define DA7280_IMAX_LIMIT		252000
+
+#define DA7280_RESONT_FREQH_DFT		0x39
+#define DA7280_RESONT_FREQL_DFT		0x32
+#define DA7280_MIN_RESONAT_FREQ_HZ	50
+#define DA7280_MAX_RESONAT_FREQ_HZ	300
+#define DA7280_MIN_PWM_FREQ_KHZ		10
+#define DA7280_MAX_PWM_FREQ_KHZ		250
+
+#define DA7280_SEQ_ID_MAX		15
+#define DA7280_SEQ_LOOP_MAX		15
+#define DA7280_GPI1_SEQ_ID_DEFT	0x0
+
+#define DA7280_SNP_MEM_SIZE		100
+#define DA7280_SNP_MEM_MAX		DA7280_SNP_MEM_99
+
+#define IRQ_NUM				3
+
+#define DA7280_SKIP_INIT		0x100
+
+enum da7280_haptic_dev_t {
+	DA7280_LRA	= 0,
+	DA7280_ERM_BAR	= 1,
+	DA7280_ERM_COIN	= 2,
+	DA7280_DEV_MAX,
+};
+
+enum da7280_op_mode {
+	DA7280_INACTIVE		= 0,
+	DA7280_DRO_MODE		= 1,
+	DA7280_PWM_MODE		= 2,
+	DA7280_RTWM_MODE	= 3,
+	DA7280_ETWM_MODE	= 4,
+	DA7280_OPMODE_MAX,
+};
+
+struct da7280_gpi_ctl {
+	u8 seq_id;
+	u8 mode;
+	u8 polarity;
+};
+
+struct da7280_haptic {
+	struct regmap *regmap;
+	struct input_dev *input_dev;
+	struct device *dev;
+	struct i2c_client *client;
+	struct pwm_device *pwm_dev;
+	bool	legacy;
+	int pwm_id;
+	struct work_struct work;
+
+	unsigned int magnitude;
+
+	u8 dev_type;
+	u8 op_mode;
+	u16 nommax;
+	u16 absmax;
+	u32 imax;
+	u32 impd;
+	u32 resonant_freq_h;
+	u32 resonant_freq_l;
+	bool bemf_sense_en;
+	bool freq_track_en;
+	bool acc_en;
+	bool rapid_stop_en;
+	bool amp_pid_en;
+	u8 ps_seq_id;
+	u8 ps_seq_loop;
+	struct da7280_gpi_ctl gpi_ctl[3];
+	bool mem_update;
+	u8 snp_mem[DA7280_SNP_MEM_SIZE];
+	u8 enabled;
+};
+
+static bool da7280_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case DA7280_IRQ_EVENT1:
+	case DA7280_IRQ_EVENT_WARNING_DIAG:
+	case DA7280_IRQ_EVENT_SEQ_DIAG:
+	case DA7280_IRQ_STATUS1:
+	case DA7280_TOP_CTL1:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config da7280_haptic_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = DA7280_SNP_MEM_MAX,
+	.volatile_reg = da7280_volatile_register,
+};
+
+static int da7280_haptic_mem_update(struct da7280_haptic *haptics)
+{
+	int error;
+	unsigned int val;
+
+	/* It is recommended to update the patterns
+	 * during haptic is not working in order to avoid conflict
+	 */
+	error = regmap_read(haptics->regmap, DA7280_IRQ_STATUS1, &val);
+	if (error)
+		return error;
+	if (val & DA7280_STA_WARNING_MASK) {
+		dev_warn(haptics->dev,
+			 "Warning! Please check HAPTIC status.\n");
+		return -EBUSY;
+	}
+
+	/* Patterns are not updated if the lock bit is enabled */
+	val = 0;
+	error = regmap_read(haptics->regmap, DA7280_MEM_CTL2, &val);
+	if (error)
+		return error;
+	if (~val & DA7280_WAV_MEM_LOCK_MASK) {
+		dev_warn(haptics->dev,
+			 "Please unlock the bit first\n");
+		return -EACCES;
+	}
+
+	/* Set to Inactive mode to make sure safety */
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_OPERATION_MODE_MASK,
+				   0);
+	if (error)
+		return error;
+
+	error = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+	if (error)
+		return error;
+
+	return regmap_bulk_write(haptics->regmap, val,
+			haptics->snp_mem, DA7280_SNP_MEM_MAX - val + 1);
+}
+
+static int da7280_haptic_set_pwm(struct da7280_haptic *haptics)
+{
+	struct pwm_args pargs;
+	u64 period_mag_multi;
+	unsigned int pwm_duty;
+	int error;
+
+	pwm_get_args(haptics->pwm_dev, &pargs);
+	period_mag_multi =
+		(u64)(pargs.period * haptics->magnitude);
+	if (haptics->acc_en)
+		pwm_duty =
+			(unsigned int)(period_mag_multi >> 16);
+	else
+		pwm_duty =
+			(unsigned int)((period_mag_multi >> 16)
+				+ pargs.period) / 2;
+
+	error = pwm_config(haptics->pwm_dev,
+			   pwm_duty, pargs.period);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to configure pwm : %d\n", error);
+		return error;
+	}
+
+	error = pwm_enable(haptics->pwm_dev);
+	if (error) {
+		pwm_disable(haptics->pwm_dev);
+		dev_err(haptics->dev,
+			"failed to enable haptics pwm device : %d\n", error);
+	}
+
+	return error;
+}
+
+static void da7280_haptic_enable(struct da7280_haptic *haptics)
+{
+	int error = 0;
+
+	if (haptics->enabled)
+		return;
+
+	switch (haptics->op_mode) {
+	case DA7280_DRO_MODE:
+		/* the valid range check when acc_en is enabled */
+		if (haptics->acc_en && haptics->magnitude > 0x7F)
+			haptics->magnitude = 0x7F;
+		else if (haptics->magnitude > 0xFF)
+			haptics->magnitude = 0xFF;
+
+		/* Set driver level
+		 * as a % of ACTUATOR_NOMMAX(nommax)
+		 */
+		error = regmap_write(haptics->regmap,
+				     DA7280_TOP_CTL2,
+				     haptics->magnitude);
+		if (error) {
+			dev_err(haptics->dev,
+				"i2c err for driving level set : %d\n",
+				error);
+			return;
+		}
+		break;
+	case DA7280_PWM_MODE:
+		if (da7280_haptic_set_pwm(haptics))
+			return;
+		break;
+	case DA7280_RTWM_MODE:
+		/* PS_SEQ_ID will be played
+		 * as many times as the PS_SEQ_LOOP
+		 */
+	case DA7280_ETWM_MODE:
+		/* Now users are able to control the GPI(N)
+		 * assigned to GPI_0, GPI1 and GPI2 accordingly
+		 * please see the datasheet for details.
+		 * GPI(N)_SEQUENCE_ID will be played
+		 * as many times as the PS_SEQ_LOOP
+		 */
+		break;
+	default:
+		dev_err(haptics->dev,
+			"Invalid Mode(%d)\n", haptics->op_mode);
+		return;
+	}
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_OPERATION_MODE_MASK,
+				   haptics->op_mode);
+	if (error) {
+		dev_err(haptics->dev,
+			"i2c err for op_mode setting : %d\n", error);
+		return;
+	}
+
+	if (haptics->op_mode == DA7280_PWM_MODE ||
+	    haptics->op_mode == DA7280_RTWM_MODE) {
+		error = regmap_update_bits(haptics->regmap,
+					   DA7280_TOP_CTL1,
+					   DA7280_SEQ_START_MASK,
+					   DA7280_SEQ_START_MASK);
+		if (error) {
+			dev_err(haptics->dev,
+				"i2c err for sequence triggering : %d\n",
+				error);
+			return;
+		}
+	}
+
+	haptics->enabled = true;
+}
+
+static void da7280_haptic_disable(struct da7280_haptic *haptics)
+{
+	int error;
+
+	if (!haptics->enabled)
+		return;
+
+	/* Set to Inactive mode */
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_OPERATION_MODE_MASK, 0);
+	if (error) {
+		dev_err(haptics->dev,
+			"i2c err for op_mode off : %d\n", error);
+		return;
+	}
+
+	switch (haptics->op_mode) {
+	case DA7280_RTWM_MODE:
+	case DA7280_ETWM_MODE:
+		error = regmap_update_bits(haptics->regmap,
+					   DA7280_TOP_CTL1,
+					   DA7280_SEQ_START_MASK, 0);
+		if (error) {
+			dev_err(haptics->dev,
+				"i2c err for RTWM or ETWM mode off : %d\n",
+				error);
+			return;
+		}
+		break;
+	case DA7280_DRO_MODE:
+		error = regmap_write(haptics->regmap,
+				     DA7280_TOP_CTL2, 0);
+		if (error) {
+			dev_err(haptics->dev,
+				"i2c err for DRO mode off : %d\n",
+				error);
+			return;
+		}
+		break;
+	case DA7280_PWM_MODE:
+		pwm_disable(haptics->pwm_dev);
+		break;
+	default:
+		dev_err(haptics->dev,
+			"Invalid Mode(%d)\n", haptics->op_mode);
+		break;
+	}
+
+	if (haptics->op_mode < DA7280_OPMODE_MAX)
+		haptics->enabled = false;
+}
+
+static void da7280_haptic_work(struct work_struct *work)
+{
+	struct da7280_haptic *haptics =
+			container_of(work, struct da7280_haptic, work);
+
+	if (haptics->magnitude)
+		da7280_haptic_enable(haptics);
+	else
+		da7280_haptic_disable(haptics);
+}
+
+static int da7280_haptic_play(struct input_dev *dev, void *data,
+			      struct ff_effect *effect)
+{
+	struct da7280_haptic *haptics = input_get_drvdata(dev);
+
+	if (effect->u.rumble.strong_magnitude > 0)
+		haptics->magnitude = effect->u.rumble.strong_magnitude;
+	else if (effect->u.rumble.weak_magnitude > 0)
+		haptics->magnitude = effect->u.rumble.weak_magnitude;
+	else
+		haptics->magnitude = 0;
+
+	schedule_work(&haptics->work);
+
+	return 0;
+}
+
+static int da7280_haptic_open(struct input_dev *dev)
+{
+	struct da7280_haptic *haptics = input_get_drvdata(dev);
+	int error;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_STANDBY_EN_MASK,
+				   DA7280_STANDBY_EN_MASK);
+	if (error)
+		dev_err(haptics->dev,
+			"Failed to open haptic, i2c error : %d\n", error);
+
+	return error;
+}
+
+static void da7280_haptic_close(struct input_dev *dev)
+{
+	struct da7280_haptic *haptics = input_get_drvdata(dev);
+	int error;
+
+	cancel_work_sync(&haptics->work);
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_OPERATION_MODE_MASK, 0);
+	if (error)
+		goto error_i2c;
+
+	if (haptics->op_mode == DA7280_DRO_MODE) {
+		error = regmap_write(haptics->regmap,
+				     DA7280_TOP_CTL2, 0);
+
+		if (error)
+			goto error_i2c;
+	}
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_STANDBY_EN_MASK, 0);
+	if (error)
+		goto error_i2c;
+
+	return;
+
+error_i2c:
+	dev_err(haptics->dev, "DA7280-haptic i2c error : %d\n", error);
+}
+
+static u8 da7280_haptic_of_mode_str(struct device *dev,
+				    const char *str)
+{
+	if (!strcmp(str, "LRA"))
+		return DA7280_LRA;
+	else if (!strcmp(str, "ERM-bar"))
+		return DA7280_ERM_BAR;
+	else if (!strcmp(str, "ERM-coin"))
+		return DA7280_ERM_COIN;
+
+	dev_warn(dev, "Invalid string - set to default\n");
+	return DA7280_LRA;
+}
+
+static u8 da7280_haptic_of_gpi_mode_str(struct device *dev,
+					const char *str)
+{
+	if (!strcmp(str, "Single-pattern"))
+		return 0;
+	else if (!strcmp(str, "Multi-pattern"))
+		return 1;
+
+	dev_warn(dev, "Invalid string - set to default\n");
+	return 0;
+}
+
+static u8 da7280_haptic_of_gpi_pol_str(struct device *dev,
+				       const char *str)
+{
+	if (!strcmp(str, "Rising-edge"))
+		return 0;
+	else if (!strcmp(str, "Falling-edge"))
+		return 1;
+	else if (!strcmp(str, "Both-edge"))
+		return 2;
+
+	dev_warn(dev, "Invalid string - set to default\n");
+	return 0;
+}
+
+static u8 da7280_haptic_of_volt_rating_set(u32 val)
+{
+	u32 voltage;
+
+	voltage = val / DA7280_VOLTAGE_RATE_STEP + 1;
+
+	if (voltage > 0xFF)
+		return 0xFF;
+	return (u8)voltage;
+}
+
+static void da7280_parse_properties(struct device *dev,
+				    struct da7280_haptic *haptics)
+{
+	char gpi_str1[] = "dlg,gpi0-seq-id";
+	char gpi_str2[] = "dlg,gpi0-mode";
+	char gpi_str3[] = "dlg,gpi0-polarity";
+	unsigned int i, mem[DA7280_SNP_MEM_SIZE];
+	const char *str;
+	u32 val;
+
+	if (!device_property_read_string(dev, "dlg,actuator-type", &str))
+		haptics->dev_type =
+			da7280_haptic_of_mode_str(dev, str);
+	else /* if no property, then use the mode inside chip */
+		haptics->dev_type = DA7280_DEV_MAX;
+
+	if (device_property_read_u32(dev, "dlg,op-mode", &val) >= 0)
+		if (val > 0 && val < DA7280_OPMODE_MAX)
+			haptics->op_mode = val;
+		else
+			haptics->op_mode = DA7280_DRO_MODE;
+	else
+		haptics->op_mode = DA7280_DRO_MODE;
+
+	if (device_property_read_u32(dev, "dlg,nom-microvolt", &val) >= 0)
+		if (val < DA7280_VOLTAGE_RATE_MAX)
+			haptics->nommax =
+				da7280_haptic_of_volt_rating_set(val);
+		else
+			haptics->nommax = DA7280_SKIP_INIT;
+	else /* if no property, then use the value inside chip */
+		haptics->nommax = DA7280_SKIP_INIT;
+
+	if (device_property_read_u32(dev, "dlg,abs-max-microvolt",
+				     &val) >= 0)
+		if (val < DA7280_VOLTAGE_RATE_MAX)
+			haptics->absmax =
+				da7280_haptic_of_volt_rating_set(val);
+		else
+			haptics->absmax = DA7280_SKIP_INIT;
+	else
+		haptics->absmax = DA7280_SKIP_INIT;
+
+	if (device_property_read_u32(dev, "dlg,imax-microamp", &val) >= 0)
+		if (val < DA7280_IMAX_LIMIT)
+			haptics->imax = (val - 28600)
+					/ DA7280_IMAX_STEP + 1;
+		else
+			haptics->imax = DA7280_IMAX_DEFAULT;
+	else
+		haptics->imax = DA7280_IMAX_DEFAULT;
+
+	if (device_property_read_u32(dev, "dlg,impd-micro-ohms", &val) >= 0)
+		if (val <= DA7280_IMPD_MAX)
+			haptics->impd = val;
+		else
+			haptics->impd = DA7280_IMPD_DEFAULT;
+	else
+		haptics->impd = DA7280_IMPD_DEFAULT;
+
+	if (device_property_read_u32(dev, "dlg,resonant-freq-hz",
+				     &val) >= 0) {
+		if (val < DA7280_MAX_RESONAT_FREQ_HZ &&
+		    val > DA7280_MIN_RESONAT_FREQ_HZ) {
+			haptics->resonant_freq_h =
+				((1000000000 / (val * 1333)) >> 7) & 0xFF;
+			haptics->resonant_freq_l =
+				(1000000000 / (val * 1333)) & 0x7F;
+		} else {
+			haptics->resonant_freq_h =
+				DA7280_RESONT_FREQH_DFT;
+			haptics->resonant_freq_l =
+				DA7280_RESONT_FREQL_DFT;
+		}
+	} else {
+		haptics->resonant_freq_h = DA7280_SKIP_INIT;
+		haptics->resonant_freq_l = DA7280_SKIP_INIT;
+	}
+
+	if (device_property_read_u32(dev, "dlg,ps-seq-id", &val) >= 0)
+		if (val <= DA7280_SEQ_ID_MAX)
+			haptics->ps_seq_id = val;
+		else
+			haptics->ps_seq_id = 0;
+	else /* if no property, set to zero as a default do nothing */
+		haptics->ps_seq_id = 0;
+
+	if (device_property_read_u32(dev, "dlg,ps-seq-loop", &val) >= 0)
+		if (val <= DA7280_SEQ_LOOP_MAX)
+			haptics->ps_seq_loop = val;
+		else
+			haptics->ps_seq_loop = 0;
+	else /* if no property, then do nothing */
+		haptics->ps_seq_loop = 0;
+
+	/* GPI0~2 Control */
+	for (i = 0; i < 3; i++) {
+		gpi_str1[7] = '0' + i;
+		if (device_property_read_u32 (dev, gpi_str1, &val) >= 0)
+			if (val <= DA7280_SEQ_ID_MAX)
+				haptics->gpi_ctl[i].seq_id = val;
+			else
+				haptics->gpi_ctl[i].seq_id =
+					DA7280_GPI1_SEQ_ID_DEFT + i;
+		else /* if no property, then do nothing */
+			haptics->gpi_ctl[i].seq_id =
+				DA7280_GPI1_SEQ_ID_DEFT + i;
+
+		gpi_str2[7] = '0' + i;
+		if (!device_property_read_string(dev, gpi_str2, &str))
+			haptics->gpi_ctl[i].mode =
+				da7280_haptic_of_gpi_mode_str(dev, str);
+		else
+			haptics->gpi_ctl[i].mode = 0;
+
+		gpi_str3[7] = '0' + i;
+		if (!device_property_read_string(dev, gpi_str3, &str))
+			haptics->gpi_ctl[i].polarity =
+				da7280_haptic_of_gpi_pol_str(dev, str);
+		else
+			haptics->gpi_ctl[i].polarity = 0;
+	}
+
+	haptics->bemf_sense_en =
+		device_property_read_bool(dev, "dlg,bemf-sens-enable");
+	haptics->freq_track_en =
+		device_property_read_bool(dev, "dlg,freq-track-enable");
+	haptics->acc_en =
+		device_property_read_bool(dev, "dlg,acc-enable");
+	haptics->rapid_stop_en =
+		device_property_read_bool(dev, "dlg,rapid-stop-enable");
+	haptics->amp_pid_en =
+		device_property_read_bool(dev, "dlg,amp-pid-enable");
+
+	if (device_property_read_u32_array(dev, "dlg,mem-array",
+					   &mem[0],
+					   DA7280_SNP_MEM_SIZE) >= 0) {
+		haptics->mem_update = true;
+		for (i = 0; i < DA7280_SNP_MEM_SIZE; i++) {
+			if (mem[i] > 0xff)
+				haptics->snp_mem[i] = 0x0;
+			else
+				haptics->snp_mem[i] = (u8)mem[i];
+		}
+	} else {
+		haptics->mem_update = false;
+	}
+}
+
+static irqreturn_t da7280_irq_handler(int irq, void *data)
+{
+	struct da7280_haptic *haptics = data;
+	u8 events[IRQ_NUM];
+	int error;
+
+	/* Check what events have happened */
+	error = regmap_bulk_read(haptics->regmap,
+				 DA7280_IRQ_EVENT1,
+				 events, IRQ_NUM);
+	if (error)
+		goto error_i2c;
+
+	/* Empty check due to shared interrupt */
+	if ((events[0] | events[1] | events[2]) == 0x00)
+		return IRQ_HANDLED;
+
+	if (events[0] & DA7280_E_SEQ_FAULT_MASK) {
+		/* Stop first if Haptic is working
+		 * Otherwise, the fault may happen continually
+		 * even though the bit is cleared.
+		 */
+		error = regmap_update_bits(haptics->regmap,
+					   DA7280_TOP_CTL1,
+					   DA7280_OPERATION_MODE_MASK, 0);
+		if (error)
+			goto error_i2c;
+	}
+
+	/* Clear events */
+	error = regmap_write(haptics->regmap,
+			     DA7280_IRQ_EVENT1, events[0]);
+	if (error)
+		goto error_i2c;
+
+	return IRQ_HANDLED;
+
+error_i2c:
+	dev_err(haptics->dev, "da7280 i2c error : %d\n", error);
+	return IRQ_NONE;
+}
+
+static int da7280_init(struct da7280_haptic *haptics)
+{
+	int error, i;
+	unsigned int val = 0;
+	u32 v2i_factor;
+	u8 mask = 0;
+
+	/* If device type is DA7280_DEV_MAX,
+	 * then just use default value inside chip.
+	 */
+	if (haptics->dev_type == DA7280_DEV_MAX) {
+		error = regmap_read(haptics->regmap, DA7280_TOP_CFG1, &val);
+		if (error)
+			goto error_i2c;
+		if (val & DA7280_ACTUATOR_TYPE_MASK)
+			haptics->dev_type = DA7280_ERM_COIN;
+		else
+			haptics->dev_type = DA7280_LRA;
+	}
+
+	/* Apply user settings */
+	if (haptics->dev_type == DA7280_LRA) {
+		if (haptics->resonant_freq_l != DA7280_SKIP_INIT) {
+			error = regmap_write(haptics->regmap,
+					     DA7280_FRQ_LRA_PER_H,
+					     haptics->resonant_freq_h);
+			if (error)
+				goto error_i2c;
+			error = regmap_write(haptics->regmap,
+					     DA7280_FRQ_LRA_PER_L,
+					     haptics->resonant_freq_l);
+			if (error)
+				goto error_i2c;
+		}
+	} else if (haptics->dev_type == DA7280_ERM_COIN) {
+		error = regmap_update_bits(haptics->regmap,
+					   DA7280_TOP_INT_CFG1,
+					   DA7280_BEMF_FAULT_LIM_MASK, 0);
+		if (error)
+			goto error_i2c;
+
+		mask = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+			DA7280_V2I_FACTOR_FREEZE_MASK;
+		val = DA7280_TST_CALIB_IMPEDANCE_DIS_MASK |
+			DA7280_V2I_FACTOR_FREEZE_MASK;
+		error = regmap_update_bits(haptics->regmap,
+					   DA7280_TOP_CFG4,
+					   mask, val);
+		if (error)
+			goto error_i2c;
+
+		haptics->acc_en = false;
+		haptics->rapid_stop_en = false;
+		haptics->amp_pid_en = false;
+	}
+
+	/* Should be set to 0 only
+	 * in custom waveform and wideband operation
+	 */
+	if (haptics->op_mode >= DA7280_RTWM_MODE)
+		haptics->bemf_sense_en = false;
+
+	mask = DA7280_ACTUATOR_TYPE_MASK |
+			DA7280_BEMF_SENSE_EN_MASK |
+			DA7280_FREQ_TRACK_EN_MASK |
+			DA7280_ACCELERATION_EN_MASK |
+			DA7280_RAPID_STOP_EN_MASK |
+			DA7280_AMP_PID_EN_MASK;
+
+	val = (haptics->dev_type ? 1 : 0)
+			<< DA7280_ACTUATOR_TYPE_SHIFT |
+		(haptics->bemf_sense_en ? 1 : 0)
+			<< DA7280_BEMF_SENSE_EN_SHIFT |
+		(haptics->freq_track_en ? 1 : 0)
+			<< DA7280_FREQ_TRACK_EN_SHIFT |
+		(haptics->acc_en ? 1 : 0)
+			<< DA7280_ACCELERATION_EN_SHIFT |
+		(haptics->rapid_stop_en ? 1 : 0)
+			<< DA7280_RAPID_STOP_EN_SHIFT |
+		(haptics->amp_pid_en ? 1 : 0)
+			<< DA7280_AMP_PID_EN_SHIFT;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CFG1, mask, val);
+	if (error)
+		goto error_i2c;
+
+	if (haptics->nommax != DA7280_SKIP_INIT) {
+		error = regmap_write(haptics->regmap,
+				     DA7280_ACTUATOR1,
+				     haptics->nommax);
+		if (error)
+			goto error_i2c;
+	}
+
+	if (haptics->absmax != DA7280_SKIP_INIT) {
+		error = regmap_write(haptics->regmap, DA7280_ACTUATOR2,
+				     haptics->absmax);
+		if (error)
+			goto error_i2c;
+	}
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_ACTUATOR3,
+				   DA7280_IMAX_MASK,
+				   haptics->imax);
+	if (error)
+		goto error_i2c;
+
+	v2i_factor =
+		haptics->impd * (haptics->imax + 4) / 1610400;
+	error = regmap_write(haptics->regmap,
+			     DA7280_CALIB_V2I_L,
+			     v2i_factor & 0xff);
+	if (error)
+		goto error_i2c;
+	error = regmap_write(haptics->regmap,
+			     DA7280_CALIB_V2I_H,
+			     v2i_factor >> 8);
+	if (error)
+		goto error_i2c;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_STANDBY_EN_MASK,
+				   DA7280_STANDBY_EN_MASK);
+	if (error)
+		goto error_i2c;
+
+	if (haptics->mem_update) {
+		error = da7280_haptic_mem_update(haptics);
+		if (error)
+			goto error_i2c;
+	}
+
+	/* Set  PS_SEQ_ID and PS_SEQ_LOOP */
+	val = haptics->ps_seq_id << DA7280_PS_SEQ_ID_SHIFT |
+		haptics->ps_seq_loop << DA7280_PS_SEQ_LOOP_SHIFT;
+	error = regmap_write(haptics->regmap,
+			     DA7280_SEQ_CTL2, val);
+	if (error)
+		goto error_i2c;
+
+	/* GPI(N) CTL */
+	for (i = 0; i < 3; i++) {
+		val = haptics->gpi_ctl[i].seq_id
+				<< DA7280_GPI0_SEQUENCE_ID_SHIFT |
+			haptics->gpi_ctl[i].mode
+				<< DA7280_GPI0_MODE_SHIFT |
+			haptics->gpi_ctl[i].polarity
+				<< DA7280_GPI0_POLARITY_SHIFT;
+		error = regmap_write(haptics->regmap,
+				     DA7280_GPI_0_CTL + i, val);
+		if (error)
+			goto error_i2c;
+	}
+
+	/* Clear Interrupts */
+	error = regmap_write(haptics->regmap, DA7280_IRQ_EVENT1, 0xff);
+	if (error)
+		goto error_i2c;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_IRQ_MASK1,
+				   DA7280_SEQ_FAULT_M_MASK
+				   | DA7280_SEQ_DONE_M_MASK, 0);
+	if (error)
+		goto error_i2c;
+
+	haptics->enabled = false;
+	return 0;
+
+error_i2c:
+	dev_err(haptics->dev, "haptic init - I2C error : %d\n", error);
+	return error;
+}
+
+/* Valid format for ps_seq_id
+ * echo X > ps_seq_id
+ * ex) echo 2 > /sys/class/..../ps_seq_id
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_id_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf,
+			       size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	long val = 0xff;
+	int error;
+
+	if (kstrtol(&buf[0], 0, &val) < 0)
+		goto err;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_SEQ_CTL2,
+				   DA7280_PS_SEQ_ID_MASK,
+				   (val & 0xf) >> DA7280_PS_SEQ_ID_SHIFT);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to update register : %d\n", error);
+		return error;
+	}
+
+	haptics->ps_seq_id = val & 0xf;
+
+	return count;
+
+err:
+	dev_err(dev, "Invalid input\n");
+	return count;
+}
+
+static ssize_t ps_seq_id_show(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error;
+	unsigned int val;
+
+	error = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to read register : %d\n", error);
+		return error;
+	}
+	val = (val & DA7280_PS_SEQ_ID_MASK)
+		>> DA7280_PS_SEQ_ID_SHIFT;
+
+	return sprintf(buf, "ps_seq_id is %d\n", val);
+}
+
+/* Valid format for ps_seq_loop
+ * echo X > ps_seq_loop
+ * ex) echo 2 > /sys/class/..../ps_seq_loop
+ * 0 <= X <= 15.
+ */
+static ssize_t ps_seq_loop_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	long val = 0xff;
+	int error;
+
+	if (kstrtol(&buf[0], 0, &val) < 0)
+		goto err;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_SEQ_CTL2,
+				   DA7280_PS_SEQ_LOOP_MASK,
+				   (val & 0xF) << DA7280_PS_SEQ_LOOP_SHIFT);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to update register : %d\n", error);
+		return error;
+	}
+
+	haptics->ps_seq_loop = (val & 0xF);
+
+	return count;
+err:
+	dev_err(dev, "Invalid input value!\n");
+	return count;
+}
+
+static ssize_t ps_seq_loop_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error;
+	unsigned int val;
+
+	error = regmap_read(haptics->regmap, DA7280_SEQ_CTL2, &val);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to read register : %d\n", error);
+		return error;
+	}
+	val = (val & DA7280_PS_SEQ_LOOP_MASK)
+				>> DA7280_PS_SEQ_LOOP_SHIFT;
+
+	return sprintf(buf, "ps_seq_loop is %d\n", val);
+}
+
+/* Valid format for GPIx_SEQUENCE_ID
+ * echo X > ./gpi_seq_id0
+ * Range of X: 0 <= X <= 15
+ * ex)
+ *	echo 1 > /sys/class/..../gpi_seq_id0
+ *	echo 2 > /sys/class/..../gpi_seq_id1
+ *	echo 3 > /sys/class/..../gpi_seq_id2
+ */
+static ssize_t gpi_seq_id0_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	long val = 0xff;
+	int error;
+
+	if (kstrtol(&buf[0], 0, &val) < 0)
+		goto err;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_GPI_0_CTL,
+				   DA7280_GPI0_SEQUENCE_ID_MASK,
+				   (val & 0xf)
+				   << DA7280_GPI0_SEQUENCE_ID_SHIFT);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to update register : %d\n", error);
+		return error;
+	}
+
+	haptics->gpi_ctl[0].seq_id = val & 0xf;
+
+	return count;
+
+err:
+	dev_err(dev, "Invalid input\n");
+	return count;
+}
+
+static ssize_t gpi_seq_id0_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error;
+	unsigned int val;
+
+	error = regmap_read(haptics->regmap, DA7280_GPI_0_CTL, &val);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to read register : %d\n", error);
+		return error;
+	}
+	val = (val & DA7280_GPI0_SEQUENCE_ID_MASK)
+		>> DA7280_GPI0_SEQUENCE_ID_SHIFT;
+
+	return sprintf(buf, "gpi_seq_id0 is %d\n", val);
+}
+
+static ssize_t gpi_seq_id1_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	long val = 0xff;
+	int error;
+
+	if (kstrtol(&buf[0], 0, &val) < 0)
+		goto err;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_GPI_1_CTL,
+				   DA7280_GPI1_SEQUENCE_ID_MASK,
+				   (val & 0xf)
+				   << DA7280_GPI1_SEQUENCE_ID_SHIFT);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to update register : %d\n", error);
+		return error;
+	}
+
+	haptics->gpi_ctl[1].seq_id = val & 0xf;
+
+	return count;
+
+err:
+	dev_err(dev, "Invalid input\n");
+	return count;
+}
+
+static ssize_t gpi_seq_id1_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error;
+	unsigned int val;
+
+	error = regmap_read(haptics->regmap, DA7280_GPI_1_CTL, &val);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to read register : %d\n", error);
+		return error;
+	}
+	val = (val & DA7280_GPI1_SEQUENCE_ID_MASK)
+		>> DA7280_GPI1_SEQUENCE_ID_SHIFT;
+
+	return sprintf(buf, "gpi_seq_id1 is %d\n", val);
+}
+
+static ssize_t gpi_seq_id2_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf,
+				 size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	long val = 0xff;
+	int error;
+
+	if (kstrtol(&buf[0], 0, &val) < 0)
+		goto err;
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_GPI_2_CTL,
+				   DA7280_GPI2_SEQUENCE_ID_MASK,
+				   (val & 0xf)
+				   << DA7280_GPI2_SEQUENCE_ID_SHIFT);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to update register : %d\n", error);
+		return error;
+	}
+
+	haptics->gpi_ctl[2].seq_id = val & 0xf;
+
+	return count;
+
+err:
+	dev_err(dev, "Invalid input\n");
+	return count;
+}
+
+static ssize_t gpi_seq_id2_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error;
+	unsigned int val;
+
+	error = regmap_read(haptics->regmap, DA7280_GPI_2_CTL, &val);
+	if (error) {
+		dev_err(haptics->dev,
+			"failed to read register : %d\n", error);
+		return error;
+	}
+	val = (val & DA7280_GPI2_SEQUENCE_ID_MASK)
+		>> DA7280_GPI2_SEQUENCE_ID_SHIFT;
+
+	return sprintf(buf, "gpi_seq_id2 is %d\n", val);
+}
+
+#define MAX_PTN_REGS DA7280_SNP_MEM_SIZE
+#define MAX_USER_INPUT_LEN (5 * DA7280_SNP_MEM_SIZE)
+struct parse_data_t {
+	int len;
+	u8 val[MAX_PTN_REGS];
+};
+
+static int da7280_parse_args(struct device *dev,
+			     char *cmd, struct parse_data_t *ptn)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	char *tok;		/* used to separate tokens */
+	const char ct[] = " \t"; /* space or tab delimits the tokens */
+	int tok_count = 0;	/* total number of tokens parsed */
+	int i = 0, val;
+
+	ptn->len = 0;
+
+	/* parse the input string */
+	while ((tok = strsep(&cmd, ct)) != NULL) {
+		/* this is a value to be written to the register */
+		if (kstrtouint(tok, 0, &val) < 0) {
+			dev_err(haptics->dev,
+				"failed to read from %s\n", tok);
+			break;
+		}
+
+		if (i < MAX_PTN_REGS) {
+			ptn->val[i] = val;
+			i++;
+		}
+		tok_count++;
+	}
+
+	/* decide whether it is a read or write operation based on the
+	 * value of tok_count and count_flag.
+	 * tok_count = 0: no inputs, invalid case.
+	 * tok_count = 1: write one value.
+	 * tok_count > 1: write multiple values/patterns.
+	 */
+	switch (tok_count) {
+	case 0:
+		return -EINVAL;
+	case 1:
+		ptn->len = 1;
+		break;
+	default:
+		ptn->len = i;
+	}
+	return 0;
+}
+
+static ssize_t
+patterns_store(struct device *dev,
+	       struct device_attribute *attr,
+	       const char *buf,
+	       size_t count)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	struct parse_data_t mem;
+	char cmd[MAX_USER_INPUT_LEN];
+	unsigned int val;
+	int error;
+
+	error = regmap_read(haptics->regmap, DA7280_MEM_CTL1, &val);
+	if (error)
+		return error;
+
+	if (count > MAX_USER_INPUT_LEN)
+		memcpy(cmd, buf, MAX_USER_INPUT_LEN);
+	else
+		memcpy(cmd, buf, count);
+
+	/* chop of '\n' introduced by echo at the end of the input */
+	if (cmd[count - 1] == '\n')
+		cmd[count - 1] = '\0';
+
+	if (da7280_parse_args(dev, cmd, &mem) < 0)
+		return -EINVAL;
+
+	memcpy(haptics->snp_mem, mem.val, mem.len);
+
+	error = da7280_haptic_mem_update(haptics);
+	if (error)
+		return error;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(ps_seq_id);
+static DEVICE_ATTR_RW(ps_seq_loop);
+static DEVICE_ATTR_RW(gpi_seq_id0);
+static DEVICE_ATTR_RW(gpi_seq_id1);
+static DEVICE_ATTR_RW(gpi_seq_id2);
+static DEVICE_ATTR_WO(patterns);
+static struct attribute *da7280_sysfs_attr[] = {
+	&dev_attr_ps_seq_id.attr,
+	&dev_attr_ps_seq_loop.attr,
+	&dev_attr_gpi_seq_id0.attr,
+	&dev_attr_gpi_seq_id1.attr,
+	&dev_attr_gpi_seq_id2.attr,
+	&dev_attr_patterns.attr,
+	NULL,
+};
+
+static const struct attribute_group da7280_attr_group = {
+	.attrs = da7280_sysfs_attr,
+};
+
+static int da7280_probe(struct i2c_client *client,
+			const struct i2c_device_id *id)
+{
+	struct device *dev = &client->dev;
+	struct da7280_haptic *haptics;
+	unsigned int period2freq;
+	int error;
+
+	haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
+	if (!haptics)
+		return -ENOMEM;
+	haptics->dev = dev;
+
+	if (!client->irq) {
+		dev_err(dev, "No IRQ configured\n");
+		return -EINVAL;
+	}
+
+	da7280_parse_properties(&client->dev, haptics);
+
+	if (haptics->op_mode == DA7280_PWM_MODE) {
+		/* Get pwm and regulatot for haptics device */
+		haptics->pwm_dev = devm_pwm_get(&client->dev, NULL);
+		if (IS_ERR(haptics->pwm_dev)) {
+			dev_err(dev, "failed to get PWM device\n");
+			return PTR_ERR(haptics->pwm_dev);
+		}
+
+		/*
+		 * FIXME: pwm_apply_args() should be removed when switching to
+		 * the atomic PWM API.
+		 */
+		pwm_apply_args(haptics->pwm_dev);
+
+		/* Check PWM Period, it must be in 10k ~ 250kHz */
+		period2freq = 1000000 / pwm_get_period(haptics->pwm_dev);
+		if (period2freq < DA7280_MIN_PWM_FREQ_KHZ ||
+		    period2freq > DA7280_MAX_PWM_FREQ_KHZ) {
+			dev_err(dev, "Not supported PWM frequency(%d)\n",
+				period2freq);
+			return -EINVAL;
+		}
+	}
+
+	INIT_WORK(&haptics->work, da7280_haptic_work);
+	haptics->client = client;
+	i2c_set_clientdata(client, haptics);
+
+	haptics->regmap =
+		devm_regmap_init_i2c(client, &da7280_haptic_regmap_config);
+	if (IS_ERR(haptics->regmap)) {
+		error = PTR_ERR(haptics->regmap);
+		dev_err(dev, "Failed to allocate register map : %d\n",
+			error);
+		return error;
+	}
+
+	error = devm_request_threaded_irq(dev, client->irq, NULL,
+					  da7280_irq_handler,
+					  IRQF_ONESHOT,
+					  "da7280-haptics", haptics);
+	if (error != 0) {
+		dev_err(dev,
+			"Failed to request IRQ : %d\n", client->irq);
+		return error;
+	}
+
+	error = da7280_init(haptics);
+	if (error) {
+		dev_err(dev, "failed to initialize device\n");
+		return error;
+	}
+
+	/* Initialize input device for haptic device */
+	haptics->input_dev = devm_input_allocate_device(dev);
+	if (!haptics->input_dev) {
+		dev_err(dev, "failed to allocate input device\n");
+		return -ENOMEM;
+	}
+
+	haptics->input_dev->name = "da7280-haptic";
+	haptics->input_dev->dev.parent = client->dev.parent;
+	haptics->input_dev->open = da7280_haptic_open;
+	haptics->input_dev->close = da7280_haptic_close;
+	input_set_drvdata(haptics->input_dev, haptics);
+	input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
+
+	error = input_ff_create_memless(haptics->input_dev, NULL,
+					da7280_haptic_play);
+	if (error) {
+		dev_err(dev, "failed to create force-feedback\n");
+		return error;
+	}
+
+	error = input_register_device(haptics->input_dev);
+	if (error) {
+		dev_err(dev, "failed to register input device\n");
+		return error;
+	}
+
+	error = devm_device_add_group(dev, &da7280_attr_group);
+	if (error) {
+		dev_err(dev, "Failed to create sysfs attributes: %d\n",
+			error);
+	}
+
+	return error;
+}
+
+static int __maybe_unused da7280_suspend(struct device *dev)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error = 0;
+
+	mutex_lock(&haptics->input_dev->mutex);
+
+	da7280_haptic_disable(haptics);
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_STANDBY_EN_MASK, 0);
+	if (error)
+		dev_err(haptics->dev,
+			"I2C error : %d\n", error);
+
+	mutex_unlock(&haptics->input_dev->mutex);
+	return error;
+}
+
+static int __maybe_unused da7280_resume(struct device *dev)
+{
+	struct da7280_haptic *haptics = dev_get_drvdata(dev);
+	int error = 0;
+
+	mutex_lock(&haptics->input_dev->mutex);
+
+	error = regmap_update_bits(haptics->regmap,
+				   DA7280_TOP_CTL1,
+				   DA7280_STANDBY_EN_MASK,
+				   DA7280_STANDBY_EN_MASK);
+	if (error)
+		dev_err(haptics->dev,
+			"i2c error : %d\n", error);
+
+	mutex_unlock(&haptics->input_dev->mutex);
+	return error;
+}
+
+static const struct of_device_id da7280_of_match[] = {
+	{ .compatible = "dlg,da7280", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, da7280_of_match);
+
+static const struct i2c_device_id da7280_i2c_id[] = {
+	{ "da7280", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, da7280_i2c_id);
+
+static SIMPLE_DEV_PM_OPS(da7280_pm_ops,
+		 da7280_suspend, da7280_resume);
+
+static struct i2c_driver da7280_driver = {
+	.driver		= {
+		.name	= "da7280",
+		.of_match_table = of_match_ptr(da7280_of_match),
+		.pm	= &da7280_pm_ops,
+	},
+	.probe	= da7280_probe,
+	.id_table	= da7280_i2c_id,
+};
+module_i2c_driver(da7280_driver);
+
+MODULE_DESCRIPTION("DA7280 haptics driver");
+MODULE_AUTHOR("Roy Im <Roy.Im.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/misc/da7280.h b/drivers/input/misc/da7280.h
new file mode 100644
index 0000000..d9310b6
--- /dev/null
+++ b/drivers/input/misc/da7280.h
@@ -0,0 +1,412 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * DA7280 Haptic device driver registers
+ *
+ * Copyright (c) 2017 Dialog Semiconductor.
+ * Author: Roy Im <Roy.Im.Opensource@diasemi.com>
+ */
+
+#ifndef _DA7280_REG_DEFS_H
+#define _DA7280_REG_DEFS_H
+
+#include <linux/bitops.h>
+
+/* Registers */
+
+#define DA7280_CHIP_REV                       0x00
+#define DA7280_IRQ_EVENT1                     0x03
+#define DA7280_IRQ_EVENT_WARNING_DIAG         0x04
+#define DA7280_IRQ_EVENT_SEQ_DIAG             0x05
+#define DA7280_IRQ_STATUS1                    0x06
+#define DA7280_IRQ_MASK1                      0x07
+#define DA7280_CIF_I2C1                       0x08
+#define DA7280_FRQ_LRA_PER_H                  0x0A
+#define DA7280_FRQ_LRA_PER_L                  0x0B
+#define DA7280_ACTUATOR1                      0x0C
+#define DA7280_ACTUATOR2                      0x0D
+#define DA7280_ACTUATOR3                      0x0E
+#define DA7280_CALIB_V2I_H                    0x0F
+#define DA7280_CALIB_V2I_L                    0x10
+#define DA7280_CALIB_IMP_H                    0x11
+#define DA7280_CALIB_IMP_L                    0x12
+#define DA7280_TOP_CFG1                       0x13
+#define DA7280_TOP_CFG2                       0x14
+#define DA7280_TOP_CFG3                       0x15
+#define DA7280_TOP_CFG4                       0x16
+#define DA7280_TOP_INT_CFG1                   0x17
+#define DA7280_TOP_INT_CFG6_H                 0x1C
+#define DA7280_TOP_INT_CFG6_L                 0x1D
+#define DA7280_TOP_INT_CFG7_H                 0x1E
+#define DA7280_TOP_INT_CFG7_L                 0x1F
+#define DA7280_TOP_INT_CFG8                   0x20
+#define DA7280_TOP_CTL1                       0x22
+#define DA7280_TOP_CTL2                       0x23
+#define DA7280_SEQ_CTL1                       0x24
+#define DA7280_SWG_C1                         0x25
+#define DA7280_SWG_C2                         0x26
+#define DA7280_SWG_C3                         0x27
+#define DA7280_SEQ_CTL2                       0x28
+#define DA7280_GPI_0_CTL                      0x29
+#define DA7280_GPI_1_CTL                      0x2A
+#define DA7280_GPI_2_CTL                      0x2B
+#define DA7280_MEM_CTL1                       0x2C
+#define DA7280_MEM_CTL2                       0x2D
+#define DA7280_ADC_DATA_H1                    0x2E
+#define DA7280_ADC_DATA_L1                    0x2F
+#define DA7280_POLARITY                       0x43
+#define DA7280_LRA_AVR_H                      0x44
+#define DA7280_LRA_AVR_L                      0x45
+#define DA7280_FRQ_LRA_PER_ACT_H              0x46
+#define DA7280_FRQ_LRA_PER_ACT_L              0x47
+#define DA7280_FRQ_PHASE_H                    0x48
+#define DA7280_FRQ_PHASE_L                    0x49
+#define DA7280_FRQ_CTL                        0x4C
+#define DA7280_TRIM3                          0x5F
+#define DA7280_TRIM4                          0x60
+#define DA7280_TRIM6                          0x62
+#define DA7280_TOP_CFG5                       0x6E
+#define DA7280_IRQ_EVENT_ACTUATOR_FAULT       0x81
+#define DA7280_IRQ_STATUS2                    0x82
+#define DA7280_IRQ_MASK2                      0x83
+#define DA7280_SNP_MEM_0                      0x84
+#define DA7280_SNP_MEM_99                     0xE7
+
+/* DA7280_CHIP_REV (Address 0x00) */
+#define DA7280_CHIP_REV_MAJOR_SHIFT		0
+#define DA7280_CHIP_REV_MAJOR_MASK		(15 << 0)
+#define DA7280_CHIP_REV_MINOR_SHIFT		4
+#define DA7280_CHIP_REV_MINOR_MASK		(15 << 4)
+
+/* DA7280_IRQ_EVENT1 (Address 0x03) */
+#define DA7280_E_SEQ_CONTINUE_SHIFT		0
+#define DA7280_E_SEQ_CONTINUE_MASK		BIT(0)
+#define DA7280_E_UVLO_SHIFT			1
+#define DA7280_E_UVLO_MASK			BIT(1)
+#define DA7280_E_SEQ_DONE_SHIFT			2
+#define DA7280_E_SEQ_DONE_MASK			BIT(2)
+#define DA7280_E_OVERTEMP_CRIT_SHIFT		3
+#define DA7280_E_OVERTEMP_CRIT_MASK		BIT(3)
+#define DA7280_E_SEQ_FAULT_SHIFT		4
+#define DA7280_E_SEQ_FAULT_MASK			BIT(4)
+#define DA7280_E_WARNING_SHIFT			5
+#define DA7280_E_WARNING_MASK			BIT(5)
+#define DA7280_E_ACTUATOR_FAULT_SHIFT		6
+#define DA7280_E_ACTUATOR_FAULT_MASK		BIT(6)
+#define DA7280_E_OC_FAULT_SHIFT			7
+#define DA7280_E_OC_FAULT_MASK			BIT(7)
+
+/* DA7280_IRQ_EVENT_WARNING_DIAG (Address 0x04) */
+#define DA7280_E_OVERTEMP_WARN_SHIFT            3
+#define DA7280_E_OVERTEMP_WARN_MASK             BIT(3)
+#define DA7280_E_MEM_TYPE_SHIFT                 4
+#define DA7280_E_MEM_TYPE_MASK                  BIT(4)
+#define DA7280_E_LIM_DRIVE_ACC_SHIFT            6
+#define DA7280_E_LIM_DRIVE_ACC_MASK             BIT(6)
+#define DA7280_E_LIM_DRIVE_SHIFT                7
+#define DA7280_E_LIM_DRIVE_MASK                 BIT(7)
+
+/* DA7280_IRQ_EVENT_PAT_DIAG (Address 0x05) */
+#define DA7280_E_PWM_FAULT_SHIFT		5
+#define DA7280_E_PWM_FAULT_MASK			BIT(5)
+#define DA7280_E_MEM_FAULT_SHIFT		6
+#define DA7280_E_MEM_FAULT_MASK			BIT(6)
+#define DA7280_E_SEQ_ID_FAULT_SHIFT		7
+#define DA7280_E_SEQ_ID_FAULT_MASK		BIT(7)
+
+/* DA7280_IRQ_STATUS1 (Address 0x06) */
+#define DA7280_STA_SEQ_CONTINUE_SHIFT		0
+#define DA7280_STA_SEQ_CONTINUE_MASK		BIT(0)
+#define DA7280_STA_UVLO_VBAT_OK_SHIFT		1
+#define DA7280_STA_UVLO_VBAT_OK_MASK		BIT(1)
+#define DA7280_STA_SEQ_DONE_SHIFT		2
+#define DA7280_STA_SEQ_DONE_MASK		BIT(2)
+#define DA7280_STA_OVERTEMP_CRIT_SHIFT		3
+#define DA7280_STA_OVERTEMP_CRIT_MASK		BIT(3)
+#define DA7280_STA_SEQ_FAULT_SHIFT		4
+#define DA7280_STA_SEQ_FAULT_MASK		BIT(4)
+#define DA7280_STA_WARNING_SHIFT		5
+#define DA7280_STA_WARNING_MASK			BIT(5)
+#define DA7280_STA_ACTUATOR_SHIFT		6
+#define DA7280_STA_ACTUATOR_MASK		BIT(6)
+#define DA7280_STA_OC_SHIFT			7
+#define DA7280_STA_OC_MASK			BIT(7)
+
+/* DA7280_IRQ_MASK1 (Address 0x07) */
+#define DA7280_SEQ_CONTINUE_M_SHIFT		0
+#define DA7280_SEQ_CONTINUE_M_MASK		BIT(0)
+#define DA7280_E_UVLO_M_SHIFT			1
+#define DA7280_E_UVLO_M_MASK			BIT(1)
+#define DA7280_SEQ_DONE_M_SHIFT			2
+#define DA7280_SEQ_DONE_M_MASK			BIT(2)
+#define DA7280_OVERTEMP_CRIT_M_SHIFT		3
+#define DA7280_OVERTEMP_CRIT_M_MASK		BIT(3)
+#define DA7280_SEQ_FAULT_M_SHIFT		4
+#define DA7280_SEQ_FAULT_M_MASK			BIT(4)
+#define DA7280_WARNING_M_SHIFT			5
+#define DA7280_WARNING_M_MASK			BIT(5)
+#define DA7280_ACTUATOR_M_SHIFT			6
+#define DA7280_ACTUATOR_M_MASK			BIT(6)
+#define DA7280_OC_M_SHIFT			7
+#define DA7280_OC_M_MASK			BIT(7)
+
+/* DA7280_CIF_I2C1 (Address 0x08) */
+#define DA7280_I2C_TO_ENABLE_SHIFT		6
+#define DA7280_I2C_TO_ENABLE_MASK		BIT(6)
+#define DA7280_I2C_WR_MODE_SHIFT		7
+#define DA7280_I2C_WR_MODE_MASK			BIT(7)
+
+/* DA7280_FRQ_LRA_PER_H (Address 0x0a) */
+#define DA7280_LRA_PER_H_SHIFT			0
+#define DA7280_LRA_PER_H_MASK			(255 << 0)
+
+/* DA7280_FRQ_LRA_PER_L (Address 0x0b) */
+#define DA7280_LRA_PER_L_SHIFT			0
+#define DA7280_LRA_PER_L_MASK			(127 << 0)
+
+/* DA7280_ACTUATOR1 (Address 0x0c) */
+#define DA7280_ACTUATOR_NOMMAX_SHIFT		0
+#define DA7280_ACTUATOR_NOMMAX_MASK		(255 << 0)
+
+/* DA7280_ACTUATOR2 (Address 0x0d) */
+#define DA7280_ACTUATOR_ABSMAX_SHIFT		0
+#define DA7280_ACTUATOR_ABSMAX_MASK		(255 << 0)
+
+/* DA7280_ACTUATOR3 (Address 0x0e) */
+#define DA7280_IMAX_SHIFT			0
+#define DA7280_IMAX_MASK			(31 << 0)
+
+/* DA7280_CALIB_V2I_H (Address 0x0f) */
+#define DA7280_V2I_FACTOR_H_SHIFT		0
+#define DA7280_V2I_FACTOR_H_MASK		(255 << 0)
+
+/* DA7280_CALIB_V2I_L (Address 0x10) */
+#define DA7280_V2I_FACTOR_L_SHIFT		0
+#define DA7280_V2I_FACTOR_L_MASK		(255 << 0)
+
+/* DA7280_CALIB_IMP_H (Address 0x11) */
+#define DA7280_IMPEDANCE_H_SHIFT		0
+#define DA7280_IMPEDANCE_H_MASK			(255 << 0)
+
+/* DA7280_CALIB_IMP_L (Address 0x12) */
+#define DA7280_IMPEDANCE_L_SHIFT		0
+#define DA7280_IMPEDANCE_L_MASK			(3 << 0)
+
+/* DA7280_TOP_CFG1 (Address 0x13) */
+#define DA7280_AMP_PID_EN_SHIFT			0
+#define DA7280_AMP_PID_EN_MASK			BIT(0)
+#define DA7280_RAPID_STOP_EN_SHIFT		1
+#define DA7280_RAPID_STOP_EN_MASK		BIT(1)
+#define DA7280_ACCELERATION_EN_SHIFT		2
+#define DA7280_ACCELERATION_EN_MASK		BIT(2)
+#define DA7280_FREQ_TRACK_EN_SHIFT		3
+#define DA7280_FREQ_TRACK_EN_MASK		BIT(3)
+#define DA7280_BEMF_SENSE_EN_SHIFT		 4
+#define DA7280_BEMF_SENSE_EN_MASK		BIT(4)
+#define DA7280_ACTUATOR_TYPE_SHIFT		5
+#define DA7280_ACTUATOR_TYPE_MASK		BIT(5)
+#define DA7280_EMBEDDED_MODE_SHIFT		7
+#define DA7280_EMBEDDED_MODE_MASK		BIT(7)
+
+/* DA7280_TOP_CFG2 (Address 0x14) */
+#define DA7280_FULL_BRAKE_THR_SHIFT		0
+#define DA7280_FULL_BRAKE_THR_MASK		(15 << 0)
+#define DA7280_MEM_DATA_SIGNED_SHIFT		4
+#define DA7280_MEM_DATA_SIGNED_MASK		BIT(4)
+
+/* DA7280_TOP_CFG3 (Address 0x15) */
+#define DA7280_VDD_MARGIN_SHIFT			0
+#define DA7280_VDD_MARGIN_MASK			(15 << 0)
+
+/* DA7280_TOP_CFG4 (Address 0x16) */
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_SHIFT	6
+#define DA7280_TST_CALIB_IMPEDANCE_DIS_MASK	BIT(6)
+#define DA7280_V2I_FACTOR_FREEZE_SHIFT		7
+#define DA7280_V2I_FACTOR_FREEZE_MASK		BIT(7)
+
+/* DA7280_TOP_INT_CFG1 (Address 0x17) */
+#define DA7280_BEMF_FAULT_LIM_SHIFT		0
+#define DA7280_BEMF_FAULT_LIM_MASK		(3 << 0)
+#define DA7280_FRQ_LOCKED_LIM_SHIFT		2
+#define DA7280_FRQ_LOCKED_LIM_MASK		(63 << 2)
+
+/* DA7280_TOP_INT_CFG6_H (Address 0x1c) */
+#define DA7280_FRQ_PID_KP_H_SHIFT		0
+#define DA7280_FRQ_PID_KP_H_MASK		(255 << 0)
+
+/* DA7280_TOP_INT_CFG6_L (Address 0x1d) */
+#define DA7280_FRQ_PID_KP_L_SHIFT		0
+#define DA7280_FRQ_PID_KP_L_MASK		(255 << 0)
+
+/* DA7280_TOP_INT_CFG7_H (Address 0x1e) */
+#define DA7280_FRQ_PID_KI_H_SHIFT		0
+#define DA7280_FRQ_PID_KI_H_MASK		(255 << 0)
+
+/* DA7280_TOP_INT_CFG7_L (Address 0x1f) */
+#define DA7280_FRQ_PID_KI_L_SHIFT		0
+#define DA7280_FRQ_PID_KI_L_MASK		(255 << 0)
+
+/* DA7280_TOP_INT_CFG8 (Address 0x20) */
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_SHIFT     0
+#define DA7280_TST_FRQ_TRACK_BEMF_LIM_MASK      (15 << 0)
+#define DA7280_TST_AMP_RAPID_STOP_LIM_SHIFT     4
+#define DA7280_TST_AMP_RAPID_STOP_LIM_MASK      (7 << 4)
+
+/* DA7280_TOP_CTL1 (Address 0x22) */
+#define DA7280_OPERATION_MODE_SHIFT		0
+#define DA7280_OPERATION_MODE_MASK		(7 << 0)
+#define DA7280_STANDBY_EN_SHIFT			3
+#define DA7280_STANDBY_EN_MASK			BIT(3)
+#define DA7280_SEQ_START_SHIFT			4
+#define DA7280_SEQ_START_MASK			BIT(4)
+
+/* DA7280_TOP_CTL2 (Address 0x23) */
+#define DA7280_OVERRIDE_VAL_SHIFT		0
+#define DA7280_OVERRIDE_VAL_MASK		(255 << 0)
+
+/* DA7280_SEQ_CTL1 (Address 0x24) */
+#define DA7280_SEQ_CONTINUE_SHIFT		0
+#define DA7280_SEQ_CONTINUE_MASK		BIT(0)
+#define DA7280_WAVEGEN_MODE_SHIFT		1
+#define DA7280_WAVEGEN_MODE_MASK		BIT(1)
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_SHIFT	2
+#define DA7280_FREQ_WAVEFORM_TIMEBASE_MASK	BIT(2)
+
+/* DA7280_SWG_C1 (Address 0x25) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_SHIFT	0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF1_MASK	(255 << 0)
+
+/* DA7280_SWG_C2 (Address 0x26) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_SHIFT	0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF2_MASK	(255 << 0)
+
+/* DA7280_SWG_C3 (Address 0x27) */
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_SHIFT	0
+#define DA7280_CUSTOM_WAVE_GEN_COEFF3_MASK	(255 << 0)
+
+/* DA7280_SEQ_CTL2 (Address 0x28) */
+#define DA7280_PS_SEQ_ID_SHIFT			0
+#define DA7280_PS_SEQ_ID_MASK			(15 << 0)
+#define DA7280_PS_SEQ_LOOP_SHIFT		4
+#define DA7280_PS_SEQ_LOOP_MASK			(15 << 4)
+
+/* DA7280_GPIO_0_CTL (Address 0x29) */
+#define DA7280_GPI0_POLARITY_SHIFT		0
+#define DA7280_GPI0_POLARITY_MASK		(3 << 0)
+#define DA7280_GPI0_MODE_SHIFT			2
+#define DA7280_GPI0_MODE_MASK			BIT(2)
+#define DA7280_GPI0_SEQUENCE_ID_SHIFT		3
+#define DA7280_GPI0_SEQUENCE_ID_MASK		(15 << 3)
+
+/* DA7280_GPIO_1_CTL (Address 0x2a) */
+#define DA7280_GPI1_POLARITY_SHIFT		0
+#define DA7280_GPI1_POLARITY_MASK		(3 << 0)
+#define DA7280_GPI1_MODE_SHIFT			2
+#define DA7280_GPI1_MODE_MASK			BIT(2)
+#define DA7280_GPI1_SEQUENCE_ID_SHIFT		3
+#define DA7280_GPI1_SEQUENCE_ID_MASK		(15 << 3)
+
+/* DA7280_GPIO_2_CTL (Address 0x2b) */
+#define DA7280_GPI2_POLARITY_SHIFT		0
+#define DA7280_GPI2_POLARITY_MASK		(3 << 0)
+#define DA7280_GPI2_MODE_SHIFT			2
+#define DA7280_GPI2_MODE_MASK			BIT(2)
+#define DA7280_GPI2_SEQUENCE_ID_SHIFT		3
+#define DA7280_GPI2_SEQUENCE_ID_MASK		(15 << 3)
+
+/* DA7280_MEM_CTL1 (Address 0x2c) */
+#define DA7280_WAV_MEM_BASE_ADDR_SHIFT		0
+#define DA7280_WAV_MEM_BASE_ADDR_MASK		(255 << 0)
+
+/* DA7280_MEM_CTL2 (Address 0x2d) */
+#define DA7280_WAV_MEM_LOCK_SHIFT		7
+#define DA7280_WAV_MEM_LOCK_MASK		BIT(7)
+
+/* DA7280_ADC_DATA_H1 (Address 0x2e) */
+#define DA7280_ADC_VDD_H_SHIFT			0
+#define DA7280_ADC_VDD_H_MASK			(255 << 0)
+
+/* DA7280_ADC_DATA_L1 (Address 0x2f) */
+#define DA7280_ADC_VDD_L_SHIFT			0
+#define DA7280_ADC_VDD_L_MASK			(127 << 0)
+
+/* DA7280_POLARITY (Address 0x43) */
+#define DA7280_POLARITY_SHIFT			0
+#define DA7280_POLARITY_MASK			BIT(0)
+
+/* DA7280_LRA_AVR_H (Address 0x44) */
+#define DA7280_LRA_PER_AVERAGE_H_SHIFT		0
+#define DA7280_LRA_PER_AVERAGE_H_MASK		(255 << 0)
+
+/* DA7280_LRA_AVR_L (Address 0x45) */
+#define DA7280_LRA_PER_AVERAGE_L_SHIFT		0
+#define DA7280_LRA_PER_AVERAGE_L_MASK		(127 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_H (Address 0x46) */
+#define DA7280_LRA_PER_ACTUAL_H_SHIFT		0
+#define DA7280_LRA_PER_ACTUAL_H_MASK		(255 << 0)
+
+/* DA7280_FRQ_LRA_PER_ACT_L (Address 0x47) */
+#define DA7280_LRA_PER_ACTUAL_L_SHIFT		0
+#define DA7280_LRA_PER_ACTUAL_L_MASK		(127 << 0)
+
+/* DA7280_FRQ_PHASE_H (Address 0x48) */
+#define DA7280_PHASE_DELAY_H_SHIFT		0
+#define DA7280_PHASE_DELAY_H_MASK		(255 << 0)
+
+/* DA7280_FRQ_PHASE_L (Address 0x49) */
+#define DA7280_DELAY_SHIFT_L_SHIFT		0
+#define DA7280_DELAY_SHIFT_L_MASK		(7 << 0)
+#define DA7280_DELAY_SHIFT_FREEZE_SHIFT		7
+#define DA7280_DELAY_SHIFT_FREEZE_MASK		BIT(7)
+
+/* DA7280_FRQ_CTL (Address 0x4c) */
+#define DA7280_FREQ_TRACKING_FORCE_ON_SHIFT	0
+#define DA7280_FREQ_TRACKING_FORCE_ON_MASK	BIT(0)
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_SHIFT	1
+#define DA7280_FREQ_TRACKING_AUTO_ADJ_MASK	BIT(1)
+
+/* DA7280_TRIM3 (Address 0x5f) */
+#define DA7280_REF_UVLO_THRES_SHIFT		3
+#define DA7280_REF_UVLO_THRES_MASK		(3 << 3)
+#define DA7280_LOOP_FILT_LOW_BW_SHIFT		5
+#define DA7280_LOOP_FILT_LOW_BW_MASK		BIT(5)
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_SHIFT	6
+#define DA7280_LOOP_IDAC_DOUBLE_RANGE_MASK	BIT(6)
+
+/* DA7280_TRIM4 (Address 0x60) */
+#define DA7280_LOOP_FILT_RES_TRIM_SHIFT		0
+#define DA7280_LOOP_FILT_RES_TRIM_MASK		(3 << 0)
+#define DA7280_LOOP_FILT_CAP_TRIM_SHIFT		2
+#define DA7280_LOOP_FILT_CAP_TRIM_MASK		(3 << 2)
+
+/* DA7280_TRIM6 (Address 0x62) */
+#define DA7280_HBRIDGE_ERC_HS_TRIM_SHIFT	0
+#define DA7280_HBRIDGE_ERC_HS_TRIM_MASK		(3 << 0)
+#define DA7280_HBRIDGE_ERC_LS_TRIM_SHIFT	2
+#define DA7280_HBRIDGE_ERC_LS_TRIM_MASK		(3 << 2)
+
+/* DA7280_TOP_CFG5 (Address 0x6e) */
+#define DA7280_V2I_FACTOR_OFFSET_EN_SHIFT		0
+#define DA7280_V2I_FACTOR_OFFSET_EN_MASK		BIT(0)
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_SHIFT	1
+#define DA7280_FRQ_PAUSE_ON_POLARITY_CHANGE_MASK	BIT(1)
+#define DA7280_DELAY_BYPASS_SHIFT			2
+#define DA7280_DELAY_BYPASS_MASK			BIT(2)
+
+/* DA7280_IRQ_EVENT_ACTUATOR_FAULT (Address 0x81) */
+#define DA7280_ADC_SAT_FAULT_SHIFT		2
+#define DA7280_ADC_SAT_FAULT_MASK		BIT(2)
+
+/* DA7280_IRQ_STATUS2 (Address 0x82) */
+#define DA7280_STA_ADC_SAT_SHIFT		7
+#define DA7280_STA_ADC_SAT_MASK			BIT(7)
+
+/* DA7280_IRQ_MASK2 (Address 0x83) */
+#define DA7280_ADC_SAT_M_SHIFT			7
+#define DA7280_ADC_SAT_M_MASK			BIT(7)
+
+/* DA7280_SNP_MEM_XX (Address 0x84 ~ 0xe7) */
+#define DA7280_SNP_MEM_SHIFT			0
+#define DA7280_SNP_MEM_MASK			(255 << 0)
+
+#endif
-- 
end-of-patch for PATCH V7

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox