ChangeSet 1.1248, 2003/06/18 16:54:52-07:00, bcollins@debian.org

[PATCH] USB Multi-input quirk


 drivers/usb/hid-core.c  |    8 +--
 drivers/usb/hid-input.c |  120 +++++++++++++++++++++++++++++++++++++-----------
 drivers/usb/hid.h       |   10 +++-
 3 files changed, 107 insertions(+), 31 deletions(-)


diff -Nru a/drivers/usb/hid-core.c b/drivers/usb/hid-core.c
--- a/drivers/usb/hid-core.c	Wed Jun 18 17:35:10 2003
+++ b/drivers/usb/hid-core.c	Wed Jun 18 17:35:10 2003
@@ -1146,9 +1146,9 @@
 	{ USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_POWERMATE, HID_QUIRK_IGNORE },
 	{ USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_SOUNDKNOB, HID_QUIRK_IGNORE },
 	{ USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD },
-	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD },
-	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD },
-	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD },
+	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT },
+	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT },
+	{ USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING, HID_QUIRK_BADPAD|HID_QUIRK_MULTI_INPUT },
  	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE },
  	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100, HID_QUIRK_IGNORE },
  	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE },
@@ -1304,7 +1304,7 @@
 	printk(KERN_INFO);
 
 	if (hid->claimed & HID_CLAIMED_INPUT)
-		printk("input%d", hid->input.number);
+		printk("input");
 	if (hid->claimed == (HID_CLAIMED_INPUT | HID_CLAIMED_HIDDEV))
 		printk(",");
 	if (hid->claimed & HID_CLAIMED_HIDDEV)
diff -Nru a/drivers/usb/hid-input.c b/drivers/usb/hid-input.c
--- a/drivers/usb/hid-input.c	Wed Jun 18 17:35:10 2003
+++ b/drivers/usb/hid-input.c	Wed Jun 18 17:35:10 2003
@@ -63,9 +63,36 @@
 	__s32 y;
 }  hid_hat_to_axis[] = {{0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}};
 
