From: Takashi Iwai <tiwai@suse.de>
Subject: [PATCH v3] HID: multitouch: add support for Atmel 212c touchscreen
Patch-mainline: Never, only for SLE11-SP3/SP4
References: bnc#793727,bnc#863946,bnc#924142

This patch enables hid-multitouch support for the new Atmel maxTouch device
with the device ID 212c.

This device supports two modes, mouse and digitizer, and both modes can be
switched dynamically, and badly enough, the device ID remains same.
Thus we need to decide which driver to bind also on the fly at boot time.

The upstream code has a check of hid group by parsing the descriptor
beforehand.  Unfortunately, this will need lots of more changes (also the
corresponding part in udev), hence not appropriate for SLE11.

In this patch, instead of implementing the whole pre-parse, we do parse
and check only Win8 compliant device for only the given devices.  And
skip the generic driver when a Win8 MT device has been detected and
forcibly bind to hid-multitouch.

Yes, this is pretty hackish and doesn't scale.  But, it's safer and less
intrusive.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

---
 drivers/hid/hid-core.c          |  106 +++++++++++++++++++++++++++++++++++++++-
 drivers/hid/hid-ids.h           |    3 +
 drivers/hid/hid-multitouch.c    |    5 +
 drivers/hid/usbhid/hid-quirks.c |    1 
 4 files changed, 113 insertions(+), 2 deletions(-)

--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -634,6 +634,86 @@ static u8 *fetch_item(__u8 *start, __u8
 	return NULL;
 }
 
+/* SLE11 specific */
+#define HID_QUIRK_MULTITOUCH		0x04000000
+#define HID_QUIRK_MULTITOUCH_WIN_8	0x08000000
+
+static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
+{
+	struct hid_device *hid = parser->device;
+	__u32 data;
+	int i;
+
+	data = item_udata(item);
+
+	switch (item->tag) {
+	case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
+		break;
+	case HID_MAIN_ITEM_TAG_END_COLLECTION:
+		break;
+	case HID_MAIN_ITEM_TAG_INPUT:
+		/* ignore constant inputs, they will be ignored by hid-input */
+		if (data & HID_MAIN_ITEM_CONSTANT)
+			break;
+		for (i = 0; i < parser->local.usage_index; i++)
+			if (parser->local.usage[i] == HID_DG_CONTACTID)
+				hid->quirks |= HID_QUIRK_MULTITOUCH;
+		break;
+	case HID_MAIN_ITEM_TAG_OUTPUT:
+		break;
+	case HID_MAIN_ITEM_TAG_FEATURE:
+		for (i = 0; i < parser->local.usage_index; i++)
+			if (parser->local.usage[i] == 0xff0000c5 &&
+			    parser->global.report_count == 256 &&
+			    parser->global.report_size == 8)
+				hid->quirks |= HID_QUIRK_MULTITOUCH_WIN_8;
+		break;
+	}
+
+	/* Reset the local parser environment */
+	memset(&parser->local, 0, sizeof(parser->local));
+
+	return 0;
+}
+
+/*
+ * Parse a report descriptor to check whether it's a Win-8 multitouch device
+ * Two hid->quirks bits are used for keeping the parsed flags where the
+ * upstream driver uses parser->scan_flags instead.
+ */
+static int hid_parse_multitouch_win8(struct hid_device *hid)
+{
+	struct hid_parser *parser;
+	struct hid_item item;
+	__u8 *start = hid->rdesc;
+	__u8 *end = start + hid->rsize;
+	static int (*dispatch_type[])(struct hid_parser *parser,
+				      struct hid_item *item) = {
+		hid_scan_main,
+		hid_parser_global,
+		hid_parser_local,
+		hid_parser_reserved
+	};
+
+	parser = vzalloc(sizeof(struct hid_parser));
+	if (!parser)
+		return -ENOMEM;
+
+	parser->device = hid;
+	hid->quirks &=~ (HID_QUIRK_MULTITOUCH | HID_QUIRK_MULTITOUCH_WIN_8);
+
+	while ((start = fetch_item(start, end, &item)) != NULL)
+		dispatch_type[item.type](parser, &item);
+
+	if ((hid->quirks & HID_QUIRK_MULTITOUCH) &&
+	    (hid->quirks & HID_QUIRK_MULTITOUCH_WIN_8))
+		hid_info(hid, "Detected Win8 compliant touchscreen\n");
+	else
+		hid->quirks &=~ (HID_QUIRK_MULTITOUCH | HID_QUIRK_MULTITOUCH_WIN_8);
+	vfree(parser);
+	return 0;
+}
+
 /**
  * hid_parse_report - parse device report
  *
@@ -660,7 +740,7 @@ int hid_parse_report(struct hid_device *
 		hid_parser_reserved
 	};
 
-	if (device->driver->report_fixup)
+	if (device->driver && device->driver->report_fixup)
 		start = device->driver->report_fixup(device, start, &size);
 
 	device->rdesc = kmemdup(start, size, GFP_KERNEL);
@@ -1685,12 +1765,19 @@ static int hid_bus_match(struct device *
 	struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver);
 	struct hid_device *hdev = container_of(dev, struct hid_device, dev);
 
+	if ((hdev->quirks & HID_QUIRK_MULTITOUCH_WIN_8) &&
+	    !strcmp(hdrv->name, "hid-multitouch"))
+		return 1;
+
 	if (!hid_match_device(hdev, hdrv))
 		return 0;
 
 	/* generic wants all that don't have specialized driver */