-static void hidinput_configure_usage(struct hid_device *device, struct hid_field *field, struct hid_usage *usage)
+static struct input_dev *find_input(struct hid_device *hid, struct hid_field *field)
 {
-	struct input_dev *input = &device->input;
+	struct list_head *lh;
+	struct hid_input *hidinput;
+
+	list_for_each (lh, &hid->inputs) {
+		int i;
+
+		hidinput = list_entry(lh, struct hid_input, list);
+
+		for (i = 0; i < hidinput->maxfield; i++)
+			if (hidinput->fields[i] == field)
+				return &hidinput->input;
+	}
+
+	/* Assume we only have one input and use it */
+	if (!list_empty(&hid->inputs)) {
+		hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+		return &hidinput->input;
+	}
+
+	/* This is really a bug */
+	return NULL;
+}
+
+static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
+				     struct hid_usage *usage)
+{
+	struct input_dev *input = &hidinput->input;
+	struct hid_device *device = hidinput->input.private;
 	int max;
 	unsigned long *bit;
 
@@ -310,9 +337,12 @@
 
 void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
 {
-	struct input_dev *input = &hid->input;
+	struct input_dev *input = find_input(hid, field);
 	int *quirks = &hid->quirks;
 
+	if (!input)
+		return;
+
 	if (usage->hat_min != usage->hat_max) {
 		value = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1;
 		if (value < 0 || value > 8) value = 0;
@@ -392,7 +422,10 @@
 	struct hid_report_enum *report_enum;
 	struct hid_report *report;
 	struct list_head *list;
-	int i, j, k;
+	struct hid_input *hidinput = NULL;
+	int i, j;
+
+	INIT_LIST_HEAD(&hid->inputs);
 
 	for (i = 0; i < hid->maxapplication; i++)
 		if (IS_INPUT_APPLICATION(hid->application[i]))
@@ -401,35 +434,70 @@
 	if (i == hid->maxapplication)
 		return -1;
 
-	hid->input.private = hid;
-	hid->input.event = hidinput_input_event;
-	hid->input.open = hidinput_open;
-	hid->input.close = hidinput_close;
-
-	hid->input.name = hid->name;
-	hid->input.idbus = BUS_USB;
-	hid->input.idvendor = dev->descriptor.idVendor;
-	hid->input.idproduct = dev->descriptor.idProduct;
-	hid->input.idversion = dev->descriptor.bcdDevice;
-
-	for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) {
-		report_enum = hid->report_enum + k;
-		list = report_enum->report_list.next;
-		while (list != &report_enum->report_list) {
-			report = (struct hid_report *) list;
-			for (i = 0; i < report->maxfield; i++)
-				for (j = 0; j < report->field[i]->maxusage; j++)
-					hidinput_configure_usage(hid, report->field[i], report->field[i]->usage + j);
-			list = list->next;
+	report_enum = hid->report_enum + HID_INPUT_REPORT;
+	list = report_enum->report_list.next;
+	while (list != &report_enum->report_list) {
+		report = (struct hid_report *) list;
+
+		if (!report->maxfield)
+			continue;
+
+		if (!hidinput) {
+			hidinput = kmalloc(sizeof(*hidinput), GFP_KERNEL);
+			if (!hidinput) {
+				err("Out of memory during hid input probe");
+				return -1;
+			}
+			memset(hidinput, 0, sizeof(*hidinput));
+			list_add_tail(&hidinput->list, &hid->inputs);
+
+			hidinput->input.private = hid;
+			hidinput->input.event = hidinput_input_event;
+			hidinput->input.open = hidinput_open;
+			hidinput->input.close = hidinput_close;
+
+			hidinput->input.name = hid->name;
+			hidinput->input.idbus = BUS_USB;
+			hidinput->input.idvendor = dev->descriptor.idVendor;
+			hidinput->input.idproduct = dev->descriptor.idProduct;
+			hidinput->input.idversion = dev->descriptor.bcdDevice;
 		}
+
+		for (i = 0; i < report->maxfield; i++)
+			for (j = 0; j < report->field[i]->maxusage; j++)
+				hidinput_configure_usage(hidinput, report->field[i],
+							 report->field[i]->usage + j);
+
+		if (hid->quirks & HID_QUIRK_MULTI_INPUT) {
+			/* This will leave hidinput NULL, so that it
+			 * allocates another one if we have more inputs on
+			 * the same interface. Some devices (e.g. Happ's
+			 * UGCI) cram a lot of unrelated inputs into the
+			 * same interface. */
+			hidinput->fields = report->field;
+			hidinput->maxfield = report->maxfield;
+			input_register_device(&hidinput->input);
+			hidinput = NULL;
+		}
+
+		list = list->next;
 	}
 
-	input_register_device(&hid->input);
+	if (hidinput)
+		input_register_device(&hidinput->input);
 
 	return 0;
 }
 
 void hidinput_disconnect(struct hid_device *hid)
 {
-	input_unregister_device(&hid->input);
+	struct list_head *lh, *next;
+	struct hid_input *hidinput;
+
+	list_for_each_safe (lh, next, &hid->inputs) {
+		hidinput = list_entry(lh, struct hid_input, list);
+		input_unregister_device(&hidinput->input);
+		list_del(&hidinput->list);
+		kfree(hidinput);
+	}
 }
diff -Nru a/drivers/usb/hid.h b/drivers/usb/hid.h
--- a/drivers/usb/hid.h	Wed Jun 18 17:35:10 2003
+++ b/drivers/usb/hid.h	Wed Jun 18 17:35:10 2003
@@ -188,6 +188,7 @@
 #define HID_QUIRK_NOGET		0x08
 #define HID_QUIRK_HIDDEV	0x10
 #define HID_QUIRK_BADPAD	0x20
+#define HID_QUIRK_MULTI_INPUT	0x40
 
 /*
  * This is the global environment of the parser. This information is
@@ -296,6 +297,13 @@
 #define HID_CLAIMED_INPUT	1
 #define HID_CLAIMED_HIDDEV	2
 
+struct hid_input {
+	struct list_head list;
+	struct hid_field **fields;
+	int maxfield;
+	struct input_dev input;
+};
+
 struct hid_device {							/* device report descriptor */
 	 __u8 *rdesc;
 	unsigned rsize;
@@ -318,7 +326,7 @@
 	unsigned claimed;						/* Claimed by hidinput, hiddev? */	
 	unsigned quirks;						/* Various quirks the device can pull on us */
 
-	struct input_dev input;						/* The input structure */
+	struct list_head inputs;					/* The list of inputs */
 	void *hiddev;							/* The hiddev structure */
 	int minor;							/* Hiddev minor number */