-	if (!strncmp(hdrv->name, "generic-", 8))
+	if (!strncmp(hdrv->name, "generic-", 8)) {
+		if (hdev->quirks & HID_QUIRK_MULTITOUCH_WIN_8)
+			return 0;
 		return !hid_match_id(hdev, hid_have_special_driver);
+	}
 
 	return 1;
 }
@@ -1986,6 +2073,12 @@ static const struct hid_device_id hid_mo
 	{ }
 };
 
+/* devices that need dynamic win8 touchscreen detection (e.g. Atmel) */
+static const struct hid_device_id hid_multitouch_win8_list[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATMEL, USB_DEVICE_ID_ATMEL_MXT_DIG2) },
+	{}
+};
+
 static bool hid_ignore(struct hid_device *hdev)
 {
 	switch (hdev->vendor) {
@@ -2038,6 +2131,15 @@ int hid_add_device(struct hid_device *hd
 	dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
 		     hdev->vendor, hdev->product, atomic_inc_return(&id));
 
+	if (hid_match_id(hdev, hid_multitouch_win8_list)) {
+		ret = hid_parse(hdev);
+		if (ret < 0)
+			return ret;
+		ret = hid_parse_multitouch_win8(hdev);
+		if (ret < 0)
+			return ret;
+	}
+
 	hid_debug_register(hdev, dev_name(&hdev->dev));
 	ret = device_add(&hdev->dev);
 	if (!ret)
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -149,6 +149,9 @@
 #define USB_DEVICE_ID_ATEN_4PORTKVM	0x2205
 #define USB_DEVICE_ID_ATEN_4PORTKVMC	0x2208
 
+#define USB_VENDOR_ID_ATMEL		0x03eb
+#define USB_DEVICE_ID_ATMEL_MXT_DIG2	0x212c
+
 #define USB_VENDOR_ID_AVERMEDIA		0x07ca
 #define USB_DEVICE_ID_AVER_FM_MR800	0xb800
 
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -639,6 +639,11 @@ static const struct hid_device_id mt_dev
 		HID_USB_DEVICE(USB_VENDOR_ID_ACTIONSTAR,
 			USB_DEVICE_ID_ACTIONSTAR_1011) },
 
+	/* Atmel panels */
+	{ .driver_data = MT_CLS_WIN_8,
+		HID_USB_DEVICE(USB_VENDOR_ID_ATMEL,
+			USB_DEVICE_ID_ATMEL_MXT_DIG2) },
+
 	/* Cando panels */
 	{ .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER,
 		HID_USB_DEVICE(USB_VENDOR_ID_CANDO,
--- a/drivers/hid/usbhid/hid-quirks.c
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -32,6 +32,7 @@ static const struct hid_blacklist {
 	{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD, HID_QUIRK_BADPAD },
 	{ USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR, HID_QUIRK_BADPAD },
 	{ USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD, HID_QUIRK_BADPAD },
+	{ USB_VENDOR_ID_ATMEL, USB_DEVICE_ID_ATMEL_MXT_DIG2, HID_QUIRK_MULTI_INPUT },
 	{ USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, HID_QUIRK_BADPAD },
 	{ USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER, HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
 	{ USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER, HID_QUIRK_MULTI_INPUT },
