summary refs log tree commit diff
path: root/drivers/hid
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@woody.linux-foundation.org>2007-04-30 08:58:21 -0700
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-04-30 08:58:21 -0700
commitd6454706c382ab74e2ecad7803c434cc6bd30343 (patch)
tree2a380b28eb948d114c491f0b6799c10406030849 /drivers/hid
parent152a6a9da1bd3ed5dcbbf6ff17c7ebde0eb9a754 (diff)
parent11941a321d49cd2cafc8e64f66cbfed60fc1c691 (diff)
downloadlinux-d6454706c382ab74e2ecad7803c434cc6bd30343.tar.gz
Merge branch 'for-linus' of master.kernel.org:/pub/scm/linux/kernel/git/jikos/hid
* 'for-linus' of master.kernel.org:/pub/scm/linux/kernel/git/jikos/hid: (21 commits)
  USB HID: don't warn on idVendor == 0
  USB HID: add 'quirks' module parameter
  USB HID: add support for dynamically-created quirks
  USB HID: clarify static quirk handling as squirks
  USB HID: encapsulate quirk handling into hid-quirks.c
  USB HID: EMS USBII device needs HID_QUIRK_MULTI_INPUT
  HID: update copyright and authorship macro
  HID: introduce proper zeroing of unused bits in output reports
  USB HID: add support for WiseGroup MP-8800 Quad Joypad
  USB HID: add FF support for Logitech Force 3D Pro Joystick
  USB HID: numlock quirk for dell W7658 keyboard
  USB HID: Logitech MX3000 keyboard needs report descriptor quirk
  USB HID: extend quirk for Logitech S510 keyboard
  USB HID: usbkbd/usbmouse - handle errors when registering devices
  USB HID: add QUIRK_HIDDEV for Belkin Flip KVM
  HID: enable dead keys on a belkin wireless keyboard
  USB HID: Thustmaster firestorm dual power v1 support
  USB HID: specify explicit size for hid_blacklist.quirks
  USB HID: fix retry & reset logic
  USB HID: consolidate vendor/product ids
  ...
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/Kconfig2
-rw-r--r--drivers/hid/Makefile4
-rw-r--r--drivers/hid/hid-core.c9
-rw-r--r--drivers/hid/hid-input.c27
-rw-r--r--drivers/hid/usbhid/Kconfig149
-rw-r--r--drivers/hid/usbhid/Makefile35
-rw-r--r--drivers/hid/usbhid/hid-core.c1114
-rw-r--r--drivers/hid/usbhid/hid-ff.c91
-rw-r--r--drivers/hid/usbhid/hid-lgff.c151
-rw-r--r--drivers/hid/usbhid/hid-pidff.c1331
-rw-r--r--drivers/hid/usbhid/hid-plff.c129
-rw-r--r--drivers/hid/usbhid/hid-quirks.c681
-rw-r--r--drivers/hid/usbhid/hid-tmff.c147
-rw-r--r--drivers/hid/usbhid/hid-zpff.c111
-rw-r--r--drivers/hid/usbhid/hiddev.c847
-rw-r--r--drivers/hid/usbhid/usbhid.h87
-rw-r--r--drivers/hid/usbhid/usbkbd.c367
-rw-r--r--drivers/hid/usbhid/usbmouse.c252
18 files changed, 5531 insertions, 3 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 850788f4dd2e..8fbe9fdac128 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -36,5 +36,7 @@ config HID_DEBUG
 
 	If unsure, say N
 
+source "drivers/hid/usbhid/Kconfig"
+
 endmenu
 
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 52e97d8f3c95..68d1376a53fb 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -6,3 +6,7 @@ hid-objs			:= hid-core.o hid-input.o
 obj-$(CONFIG_HID)		+= hid.o
 hid-$(CONFIG_HID_DEBUG)		+= hid-debug.o
 
+obj-$(CONFIG_USB_HID)		+= usbhid/
+obj-$(CONFIG_USB_MOUSE)		+= usbhid/
+obj-$(CONFIG_USB_KBD)		+= usbhid/
+
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 1cca32f46947..62e21cc73938 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -4,7 +4,7 @@
  *  Copyright (c) 1999 Andreas Gal
  *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
  *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
- *  Copyright (c) 2006 Jiri Kosina
+ *  Copyright (c) 2006-2007 Jiri Kosina
  */
 
 /*
@@ -37,7 +37,7 @@
  */
 
 #define DRIVER_VERSION "v2.6"
-#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik"
+#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik, Jiri Kosina"
 #define DRIVER_DESC "HID core driver"
 #define DRIVER_LICENSE "GPL"
 
@@ -872,8 +872,13 @@ static void hid_output_field(struct hid_field *field, __u8 *data)
 	unsigned count = field->report_count;
 	unsigned offset = field->report_offset;
 	unsigned size = field->report_size;
+	unsigned bitsused = offset + count * size;
 	unsigned n;
 
+	/* make sure the unused bits in the last byte are zeros */
+	if (count > 0 && size > 0 && (bitsused % 8) != 0)
+		data[(bitsused-1)/8] &= (1 << (bitsused % 8)) - 1;
+
 	for (n = 0; n < count; n++) {
 		if (field->logical_minimum < 0)	/* signed values */
 			implement(data, offset + n * size, size, s32ton(field->value[n], size));
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index c8434023ba65..a19b65ed3119 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -431,6 +431,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
 				case 0x000: goto ignore;
 				case 0x034: map_key_clear(KEY_SLEEP);		break;
 				case 0x036: map_key_clear(BTN_MISC);		break;
+				/*
+				 * The next three are reported by Belkin wireless
+				 * keyboard (1020:0006). These values are "reserved"
+				 * in HUT 1.12.
+				 */
+				case 0x03a: map_key_clear(KEY_SOUND);           break;
+				case 0x03b: map_key_clear(KEY_CAMERA);          break;
+				case 0x03c: map_key_clear(KEY_DOCUMENTS);       break;
+
 				case 0x040: map_key_clear(KEY_MENU);		break;
 				case 0x045: map_key_clear(KEY_RADIO);		break;
 
@@ -531,10 +540,26 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
 				case 0x302: map_key_clear(KEY_PROG2);		break;
 				case 0x303: map_key_clear(KEY_PROG3);		break;
 
-				/* Reported on Logitech S510 wireless keyboard */
+				/* Reported on certain Logitech wireless keyboards */
+				case 0x1001: map_key_clear(KEY_MESSENGER);	break;
+				case 0x1003: map_key_clear(KEY_SOUND);		break;
+				case 0x1004: map_key_clear(KEY_VIDEO);		break;
+				case 0x1005: map_key_clear(KEY_AUDIO);		break;
+				case 0x100a: map_key_clear(KEY_DOCUMENTS);	break;
+				case 0x1011: map_key_clear(KEY_PREVIOUSSONG);	break;
+				case 0x1012: map_key_clear(KEY_NEXTSONG);	break;
+				case 0x1013: map_key_clear(KEY_CAMERA);		break;
+				case 0x1014: map_key_clear(KEY_MESSENGER);	break;
+				case 0x1015: map_key_clear(KEY_RECORD);		break;
+				case 0x1016: map_key_clear(KEY_PLAYER);		break;
+				case 0x1017: map_key_clear(KEY_EJECTCD);	break;
+				case 0x1019: map_key_clear(KEY_PROG1);		break;
+				case 0x101a: map_key_clear(KEY_PROG2);		break;
+				case 0x101b: map_key_clear(KEY_PROG3);		break;
 				case 0x101f: map_key_clear(KEY_ZOOMIN);		break;
 				case 0x1020: map_key_clear(KEY_ZOOMOUT);	break;
 				case 0x1021: map_key_clear(KEY_ZOOMRESET);	break;
+				case 0x1023: map_key_clear(KEY_CLOSE);		break;
 				/* this one is marked as 'Rotate' */
 				case 0x1028: map_key_clear(KEY_ANGLE);		break;
 				case 0x1029: map_key_clear(KEY_SHUFFLE);	break;
diff --git a/drivers/hid/usbhid/Kconfig b/drivers/hid/usbhid/Kconfig
new file mode 100644
index 000000000000..7c87bdc538bc
--- /dev/null
+++ b/drivers/hid/usbhid/Kconfig
@@ -0,0 +1,149 @@
+comment "USB Input Devices"
+	depends on USB
+
+config USB_HID
+	tristate "USB Human Interface Device (full HID) support"
+	default y
+	depends on USB && INPUT
+	select HID
+	---help---
+	  Say Y here if you want full HID support to connect USB keyboards,
+	  mice, joysticks, graphic tablets, or any other HID based devices
+	  to your computer via USB, as well as Uninterruptible Power Supply
+	  (UPS) and monitor control devices.
+
+	  You can't use this driver and the HIDBP (Boot Protocol) keyboard
+	  and mouse drivers at the same time. More information is available:
+	  <file:Documentation/input/input.txt>.
+
+	  If unsure, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbhid.
+
+comment "Input core support is needed for USB HID input layer or HIDBP support"
+	depends on USB_HID && INPUT=n
+
+config USB_HIDINPUT_POWERBOOK
+	bool "Enable support for iBook/PowerBook special keys"
+	default n
+	depends on USB_HID
+	help
+	  Say Y here if you want support for the special keys (Fn, Numlock) on
+	  Apple iBooks and PowerBooks.
+
+	  If unsure, say N.
+
+config HID_FF
+	bool "Force feedback support (EXPERIMENTAL)"
+	depends on USB_HID && EXPERIMENTAL
+	help
+	  Say Y here is you want force feedback support for a few HID devices.
+	  See below for a list of supported devices.
+
+	  See <file:Documentation/input/ff.txt> for a description of the force
+	  feedback API.
+
+	  If unsure, say N.
+
+config HID_PID
+	bool "PID device support"
+	depends on HID_FF
+	help
+	  Say Y here if you have a PID-compliant device and wish to enable force
+	  feedback for it. Microsoft Sidewinder Force Feedback 2 is one of such
+	  devices.
+
+config LOGITECH_FF
+	bool "Logitech devices support"
+	depends on HID_FF
+	select INPUT_FF_MEMLESS if USB_HID
+	help
+	  Say Y here if you have one of these devices:
+	  - Logitech WingMan Cordless RumblePad
+	  - Logitech WingMan Cordless RumblePad 2
+	  - Logitech WingMan Force 3D
+	  - Logitech Formula Force EX
+	  - Logitech MOMO Force wheel
+
+	  and if you want to enable force feedback for them.
+	  Note: if you say N here, this device will still be supported, but without
+	  force feedback.
+
+config PANTHERLORD_FF
+	bool "PantherLord USB/PS2 2in1 Adapter support"
+	depends on HID_FF
+	select INPUT_FF_MEMLESS if USB_HID
+	help
+	  Say Y here if you have a PantherLord USB/PS2 2in1 Adapter and want
+	  to enable force feedback support for it.
+
+config THRUSTMASTER_FF
+	bool "ThrustMaster FireStorm Dual Power 2 support (EXPERIMENTAL)"
+	depends on HID_FF && EXPERIMENTAL
+	select INPUT_FF_MEMLESS if USB_HID
+	help
+	  Say Y here if you have a THRUSTMASTER FireStore Dual Power 2,
+	  and want to enable force feedback support for it.
+	  Note: if you say N here, this device will still be supported, but without
+	  force feedback.
+
+config ZEROPLUS_FF
+	bool "Zeroplus based game controller support"
+	depends on HID_FF
+	select INPUT_FF_MEMLESS if USB_HID
+	help
+	  Say Y here if you have a Zeroplus based game controller and want to
+	  enable force feedback for it.
+
+config USB_HIDDEV
+	bool "/dev/hiddev raw HID device support"
+	depends on USB_HID
+	help
+	  Say Y here if you want to support HID devices (from the USB
+	  specification standpoint) that aren't strictly user interface
+	  devices, like monitor controls and Uninterruptable Power Supplies.
+
+	  This module supports these devices separately using a separate
+	  event interface on /dev/usb/hiddevX (char 180:96 to 180:111).
+
+	  If unsure, say Y.
+
+menu "USB HID Boot Protocol drivers"
+	depends on USB!=n && USB_HID!=y
+
+config USB_KBD
+	tristate "USB HIDBP Keyboard (simple Boot) support"
+	depends on USB && INPUT
+	---help---
+	  Say Y here only if you are absolutely sure that you don't want
+	  to use the generic HID driver for your USB keyboard and prefer
+	  to use the keyboard in its limited Boot Protocol mode instead.
+
+	  This is almost certainly not what you want.  This is mostly
+	  useful for embedded applications or simple keyboards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbkbd.
+
+	  If even remotely unsure, say N.
+
+config USB_MOUSE
+	tristate "USB HIDBP Mouse (simple Boot) support"
+	depends on USB && INPUT
+	---help---
+	  Say Y here only if you are absolutely sure that you don't want
+	  to use the generic HID driver for your USB mouse and prefer
+	  to use the mouse in its limited Boot Protocol mode instead.
+
+	  This is almost certainly not what you want.  This is mostly
+	  useful for embedded applications or simple mice.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbmouse.
+
+	  If even remotely unsure, say N.
+
+endmenu
+
+
diff --git a/drivers/hid/usbhid/Makefile b/drivers/hid/usbhid/Makefile
new file mode 100644
index 000000000000..8e6ab5b164a2
--- /dev/null
+++ b/drivers/hid/usbhid/Makefile
@@ -0,0 +1,35 @@
+#
+# Makefile for the USB input drivers
+#
+
+# Multipart objects.
+usbhid-objs	:= hid-core.o hid-quirks.o
+
+# Optional parts of multipart objects.
+
+ifeq ($(CONFIG_USB_HIDDEV),y)
+	usbhid-objs	+= hiddev.o
+endif
+ifeq ($(CONFIG_HID_PID),y)
+	usbhid-objs	+= hid-pidff.o
+endif
+ifeq ($(CONFIG_LOGITECH_FF),y)
+	usbhid-objs	+= hid-lgff.o
+endif
+ifeq ($(CONFIG_PANTHERLORD_FF),y)
+	usbhid-objs	+= hid-plff.o
+endif
+ifeq ($(CONFIG_THRUSTMASTER_FF),y)
+	usbhid-objs	+= hid-tmff.o
+endif
+ifeq ($(CONFIG_ZEROPLUS_FF),y)
+	usbhid-objs	+= hid-zpff.o
+endif
+ifeq ($(CONFIG_HID_FF),y)
+	usbhid-objs	+= hid-ff.o
+endif
+
+obj-$(CONFIG_USB_HID)		+= usbhid.o
+obj-$(CONFIG_USB_KBD)		+= usbkbd.o
+obj-$(CONFIG_USB_MOUSE)		+= usbmouse.o
+
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
new file mode 100644
index 000000000000..91d610358d57
--- /dev/null
+++ b/drivers/hid/usbhid/hid-core.c
@@ -0,0 +1,1114 @@
+/*
+ *  USB HID support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+#include <linux/input.h>
+#include <linux/wait.h>
+
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/hid-debug.h>
+#include "usbhid.h"
+
+/*
+ * Version Information
+ */
+
+#define DRIVER_VERSION "v2.6"
+#define DRIVER_AUTHOR "Andreas Gal, Vojtech Pavlik, Jiri Kosina"
+#define DRIVER_DESC "USB HID core driver"
+#define DRIVER_LICENSE "GPL"
+
+static char *hid_types[] = {"Device", "Pointer", "Mouse", "Device", "Joystick",
+				"Gamepad", "Keyboard", "Keypad", "Multi-Axis Controller"};
+/*
+ * Module parameters.
+ */
+
+static unsigned int hid_mousepoll_interval;
+module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
+MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
+
+/* Quirks specified at module load time */
+static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
+module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
+MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
+		" quirks=vendorID:productID:quirks"
+		" where vendorID, productID, and quirks are all in"
+		" 0x-prefixed hex");
+/*
+ * Input submission and I/O error handler.
+ */
+
+static void hid_io_error(struct hid_device *hid);
+
+/* Start up the input URB */
+static int hid_start_in(struct hid_device *hid)
+{
+	unsigned long flags;
+	int rc = 0;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irqsave(&usbhid->inlock, flags);
+	if (hid->open > 0 && !test_bit(HID_SUSPENDED, &usbhid->iofl) &&
+			!test_and_set_bit(HID_IN_RUNNING, &usbhid->iofl)) {
+		rc = usb_submit_urb(usbhid->urbin, GFP_ATOMIC);
+		if (rc != 0)
+			clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+	}
+	spin_unlock_irqrestore(&usbhid->inlock, flags);
+	return rc;
+}
+
+/* I/O retry timer routine */
+static void hid_retry_timeout(unsigned long _hid)
+{
+	struct hid_device *hid = (struct hid_device *) _hid;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	dev_dbg(&usbhid->intf->dev, "retrying intr urb\n");
+	if (hid_start_in(hid))
+		hid_io_error(hid);
+}
+
+/* Workqueue routine to reset the device or clear a halt */
+static void hid_reset(struct work_struct *work)
+{
+	struct usbhid_device *usbhid =
+		container_of(work, struct usbhid_device, reset_work);
+	struct hid_device *hid = usbhid->hid;
+	int rc_lock, rc = 0;
+
+	if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) {
+		dev_dbg(&usbhid->intf->dev, "clear halt\n");
+		rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe);
+		clear_bit(HID_CLEAR_HALT, &usbhid->iofl);
+		hid_start_in(hid);
+	}
+
+	else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) {
+		dev_dbg(&usbhid->intf->dev, "resetting device\n");
+		rc = rc_lock = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf);
+		if (rc_lock >= 0) {
+			rc = usb_reset_composite_device(hid_to_usb_dev(hid), usbhid->intf);
+			if (rc_lock)
+				usb_unlock_device(hid_to_usb_dev(hid));
+		}
+		clear_bit(HID_RESET_PENDING, &usbhid->iofl);
+	}
+
+	switch (rc) {
+	case 0:
+		if (!test_bit(HID_IN_RUNNING, &usbhid->iofl))
+			hid_io_error(hid);
+		break;
+	default:
+		err("can't reset device, %s-%s/input%d, status %d",
+				hid_to_usb_dev(hid)->bus->bus_name,
+				hid_to_usb_dev(hid)->devpath,
+				usbhid->ifnum, rc);
+		/* FALLTHROUGH */
+	case -EHOSTUNREACH:
+	case -ENODEV:
+	case -EINTR:
+		break;
+	}
+}
+
+/* Main I/O error handler */
+static void hid_io_error(struct hid_device *hid)
+{
+	unsigned long flags;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irqsave(&usbhid->inlock, flags);
+
+	/* Stop when disconnected */
+	if (usb_get_intfdata(usbhid->intf) == NULL)
+		goto done;
+
+	/* If it has been a while since the last error, we'll assume
+	 * this a brand new error and reset the retry timeout. */
+	if (time_after(jiffies, usbhid->stop_retry + HZ/2))
+		usbhid->retry_delay = 0;
+
+	/* When an error occurs, retry at increasing intervals */
+	if (usbhid->retry_delay == 0) {
+		usbhid->retry_delay = 13;	/* Then 26, 52, 104, 104, ... */
+		usbhid->stop_retry = jiffies + msecs_to_jiffies(1000);
+	} else if (usbhid->retry_delay < 100)
+		usbhid->retry_delay *= 2;
+
+	if (time_after(jiffies, usbhid->stop_retry)) {
+
+		/* Retries failed, so do a port reset */
+		if (!test_and_set_bit(HID_RESET_PENDING, &usbhid->iofl)) {
+			schedule_work(&usbhid->reset_work);
+			goto done;
+		}
+	}
+
+	mod_timer(&usbhid->io_retry,
+			jiffies + msecs_to_jiffies(usbhid->retry_delay));
+done:
+	spin_unlock_irqrestore(&usbhid->inlock, flags);
+}
+
+/*
+ * Input interrupt completion handler.
+ */
+
+static void hid_irq_in(struct urb *urb)
+{
+	struct hid_device	*hid = urb->context;
+	struct usbhid_device 	*usbhid = hid->driver_data;
+	int			status;
+
+	switch (urb->status) {
+		case 0:			/* success */
+			usbhid->retry_delay = 0;
+			hid_input_report(urb->context, HID_INPUT_REPORT,
+					 urb->transfer_buffer,
+					 urb->actual_length, 1);
+			break;
+		case -EPIPE:		/* stall */
+			clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+			set_bit(HID_CLEAR_HALT, &usbhid->iofl);
+			schedule_work(&usbhid->reset_work);
+			return;
+		case -ECONNRESET:	/* unlink */
+		case -ENOENT:
+		case -ESHUTDOWN:	/* unplug */
+			clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+			return;
+		case -EILSEQ:		/* protocol error or unplug */
+		case -EPROTO:		/* protocol error or unplug */
+		case -ETIME:		/* protocol error or unplug */
+		case -ETIMEDOUT:	/* Should never happen, but... */
+			clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+			hid_io_error(hid);
+			return;
+		default:		/* error */
+			warn("input irq status %d received", urb->status);
+	}
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status) {
+		clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+		if (status != -EPERM) {
+			err("can't resubmit intr, %s-%s/input%d, status %d",
+					hid_to_usb_dev(hid)->bus->bus_name,
+					hid_to_usb_dev(hid)->devpath,
+					usbhid->ifnum, status);
+			hid_io_error(hid);
+		}
+	}
+}
+
+static int hid_submit_out(struct hid_device *hid)
+{
+	struct hid_report *report;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	report = usbhid->out[usbhid->outtail];
+
+	hid_output_report(report, usbhid->outbuf);
+	usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) + 1 + (report->id > 0);
+	usbhid->urbout->dev = hid_to_usb_dev(hid);
+
+	dbg("submitting out urb");
+
+	if (usb_submit_urb(usbhid->urbout, GFP_ATOMIC)) {
+		err("usb_submit_urb(out) failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int hid_submit_ctrl(struct hid_device *hid)
+{
+	struct hid_report *report;
+	unsigned char dir;
+	int len;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	report = usbhid->ctrl[usbhid->ctrltail].report;
+	dir = usbhid->ctrl[usbhid->ctrltail].dir;
+
+	len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
+	if (dir == USB_DIR_OUT) {
+		hid_output_report(report, usbhid->ctrlbuf);
+		usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
+		usbhid->urbctrl->transfer_buffer_length = len;
+	} else {
+		int maxpacket, padlen;
+
+		usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0);
+		maxpacket = usb_maxpacket(hid_to_usb_dev(hid), usbhid->urbctrl->pipe, 0);
+		if (maxpacket > 0) {
+			padlen = (len + maxpacket - 1) / maxpacket;
+			padlen *= maxpacket;
+			if (padlen > usbhid->bufsize)
+				padlen = usbhid->bufsize;
+		} else
+			padlen = 0;
+		usbhid->urbctrl->transfer_buffer_length = padlen;
+	}
+	usbhid->urbctrl->dev = hid_to_usb_dev(hid);
+
+	usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir;
+	usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT : HID_REQ_GET_REPORT;
+	usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) | report->id);
+	usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum);
+	usbhid->cr->wLength = cpu_to_le16(len);
+
+	dbg("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u",
+		usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" : "Get_Report",
+		usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength);
+
+	if (usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC)) {
+		err("usb_submit_urb(ctrl) failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Output interrupt completion handler.
+ */
+
+static void hid_irq_out(struct urb *urb)
+{
+	struct hid_device *hid = urb->context;
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned long flags;
+	int unplug = 0;
+
+	switch (urb->status) {
+		case 0:			/* success */
+			break;
+		case -ESHUTDOWN:	/* unplug */
+			unplug = 1;
+		case -EILSEQ:		/* protocol error or unplug */
+		case -EPROTO:		/* protocol error or unplug */
+		case -ECONNRESET:	/* unlink */
+		case -ENOENT:
+			break;
+		default:		/* error */
+			warn("output irq status %d received", urb->status);
+	}
+
+	spin_lock_irqsave(&usbhid->outlock, flags);
+
+	if (unplug)
+		usbhid->outtail = usbhid->outhead;
+	else
+		usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
+
+	if (usbhid->outhead != usbhid->outtail) {
+		if (hid_submit_out(hid)) {
+			clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+			wake_up(&hid->wait);
+		}
+		spin_unlock_irqrestore(&usbhid->outlock, flags);
+		return;
+	}
+
+	clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+	spin_unlock_irqrestore(&usbhid->outlock, flags);
+	wake_up(&hid->wait);
+}
+
+/*
+ * Control pipe completion handler.
+ */
+
+static void hid_ctrl(struct urb *urb)
+{
+	struct hid_device *hid = urb->context;
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned long flags;
+	int unplug = 0;
+
+	spin_lock_irqsave(&usbhid->ctrllock, flags);
+
+	switch (urb->status) {
+		case 0:			/* success */
+			if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN)
+				hid_input_report(urb->context, usbhid->ctrl[usbhid->ctrltail].report->type,
+						urb->transfer_buffer, urb->actual_length, 0);
+			break;
+		case -ESHUTDOWN:	/* unplug */
+			unplug = 1;
+		case -EILSEQ:		/* protocol error or unplug */
+		case -EPROTO:		/* protocol error or unplug */
+		case -ECONNRESET:	/* unlink */
+		case -ENOENT:
+		case -EPIPE:		/* report not available */
+			break;
+		default:		/* error */
+			warn("ctrl urb status %d received", urb->status);
+	}
+
+	if (unplug)
+		usbhid->ctrltail = usbhid->ctrlhead;
+	else
+		usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
+
+	if (usbhid->ctrlhead != usbhid->ctrltail) {
+		if (hid_submit_ctrl(hid)) {
+			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+			wake_up(&hid->wait);
+		}
+		spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+		return;
+	}
+
+	clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+	spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+	wake_up(&hid->wait);
+}
+
+void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
+{
+	int head;
+	unsigned long flags;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
+		return;
+
+	if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
+
+		spin_lock_irqsave(&usbhid->outlock, flags);
+
+		if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
+			spin_unlock_irqrestore(&usbhid->outlock, flags);
+			warn("output queue full");
+			return;
+		}
+
+		usbhid->out[usbhid->outhead] = report;
+		usbhid->outhead = head;
+
+		if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl))
+			if (hid_submit_out(hid))
+				clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+
+		spin_unlock_irqrestore(&usbhid->outlock, flags);
+		return;
+	}
+
+	spin_lock_irqsave(&usbhid->ctrllock, flags);
+
+	if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
+		spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+		warn("control queue full");
+		return;
+	}
+
+	usbhid->ctrl[usbhid->ctrlhead].report = report;
+	usbhid->ctrl[usbhid->ctrlhead].dir = dir;
+	usbhid->ctrlhead = head;
+
+	if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+		if (hid_submit_ctrl(hid))
+			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+
+	spin_unlock_irqrestore(&usbhid->ctrllock, flags);
+}
+
+static int usb_hidinput_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
+{
+	struct hid_device *hid = dev->private;
+	struct hid_field *field;
+	int offset;
+
+	if (type == EV_FF)
+		return input_ff_event(dev, type, code, value);
+
+	if (type != EV_LED)
+		return -1;
+
+	if ((offset = hidinput_find_field(hid, type, code, &field)) == -1) {
+		warn("event field not found");
+		return -1;
+	}
+
+	hid_set_field(field, offset, value);
+	usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+
+	return 0;
+}
+
+int usbhid_wait_io(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (!wait_event_timeout(hid->wait, (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl) &&
+					!test_bit(HID_OUT_RUNNING, &usbhid->iofl)),
+					10*HZ)) {
+		dbg("timeout waiting for ctrl or out queue to clear");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle)
+{
+	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+		HID_REQ_SET_IDLE, USB_TYPE_CLASS | USB_RECIP_INTERFACE, (idle << 8) | report,
+		ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+static int hid_get_class_descriptor(struct usb_device *dev, int ifnum,
+		unsigned char type, void *buf, int size)
+{
+	int result, retries = 4;
+
+	memset(buf, 0, size);
+
+	do {
+		result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+				USB_REQ_GET_DESCRIPTOR, USB_RECIP_INTERFACE | USB_DIR_IN,
+				(type << 8), ifnum, buf, size, USB_CTRL_GET_TIMEOUT);
+		retries--;
+	} while (result < size && retries);
+	return result;
+}
+
+int usbhid_open(struct hid_device *hid)
+{
+	++hid->open;
+	if (hid_start_in(hid))
+		hid_io_error(hid);
+	return 0;
+}
+
+void usbhid_close(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (!--hid->open)
+		usb_kill_urb(usbhid->urbin);
+}
+
+/*
+ * Initialize all reports
+ */
+
+void usbhid_init_reports(struct hid_device *hid)
+{
+	struct hid_report *report;
+	struct usbhid_device *usbhid = hid->driver_data;
+	int err, ret;
+
+	list_for_each_entry(report, &hid->report_enum[HID_INPUT_REPORT].report_list, list)
+		usbhid_submit_report(hid, report, USB_DIR_IN);
+
+	list_for_each_entry(report, &hid->report_enum[HID_FEATURE_REPORT].report_list, list)
+		usbhid_submit_report(hid, report, USB_DIR_IN);
+
+	err = 0;
+	ret = usbhid_wait_io(hid);
+	while (ret) {
+		err |= ret;
+		if (test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+			usb_kill_urb(usbhid->urbctrl);
+		if (test_bit(HID_OUT_RUNNING, &usbhid->iofl))
+			usb_kill_urb(usbhid->urbout);
+		ret = usbhid_wait_io(hid);
+	}
+
+	if (err)
+		warn("timeout initializing reports");
+}
+
+/*
+ * Reset LEDs which BIOS might have left on. For now, just NumLock (0x01).
+ */
+static int hid_find_field_early(struct hid_device *hid, unsigned int page,
+    unsigned int hid_code, struct hid_field **pfield)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	struct hid_usage *usage;
+	int i, j;
+
+	list_for_each_entry(report, &hid->report_enum[HID_OUTPUT_REPORT].report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++) {
+				usage = &field->usage[j];
+				if ((usage->hid & HID_USAGE_PAGE) == page &&
+				    (usage->hid & 0xFFFF) == hid_code) {
+					*pfield = field;
+					return j;
+				}
+			}
+		}
+	}
+	return -1;
+}
+
+static void usbhid_set_leds(struct hid_device *hid)
+{
+	struct hid_field *field;
+	int offset;
+
+	if ((offset = hid_find_field_early(hid, HID_UP_LED, 0x01, &field)) != -1) {
+		hid_set_field(field, offset, 0);
+		usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+	}
+}
+
+/*
+ * Traverse the supplied list of reports and find the longest
+ */
+static void hid_find_max_report(struct hid_device *hid, unsigned int type, int *max)
+{
+	struct hid_report *report;
+	int size;
+
+	list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+		size = ((report->size - 1) >> 3) + 1;
+		if (type == HID_INPUT_REPORT && hid->report_enum[type].numbered)
+			size++;
+		if (*max < size)
+			*max = size;
+	}
+}
+
+static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (!(usbhid->inbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->inbuf_dma)))
+		return -1;
+	if (!(usbhid->outbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->outbuf_dma)))
+		return -1;
+	if (!(usbhid->cr = usb_buffer_alloc(dev, sizeof(*(usbhid->cr)), GFP_ATOMIC, &usbhid->cr_dma)))
+		return -1;
+	if (!(usbhid->ctrlbuf = usb_buffer_alloc(dev, usbhid->bufsize, GFP_ATOMIC, &usbhid->ctrlbuf_dma)))
+		return -1;
+
+	return 0;
+}
+
+static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (usbhid->inbuf)
+		usb_buffer_free(dev, usbhid->bufsize, usbhid->inbuf, usbhid->inbuf_dma);
+	if (usbhid->outbuf)
+		usb_buffer_free(dev, usbhid->bufsize, usbhid->outbuf, usbhid->outbuf_dma);
+	if (usbhid->cr)
+		usb_buffer_free(dev, sizeof(*(usbhid->cr)), usbhid->cr, usbhid->cr_dma);
+	if (usbhid->ctrlbuf)
+		usb_buffer_free(dev, usbhid->bufsize, usbhid->ctrlbuf, usbhid->ctrlbuf_dma);
+}
+
+/*
+ * Cherry Cymotion keyboard have an invalid HID report descriptor,
+ * that needs fixing before we can parse it.
+ */
+
+static void hid_fixup_cymotion_descriptor(char *rdesc, int rsize)
+{
+	if (rsize >= 17 && rdesc[11] == 0x3c && rdesc[12] == 0x02) {
+		info("Fixing up Cherry Cymotion report descriptor");
+		rdesc[11] = rdesc[16] = 0xff;
+		rdesc[12] = rdesc[17] = 0x03;
+	}
+}
+
+/*
+ * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller
+ * to "operational".  Without this, the ps3 controller will not report any
+ * events.
+ */
+static void hid_fixup_sony_ps3_controller(struct usb_device *dev, int ifnum)
+{
+	int result;
+	char *buf = kmalloc(18, GFP_KERNEL);
+
+	if (!buf)
+		return;
+
+	result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+				 HID_REQ_GET_REPORT,
+				 USB_DIR_IN | USB_TYPE_CLASS |
+				 USB_RECIP_INTERFACE,
+				 (3 << 8) | 0xf2, ifnum, buf, 17,
+				 USB_CTRL_GET_TIMEOUT);
+
+	if (result < 0)
+		err("%s failed: %d\n", __func__, result);
+
+	kfree(buf);
+}
+
+/*
+ * Certain Logitech keyboards send in report #3 keys which are far
+ * above the logical maximum described in descriptor. This extends
+ * the original value of 0x28c of logical maximum to 0x104d
+ */
+static void hid_fixup_logitech_descriptor(unsigned char *rdesc, int rsize)
+{
+	if (rsize >= 90 && rdesc[83] == 0x26
+			&& rdesc[84] == 0x8c
+			&& rdesc[85] == 0x02) {
+		info("Fixing up Logitech keyboard report descriptor");
+		rdesc[84] = rdesc[89] = 0x4d;
+		rdesc[85] = rdesc[90] = 0x10;
+	}
+}
+
+static struct hid_device *usb_hid_configure(struct usb_interface *intf)
+{
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	struct usb_device *dev = interface_to_usbdev (intf);
+	struct hid_descriptor *hdesc;
+	struct hid_device *hid;
+	u32 quirks = 0;
+	unsigned rsize = 0;
+	char *rdesc;
+	int n, len, insize = 0;
+	struct usbhid_device *usbhid;
+
+	quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
+			le16_to_cpu(dev->descriptor.idProduct));
+
+	/* Many keyboards and mice don't like to be polled for reports,
+	 * so we will always set the HID_QUIRK_NOGET flag for them. */
+	if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {
+		if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||
+			interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)
+				quirks |= HID_QUIRK_NOGET;
+	}
+
+	if (quirks & HID_QUIRK_IGNORE)
+		return NULL;
+
+	if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&
+		(interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))
+			return NULL;
+
+
+	if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&
+	    (!interface->desc.bNumEndpoints ||
+	     usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {
+		dbg("class descriptor not present\n");
+		return NULL;
+	}
+
+	for (n = 0; n < hdesc->bNumDescriptors; n++)
+		if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
+			rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
+
+	if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+		dbg("weird size of report descriptor (%u)", rsize);
+		return NULL;
+	}
+
+	if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
+		dbg("couldn't allocate rdesc memory");
+		return NULL;
+	}
+
+	hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);
+
+	if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
+		dbg("reading report descriptor failed");
+		kfree(rdesc);
+		return NULL;
+	}
+
+	if ((quirks & HID_QUIRK_CYMOTION))
+		hid_fixup_cymotion_descriptor(rdesc, rsize);
+
+	if (quirks & HID_QUIRK_LOGITECH_DESCRIPTOR)
+		hid_fixup_logitech_descriptor(rdesc, rsize);
+
+#ifdef CONFIG_HID_DEBUG
+	printk(KERN_DEBUG __FILE__ ": report descriptor (size %u, read %d) = ", rsize, n);
+	for (n = 0; n < rsize; n++)
+		printk(" %02x", (unsigned char) rdesc[n]);
+	printk("\n");
+#endif
+
+	if (!(hid = hid_parse_report(rdesc, n))) {
+		dbg("parsing report descriptor failed");
+		kfree(rdesc);
+		return NULL;
+	}
+
+	kfree(rdesc);
+	hid->quirks = quirks;
+
+	if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))
+		goto fail;
+
+	hid->driver_data = usbhid;
+	usbhid->hid = hid;
+
+	usbhid->bufsize = HID_MIN_BUFFER_SIZE;
+	hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);
+	hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);
+	hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);
+
+	if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)
+		usbhid->bufsize = HID_MAX_BUFFER_SIZE;
+
+	hid_find_max_report(hid, HID_INPUT_REPORT, &insize);
+
+	if (insize > HID_MAX_BUFFER_SIZE)
+		insize = HID_MAX_BUFFER_SIZE;
+
+	if (hid_alloc_buffers(dev, hid)) {
+		hid_free_buffers(dev, hid);
+		goto fail;
+	}
+
+	for (n = 0; n < interface->desc.bNumEndpoints; n++) {
+
+		struct usb_endpoint_descriptor *endpoint;
+		int pipe;
+		int interval;
+
+		endpoint = &interface->endpoint[n].desc;
+		if ((endpoint->bmAttributes & 3) != 3)		/* Not an interrupt endpoint */
+			continue;
+
+		interval = endpoint->bInterval;
+
+		/* Change the polling interval of mice. */
+		if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)
+			interval = hid_mousepoll_interval;
+
+		if (usb_endpoint_dir_in(endpoint)) {
+			if (usbhid->urbin)
+				continue;
+			if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))
+				goto fail;
+			pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+			usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,
+					 hid_irq_in, hid, interval);
+			usbhid->urbin->transfer_dma = usbhid->inbuf_dma;
+			usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		} else {
+			if (usbhid->urbout)
+				continue;
+			if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))
+				goto fail;
+			pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);
+			usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,
+					 hid_irq_out, hid, interval);
+			usbhid->urbout->transfer_dma = usbhid->outbuf_dma;
+			usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		}
+	}
+
+	if (!usbhid->urbin) {
+		err("couldn't find an input interrupt endpoint");
+		goto fail;
+	}
+
+	init_waitqueue_head(&hid->wait);
+
+	INIT_WORK(&usbhid->reset_work, hid_reset);
+	setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);
+
+	spin_lock_init(&usbhid->inlock);
+	spin_lock_init(&usbhid->outlock);
+	spin_lock_init(&usbhid->ctrllock);
+
+	hid->version = le16_to_cpu(hdesc->bcdHID);
+	hid->country = hdesc->bCountryCode;
+	hid->dev = &intf->dev;
+	usbhid->intf = intf;
+	usbhid->ifnum = interface->desc.bInterfaceNumber;
+
+	hid->name[0] = 0;
+
+	if (dev->manufacturer)
+		strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(hid->name, " ", sizeof(hid->name));
+		strlcat(hid->name, dev->product, sizeof(hid->name));
+	}
+
+	if (!strlen(hid->name))
+		snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	hid->bus = BUS_USB;
+	hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
+	hid->product = le16_to_cpu(dev->descriptor.idProduct);
+
+	usb_make_path(dev, hid->phys, sizeof(hid->phys));
+	strlcat(hid->phys, "/input", sizeof(hid->phys));
+	len = strlen(hid->phys);
+	if (len < sizeof(hid->phys) - 1)
+		snprintf(hid->phys + len, sizeof(hid->phys) - len,
+			 "%d", intf->altsetting[0].desc.bInterfaceNumber);
+
+	if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)
+		hid->uniq[0] = 0;
+
+	usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);
+	if (!usbhid->urbctrl)
+		goto fail;
+
+	usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,
+			     usbhid->ctrlbuf, 1, hid_ctrl, hid);
+	usbhid->urbctrl->setup_dma = usbhid->cr_dma;
+	usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;
+	usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
+	hid->hidinput_input_event = usb_hidinput_input_event;
+	hid->hid_open = usbhid_open;
+	hid->hid_close = usbhid_close;
+#ifdef CONFIG_USB_HIDDEV
+	hid->hiddev_hid_event = hiddev_hid_event;
+	hid->hiddev_report_event = hiddev_report_event;
+#endif
+	return hid;
+
+fail:
+	usb_free_urb(usbhid->urbin);
+	usb_free_urb(usbhid->urbout);
+	usb_free_urb(usbhid->urbctrl);
+	hid_free_buffers(dev, hid);
+	hid_free_device(hid);
+
+	return NULL;
+}
+
+static void hid_disconnect(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata (intf);
+	struct usbhid_device *usbhid;
+
+	if (!hid)
+		return;
+
+	usbhid = hid->driver_data;
+
+	spin_lock_irq(&usbhid->inlock);	/* Sync with error handler */
+	usb_set_intfdata(intf, NULL);
+	spin_unlock_irq(&usbhid->inlock);
+	usb_kill_urb(usbhid->urbin);
+	usb_kill_urb(usbhid->urbout);
+	usb_kill_urb(usbhid->urbctrl);
+
+	del_timer_sync(&usbhid->io_retry);
+	flush_scheduled_work();
+
+	if (hid->claimed & HID_CLAIMED_INPUT)
+		hidinput_disconnect(hid);
+	if (hid->claimed & HID_CLAIMED_HIDDEV)
+		hiddev_disconnect(hid);
+
+	usb_free_urb(usbhid->urbin);
+	usb_free_urb(usbhid->urbctrl);
+	usb_free_urb(usbhid->urbout);
+
+	hid_free_buffers(hid_to_usb_dev(hid), hid);
+	hid_free_device(hid);
+}
+
+static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct hid_device *hid;
+	char path[64];
+	int i;
+	char *c;
+
+	dbg("HID probe called for ifnum %d",
+			intf->altsetting->desc.bInterfaceNumber);
+
+	if (!(hid = usb_hid_configure(intf)))
+		return -ENODEV;
+
+	usbhid_init_reports(hid);
+	hid_dump_device(hid);
+	if (hid->quirks & HID_QUIRK_RESET_LEDS)
+		usbhid_set_leds(hid);
+
+	if (!hidinput_connect(hid))
+		hid->claimed |= HID_CLAIMED_INPUT;
+	if (!hiddev_connect(hid))
+		hid->claimed |= HID_CLAIMED_HIDDEV;
+
+	usb_set_intfdata(intf, hid);
+
+	if (!hid->claimed) {
+		printk ("HID device not claimed by input or hiddev\n");
+		hid_disconnect(intf);
+		return -ENODEV;
+	}
+
+	if ((hid->claimed & HID_CLAIMED_INPUT))
+		hid_ff_init(hid);
+
+	if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)
+		hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),
+			intf->cur_altsetting->desc.bInterfaceNumber);
+
+	printk(KERN_INFO);
+
+	if (hid->claimed & HID_CLAIMED_INPUT)
+		printk("input");
+	if (hid->claimed == (HID_CLAIMED_INPUT | HID_CLAIMED_HIDDEV))
+		printk(",");
+	if (hid->claimed & HID_CLAIMED_HIDDEV)
+		printk("hiddev%d", hid->minor);
+
+	c = "Device";
+	for (i = 0; i < hid->maxcollection; i++) {
+		if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&
+		    (hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&
+		    (hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {
+			c = hid_types[hid->collection[i].usage & 0xffff];
+			break;
+		}
+	}
+
+	usb_make_path(interface_to_usbdev(intf), path, 63);
+
+	printk(": USB HID v%x.%02x %s [%s] on %s\n",
+		hid->version >> 8, hid->version & 0xff, c, hid->name, path);
+
+	return 0;
+}
+
+static int hid_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct hid_device *hid = usb_get_intfdata (intf);
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irq(&usbhid->inlock);	/* Sync with error handler */
+	set_bit(HID_SUSPENDED, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->inlock);
+	del_timer(&usbhid->io_retry);
+	usb_kill_urb(usbhid->urbin);
+	dev_dbg(&intf->dev, "suspend\n");
+	return 0;
+}
+
+static int hid_resume(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata (intf);
+	struct usbhid_device *usbhid = hid->driver_data;
+	int status;
+
+	clear_bit(HID_SUSPENDED, &usbhid->iofl);
+	usbhid->retry_delay = 0;
+	status = hid_start_in(hid);
+	dev_dbg(&intf->dev, "resume status %d\n", status);
+	return status;
+}
+
+/* Treat USB reset pretty much the same as suspend/resume */
+static void hid_pre_reset(struct usb_interface *intf)
+{
+	/* FIXME: What if the interface is already suspended? */
+	hid_suspend(intf, PMSG_ON);
+}
+
+static void hid_post_reset(struct usb_interface *intf)
+{
+	struct usb_device *dev = interface_to_usbdev (intf);
+
+	hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0);
+	/* FIXME: Any more reinitialization needed? */
+
+	hid_resume(intf);
+}
+
+static struct usb_device_id hid_usb_ids [] = {
+	{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
+		.bInterfaceClass = USB_INTERFACE_CLASS_HID },
+	{ }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, hid_usb_ids);
+
+static struct usb_driver hid_driver = {
+	.name =		"usbhid",
+	.probe =	hid_probe,
+	.disconnect =	hid_disconnect,
+	.suspend =	hid_suspend,
+	.resume =	hid_resume,
+	.pre_reset =	hid_pre_reset,
+	.post_reset =	hid_post_reset,
+	.id_table =	hid_usb_ids,
+};
+
+static int __init hid_init(void)
+{
+	int retval;
+	retval = usbhid_quirks_init(quirks_param);
+	if (retval)
+		goto usbhid_quirks_init_fail;
+	retval = hiddev_init();
+	if (retval)
+		goto hiddev_init_fail;
+	retval = usb_register(&hid_driver);
+	if (retval)
+		goto usb_register_fail;
+	info(DRIVER_VERSION ":" DRIVER_DESC);
+
+	return 0;
+usb_register_fail:
+	hiddev_exit();
+hiddev_init_fail:
+	usbhid_quirks_exit();
+usbhid_quirks_init_fail:
+	return retval;
+}
+
+static void __exit hid_exit(void)
+{
+	usb_deregister(&hid_driver);
+	hiddev_exit();
+	usbhid_quirks_exit();
+}
+
+module_init(hid_init);
+module_exit(hid_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
diff --git a/drivers/hid/usbhid/hid-ff.c b/drivers/hid/usbhid/hid-ff.c
new file mode 100644
index 000000000000..23431fbbc3d7
--- /dev/null
+++ b/drivers/hid/usbhid/hid-ff.c
@@ -0,0 +1,91 @@
+/*
+ * $Id: hid-ff.c,v 1.2 2002/04/18 22:02:47 jdeneux Exp $
+ *
+ *  Force feedback support for hid devices.
+ *  Not all hid devices use the same protocol. For example, some use PID,
+ *  other use their own proprietary procotol.
+ *
+ *  Copyright (c) 2002-2004 Johann Deneux
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <johann.deneux@it.uu.se>
+ */
+
+#include <linux/input.h>
+
+#undef DEBUG
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+#include "usbhid.h"
+
+/*
+ * This table contains pointers to initializers. To add support for new
+ * devices, you need to add the USB vendor and product ids here.
+ */
+struct hid_ff_initializer {
+	u16 idVendor;
+	u16 idProduct;
+	int (*init)(struct hid_device*);
+};
+
+/*
+ * We try pidff when no other driver is found because PID is the
+ * standards compliant way of implementing force feedback in HID.
+ * pidff_init() will quickly abort if the device doesn't appear to
+ * be a PID device
+ */
+static struct hid_ff_initializer inits[] = {
+#ifdef CONFIG_LOGITECH_FF
+	{ 0x46d, 0xc211, hid_lgff_init }, /* Logitech Cordless rumble pad */
+	{ 0x46d, 0xc219, hid_lgff_init }, /* Logitech Cordless rumble pad 2 */
+	{ 0x46d, 0xc283, hid_lgff_init }, /* Logitech Wingman Force 3d */
+	{ 0x46d, 0xc286, hid_lgff_init }, /* Logitech Force 3D Pro Joystick */
+	{ 0x46d, 0xc294, hid_lgff_init }, /* Logitech Formula Force EX */
+	{ 0x46d, 0xc295, hid_lgff_init }, /* Logitech MOMO force wheel */
+	{ 0x46d, 0xca03, hid_lgff_init }, /* Logitech MOMO force wheel */
+#endif
+#ifdef CONFIG_PANTHERLORD_FF
+	{ 0x810, 0x0001, hid_plff_init },
+#endif
+#ifdef CONFIG_THRUSTMASTER_FF
+	{ 0x44f, 0xb300, hid_tmff_init },
+	{ 0x44f, 0xb304, hid_tmff_init },
+#endif
+#ifdef CONFIG_ZEROPLUS_FF
+	{ 0xc12, 0x0005, hid_zpff_init },
+	{ 0xc12, 0x0030, hid_zpff_init },
+#endif
+	{ 0,	 0,	 hid_pidff_init}  /* Matches anything */
+};
+
+int hid_ff_init(struct hid_device* hid)
+{
+	struct hid_ff_initializer *init;
+	int vendor = le16_to_cpu(hid_to_usb_dev(hid)->descriptor.idVendor);
+	int product = le16_to_cpu(hid_to_usb_dev(hid)->descriptor.idProduct);
+
+	for (init = inits; init->idVendor; init++)
+		if (init->idVendor == vendor && init->idProduct == product)
+			break;
+
+	return init->init(hid);
+}
+EXPORT_SYMBOL_GPL(hid_ff_init);
+
diff --git a/drivers/hid/usbhid/hid-lgff.c b/drivers/hid/usbhid/hid-lgff.c
new file mode 100644
index 000000000000..92d2553f17b6
--- /dev/null
+++ b/drivers/hid/usbhid/hid-lgff.c
@@ -0,0 +1,151 @@
+/*
+ * Force feedback support for hid-compliant for some of the devices from
+ * Logitech, namely:
+ * - WingMan Cordless RumblePad
+ * - WingMan Force 3D
+ *
+ *  Copyright (c) 2002-2004 Johann Deneux
+ *  Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <johann.deneux@it.uu.se>
+ */
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include "usbhid.h"
+
+struct dev_type {
+	u16 idVendor;
+	u16 idProduct;
+	const signed short *ff;
+};
+
+static const signed short ff_rumble[] = {
+	FF_RUMBLE,
+	-1
+};
+
+static const signed short ff_joystick[] = {
+	FF_CONSTANT,
+	-1
+};
+
+static const struct dev_type devices[] = {
+	{ 0x046d, 0xc211, ff_rumble },
+	{ 0x046d, 0xc219, ff_rumble },
+	{ 0x046d, 0xc283, ff_joystick },
+	{ 0x046d, 0xc286, ff_joystick },
+	{ 0x046d, 0xc294, ff_joystick },
+	{ 0x046d, 0xc295, ff_joystick },
+	{ 0x046d, 0xca03, ff_joystick },
+};
+
+static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = dev->private;
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	int x, y;
+	unsigned int left, right;
+
+#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		x = effect->u.ramp.start_level + 0x7f;	/* 0x7f is center */
+		y = effect->u.ramp.end_level + 0x7f;
+		CLAMP(x);
+		CLAMP(y);
+		report->field[0]->value[0] = 0x51;
+		report->field[0]->value[1] = 0x08;
+		report->field[0]->value[2] = x;
+		report->field[0]->value[3] = y;
+		dbg("(x, y)=(%04x, %04x)", x, y);
+		usbhid_submit_report(hid, report, USB_DIR_OUT);
+		break;
+
+	case FF_RUMBLE:
+		right = effect->u.rumble.strong_magnitude;
+		left = effect->u.rumble.weak_magnitude;
+		right = right * 0xff / 0xffff;
+		left = left * 0xff / 0xffff;
+		CLAMP(left);
+		CLAMP(right);
+		report->field[0]->value[0] = 0x42;
+		report->field[0]->value[1] = 0x00;
+		report->field[0]->value[2] = left;
+		report->field[0]->value[3] = right;
+		dbg("(left, right)=(%04x, %04x)", left, right);
+		usbhid_submit_report(hid, report, USB_DIR_OUT);
+		break;
+	}
+	return 0;
+}
+
+int hid_lgff_init(struct hid_device* hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	struct hid_report *report;
+	struct hid_field *field;
+	const signed short *ff_bits = ff_joystick;
+	int error;
+	int i;
+
+	/* Find the report to use */
+	if (list_empty(report_list)) {
+		err("No output report found");
+		return -1;
+	}
+
+	/* Check that the report looks ok */
+	report = list_entry(report_list->next, struct hid_report, list);
+	if (!report) {
+		err("NULL output report");
+		return -1;
+	}
+
+	field = report->field[0];
+	if (!field) {
+		err("NULL field");
+		return -1;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(devices); i++) {
+		if (dev->id.vendor == devices[i].idVendor &&
+		    dev->id.product == devices[i].idProduct) {
+			ff_bits = devices[i].ff;
+			break;
+		}
+	}
+
+	for (i = 0; ff_bits[i] >= 0; i++)
+		set_bit(ff_bits[i], dev->ffbit);
+
+	error = input_ff_create_memless(dev, NULL, hid_lgff_play);
+	if (error)
+		return error;
+
+	printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n");
+
+	return 0;
+}
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
new file mode 100644
index 000000000000..f5a90e950e6b
--- /dev/null
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -0,0 +1,1331 @@
+/*
+ *  Force feedback driver for USB HID PID compliant devices
+ *
+ *  Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define DEBUG */
+
+#define debug(format, arg...) pr_debug("hid-pidff: " format "\n" , ## arg)
+
+#include <linux/input.h>
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+
+#include "usbhid.h"
+
+#define	PID_EFFECTS_MAX		64
+
+/* Report usage table used to put reports into an array */
+
+#define PID_SET_EFFECT		0
+#define PID_EFFECT_OPERATION	1
+#define PID_DEVICE_GAIN		2
+#define PID_POOL		3
+#define PID_BLOCK_LOAD		4
+#define PID_BLOCK_FREE		5
+#define PID_DEVICE_CONTROL	6
+#define PID_CREATE_NEW_EFFECT	7
+
+#define PID_REQUIRED_REPORTS	7
+
+#define PID_SET_ENVELOPE	8
+#define PID_SET_CONDITION	9
+#define PID_SET_PERIODIC	10
+#define PID_SET_CONSTANT	11
+#define PID_SET_RAMP		12
+static const u8 pidff_reports[] = {
+	0x21, 0x77, 0x7d, 0x7f, 0x89, 0x90, 0x96, 0xab,
+	0x5a, 0x5f, 0x6e, 0x73, 0x74
+};
+
+/* device_control is really 0x95, but 0x96 specified as it is the usage of
+the only field in that report */
+
+/* Value usage tables used to put fields and values into arrays */
+
+#define PID_EFFECT_BLOCK_INDEX	0
+
+#define PID_DURATION		1
+#define PID_GAIN		2
+#define PID_TRIGGER_BUTTON	3
+#define PID_TRIGGER_REPEAT_INT	4
+#define PID_DIRECTION_ENABLE	5
+#define PID_START_DELAY		6
+static const u8 pidff_set_effect[] = {
+	0x22, 0x50, 0x52, 0x53, 0x54, 0x56, 0xa7
+};
+
+#define PID_ATTACK_LEVEL	1
+#define PID_ATTACK_TIME		2
+#define PID_FADE_LEVEL		3
+#define PID_FADE_TIME		4
+static const u8 pidff_set_envelope[] = { 0x22, 0x5b, 0x5c, 0x5d, 0x5e };
+
+#define PID_PARAM_BLOCK_OFFSET	1
+#define PID_CP_OFFSET		2
+#define PID_POS_COEFFICIENT	3
+#define PID_NEG_COEFFICIENT	4
+#define PID_POS_SATURATION	5
+#define PID_NEG_SATURATION	6
+#define PID_DEAD_BAND		7
+static const u8 pidff_set_condition[] = {
+	0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65
+};
+
+#define PID_MAGNITUDE		1
+#define PID_OFFSET		2
+#define PID_PHASE		3
+#define PID_PERIOD		4
+static const u8 pidff_set_periodic[] = { 0x22, 0x70, 0x6f, 0x71, 0x72 };
+static const u8 pidff_set_constant[] = { 0x22, 0x70 };
+
+#define PID_RAMP_START		1
+#define PID_RAMP_END		2
+static const u8 pidff_set_ramp[] = { 0x22, 0x75, 0x76 };
+
+#define PID_RAM_POOL_AVAILABLE	1
+static const u8 pidff_block_load[] = { 0x22, 0xac };
+
+#define PID_LOOP_COUNT		1
+static const u8 pidff_effect_operation[] = { 0x22, 0x7c };
+
+static const u8 pidff_block_free[] = { 0x22 };
+
+#define PID_DEVICE_GAIN_FIELD	0
+static const u8 pidff_device_gain[] = { 0x7e };
+
+#define PID_RAM_POOL_SIZE	0
+#define PID_SIMULTANEOUS_MAX	1
+#define PID_DEVICE_MANAGED_POOL	2
+static const u8 pidff_pool[] = { 0x80, 0x83, 0xa9 };
+
+/* Special field key tables used to put special field keys into arrays */
+
+#define PID_ENABLE_ACTUATORS	0
+#define PID_RESET		1
+static const u8 pidff_device_control[] = { 0x97, 0x9a };
+
+#define PID_CONSTANT	0
+#define PID_RAMP	1
+#define PID_SQUARE	2
+#define PID_SINE	3
+#define PID_TRIANGLE	4
+#define PID_SAW_UP	5
+#define PID_SAW_DOWN	6
+#define PID_SPRING	7
+#define PID_DAMPER	8
+#define PID_INERTIA	9
+#define PID_FRICTION	10
+static const u8 pidff_effect_types[] = {
+	0x26, 0x27, 0x30, 0x31, 0x32, 0x33, 0x34,
+	0x40, 0x41, 0x42, 0x43
+};
+
+#define PID_BLOCK_LOAD_SUCCESS	0
+#define PID_BLOCK_LOAD_FULL	1
+static const u8 pidff_block_load_status[] = { 0x8c, 0x8d };
+
+#define PID_EFFECT_START	0
+#define PID_EFFECT_STOP		1
+static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b };
+
+struct pidff_usage {
+	struct hid_field *field;
+	s32 *value;
+};
+
+struct pidff_device {
+	struct hid_device *hid;
+
+	struct hid_report *reports[sizeof(pidff_reports)];
+
+	struct pidff_usage set_effect[sizeof(pidff_set_effect)];
+	struct pidff_usage set_envelope[sizeof(pidff_set_envelope)];
+	struct pidff_usage set_condition[sizeof(pidff_set_condition)];
+	struct pidff_usage set_periodic[sizeof(pidff_set_periodic)];
+	struct pidff_usage set_constant[sizeof(pidff_set_constant)];
+	struct pidff_usage set_ramp[sizeof(pidff_set_ramp)];
+
+	struct pidff_usage device_gain[sizeof(pidff_device_gain)];
+	struct pidff_usage block_load[sizeof(pidff_block_load)];
+	struct pidff_usage pool[sizeof(pidff_pool)];
+	struct pidff_usage effect_operation[sizeof(pidff_effect_operation)];
+	struct pidff_usage block_free[sizeof(pidff_block_free)];
+
+	/* Special field is a field that is not composed of
+	   usage<->value pairs that pidff_usage values are */
+
+	/* Special field in create_new_effect */
+	struct hid_field *create_new_effect_type;
+
+	/* Special fields in set_effect */
+	struct hid_field *set_effect_type;
+	struct hid_field *effect_direction;
+
+	/* Special field in device_control */
+	struct hid_field *device_control;
+
+	/* Special field in block_load */
+	struct hid_field *block_load_status;
+
+	/* Special field in effect_operation */
+	struct hid_field *effect_operation_status;
+
+	int control_id[sizeof(pidff_device_control)];
+	int type_id[sizeof(pidff_effect_types)];
+	int status_id[sizeof(pidff_block_load_status)];
+	int operation_id[sizeof(pidff_effect_operation_status)];
+
+	int pid_id[PID_EFFECTS_MAX];
+};
+
+/*
+ * Scale an unsigned value with range 0..max for the given field
+ */
+static int pidff_rescale(int i, int max, struct hid_field *field)
+{
+	return i * (field->logical_maximum - field->logical_minimum) / max +
+	    field->logical_minimum;
+}
+
+/*
+ * Scale a signed value in range -0x8000..0x7fff for the given field
+ */
+static int pidff_rescale_signed(int i, struct hid_field *field)
+{
+	return i == 0 ? 0 : i >
+	    0 ? i * field->logical_maximum / 0x7fff : i *
+	    field->logical_minimum / -0x8000;
+}
+
+static void pidff_set(struct pidff_usage *usage, u16 value)
+{
+	usage->value[0] = pidff_rescale(value, 0xffff, usage->field);
+	debug("calculated from %d to %d", value, usage->value[0]);
+}
+
+static void pidff_set_signed(struct pidff_usage *usage, s16 value)
+{
+	if (usage->field->logical_minimum < 0)
+		usage->value[0] = pidff_rescale_signed(value, usage->field);
+	else {
+		if (value < 0)
+			usage->value[0] =
+			    pidff_rescale(-value, 0x8000, usage->field);
+		else
+			usage->value[0] =
+			    pidff_rescale(value, 0x7fff, usage->field);
+	}
+	debug("calculated from %d to %d", value, usage->value[0]);
+}
+
+/*
+ * Send envelope report to the device
+ */
+static void pidff_set_envelope_report(struct pidff_device *pidff,
+				      struct ff_envelope *envelope)
+{
+	pidff->set_envelope[PID_EFFECT_BLOCK_INDEX].value[0] =
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	pidff->set_envelope[PID_ATTACK_LEVEL].value[0] =
+	    pidff_rescale(envelope->attack_level >
+			  0x7fff ? 0x7fff : envelope->attack_level, 0x7fff,
+			  pidff->set_envelope[PID_ATTACK_LEVEL].field);
+	pidff->set_envelope[PID_FADE_LEVEL].value[0] =
+	    pidff_rescale(envelope->fade_level >
+			  0x7fff ? 0x7fff : envelope->fade_level, 0x7fff,
+			  pidff->set_envelope[PID_FADE_LEVEL].field);
+
+	pidff->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length;
+	pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length;
+
+	debug("attack %u => %d", envelope->attack_level,
+	      pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
+
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_ENVELOPE],
+			  USB_DIR_OUT);
+}
+
+/*
+ * Test if the new envelope differs from old one
+ */
+static int pidff_needs_set_envelope(struct ff_envelope *envelope,
+				    struct ff_envelope *old)
+{
+	return envelope->attack_level != old->attack_level ||
+	       envelope->fade_level != old->fade_level ||
+	       envelope->attack_length != old->attack_length ||
+	       envelope->fade_length != old->fade_length;
+}
+
+/*
+ * Send constant force report to the device
+ */
+static void pidff_set_constant_force_report(struct pidff_device *pidff,
+					    struct ff_effect *effect)
+{
+	pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_constant[PID_MAGNITUDE],
+			 effect->u.constant.level);
+
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONSTANT],
+			  USB_DIR_OUT);
+}
+
+/*
+ * Test if the constant parameters have changed between effects
+ */
+static int pidff_needs_set_constant(struct ff_effect *effect,
+				    struct ff_effect *old)
+{
+	return effect->u.constant.level != old->u.constant.level;
+}
+
+/*
+ * Send set effect report to the device
+ */
+static void pidff_set_effect_report(struct pidff_device *pidff,
+				    struct ff_effect *effect)
+{
+	pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff->set_effect_type->value[0] =
+		pidff->create_new_effect_type->value[0];
+	pidff->set_effect[PID_DURATION].value[0] = effect->replay.length;
+	pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button;
+	pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] =
+		effect->trigger.interval;
+	pidff->set_effect[PID_GAIN].value[0] =
+		pidff->set_effect[PID_GAIN].field->logical_maximum;
+	pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
+	pidff->effect_direction->value[0] =
+		pidff_rescale(effect->direction, 0xffff,
+				pidff->effect_direction);
+	pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay;
+
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT],
+			  USB_DIR_OUT);
+}
+
+/*
+ * Test if the values used in set_effect have changed
+ */
+static int pidff_needs_set_effect(struct ff_effect *effect,
+				  struct ff_effect *old)
+{
+	return effect->replay.length != old->replay.length ||
+	       effect->trigger.interval != old->trigger.interval ||
+	       effect->trigger.button != old->trigger.button ||
+	       effect->direction != old->direction ||
+	       effect->replay.delay != old->replay.delay;
+}
+
+/*
+ * Send periodic effect report to the device
+ */
+static void pidff_set_periodic_report(struct pidff_device *pidff,
+				      struct ff_effect *effect)
+{
+	pidff->set_periodic[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_periodic[PID_MAGNITUDE],
+			 effect->u.periodic.magnitude);
+	pidff_set_signed(&pidff->set_periodic[PID_OFFSET],
+			 effect->u.periodic.offset);
+	pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase);
+	pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period;
+
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_PERIODIC],
+			  USB_DIR_OUT);
+
+}
+
+/*
+ * Test if periodic effect parameters have changed
+ */
+static int pidff_needs_set_periodic(struct ff_effect *effect,
+				    struct ff_effect *old)
+{
+	return effect->u.periodic.magnitude != old->u.periodic.magnitude ||
+	       effect->u.periodic.offset != old->u.periodic.offset ||
+	       effect->u.periodic.phase != old->u.periodic.phase ||
+	       effect->u.periodic.period != old->u.periodic.period;
+}
+
+/*
+ * Send condition effect reports to the device
+ */
+static void pidff_set_condition_report(struct pidff_device *pidff,
+				       struct ff_effect *effect)
+{
+	int i;
+
+	pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	for (i = 0; i < 2; i++) {
+		pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i;
+		pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET],
+				 effect->u.condition[i].center);
+		pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT],
+				 effect->u.condition[i].right_coeff);
+		pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT],
+				 effect->u.condition[i].left_coeff);
+		pidff_set(&pidff->set_condition[PID_POS_SATURATION],
+			  effect->u.condition[i].right_saturation);
+		pidff_set(&pidff->set_condition[PID_NEG_SATURATION],
+			  effect->u.condition[i].left_saturation);
+		pidff_set(&pidff->set_condition[PID_DEAD_BAND],
+			  effect->u.condition[i].deadband);
+		usbhid_wait_io(pidff->hid);
+		usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_CONDITION],
+				  USB_DIR_OUT);
+	}
+}
+
+/*
+ * Test if condition effect parameters have changed
+ */
+static int pidff_needs_set_condition(struct ff_effect *effect,
+				     struct ff_effect *old)
+{
+	int i;
+	int ret = 0;
+
+	for (i = 0; i < 2; i++) {
+		struct ff_condition_effect *cond = &effect->u.condition[i];
+		struct ff_condition_effect *old_cond = &old->u.condition[i];
+
+		ret |= cond->center != old_cond->center ||
+		       cond->right_coeff != old_cond->right_coeff ||
+		       cond->left_coeff != old_cond->left_coeff ||
+		       cond->right_saturation != old_cond->right_saturation ||
+		       cond->left_saturation != old_cond->left_saturation ||
+		       cond->deadband != old_cond->deadband;
+	}
+
+	return ret;
+}
+
+/*
+ * Send ramp force report to the device
+ */
+static void pidff_set_ramp_force_report(struct pidff_device *pidff,
+					struct ff_effect *effect)
+{
+	pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_ramp[PID_RAMP_START],
+			 effect->u.ramp.start_level);
+	pidff_set_signed(&pidff->set_ramp[PID_RAMP_END],
+			 effect->u.ramp.end_level);
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_RAMP],
+			  USB_DIR_OUT);
+}
+
+/*
+ * Test if ramp force parameters have changed
+ */
+static int pidff_needs_set_ramp(struct ff_effect *effect, struct ff_effect *old)
+{
+	return effect->u.ramp.start_level != old->u.ramp.start_level ||
+	       effect->u.ramp.end_level != old->u.ramp.end_level;
+}
+
+/*
+ * Send a request for effect upload to the device
+ *
+ * Returns 0 if device reported success, -ENOSPC if the device reported memory
+ * is full. Upon unknown response the function will retry for 60 times, if
+ * still unsuccessful -EIO is returned.
+ */
+static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
+{
+	int j;
+
+	pidff->create_new_effect_type->value[0] = efnum;
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
+			  USB_DIR_OUT);
+	debug("create_new_effect sent, type: %d", efnum);
+
+	pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+	pidff->block_load_status->value[0] = 0;
+	usbhid_wait_io(pidff->hid);
+
+	for (j = 0; j < 60; j++) {
+		debug("pid_block_load requested");
+		usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_LOAD],
+				  USB_DIR_IN);
+		usbhid_wait_io(pidff->hid);
+		if (pidff->block_load_status->value[0] ==
+		    pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) {
+			debug("device reported free memory: %d bytes",
+			      pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
+				pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+			return 0;
+		}
+		if (pidff->block_load_status->value[0] ==
+		    pidff->status_id[PID_BLOCK_LOAD_FULL]) {
+			debug("not enough memory free: %d bytes",
+			      pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
+				pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+			return -ENOSPC;
+		}
+	}
+	printk(KERN_ERR "hid-pidff: pid_block_load failed 60 times\n");
+	return -EIO;
+}
+
+/*
+ * Play the effect with PID id n times
+ */
+static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
+{
+	pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+
+	if (n == 0) {
+		pidff->effect_operation_status->value[0] =
+			pidff->operation_id[PID_EFFECT_STOP];
+	} else {
+		pidff->effect_operation_status->value[0] =
+			pidff->operation_id[PID_EFFECT_START];
+		pidff->effect_operation[PID_LOOP_COUNT].value[0] = n;
+	}
+
+	usbhid_wait_io(pidff->hid);
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
+			  USB_DIR_OUT);
+}
+
+/**
+ * Play the effect with effect id @effect_id for @value times
+ */
+static int pidff_playback(struct input_dev *dev, int effect_id, int value)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_playback_pid(pidff, pidff->pid_id[effect_id], value);
+
+	return 0;
+}
+
+/*
+ * Erase effect with PID id
+ */
+static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
+{
+	pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_BLOCK_FREE],
+			  USB_DIR_OUT);
+}
+
+/*
+ * Stop and erase effect with effect_id
+ */
+static int pidff_erase_effect(struct input_dev *dev, int effect_id)
+{
+	struct pidff_device *pidff = dev->ff->private;
+	int pid_id = pidff->pid_id[effect_id];
+
+	debug("starting to erase %d/%d", effect_id, pidff->pid_id[effect_id]);
+	pidff_playback_pid(pidff, pid_id, 0);
+	pidff_erase_pid(pidff, pid_id);
+
+	return 0;
+}
+
+/*
+ * Effect upload handler
+ */
+static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+			       struct ff_effect *old)
+{
+	struct pidff_device *pidff = dev->ff->private;
+	int type_id;
+	int error;
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_CONSTANT]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_constant(effect, old))
+			pidff_set_constant_force_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.constant.envelope,
+					&old->u.constant.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.constant.envelope);
+		break;
+
+	case FF_PERIODIC:
+		if (!old) {
+			switch (effect->u.periodic.waveform) {
+			case FF_SQUARE:
+				type_id = PID_SQUARE;
+				break;
+			case FF_TRIANGLE:
+				type_id = PID_TRIANGLE;
+				break;
+			case FF_SINE:
+				type_id = PID_SINE;
+				break;
+			case FF_SAW_UP:
+				type_id = PID_SAW_UP;
+				break;
+			case FF_SAW_DOWN:
+				type_id = PID_SAW_DOWN;
+				break;
+			default:
+				printk(KERN_ERR
+				       "hid-pidff: invalid waveform\n");
+				return -EINVAL;
+			}
+
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[type_id]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_periodic(effect, old))
+			pidff_set_periodic_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.periodic.envelope,
+					&old->u.periodic.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.periodic.envelope);
+		break;
+
+	case FF_RAMP:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_RAMP]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_ramp(effect, old))
+			pidff_set_ramp_force_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.ramp.envelope,
+					&old->u.ramp.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.ramp.envelope);
+		break;
+
+	case FF_SPRING:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_SPRING]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_FRICTION:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_FRICTION]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_DAMPER:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_DAMPER]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_INERTIA:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_INERTIA]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	default:
+		printk(KERN_ERR "hid-pidff: invalid type\n");
+		return -EINVAL;
+	}
+
+	if (!old)
+		pidff->pid_id[effect->id] =
+		    pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	debug("uploaded");
+
+	return 0;
+}
+
+/*
+ * set_gain() handler
+ */
+static void pidff_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain);
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
+			  USB_DIR_OUT);
+}
+
+static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
+{
+	struct hid_field *field =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].field;
+
+	if (!magnitude) {
+		pidff_playback_pid(pidff, field->logical_minimum, 0);
+		return;
+	}
+
+	pidff_playback_pid(pidff, field->logical_minimum, 1);
+
+	pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum;
+	pidff->set_effect_type->value[0] = pidff->type_id[PID_SPRING];
+	pidff->set_effect[PID_DURATION].value[0] = 0;
+	pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = 0;
+	pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0;
+	pidff_set(&pidff->set_effect[PID_GAIN], magnitude);
+	pidff->set_effect[PID_START_DELAY].value[0] = 0;
+
+	usbhid_submit_report(pidff->hid, pidff->reports[PID_SET_EFFECT],
+			  USB_DIR_OUT);
+}
+
+/*
+ * pidff_set_autocenter() handler
+ */
+static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_autocenter(pidff, magnitude);
+}
+
+/*
+ * Find fields from a report and fill a pidff_usage
+ */
+static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
+			     struct hid_report *report, int count, int strict)
+{
+	int i, j, k, found;
+
+	for (k = 0; k < count; k++) {
+		found = 0;
+		for (i = 0; i < report->maxfield; i++) {
+			if (report->field[i]->maxusage !=
+			    report->field[i]->report_count) {
+				debug("maxusage and report_count do not match, "
+				      "skipping");
+				continue;
+			}
+			for (j = 0; j < report->field[i]->maxusage; j++) {
+				if (report->field[i]->usage[j].hid ==
+				    (HID_UP_PID | table[k])) {
+					debug("found %d at %d->%d", k, i, j);
+					usage[k].field = report->field[i];
+					usage[k].value =
+						&report->field[i]->value[j];
+					found = 1;
+					break;
+				}
+			}
+			if (found)
+				break;
+		}
+		if (!found && strict) {
+			debug("failed to locate %d", k);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Return index into pidff_reports for the given usage
+ */
+static int pidff_check_usage(int usage)
+{
+	int i;
+
+	for (i = 0; i < sizeof(pidff_reports); i++)
+		if (usage == (HID_UP_PID | pidff_reports[i]))
+			return i;
+
+	return -1;
+}
+
+/*
+ * Find the reports and fill pidff->reports[]
+ * report_type specifies either OUTPUT or FEATURE reports
+ */
+static void pidff_find_reports(struct hid_device *hid, int report_type,
+			       struct pidff_device *pidff)
+{
+	struct hid_report *report;
+	int i, ret;
+
+	list_for_each_entry(report,
+			    &hid->report_enum[report_type].report_list, list) {
+		if (report->maxfield < 1)
+			continue;
+		ret = pidff_check_usage(report->field[0]->logical);
+		if (ret != -1) {
+			debug("found usage 0x%02x from field->logical",
+			      pidff_reports[ret]);
+			pidff->reports[ret] = report;
+			continue;
+		}
+
+		/*
+		 * Sometimes logical collections are stacked to indicate
+		 * different usages for the report and the field, in which
+		 * case we want the usage of the parent. However, Linux HID
+		 * implementation hides this fact, so we have to dig it up
+		 * ourselves
+		 */
+		i = report->field[0]->usage[0].collection_index;
+		if (i <= 0 ||
+		    hid->collection[i - 1].type != HID_COLLECTION_LOGICAL)
+			continue;
+		ret = pidff_check_usage(hid->collection[i - 1].usage);
+		if (ret != -1 && !pidff->reports[ret]) {
+			debug("found usage 0x%02x from collection array",
+			      pidff_reports[ret]);
+			pidff->reports[ret] = report;
+		}
+	}
+}
+
+/*
+ * Test if the required reports have been found
+ */
+static int pidff_reports_ok(struct pidff_device *pidff)
+{
+	int i;
+
+	for (i = 0; i <= PID_REQUIRED_REPORTS; i++) {
+		if (!pidff->reports[i]) {
+			debug("%d missing", i);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/*
+ * Find a field with a specific usage within a report
+ */
+static struct hid_field *pidff_find_special_field(struct hid_report *report,
+						  int usage, int enforce_min)
+{
+	int i;
+
+	for (i = 0; i < report->maxfield; i++) {
+		if (report->field[i]->logical == (HID_UP_PID | usage) &&
+		    report->field[i]->report_count > 0) {
+			if (!enforce_min ||
+			    report->field[i]->logical_minimum == 1)
+				return report->field[i];
+			else {
+				printk(KERN_ERR "hid-pidff: logical_minimum "
+					"is not 1 as it should be\n");
+				return NULL;
+			}
+		}
+	}
+	return NULL;
+}
+
+/*
+ * Fill a pidff->*_id struct table
+ */
+static int pidff_find_special_keys(int *keys, struct hid_field *fld,
+				   const u8 *usagetable, int count)
+{
+
+	int i, j;
+	int found = 0;
+
+	for (i = 0; i < count; i++) {
+		for (j = 0; j < fld->maxusage; j++) {
+			if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) {
+				keys[i] = j + 1;
+				found++;
+				break;
+			}
+		}
+	}
+	return found;
+}
+
+#define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \
+	pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
+		sizeof(pidff_ ## name))
+
+/*
+ * Find and check the special fields
+ */
+static int pidff_find_special_fields(struct pidff_device *pidff)
+{
+	debug("finding special fields");
+
+	pidff->create_new_effect_type =
+		pidff_find_special_field(pidff->reports[PID_CREATE_NEW_EFFECT],
+					 0x25, 1);
+	pidff->set_effect_type =
+		pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+					 0x25, 1);
+	pidff->effect_direction =
+		pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+					 0x57, 0);
+	pidff->device_control =
+		pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL],
+					 0x96, 1);
+	pidff->block_load_status =
+		pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD],
+					 0x8b, 1);
+	pidff->effect_operation_status =
+		pidff_find_special_field(pidff->reports[PID_EFFECT_OPERATION],
+					 0x78, 1);
+
+	debug("search done");
+
+	if (!pidff->create_new_effect_type || !pidff->set_effect_type) {
+		printk(KERN_ERR "hid-pidff: effect lists not found\n");
+		return -1;
+	}
+
+	if (!pidff->effect_direction) {
+		printk(KERN_ERR "hid-pidff: direction field not found\n");
+		return -1;
+	}
+
+	if (!pidff->device_control) {
+		printk(KERN_ERR "hid-pidff: device control field not found\n");
+		return -1;
+	}
+
+	if (!pidff->block_load_status) {
+		printk(KERN_ERR
+		       "hid-pidff: block load status field not found\n");
+		return -1;
+	}
+
+	if (!pidff->effect_operation_status) {
+		printk(KERN_ERR
+		       "hid-pidff: effect operation field not found\n");
+		return -1;
+	}
+
+	pidff_find_special_keys(pidff->control_id, pidff->device_control,
+				pidff_device_control,
+				sizeof(pidff_device_control));
+
+	PIDFF_FIND_SPECIAL_KEYS(control_id, device_control, device_control);
+
+	if (!PIDFF_FIND_SPECIAL_KEYS(type_id, create_new_effect_type,
+				     effect_types)) {
+		printk(KERN_ERR "hid-pidff: no effect types found\n");
+		return -1;
+	}
+
+	if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status,
+				    block_load_status) !=
+			sizeof(pidff_block_load_status)) {
+		printk(KERN_ERR
+		       "hidpidff: block load status identifiers not found\n");
+		return -1;
+	}
+
+	if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status,
+				    effect_operation_status) !=
+			sizeof(pidff_effect_operation_status)) {
+		printk(KERN_ERR
+		       "hidpidff: effect operation identifiers not found\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Find the implemented effect types
+ */
+static int pidff_find_effects(struct pidff_device *pidff,
+			      struct input_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < sizeof(pidff_effect_types); i++) {
+		int pidff_type = pidff->type_id[i];
+		if (pidff->set_effect_type->usage[pidff_type].hid !=
+		    pidff->create_new_effect_type->usage[pidff_type].hid) {
+			printk(KERN_ERR "hid-pidff: "
+			       "effect type number %d is invalid\n", i);
+			return -1;
+		}
+	}
+
+	if (pidff->type_id[PID_CONSTANT])
+		set_bit(FF_CONSTANT, dev->ffbit);
+	if (pidff->type_id[PID_RAMP])
+		set_bit(FF_RAMP, dev->ffbit);
+	if (pidff->type_id[PID_SQUARE]) {
+		set_bit(FF_SQUARE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SINE]) {
+		set_bit(FF_SINE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_TRIANGLE]) {
+		set_bit(FF_TRIANGLE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SAW_UP]) {
+		set_bit(FF_SAW_UP, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SAW_DOWN]) {
+		set_bit(FF_SAW_DOWN, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SPRING])
+		set_bit(FF_SPRING, dev->ffbit);
+	if (pidff->type_id[PID_DAMPER])
+		set_bit(FF_DAMPER, dev->ffbit);
+	if (pidff->type_id[PID_INERTIA])
+		set_bit(FF_INERTIA, dev->ffbit);
+	if (pidff->type_id[PID_FRICTION])
+		set_bit(FF_FRICTION, dev->ffbit);
+
+	return 0;
+
+}
+
+#define PIDFF_FIND_FIELDS(name, report, strict) \
+	pidff_find_fields(pidff->name, pidff_ ## name, \
+		pidff->reports[report], \
+		sizeof(pidff_ ## name), strict)
+
+/*
+ * Fill and check the pidff_usages
+ */
+static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
+{
+	int envelope_ok = 0;
+
+	if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
+		printk(KERN_ERR
+		       "hid-pidff: unknown set_effect report layout\n");
+		return -ENODEV;
+	}
+
+	PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0);
+	if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) {
+		printk(KERN_ERR
+		       "hid-pidff: unknown pid_block_load report layout\n");
+		return -ENODEV;
+	}
+
+	if (PIDFF_FIND_FIELDS(effect_operation, PID_EFFECT_OPERATION, 1)) {
+		printk(KERN_ERR
+		       "hid-pidff: unknown effect_operation report layout\n");
+		return -ENODEV;
+	}
+
+	if (PIDFF_FIND_FIELDS(block_free, PID_BLOCK_FREE, 1)) {
+		printk(KERN_ERR
+		       "hid-pidff: unknown pid_block_free report layout\n");
+		return -ENODEV;
+	}
+
+	if (!PIDFF_FIND_FIELDS(set_envelope, PID_SET_ENVELOPE, 1))
+		envelope_ok = 1;
+
+	if (pidff_find_special_fields(pidff) || pidff_find_effects(pidff, dev))
+		return -ENODEV;
+
+	if (!envelope_ok) {
+		if (test_and_clear_bit(FF_CONSTANT, dev->ffbit))
+			printk(KERN_WARNING "hid-pidff: "
+			       "has constant effect but no envelope\n");
+		if (test_and_clear_bit(FF_RAMP, dev->ffbit))
+			printk(KERN_WARNING "hid-pidff: "
+				"has ramp effect but no envelope\n");
+
+		if (test_and_clear_bit(FF_PERIODIC, dev->ffbit))
+			printk(KERN_WARNING "hid-pidff: "
+				"has periodic effect but no envelope\n");
+	}
+
+	if (test_bit(FF_CONSTANT, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) {
+		printk(KERN_WARNING
+		       "hid-pidff: unknown constant effect layout\n");
+		clear_bit(FF_CONSTANT, dev->ffbit);
+	}
+
+	if (test_bit(FF_RAMP, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) {
+		printk(KERN_WARNING "hid-pidff: unknown ramp effect layout\n");
+		clear_bit(FF_RAMP, dev->ffbit);
+	}
+
+	if ((test_bit(FF_SPRING, dev->ffbit) ||
+	     test_bit(FF_DAMPER, dev->ffbit) ||
+	     test_bit(FF_FRICTION, dev->ffbit) ||
+	     test_bit(FF_INERTIA, dev->ffbit)) &&
+	    PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
+		printk(KERN_WARNING
+		       "hid-pidff: unknown condition effect layout\n");
+		clear_bit(FF_SPRING, dev->ffbit);
+		clear_bit(FF_DAMPER, dev->ffbit);
+		clear_bit(FF_FRICTION, dev->ffbit);
+		clear_bit(FF_INERTIA, dev->ffbit);
+	}
+
+	if (test_bit(FF_PERIODIC, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) {
+		printk(KERN_WARNING
+		       "hid-pidff: unknown periodic effect layout\n");
+		clear_bit(FF_PERIODIC, dev->ffbit);
+	}
+
+	PIDFF_FIND_FIELDS(pool, PID_POOL, 0);
+
+	if (!PIDFF_FIND_FIELDS(device_gain, PID_DEVICE_GAIN, 1))
+		set_bit(FF_GAIN, dev->ffbit);
+
+	return 0;
+}
+
+/*
+ * Reset the device
+ */
+static void pidff_reset(struct pidff_device *pidff)
+{
+	struct hid_device *hid = pidff->hid;
+	int i = 0;
+
+	pidff->device_control->value[0] = pidff->control_id[PID_RESET];
+	/* We reset twice as sometimes hid_wait_io isn't waiting long enough */
+	usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT);
+	usbhid_wait_io(hid);
+	usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT);
+	usbhid_wait_io(hid);
+
+	pidff->device_control->value[0] =
+		pidff->control_id[PID_ENABLE_ACTUATORS];
+	usbhid_submit_report(hid, pidff->reports[PID_DEVICE_CONTROL], USB_DIR_OUT);
+	usbhid_wait_io(hid);
+
+	/* pool report is sometimes messed up, refetch it */
+	usbhid_submit_report(hid, pidff->reports[PID_POOL], USB_DIR_IN);
+	usbhid_wait_io(hid);
+
+	if (pidff->pool[PID_SIMULTANEOUS_MAX].value) {
+		int sim_effects = pidff->pool[PID_SIMULTANEOUS_MAX].value[0];
+		while (sim_effects < 2) {
+			if (i++ > 20) {
+				printk(KERN_WARNING "hid-pidff: device reports "
+				       "%d simultaneous effects\n",
+				       sim_effects);
+				break;
+			}
+			debug("pid_pool requested again");
+			usbhid_submit_report(hid, pidff->reports[PID_POOL],
+					  USB_DIR_IN);
+			usbhid_wait_io(hid);
+		}
+	}
+}
+
+/*
+ * Test if autocenter modification is using the supported method
+ */
+static int pidff_check_autocenter(struct pidff_device *pidff,
+				  struct input_dev *dev)
+{
+	int error;
+
+	/*
+	 * Let's find out if autocenter modification is supported
+	 * Specification doesn't specify anything, so we request an
+	 * effect upload and cancel it immediately. If the approved
+	 * effect id was one above the minimum, then we assume the first
+	 * effect id is a built-in spring type effect used for autocenter
+	 */
+
+	error = pidff_request_effect_upload(pidff, 1);
+	if (error) {
+		printk(KERN_ERR "hid-pidff: upload request failed\n");
+		return error;
+	}
+
+	if (pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] ==
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + 1) {
+		pidff_autocenter(pidff, 0xffff);
+		set_bit(FF_AUTOCENTER, dev->ffbit);
+	} else {
+		printk(KERN_NOTICE "hid-pidff: "
+		       "device has unknown autocenter control method\n");
+	}
+
+	pidff_erase_pid(pidff,
+			pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]);
+
+	return 0;
+
+}
+
+/*
+ * Check if the device is PID and initialize it
+ */
+int hid_pidff_init(struct hid_device *hid)
+{
+	struct pidff_device *pidff;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct ff_device *ff;
+	int max_effects;
+	int error;
+
+	debug("starting pid init");
+
+	if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) {
+		debug("not a PID device, no output report");
+		return -ENODEV;
+	}
+
+	pidff = kzalloc(sizeof(*pidff), GFP_KERNEL);
+	if (!pidff)
+		return -ENOMEM;
+
+	pidff->hid = hid;
+
+	pidff_find_reports(hid, HID_OUTPUT_REPORT, pidff);
+	pidff_find_reports(hid, HID_FEATURE_REPORT, pidff);
+
+	if (!pidff_reports_ok(pidff)) {
+		debug("reports not ok, aborting");
+		error = -ENODEV;
+		goto fail;
+	}
+
+	error = pidff_init_fields(pidff, dev);
+	if (error)
+		goto fail;
+
+	pidff_reset(pidff);
+
+	if (test_bit(FF_GAIN, dev->ffbit)) {
+		pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], 0xffff);
+		usbhid_submit_report(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
+				  USB_DIR_OUT);
+	}
+
+	error = pidff_check_autocenter(pidff, dev);
+	if (error)
+		goto fail;
+
+	max_effects =
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_maximum -
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum +
+	    1;
+	debug("max effects is %d", max_effects);
+
+	if (max_effects > PID_EFFECTS_MAX)
+		max_effects = PID_EFFECTS_MAX;
+
+	if (pidff->pool[PID_SIMULTANEOUS_MAX].value)
+		debug("max simultaneous effects is %d",
+		      pidff->pool[PID_SIMULTANEOUS_MAX].value[0]);
+
+	if (pidff->pool[PID_RAM_POOL_SIZE].value)
+		debug("device memory size is %d bytes",
+		      pidff->pool[PID_RAM_POOL_SIZE].value[0]);
+
+	if (pidff->pool[PID_DEVICE_MANAGED_POOL].value &&
+	    pidff->pool[PID_DEVICE_MANAGED_POOL].value[0] == 0) {
+		printk(KERN_NOTICE "hid-pidff: "
+		       "device does not support device managed pool\n");
+		goto fail;
+	}
+
+	error = input_ff_create(dev, max_effects);
+	if (error)
+		goto fail;
+
+	ff = dev->ff;
+	ff->private = pidff;
+	ff->upload = pidff_upload_effect;
+	ff->erase = pidff_erase_effect;
+	ff->set_gain = pidff_set_gain;
+	ff->set_autocenter = pidff_set_autocenter;
+	ff->playback = pidff_playback;
+
+	printk(KERN_INFO "Force feedback for USB HID PID devices by "
+	       "Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+
+ fail:
+	kfree(pidff);
+	return error;
+}
diff --git a/drivers/hid/usbhid/hid-plff.c b/drivers/hid/usbhid/hid-plff.c
new file mode 100644
index 000000000000..76d2e6e14db4
--- /dev/null
+++ b/drivers/hid/usbhid/hid-plff.c
@@ -0,0 +1,129 @@
+/*
+ *  Force feedback support for PantherLord USB/PS2 2in1 Adapter devices
+ *
+ *  Copyright (c) 2007 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* #define DEBUG */
+
+#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include "usbhid.h"
+
+struct plff_device {
+	struct hid_report *report;
+};
+
+static int hid_plff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = dev->private;
+	struct plff_device *plff = data;
+	int left, right;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	debug("called with 0x%04x 0x%04x", left, right);
+
+	left = left * 0x7f / 0xffff;
+	right = right * 0x7f / 0xffff;
+
+	plff->report->field[0]->value[2] = left;
+	plff->report->field[0]->value[3] = right;
+	debug("running with 0x%02x 0x%02x", left, right);
+	usbhid_submit_report(hid, plff->report, USB_DIR_OUT);
+
+	return 0;
+}
+
+int hid_plff_init(struct hid_device *hid)
+{
+	struct plff_device *plff;
+	struct hid_report *report;
+	struct hid_input *hidinput;
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct list_head *report_ptr = report_list;
+	struct input_dev *dev;
+	int error;
+
+	/* The device contains 2 output reports (one for each
+	   HID_QUIRK_MULTI_INPUT device), both containing 1 field, which
+	   contains 4 ff00.0002 usages and 4 16bit absolute values.
+
+	   The 2 input reports also contain a field which contains
+	   8 ff00.0001 usages and 8 boolean values. Their meaning is
+	   currently unknown. */
+
+	if (list_empty(report_list)) {
+		printk(KERN_ERR "hid-plff: no output reports found\n");
+		return -ENODEV;
+	}
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+
+		report_ptr = report_ptr->next;
+
+		if (report_ptr == report_list) {
+			printk(KERN_ERR "hid-plff: required output report is missing\n");
+			return -ENODEV;
+		}
+
+		report = list_entry(report_ptr, struct hid_report, list);
+		if (report->maxfield < 1) {
+			printk(KERN_ERR "hid-plff: no fields in the report\n");
+			return -ENODEV;
+		}
+
+		if (report->field[0]->report_count < 4) {
+			printk(KERN_ERR "hid-plff: not enough values in the field\n");
+			return -ENODEV;
+		}
+
+		plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
+		if (!plff)
+			return -ENOMEM;
+
+		dev = hidinput->input;
+
+		set_bit(FF_RUMBLE, dev->ffbit);
+
+		error = input_ff_create_memless(dev, plff, hid_plff_play);
+		if (error) {
+			kfree(plff);
+			return error;
+		}
+
+		plff->report = report;
+		plff->report->field[0]->value[0] = 0x00;
+		plff->report->field[0]->value[1] = 0x00;
+		plff->report->field[0]->value[2] = 0x00;
+		plff->report->field[0]->value[3] = 0x00;
+		usbhid_submit_report(hid, plff->report, USB_DIR_OUT);
+	}
+
+	printk(KERN_INFO "hid-plff: Force feedback for PantherLord USB/PS2 "
+	       "2in1 Adapters by Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+}
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
new file mode 100644
index 000000000000..17a87555e32f
--- /dev/null
+++ b/drivers/hid/usbhid/hid-quirks.c
@@ -0,0 +1,681 @@
+/*
+ *  USB HID quirks support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2007 Paul Walmsley
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+
+#define USB_VENDOR_ID_A4TECH		0x09da
+#define USB_DEVICE_ID_A4TECH_WCP32PU	0x0006
+
+#define USB_VENDOR_ID_AASHIMA		0x06d6
+#define USB_DEVICE_ID_AASHIMA_GAMEPAD	0x0025
+#define USB_DEVICE_ID_AASHIMA_PREDATOR	0x0026
+
+#define USB_VENDOR_ID_ACECAD		0x0460
+#define USB_DEVICE_ID_ACECAD_FLAIR	0x0004
+#define USB_DEVICE_ID_ACECAD_302	0x0008
+
+#define USB_VENDOR_ID_AIPTEK		0x08ca
+#define USB_DEVICE_ID_AIPTEK_01		0x0001
+#define USB_DEVICE_ID_AIPTEK_10		0x0010
+#define USB_DEVICE_ID_AIPTEK_20		0x0020
+#define USB_DEVICE_ID_AIPTEK_21		0x0021
+#define USB_DEVICE_ID_AIPTEK_22		0x0022
+#define USB_DEVICE_ID_AIPTEK_23		0x0023
+#define USB_DEVICE_ID_AIPTEK_24		0x0024
+
+#define USB_VENDOR_ID_AIRCABLE		0x16CA
+#define USB_DEVICE_ID_AIRCABLE1		0x1502
+
+#define USB_VENDOR_ID_ALCOR		0x058f
+#define USB_DEVICE_ID_ALCOR_USBRS232	0x9720
+
+#define USB_VENDOR_ID_ALPS		0x0433
+#define USB_DEVICE_ID_IBM_GAMEPAD	0x1101
+
+#define USB_VENDOR_ID_APPLE		0x05ac
+#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI	0x020e
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO	0x020f
+#define USB_DEVICE_ID_APPLE_GEYSER_ANSI	0x0214
+#define USB_DEVICE_ID_APPLE_GEYSER_ISO	0x0215
+#define USB_DEVICE_ID_APPLE_GEYSER_JIS	0x0216
+#define USB_DEVICE_ID_APPLE_GEYSER3_ANSI	0x0217
+#define USB_DEVICE_ID_APPLE_GEYSER3_ISO	0x0218
+#define USB_DEVICE_ID_APPLE_GEYSER3_JIS	0x0219
+#define USB_DEVICE_ID_APPLE_GEYSER4_ANSI	0x021a
+#define USB_DEVICE_ID_APPLE_GEYSER4_ISO	0x021b
+#define USB_DEVICE_ID_APPLE_GEYSER4_JIS	0x021c
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY	0x030a
+#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY	0x030b
+#define USB_DEVICE_ID_APPLE_IR		0x8240
+
+#define USB_VENDOR_ID_ATEN		0x0557
+#define USB_DEVICE_ID_ATEN_UC100KM	0x2004
+#define USB_DEVICE_ID_ATEN_CS124U	0x2202
+#define USB_DEVICE_ID_ATEN_2PORTKVM	0x2204
+#define USB_DEVICE_ID_ATEN_4PORTKVM	0x2205
+#define USB_DEVICE_ID_ATEN_4PORTKVMC	0x2208
+
+#define USB_VENDOR_ID_BELKIN           0x050d
+#define USB_DEVICE_ID_FLIP_KVM         0x3201
+
+#define USB_VENDOR_ID_BERKSHIRE		0x0c98
+#define USB_DEVICE_ID_BERKSHIRE_PCWD	0x1140
+
+#define USB_VENDOR_ID_CHERRY		0x046a
+#define USB_DEVICE_ID_CHERRY_CYMOTION	0x0023
+
+#define USB_VENDOR_ID_CHIC		0x05fe
+#define USB_DEVICE_ID_CHIC_GAMEPAD	0x0014
+
+#define USB_VENDOR_ID_CIDC		0x1677
+
+#define USB_VENDOR_ID_CODEMERCS		0x07c0
+#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST	0x1500
+#define USB_DEVICE_ID_CODEMERCS_IOW_LAST	0x15ff
+
+#define USB_VENDOR_ID_CYPRESS		0x04b4
+#define USB_DEVICE_ID_CYPRESS_MOUSE	0x0001
+#define USB_DEVICE_ID_CYPRESS_HIDCOM	0x5500
+#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE	0x7417
+
+#define USB_VENDOR_ID_DELL		0x413c
+#define USB_DEVICE_ID_DELL_W7658	0x2005
+
+#define USB_VENDOR_ID_DELORME		0x1163
+#define USB_DEVICE_ID_DELORME_EARTHMATE 0x0100
+#define USB_DEVICE_ID_DELORME_EM_LT20	0x0200
+
+#define USB_VENDOR_ID_ESSENTIAL_REALITY	0x0d7f
+#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
+
+#define USB_VENDOR_ID_GLAB		0x06c2
+#define USB_DEVICE_ID_4_PHIDGETSERVO_30	0x0038
+#define USB_DEVICE_ID_1_PHIDGETSERVO_30	0x0039
+#define USB_DEVICE_ID_0_0_4_IF_KIT	0x0040
+#define USB_DEVICE_ID_0_16_16_IF_KIT	0x0044
+#define USB_DEVICE_ID_8_8_8_IF_KIT	0x0045
+#define USB_DEVICE_ID_0_8_7_IF_KIT	0x0051
+#define USB_DEVICE_ID_0_8_8_IF_KIT	0x0053
+#define USB_DEVICE_ID_PHIDGET_MOTORCONTROL	0x0058
+
+#define USB_VENDOR_ID_GRIFFIN		0x077d
+#define USB_DEVICE_ID_POWERMATE		0x0410
+#define USB_DEVICE_ID_SOUNDKNOB		0x04AA
+
+#define USB_VENDOR_ID_GTCO		0x078c
+#define USB_DEVICE_ID_GTCO_90		0x0090
+#define USB_DEVICE_ID_GTCO_100		0x0100
+#define USB_DEVICE_ID_GTCO_101		0x0101
+#define USB_DEVICE_ID_GTCO_103		0x0103
+#define USB_DEVICE_ID_GTCO_104		0x0104
+#define USB_DEVICE_ID_GTCO_105		0x0105
+#define USB_DEVICE_ID_GTCO_106		0x0106
+#define USB_DEVICE_ID_GTCO_107		0x0107
+#define USB_DEVICE_ID_GTCO_108		0x0108
+#define USB_DEVICE_ID_GTCO_200		0x0200
+#define USB_DEVICE_ID_GTCO_201		0x0201
+#define USB_DEVICE_ID_GTCO_202		0x0202
+#define USB_DEVICE_ID_GTCO_203		0x0203
+#define USB_DEVICE_ID_GTCO_204		0x0204
+#define USB_DEVICE_ID_GTCO_205		0x0205
+#define USB_DEVICE_ID_GTCO_206		0x0206
+#define USB_DEVICE_ID_GTCO_207		0x0207
+#define USB_DEVICE_ID_GTCO_300		0x0300
+#define USB_DEVICE_ID_GTCO_301		0x0301
+#define USB_DEVICE_ID_GTCO_302		0x0302
+#define USB_DEVICE_ID_GTCO_303		0x0303
+#define USB_DEVICE_ID_GTCO_304		0x0304
+#define USB_DEVICE_ID_GTCO_305		0x0305
+#define USB_DEVICE_ID_GTCO_306		0x0306
+#define USB_DEVICE_ID_GTCO_307		0x0307
+#define USB_DEVICE_ID_GTCO_308		0x0308
+#define USB_DEVICE_ID_GTCO_309		0x0309
+#define USB_DEVICE_ID_GTCO_400		0x0400
+#define USB_DEVICE_ID_GTCO_401		0x0401
+#define USB_DEVICE_ID_GTCO_402		0x0402
+#define USB_DEVICE_ID_GTCO_403		0x0403
+#define USB_DEVICE_ID_GTCO_404		0x0404
+#define USB_DEVICE_ID_GTCO_405		0x0405
+#define USB_DEVICE_ID_GTCO_500		0x0500
+#define USB_DEVICE_ID_GTCO_501		0x0501
+#define USB_DEVICE_ID_GTCO_502		0x0502
+#define USB_DEVICE_ID_GTCO_503		0x0503
+#define USB_DEVICE_ID_GTCO_504		0x0504
+#define USB_DEVICE_ID_GTCO_1000		0x1000
+#define USB_DEVICE_ID_GTCO_1001		0x1001
+#define USB_DEVICE_ID_GTCO_1002		0x1002
+#define USB_DEVICE_ID_GTCO_1003		0x1003
+#define USB_DEVICE_ID_GTCO_1004		0x1004
+#define USB_DEVICE_ID_GTCO_1005		0x1005
+#define USB_DEVICE_ID_GTCO_1006		0x1006
+
+#define USB_VENDOR_ID_HAPP		0x078b
+#define USB_DEVICE_ID_UGCI_DRIVING	0x0010
+#define USB_DEVICE_ID_UGCI_FLYING	0x0020
+#define USB_DEVICE_ID_UGCI_FIGHTING	0x0030
+
+#define USB_VENDOR_ID_IMATION		0x0718
+#define USB_DEVICE_ID_DISC_STAKKA	0xd000
+
+#define USB_VENDOR_ID_KBGEAR		0x084e
+#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO	0x1001
+
+#define USB_VENDOR_ID_LD		0x0f11
+#define USB_DEVICE_ID_LD_CASSY		0x1000
+#define USB_DEVICE_ID_LD_POCKETCASSY	0x1010
+#define USB_DEVICE_ID_LD_MOBILECASSY	0x1020
+#define USB_DEVICE_ID_LD_JWM		0x1080
+#define USB_DEVICE_ID_LD_DMMP		0x1081
+#define USB_DEVICE_ID_LD_UMIP		0x1090
+#define USB_DEVICE_ID_LD_XRAY1		0x1100
+#define USB_DEVICE_ID_LD_XRAY2		0x1101
+#define USB_DEVICE_ID_LD_VIDEOCOM	0x1200
+#define USB_DEVICE_ID_LD_COM3LAB	0x2000
+#define USB_DEVICE_ID_LD_TELEPORT	0x2010
+#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020
+#define USB_DEVICE_ID_LD_POWERCONTROL	0x2030
+#define USB_DEVICE_ID_LD_MACHINETEST	0x2040
+
+#define USB_VENDOR_ID_LOGITECH		0x046d
+#define USB_DEVICE_ID_LOGITECH_RECEIVER	0xc101
+#define USB_DEVICE_ID_S510_RECEIVER	0xc50c
+#define USB_DEVICE_ID_S510_RECEIVER_2	0xc517
+#define USB_DEVICE_ID_MX3000_RECEIVER	0xc513
+#define USB_DEVICE_ID_DINOVO_EDGE	0xc714
+
+#define USB_VENDOR_ID_MCC		0x09db
+#define USB_DEVICE_ID_MCC_PMD1024LS	0x0076
+#define USB_DEVICE_ID_MCC_PMD1208LS	0x007a
+
+#define USB_VENDOR_ID_MGE		0x0463
+#define USB_DEVICE_ID_MGE_UPS		0xffff
+#define USB_DEVICE_ID_MGE_UPS1		0x0001
+
+#define USB_VENDOR_ID_NEC		0x073e
+#define USB_DEVICE_ID_NEC_USB_GAME_PAD	0x0301
+
+#define USB_VENDOR_ID_ONTRAK		0x0a07
+#define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
+
+#define USB_VENDOR_ID_PANJIT		0x134c
+
+#define USB_VENDOR_ID_PANTHERLORD	0x0810
+#define USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK	0x0001
+
+#define USB_VENDOR_ID_PLAYDOTCOM	0x0b43
+#define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII	0x0003
+
+#define USB_VENDOR_ID_SAITEK		0x06a3
+#define USB_DEVICE_ID_SAITEK_RUMBLEPAD	0xff17
+
+#define USB_VENDOR_ID_SONY			0x054c
+#define USB_DEVICE_ID_SONY_PS3_CONTROLLER	0x0268
+
+#define USB_VENDOR_ID_SUN		0x0430
+#define USB_DEVICE_ID_RARITAN_KVM_DONGLE	0xcdab
+
+#define USB_VENDOR_ID_TOPMAX		0x0663
+#define USB_DEVICE_ID_TOPMAX_COBRAPAD	0x0103
+
+#define USB_VENDOR_ID_TURBOX		0x062a
+#define USB_DEVICE_ID_TURBOX_KEYBOARD	0x0201
+
+#define USB_VENDOR_ID_VERNIER		0x08f7
+#define USB_DEVICE_ID_VERNIER_LABPRO	0x0001
+#define USB_DEVICE_ID_VERNIER_GOTEMP	0x0002
+#define USB_DEVICE_ID_VERNIER_SKIP	0x0003
+#define USB_DEVICE_ID_VERNIER_CYCLOPS	0x0004
+
+#define USB_VENDOR_ID_WACOM		0x056a
+
+#define USB_VENDOR_ID_WISEGROUP		0x0925
+#define USB_DEVICE_ID_1_PHIDGETSERVO_20	0x8101
+#define USB_DEVICE_ID_4_PHIDGETSERVO_20	0x8104
+#define USB_DEVICE_ID_8_8_4_IF_KIT	0x8201
+#define USB_DEVICE_ID_QUAD_USB_JOYPAD	0x8800
+#define USB_DEVICE_ID_DUAL_USB_JOYPAD	0x8866
+
+#define USB_VENDOR_ID_WISEGROUP_LTD	0x6677
+#define USB_DEVICE_ID_SMARTJOY_DUAL_PLUS 0x8802
+
+#define USB_VENDOR_ID_YEALINK		0x6993
+#define USB_DEVICE_ID_YEALINK_P1K_P4K_B2K	0xb001
+
+/*
+ * Alphabetically sorted blacklist by quirk type.
+ */
+
+static const struct hid_blacklist {
+	__u16 idVendor;
+	__u16 idProduct;
+	__u32 quirks;
+} hid_blacklist[] = {
+
+	{ USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU, HID_QUIRK_2WHEEL_MOUSE_HACK_7 },
+	{ USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE, HID_QUIRK_2WHEEL_MOUSE_HACK_5 },
+
+	{ USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER, HID_QUIRK_BAD_RELATIVE_KEYS },
+
+	{ 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_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD, 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_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD, HID_QUIRK_BADPAD },
+	{ USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD, HID_QUIRK_BADPAD },
+	{ USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD, HID_QUIRK_BADPAD },
+	
+	{ USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION, HID_QUIRK_CYMOTION },
+
+	{ USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE, HID_QUIRK_DUPLICATE_USAGES },
+
+	{ USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM, HID_QUIRK_HIDDEV },
+
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_01, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_10, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_20, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_21, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_22, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_23, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_24, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_AIRCABLE, USB_DEVICE_ID_AIRCABLE1, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ALCOR, USB_DEVICE_ID_ALCOR_USBRS232, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IR, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_CIDC, 0x0103, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_ULTRAMOUSE, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_4_PHIDGETSERVO_30, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_1_PHIDGETSERVO_30, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_0_4_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_16_16_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_8_8_8_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_7_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_0_8_8_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GLAB, USB_DEVICE_ID_PHIDGET_MOTORCONTROL, HID_QUIRK_IGNORE },
+	{ 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_GTCO, USB_DEVICE_ID_GTCO_90, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_100, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_101, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_103, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_104, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_105, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_106, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_107, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_108, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_200, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_201, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_202, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_203, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_204, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_205, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_206, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_207, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_300, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_301, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_302, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_303, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_304, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_305, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_306, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_307, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_308, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_309, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_400, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_401, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_402, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_403, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_404, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_405, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_500, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_501, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_502, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_503, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_504, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1000, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1001, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1002, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1003, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1004, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1005, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1006, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY1, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1024LS, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1208LS, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 20, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 30, 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 + 108, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 118, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 500, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_PANJIT, 0x0001, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_PANJIT, 0x0002, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_PANJIT, 0x0003, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_PANJIT, 0x0004, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LABPRO, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_4_PHIDGETSERVO_20, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_1_PHIDGETSERVO_20, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_8_8_4_IF_KIT, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K, HID_QUIRK_IGNORE },
+
+	{ USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_FLAIR, HID_QUIRK_IGNORE },
+	{ USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_302, HID_QUIRK_IGNORE },
+
+	{ USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER, HID_QUIRK_LOGITECH_DESCRIPTOR },
+	{ USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER, HID_QUIRK_LOGITECH_DESCRIPTOR },
+	{ USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2, HID_QUIRK_LOGITECH_DESCRIPTOR },
+
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE, HID_QUIRK_MIGHTYMOUSE | HID_QUIRK_INVERT_HWHEEL },
+
+	{ USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK, HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII, HID_QUIRK_MULTI_INPUT },
+
+	{ USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER, HID_QUIRK_SONY_PS3_CONTROLLER },
+
+	{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD, HID_QUIRK_NOGET },
+	{ USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+	{ USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+
+	{ USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS, HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_POWERBOOK_ISO_KEYBOARD},
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_POWERBOOK_ISO_KEYBOARD},
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE | HID_QUIRK_POWERBOOK_ISO_KEYBOARD},
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+	{ USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY, HID_QUIRK_POWERBOOK_HAS_FN | HID_QUIRK_IGNORE_MOUSE },
+
+	{ USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_W7658, HID_QUIRK_RESET_LEDS },
+
+	{ 0, 0 }
+};
+
+/* Dynamic HID quirks list - specified at runtime */
+struct quirks_list_struct {
+	struct hid_blacklist hid_bl_item;
+	struct list_head node;
+};
+
+static LIST_HEAD(dquirks_list);
+static DECLARE_RWSEM(dquirks_rwsem);
+
+/* Runtime ("dynamic") quirks manipulation functions */
+
+/**
+ * usbhid_exists_dquirk: find any dynamic quirks for a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ *         Scans dquirks_list for a matching dynamic quirk and returns
+ *         the pointer to the relevant struct hid_blacklist if found.
+ *         Must be called with a read lock held on dquirks_rwsem.
+ *
+ * Returns: NULL if no quirk found, struct hid_blacklist * if found.
+ */
+static struct hid_blacklist *usbhid_exists_dquirk(const u16 idVendor,
+		const u16 idProduct)
+{
+	struct quirks_list_struct *q;
+	struct hid_blacklist *bl_entry = NULL;
+
+	list_for_each_entry(q, &dquirks_list, node) {
+		if (q->hid_bl_item.idVendor == idVendor &&
+				q->hid_bl_item.idProduct == idProduct) {
+			bl_entry = &q->hid_bl_item;
+			break;
+		}
+	}
+
+	if (bl_entry != NULL)
+		dbg("Found dynamic quirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n",
+				bl_entry->quirks, bl_entry->idVendor,
+				bl_entry->idProduct);
+
+	return bl_entry;
+}
+
+
+/**
+ * usbhid_modify_dquirk: add/replace a HID quirk
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ * @quirks: the u32 quirks value to add/replace
+ *
+ * Description:
+ *         If an dynamic quirk exists in memory for this (idVendor,
+ *         idProduct) pair, replace its quirks value with what was
+ *         provided.  Otherwise, add the quirk to the dynamic quirks list.
+ *
+ * Returns: 0 OK, -error on failure.
+ */
+int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,
+		const u32 quirks)
+{
+	struct quirks_list_struct *q_new, *q;
+	int list_edited = 0;
+
+	if (!idVendor) {
+		dbg("Cannot add a quirk with idVendor = 0");
+		return -EINVAL;
+	}
+
+	q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
+	if (!q_new) {
+		dbg("Could not allocate quirks_list_struct");
+		return -ENOMEM;
+	}
+
+	q_new->hid_bl_item.idVendor = idVendor;
+	q_new->hid_bl_item.idProduct = idProduct;
+	q_new->hid_bl_item.quirks = quirks;
+
+	down_write(&dquirks_rwsem);
+
+	list_for_each_entry(q, &dquirks_list, node) {
+
+		if (q->hid_bl_item.idVendor == idVendor &&
+				q->hid_bl_item.idProduct == idProduct) {
+
+			list_replace(&q->node, &q_new->node);
+			kfree(q);
+			list_edited = 1;
+			break;
+
+		}
+
+	}
+
+	if (!list_edited)
+		list_add_tail(&q_new->node, &dquirks_list);
+
+	up_write(&dquirks_rwsem);
+
+	return 0;
+}
+
+
+/**
+ * usbhid_remove_all_dquirks: remove all runtime HID quirks from memory
+ *
+ * Description:
+ *         Free all memory associated with dynamic quirks - called before
+ *         module unload.
+ *
+ */
+static void usbhid_remove_all_dquirks(void)
+{
+	struct quirks_list_struct *q, *temp;
+
+	down_write(&dquirks_rwsem);
+	list_for_each_entry_safe(q, temp, &dquirks_list, node) {
+		list_del(&q->node);
+		kfree(q);
+	}
+	up_write(&dquirks_rwsem);
+
+}
+
+/** 
+ * usbhid_quirks_init: apply USB HID quirks specified at module load time
+ */
+int usbhid_quirks_init(char **quirks_param)
+{
+	u16 idVendor, idProduct;
+	u32 quirks;
+	int n = 0, m;
+
+	for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {
+
+		m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
+				&idVendor, &idProduct, &quirks);
+
+		if (m != 3 ||
+				usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
+			printk(KERN_WARNING
+					"Could not parse HID quirk module param %s\n",
+					quirks_param[n]);
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * usbhid_quirks_exit: release memory associated with dynamic_quirks
+ *
+ * Description:
+ *     Release all memory associated with dynamic quirks.  Called upon
+ *     module unload.
+ *
+ * Returns: nothing
+ */
+void usbhid_quirks_exit(void)
+{
+	usbhid_remove_all_dquirks();
+}
+
+/**
+ * usbhid_exists_squirk: return any static quirks for a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ *     Given a USB vendor ID and product ID, return a pointer to
+ *     the hid_blacklist entry associated with that device.
+ *
+ * Returns: pointer if quirk found, or NULL if no quirks found.
+ */
+static const struct hid_blacklist *usbhid_exists_squirk(const u16 idVendor,
+		const u16 idProduct)
+{
+	const struct hid_blacklist *bl_entry = NULL;
+	int n = 0;
+
+	for (; hid_blacklist[n].idVendor; n++)
+		if (hid_blacklist[n].idVendor == idVendor &&
+				hid_blacklist[n].idProduct == idProduct)
+			bl_entry = &hid_blacklist[n];
+
+	if (bl_entry != NULL)
+		dbg("Found squirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n",
+				bl_entry->quirks, bl_entry->idVendor, 
+				bl_entry->idProduct);
+	return bl_entry;
+}
+
+/**
+ * usbhid_lookup_quirk: return any quirks associated with a USB HID device
+ * @idVendor: the 16-bit USB vendor ID, in native byteorder
+ * @idProduct: the 16-bit USB product ID, in native byteorder
+ *
+ * Description:
+ *     Given a USB vendor ID and product ID, return any quirks associated
+ *     with that device.
+ *
+ * Returns: a u32 quirks value.
+ */
+u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+	u32 quirks = 0;
+	const struct hid_blacklist *bl_entry = NULL;
+
+	/* Ignore all Wacom devices */
+	if (idVendor == USB_VENDOR_ID_WACOM)
+		return HID_QUIRK_IGNORE;
+
+	/* ignore all Code Mercenaries IOWarrior devices */
+	if (idVendor == USB_VENDOR_ID_CODEMERCS)
+		if (idProduct >= USB_DEVICE_ID_CODEMERCS_IOW_FIRST &&
+				idProduct <= USB_DEVICE_ID_CODEMERCS_IOW_LAST)
+			return HID_QUIRK_IGNORE;
+
+	down_read(&dquirks_rwsem);
+	bl_entry = usbhid_exists_dquirk(idVendor, idProduct);
+	if (!bl_entry)
+		bl_entry = usbhid_exists_squirk(idVendor, idProduct);
+	if (bl_entry)
+		quirks = bl_entry->quirks;
+	up_read(&dquirks_rwsem);
+
+	return quirks;
+}
+
diff --git a/drivers/hid/usbhid/hid-tmff.c b/drivers/hid/usbhid/hid-tmff.c
new file mode 100644
index 000000000000..ab67331620d0
--- /dev/null
+++ b/drivers/hid/usbhid/hid-tmff.c
@@ -0,0 +1,147 @@
+/*
+ * Force feedback support for various HID compliant devices by ThrustMaster:
+ *    ThrustMaster FireStorm Dual Power 2
+ * and possibly others whose device ids haven't been added.
+ *
+ *  Modified to support ThrustMaster devices by Zinx Verituse
+ *  on 2003-01-25 from the Logitech force feedback driver,
+ *  which is by Johann Deneux.
+ *
+ *  Copyright (c) 2003 Zinx Verituse <zinx@epicsol.org>
+ *  Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/input.h>
+
+#undef DEBUG
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+#include "usbhid.h"
+
+/* Usages for thrustmaster devices I know about */
+#define THRUSTMASTER_USAGE_RUMBLE_LR	(HID_UP_GENDESK | 0xbb)
+
+
+struct tmff_device {
+	struct hid_report *report;
+	struct hid_field *rumble;
+};
+
+/* Changes values from 0 to 0xffff into values from minimum to maximum */
+static inline int hid_tmff_scale(unsigned int in, int minimum, int maximum)
+{
+	int ret;
+
+	ret = (in * (maximum - minimum) / 0xffff) + minimum;
+	if (ret < minimum)
+		return minimum;
+	if (ret > maximum)
+		return maximum;
+	return ret;
+}
+
+static int hid_tmff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = dev->private;
+	struct tmff_device *tmff = data;
+	int left, right;	/* Rumbling */
+
+	left = hid_tmff_scale(effect->u.rumble.weak_magnitude,
+		tmff->rumble->logical_minimum, tmff->rumble->logical_maximum);
+	right = hid_tmff_scale(effect->u.rumble.strong_magnitude,
+		tmff->rumble->logical_minimum, tmff->rumble->logical_maximum);
+
+	tmff->rumble->value[0] = left;
+	tmff->rumble->value[1] = right;
+	dbg("(left,right)=(%08x, %08x)", left, right);
+	usbhid_submit_report(hid, tmff->report, USB_DIR_OUT);
+
+	return 0;
+}
+
+int hid_tmff_init(struct hid_device *hid)
+{
+	struct tmff_device *tmff;
+	struct list_head *pos;
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *input_dev = hidinput->input;
+	int error;
+
+	tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL);
+	if (!tmff)
+		return -ENOMEM;
+
+	/* Find the report to use */
+	__list_for_each(pos, &hid->report_enum[HID_OUTPUT_REPORT].report_list) {
+		struct hid_report *report = (struct hid_report *)pos;
+		int fieldnum;
+
+		for (fieldnum = 0; fieldnum < report->maxfield; ++fieldnum) {
+			struct hid_field *field = report->field[fieldnum];
+
+			if (field->maxusage <= 0)
+				continue;
+
+			switch (field->usage[0].hid) {
+				case THRUSTMASTER_USAGE_RUMBLE_LR:
+					if (field->report_count < 2) {
+						warn("ignoring THRUSTMASTER_USAGE_RUMBLE_LR with report_count < 2");
+						continue;
+					}
+
+					if (field->logical_maximum == field->logical_minimum) {
+						warn("ignoring THRUSTMASTER_USAGE_RUMBLE_LR with logical_maximum == logical_minimum");
+						continue;
+					}
+
+					if (tmff->report && tmff->report != report) {
+						warn("ignoring THRUSTMASTER_USAGE_RUMBLE_LR in other report");
+						continue;
+					}
+
+					if (tmff->rumble && tmff->rumble != field) {
+						warn("ignoring duplicate THRUSTMASTER_USAGE_RUMBLE_LR");
+						continue;
+					}
+
+					tmff->report = report;
+					tmff->rumble = field;
+
+					set_bit(FF_RUMBLE, input_dev->ffbit);
+					break;
+
+				default:
+					warn("ignoring unknown output usage %08x", field->usage[0].hid);
+					continue;
+			}
+		}
+	}
+
+	error = input_ff_create_memless(input_dev, tmff, hid_tmff_play);
+	if (error) {
+		kfree(tmff);
+		return error;
+	}
+
+	info("Force feedback for ThrustMaster rumble pad devices by Zinx Verituse <zinx@epicsol.org>");
+
+	return 0;
+}
+
diff --git a/drivers/hid/usbhid/hid-zpff.c b/drivers/hid/usbhid/hid-zpff.c
new file mode 100644
index 000000000000..7bd8238ca212
--- /dev/null
+++ b/drivers/hid/usbhid/hid-zpff.c
@@ -0,0 +1,111 @@
+/*
+ *  Force feedback support for Zeroplus based devices
+ *
+ *  Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* #define DEBUG */
+
+#define debug(format, arg...) pr_debug("hid-zpff: " format "\n" , ## arg)
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include "usbhid.h"
+
+struct zpff_device {
+	struct hid_report *report;
+};
+
+static int hid_zpff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = dev->private;
+	struct zpff_device *zpff = data;
+	int left, right;
+
+	/*
+	 * The following is specified the other way around in the Zeroplus
+	 * datasheet but the order below is correct for the XFX Executioner;
+	 * however it is possible that the XFX Executioner is an exception
+	 */
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	debug("called with 0x%04x 0x%04x", left, right);
+
+	left = left * 0x7f / 0xffff;
+	right = right * 0x7f / 0xffff;
+
+	zpff->report->field[2]->value[0] = left;
+	zpff->report->field[3]->value[0] = right;
+	debug("running with 0x%02x 0x%02x", left, right);
+	usbhid_submit_report(hid, zpff->report, USB_DIR_OUT);
+
+	return 0;
+}
+
+int hid_zpff_init(struct hid_device *hid)
+{
+	struct zpff_device *zpff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	if (list_empty(report_list)) {
+		printk(KERN_ERR "hid-zpff: no output report found\n");
+		return -ENODEV;
+	}
+
+	report = list_entry(report_list->next, struct hid_report, list);
+
+	if (report->maxfield < 4) {
+		printk(KERN_ERR "hid-zpff: not enough fields in report\n");
+		return -ENODEV;
+	}
+
+	zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL);
+	if (!zpff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, zpff, hid_zpff_play);
+	if (error) {
+		kfree(zpff);
+		return error;
+	}
+
+	zpff->report = report;
+	zpff->report->field[0]->value[0] = 0x00;
+	zpff->report->field[1]->value[0] = 0x02;
+	zpff->report->field[2]->value[0] = 0x00;
+	zpff->report->field[3]->value[0] = 0x00;
+	usbhid_submit_report(hid, zpff->report, USB_DIR_OUT);
+
+	printk(KERN_INFO "Force feedback for Zeroplus based devices by "
+	       "Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+}
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
new file mode 100644
index 000000000000..a8b3d66cd498
--- /dev/null
+++ b/drivers/hid/usbhid/hiddev.c
@@ -0,0 +1,847 @@
+/*
+ *  Copyright (c) 2001 Paul Stewart
+ *  Copyright (c) 2001 Vojtech Pavlik
+ *
+ *  HID char devices, giving access to raw HID device events.
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to Paul Stewart <stewart@wetlogic.net>
+ */
+
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include "usbhid.h"
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define HIDDEV_MINOR_BASE	0
+#define HIDDEV_MINORS		256
+#else
+#define HIDDEV_MINOR_BASE	96
+#define HIDDEV_MINORS		16
+#endif
+#define HIDDEV_BUFFER_SIZE	64
+
+struct hiddev {
+	int exist;
+	int open;
+	wait_queue_head_t wait;
+	struct hid_device *hid;
+	struct list_head list;
+};
+
+struct hiddev_list {
+	struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE];
+	int head;
+	int tail;
+	unsigned flags;
+	struct fasync_struct *fasync;
+	struct hiddev *hiddev;
+	struct list_head node;
+};
+
+static struct hiddev *hiddev_table[HIDDEV_MINORS];
+
+/*
+ * Find a report, given the report's type and ID.  The ID can be specified
+ * indirectly by REPORT_ID_FIRST (which returns the first report of the given
+ * type) or by (REPORT_ID_NEXT | old_id), which returns the next report of the
+ * given type which follows old_id.
+ */
+static struct hid_report *
+hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo)
+{
+	unsigned int flags = rinfo->report_id & ~HID_REPORT_ID_MASK;
+	unsigned int rid = rinfo->report_id & HID_REPORT_ID_MASK;
+	struct hid_report_enum *report_enum;
+	struct hid_report *report;
+	struct list_head *list;
+
+	if (rinfo->report_type < HID_REPORT_TYPE_MIN ||
+	    rinfo->report_type > HID_REPORT_TYPE_MAX)
+		return NULL;
+
+	report_enum = hid->report_enum +
+		(rinfo->report_type - HID_REPORT_TYPE_MIN);
+
+	switch (flags) {
+	case 0: /* Nothing to do -- report_id is already set correctly */
+		break;
+
+	case HID_REPORT_ID_FIRST:
+		if (list_empty(&report_enum->report_list))
+			return NULL;
+
+		list = report_enum->report_list.next;
+		report = list_entry(list, struct hid_report, list);
+		rinfo->report_id = report->id;
+		break;
+
+	case HID_REPORT_ID_NEXT:
+		report = report_enum->report_id_hash[rid];
+		if (!report)
+			return NULL;
+
+		list = report->list.next;
+		if (list == &report_enum->report_list)
+			return NULL;
+
+		report = list_entry(list, struct hid_report, list);
+		rinfo->report_id = report->id;
+		break;
+
+	default:
+		return NULL;
+	}
+
+	return report_enum->report_id_hash[rinfo->report_id];
+}
+
+/*
+ * Perform an exhaustive search of the report table for a usage, given its
+ * type and usage id.
+ */
+static struct hid_field *
+hiddev_lookup_usage(struct hid_device *hid, struct hiddev_usage_ref *uref)
+{
+	int i, j;
+	struct hid_report *report;
+	struct hid_report_enum *report_enum;
+	struct hid_field *field;
+
+	if (uref->report_type < HID_REPORT_TYPE_MIN ||
+	    uref->report_type > HID_REPORT_TYPE_MAX)
+		return NULL;
+
+	report_enum = hid->report_enum +
+		(uref->report_type - HID_REPORT_TYPE_MIN);
+
+	list_for_each_entry(report, &report_enum->report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++) {
+				if (field->usage[j].hid == uref->usage_code) {
+					uref->report_id = report->id;
+					uref->field_index = i;
+					uref->usage_index = j;
+					return field;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static void hiddev_send_event(struct hid_device *hid,
+			      struct hiddev_usage_ref *uref)
+{
+	struct hiddev *hiddev = hid->hiddev;
+	struct hiddev_list *list;
+
+	list_for_each_entry(list, &hiddev->list, node) {
+		if (uref->field_index != HID_FIELD_INDEX_NONE ||
+		    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+			list->buffer[list->head] = *uref;
+			list->head = (list->head + 1) &
+				(HIDDEV_BUFFER_SIZE - 1);
+			kill_fasync(&list->fasync, SIGIO, POLL_IN);
+		}
+	}
+
+	wake_up_interruptible(&hiddev->wait);
+}
+
+/*
+ * This is where hid.c calls into hiddev to pass an event that occurred over
+ * the interrupt pipe
+ */
+void hiddev_hid_event(struct hid_device *hid, struct hid_field *field,
+		      struct hid_usage *usage, __s32 value)
+{
+	unsigned type = field->report_type;
+	struct hiddev_usage_ref uref;
+
+	uref.report_type =
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
+	uref.report_id = field->report->id;
+	uref.field_index = field->index;
+	uref.usage_index = (usage - field->usage);
+	uref.usage_code = usage->hid;
+	uref.value = value;
+
+	hiddev_send_event(hid, &uref);
+}
+EXPORT_SYMBOL_GPL(hiddev_hid_event);
+
+void hiddev_report_event(struct hid_device *hid, struct hid_report *report)
+{
+	unsigned type = report->type;
+	struct hiddev_usage_ref uref;
+
+	memset(&uref, 0, sizeof(uref));
+	uref.report_type =
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
+	uref.report_id = report->id;
+	uref.field_index = HID_FIELD_INDEX_NONE;
+
+	hiddev_send_event(hid, &uref);
+}
+
+/*
+ * fasync file op
+ */
+static int hiddev_fasync(int fd, struct file *file, int on)
+{
+	int retval;
+	struct hiddev_list *list = file->private_data;
+
+	retval = fasync_helper(fd, file, on, &list->fasync);
+
+	return retval < 0 ? retval : 0;
+}
+
+
+/*
+ * release file op
+ */
+static int hiddev_release(struct inode * inode, struct file * file)
+{
+	struct hiddev_list *list = file->private_data;
+
+	hiddev_fasync(-1, file, 0);
+	list_del(&list->node);
+
+	if (!--list->hiddev->open) {
+		if (list->hiddev->exist)
+			usbhid_close(list->hiddev->hid);
+		else
+			kfree(list->hiddev);
+	}
+
+	kfree(list);
+
+	return 0;
+}
+
+/*
+ * open file op
+ */
+static int hiddev_open(struct inode *inode, struct file *file)
+{
+	struct hiddev_list *list;
+
+	int i = iminor(inode) - HIDDEV_MINOR_BASE;
+
+	if (i >= HIDDEV_MINORS || !hiddev_table[i])
+		return -ENODEV;
+
+	if (!(list = kzalloc(sizeof(struct hiddev_list), GFP_KERNEL)))
+		return -ENOMEM;
+
+	list->hiddev = hiddev_table[i];
+	list_add_tail(&list->node, &hiddev_table[i]->list);
+	file->private_data = list;
+
+	if (!list->hiddev->open++)
+		if (list->hiddev->exist)
+			usbhid_open(hiddev_table[i]->hid);
+
+	return 0;
+}
+
+/*
+ * "write" file op
+ */
+static ssize_t hiddev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos)
+{
+	return -EINVAL;
+}
+
+/*
+ * "read" file op
+ */
+static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct hiddev_list *list = file->private_data;
+	int event_size;
+	int retval = 0;
+
+	event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ?
+		sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event);
+
+	if (count < event_size)
+		return 0;
+
+	while (retval == 0) {
+		if (list->head == list->tail) {
+			add_wait_queue(&list->hiddev->wait, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			while (list->head == list->tail) {
+				if (file->f_flags & O_NONBLOCK) {
+					retval = -EAGAIN;
+					break;
+				}
+				if (signal_pending(current)) {
+					retval = -ERESTARTSYS;
+					break;
+				}
+				if (!list->hiddev->exist) {
+					retval = -EIO;
+					break;
+				}
+
+				schedule();
+				set_current_state(TASK_INTERRUPTIBLE);
+			}
+
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&list->hiddev->wait, &wait);
+		}
+
+		if (retval)
+			return retval;
+
+
+		while (list->head != list->tail &&
+		       retval + event_size <= count) {
+			if ((list->flags & HIDDEV_FLAG_UREF) == 0) {
+				if (list->buffer[list->tail].field_index !=
+				    HID_FIELD_INDEX_NONE) {
+					struct hiddev_event event;
+					event.hid = list->buffer[list->tail].usage_code;
+					event.value = list->buffer[list->tail].value;
+					if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event)))
+						return -EFAULT;
+					retval += sizeof(struct hiddev_event);
+				}
+			} else {
+				if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE ||
+				    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+					if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref)))
+						return -EFAULT;
+					retval += sizeof(struct hiddev_usage_ref);
+				}
+			}
+			list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
+		}
+
+	}
+
+	return retval;
+}
+
+/*
+ * "poll" file op
+ * No kernel lock - fine
+ */
+static unsigned int hiddev_poll(struct file *file, poll_table *wait)
+{
+	struct hiddev_list *list = file->private_data;
+
+	poll_wait(file, &list->hiddev->wait, wait);
+	if (list->head != list->tail)
+		return POLLIN | POLLRDNORM;
+	if (!list->hiddev->exist)
+		return POLLERR | POLLHUP;
+	return 0;
+}
+
+/*
+ * "ioctl" file op
+ */
+static int hiddev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct hiddev_list *list = file->private_data;
+	struct hiddev *hiddev = list->hiddev;
+	struct hid_device *hid = hiddev->hid;
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	struct hiddev_collection_info cinfo;
+	struct hiddev_report_info rinfo;
+	struct hiddev_field_info finfo;
+	struct hiddev_usage_ref_multi *uref_multi = NULL;
+	struct hiddev_usage_ref *uref;
+	struct hiddev_devinfo dinfo;
+	struct hid_report *report;
+	struct hid_field *field;
+	struct usbhid_device *usbhid = hid->driver_data;
+	void __user *user_arg = (void __user *)arg;
+	int i;
+
+	if (!hiddev->exist)
+		return -EIO;
+
+	switch (cmd) {
+
+	case HIDIOCGVERSION:
+		return put_user(HID_VERSION, (int __user *)arg);
+
+	case HIDIOCAPPLICATION:
+		if (arg < 0 || arg >= hid->maxapplication)
+			return -EINVAL;
+
+		for (i = 0; i < hid->maxcollection; i++)
+			if (hid->collection[i].type ==
+			    HID_COLLECTION_APPLICATION && arg-- == 0)
+				break;
+
+		if (i == hid->maxcollection)
+			return -EINVAL;
+
+		return hid->collection[i].usage;
+
+	case HIDIOCGDEVINFO:
+		dinfo.bustype = BUS_USB;
+		dinfo.busnum = dev->bus->busnum;
+		dinfo.devnum = dev->devnum;
+		dinfo.ifnum = usbhid->ifnum;
+		dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor);
+		dinfo.product = le16_to_cpu(dev->descriptor.idProduct);
+		dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice);
+		dinfo.num_applications = hid->maxapplication;
+		if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
+			return -EFAULT;
+
+		return 0;
+
+	case HIDIOCGFLAG:
+		if (put_user(list->flags, (int __user *)arg))
+			return -EFAULT;
+
+		return 0;
+
+	case HIDIOCSFLAG:
+		{
+			int newflags;
+			if (get_user(newflags, (int __user *)arg))
+				return -EFAULT;
+
+			if ((newflags & ~HIDDEV_FLAGS) != 0 ||
+			    ((newflags & HIDDEV_FLAG_REPORT) != 0 &&
+			     (newflags & HIDDEV_FLAG_UREF) == 0))
+				return -EINVAL;
+
+			list->flags = newflags;
+
+			return 0;
+		}
+
+	case HIDIOCGSTRING:
+		{
+			int idx, len;
+			char *buf;
+
+			if (get_user(idx, (int __user *)arg))
+				return -EFAULT;
+
+			if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL)
+				return -ENOMEM;
+
+			if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) {
+				kfree(buf);
+				return -EINVAL;
+			}
+
+			if (copy_to_user(user_arg+sizeof(int), buf, len+1)) {
+				kfree(buf);
+				return -EFAULT;
+			}
+
+			kfree(buf);
+
+			return len;
+		}
+
+	case HIDIOCINITREPORT:
+		usbhid_init_reports(hid);
+
+		return 0;
+
+	case HIDIOCGREPORT:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo)))
+			return -EFAULT;
+
+		if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
+			return -EINVAL;
+
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			return -EINVAL;
+
+		usbhid_submit_report(hid, report, USB_DIR_IN);
+		usbhid_wait_io(hid);
+
+		return 0;
+
+	case HIDIOCSREPORT:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo)))
+			return -EFAULT;
+
+		if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
+			return -EINVAL;
+
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			return -EINVAL;
+
+		usbhid_submit_report(hid, report, USB_DIR_OUT);
+		usbhid_wait_io(hid);
+
+		return 0;
+
+	case HIDIOCGREPORTINFO:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo)))
+			return -EFAULT;
+
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			return -EINVAL;
+
+		rinfo.num_fields = report->maxfield;
+
+		if (copy_to_user(user_arg, &rinfo, sizeof(rinfo)))
+			return -EFAULT;
+
+		return 0;
+
+	case HIDIOCGFIELDINFO:
+		if (copy_from_user(&finfo, user_arg, sizeof(finfo)))
+			return -EFAULT;
+		rinfo.report_type = finfo.report_type;
+		rinfo.report_id = finfo.report_id;
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			return -EINVAL;
+
+		if (finfo.field_index >= report->maxfield)
+			return -EINVAL;
+
+		field = report->field[finfo.field_index];
+		memset(&finfo, 0, sizeof(finfo));
+		finfo.report_type = rinfo.report_type;
+		finfo.report_id = rinfo.report_id;
+		finfo.field_index = field->report_count - 1;
+		finfo.maxusage = field->maxusage;
+		finfo.flags = field->flags;
+		finfo.physical = field->physical;
+		finfo.logical = field->logical;
+		finfo.application = field->application;
+		finfo.logical_minimum = field->logical_minimum;
+		finfo.logical_maximum = field->logical_maximum;
+		finfo.physical_minimum = field->physical_minimum;
+		finfo.physical_maximum = field->physical_maximum;
+		finfo.unit_exponent = field->unit_exponent;
+		finfo.unit = field->unit;
+
+		if (copy_to_user(user_arg, &finfo, sizeof(finfo)))
+			return -EFAULT;
+
+		return 0;
+
+	case HIDIOCGUCODE:
+		uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL);
+		if (!uref_multi)
+			return -ENOMEM;
+		uref = &uref_multi->uref;
+		if (copy_from_user(uref, user_arg, sizeof(*uref)))
+			goto fault;
+
+		rinfo.report_type = uref->report_type;
+		rinfo.report_id = uref->report_id;
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			goto inval;
+
+		if (uref->field_index >= report->maxfield)
+			goto inval;
+
+		field = report->field[uref->field_index];
+		if (uref->usage_index >= field->maxusage)
+			goto inval;
+
+		uref->usage_code = field->usage[uref->usage_index].hid;
+
+		if (copy_to_user(user_arg, uref, sizeof(*uref)))
+			goto fault;
+
+		kfree(uref_multi);
+		return 0;
+
+	case HIDIOCGUSAGE:
+	case HIDIOCSUSAGE:
+	case HIDIOCGUSAGES:
+	case HIDIOCSUSAGES:
+	case HIDIOCGCOLLECTIONINDEX:
+		uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL);
+		if (!uref_multi)
+			return -ENOMEM;
+		uref = &uref_multi->uref;
+		if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
+			if (copy_from_user(uref_multi, user_arg,
+					   sizeof(*uref_multi)))
+				goto fault;
+		} else {
+			if (copy_from_user(uref, user_arg, sizeof(*uref)))
+				goto fault;
+		}
+
+		if (cmd != HIDIOCGUSAGE &&
+		    cmd != HIDIOCGUSAGES &&
+		    uref->report_type == HID_REPORT_TYPE_INPUT)
+			goto inval;
+
+		if (uref->report_id == HID_REPORT_ID_UNKNOWN) {
+			field = hiddev_lookup_usage(hid, uref);
+			if (field == NULL)
+				goto inval;
+		} else {
+			rinfo.report_type = uref->report_type;
+			rinfo.report_id = uref->report_id;
+			if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+				goto inval;
+
+			if (uref->field_index >= report->maxfield)
+				goto inval;
+
+			field = report->field[uref->field_index];
+
+			if (cmd == HIDIOCGCOLLECTIONINDEX) {
+				if (uref->usage_index >= field->maxusage)
+					goto inval;
+			} else if (uref->usage_index >= field->report_count)
+				goto inval;
+
+			else if ((cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) &&
+				 (uref_multi->num_values > HID_MAX_MULTI_USAGES ||
+				  uref->usage_index + uref_multi->num_values > field->report_count))
+				goto inval;
+			}
+
+		switch (cmd) {
+			case HIDIOCGUSAGE:
+				uref->value = field->value[uref->usage_index];
+				if (copy_to_user(user_arg, uref, sizeof(*uref)))
+					goto fault;
+				goto goodreturn;
+
+			case HIDIOCSUSAGE:
+				field->value[uref->usage_index] = uref->value;
+				goto goodreturn;
+
+			case HIDIOCGCOLLECTIONINDEX:
+				kfree(uref_multi);
+				return field->usage[uref->usage_index].collection_index;
+			case HIDIOCGUSAGES:
+				for (i = 0; i < uref_multi->num_values; i++)
+					uref_multi->values[i] =
+					    field->value[uref->usage_index + i];
+				if (copy_to_user(user_arg, uref_multi,
+						 sizeof(*uref_multi)))
+					goto fault;
+				goto goodreturn;
+			case HIDIOCSUSAGES:
+				for (i = 0; i < uref_multi->num_values; i++)
+					field->value[uref->usage_index + i] =
+					    uref_multi->values[i];
+				goto goodreturn;
+		}
+
+goodreturn:
+		kfree(uref_multi);
+		return 0;
+fault:
+		kfree(uref_multi);
+		return -EFAULT;
+inval:
+		kfree(uref_multi);
+		return -EINVAL;
+
+	case HIDIOCGCOLLECTIONINFO:
+		if (copy_from_user(&cinfo, user_arg, sizeof(cinfo)))
+			return -EFAULT;
+
+		if (cinfo.index >= hid->maxcollection)
+			return -EINVAL;
+
+		cinfo.type = hid->collection[cinfo.index].type;
+		cinfo.usage = hid->collection[cinfo.index].usage;
+		cinfo.level = hid->collection[cinfo.index].level;
+
+		if (copy_to_user(user_arg, &cinfo, sizeof(cinfo)))
+			return -EFAULT;
+		return 0;
+
+	default:
+
+		if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
+			return -EINVAL;
+
+		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
+			int len;
+			if (!hid->name)
+				return 0;
+			len = strlen(hid->name) + 1;
+			if (len > _IOC_SIZE(cmd))
+				 len = _IOC_SIZE(cmd);
+			return copy_to_user(user_arg, hid->name, len) ?
+				-EFAULT : len;
+		}
+
+		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) {
+			int len;
+			if (!hid->phys)
+				return 0;
+			len = strlen(hid->phys) + 1;
+			if (len > _IOC_SIZE(cmd))
+				len = _IOC_SIZE(cmd);
+			return copy_to_user(user_arg, hid->phys, len) ?
+				-EFAULT : len;
+		}
+	}
+	return -EINVAL;
+}
+
+static const struct file_operations hiddev_fops = {
+	.owner =	THIS_MODULE,
+	.read =		hiddev_read,
+	.write =	hiddev_write,
+	.poll =		hiddev_poll,
+	.open =		hiddev_open,
+	.release =	hiddev_release,
+	.ioctl =	hiddev_ioctl,
+	.fasync =	hiddev_fasync,
+};
+
+static struct usb_class_driver hiddev_class = {
+	.name =		"hiddev%d",
+	.fops =		&hiddev_fops,
+	.minor_base =	HIDDEV_MINOR_BASE,
+};
+
+/*
+ * This is where hid.c calls us to connect a hid device to the hiddev driver
+ */
+int hiddev_connect(struct hid_device *hid)
+{
+	struct hiddev *hiddev;
+	struct usbhid_device *usbhid = hid->driver_data;
+	int i;
+	int retval;
+
+	for (i = 0; i < hid->maxcollection; i++)
+		if (hid->collection[i].type ==
+		    HID_COLLECTION_APPLICATION &&
+		    !IS_INPUT_APPLICATION(hid->collection[i].usage))
+			break;
+
+	if (i == hid->maxcollection && (hid->quirks & HID_QUIRK_HIDDEV) == 0)
+		return -1;
+
+	if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
+		return -1;
+
+	retval = usb_register_dev(usbhid->intf, &hiddev_class);
+	if (retval) {
+		err("Not able to get a minor for this device.");
+		kfree(hiddev);
+		return -1;
+	}
+
+	init_waitqueue_head(&hiddev->wait);
+	INIT_LIST_HEAD(&hiddev->list);
+	hiddev->hid = hid;
+	hiddev->exist = 1;
+
+	hid->minor = usbhid->intf->minor;
+	hid->hiddev = hiddev;
+
+	hiddev_table[usbhid->intf->minor - HIDDEV_MINOR_BASE] = hiddev;
+
+	return 0;
+}
+
+/*
+ * This is where hid.c calls us to disconnect a hiddev device from the
+ * corresponding hid device (usually because the usb device has disconnected)
+ */
+static struct usb_class_driver hiddev_class;
+void hiddev_disconnect(struct hid_device *hid)
+{
+	struct hiddev *hiddev = hid->hiddev;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	hiddev->exist = 0;
+
+	hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL;
+	usb_deregister_dev(usbhid->intf, &hiddev_class);
+
+	if (hiddev->open) {
+		usbhid_close(hiddev->hid);
+		wake_up_interruptible(&hiddev->wait);
+	} else {
+		kfree(hiddev);
+	}
+}
+
+/* Currently this driver is a USB driver.  It's not a conventional one in
+ * the sense that it doesn't probe at the USB level.  Instead it waits to
+ * be connected by HID through the hiddev_connect / hiddev_disconnect
+ * routines.  The reason to register as a USB device is to gain part of the
+ * minor number space from the USB major.
+ *
+ * In theory, should the HID code be generalized to more than one physical
+ * medium (say, IEEE 1384), this driver will probably need to register its
+ * own major number, and in doing so, no longer need to register with USB.
+ * At that point the probe routine and hiddev_driver struct below will no
+ * longer be useful.
+ */
+
+
+/* We never attach in this manner, and rely on HID to connect us.  This
+ * is why there is no disconnect routine defined in the usb_driver either.
+ */
+static int hiddev_usbd_probe(struct usb_interface *intf,
+			     const struct usb_device_id *hiddev_info)
+{
+	return -ENODEV;
+}
+
+
+static /* const */ struct usb_driver hiddev_driver = {
+	.name =		"hiddev",
+	.probe =	hiddev_usbd_probe,
+};
+
+int __init hiddev_init(void)
+{
+	return usb_register(&hiddev_driver);
+}
+
+void hiddev_exit(void)
+{
+	usb_deregister(&hiddev_driver);
+}
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h
new file mode 100644
index 000000000000..0023f96d4294
--- /dev/null
+++ b/drivers/hid/usbhid/usbhid.h
@@ -0,0 +1,87 @@
+#ifndef __USBHID_H
+#define __USBHID_H
+
+/*
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2001 Vojtech Pavlik
+ *  Copyright (c) 2006 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+
+/*  API provided by hid-core.c for USB HID drivers */
+int usbhid_wait_io(struct hid_device* hid);
+void usbhid_close(struct hid_device *hid);
+int usbhid_open(struct hid_device *hid);
+void usbhid_init_reports(struct hid_device *hid);
+void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir);
+
+/*
+ * USB-specific HID struct, to be pointed to
+ * from struct hid_device->driver_data
+ */
+
+struct usbhid_device {
+	struct hid_device *hid;						/* pointer to corresponding HID dev */
+
+	struct usb_interface *intf;                                     /* USB interface */
+	int ifnum;                                                      /* USB interface number */
+
+	unsigned int bufsize;                                           /* URB buffer size */
+
+	struct urb *urbin;                                              /* Input URB */
+	char *inbuf;                                                    /* Input buffer */
+	dma_addr_t inbuf_dma;                                           /* Input buffer dma */
+	spinlock_t inlock;                                              /* Input fifo spinlock */
+
+	struct urb *urbctrl;                                            /* Control URB */
+	struct usb_ctrlrequest *cr;                                     /* Control request struct */
+	dma_addr_t cr_dma;                                              /* Control request struct dma */
+	struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE];  		/* Control fifo */
+	unsigned char ctrlhead, ctrltail;                               /* Control fifo head & tail */
+	char *ctrlbuf;                                                  /* Control buffer */
+	dma_addr_t ctrlbuf_dma;                                         /* Control buffer dma */
+	spinlock_t ctrllock;                                            /* Control fifo spinlock */
+
+	struct urb *urbout;                                             /* Output URB */
+	struct hid_report *out[HID_CONTROL_FIFO_SIZE];                  /* Output pipe fifo */
+	unsigned char outhead, outtail;                                 /* Output pipe fifo head & tail */
+	char *outbuf;                                                   /* Output buffer */
+	dma_addr_t outbuf_dma;                                          /* Output buffer dma */
+	spinlock_t outlock;                                             /* Output fifo spinlock */
+
+	unsigned long iofl;                                             /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
+	struct timer_list io_retry;                                     /* Retry timer */
+	unsigned long stop_retry;                                       /* Time to give up, in jiffies */
+	unsigned int retry_delay;                                       /* Delay length in ms */
+	struct work_struct reset_work;                                  /* Task context for resets */
+
+};
+
+#define	hid_to_usb_dev(hid_dev) \
+	container_of(hid_dev->dev->parent, struct usb_device, dev)
+
+#endif
+
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c
new file mode 100644
index 000000000000..65aa12e8d7b3
--- /dev/null
+++ b/drivers/hid/usbhid/usbkbd.c
@@ -0,0 +1,367 @@
+/*
+ * $Id: usbkbd.c,v 1.27 2001/12/27 10:37:41 vojtech Exp $
+ *
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ *
+ *  USB HIDBP Keyboard support
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb/input.h>
+#include <linux/hid.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION ""
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
+#define DRIVER_DESC "USB HID Boot Protocol keyboard driver"
+#define DRIVER_LICENSE "GPL"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+static unsigned char usb_kbd_keycode[256] = {
+	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
+	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
+	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
+	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
+	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
+	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
+	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
+	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
+	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
+	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
+	150,158,159,128,136,177,178,176,142,152,173,140
+};
+
+struct usb_kbd {
+	struct input_dev *dev;
+	struct usb_device *usbdev;
+	unsigned char old[8];
+	struct urb *irq, *led;
+	unsigned char newleds;
+	char name[128];
+	char phys[64];
+
+	unsigned char *new;
+	struct usb_ctrlrequest *cr;
+	unsigned char *leds;
+	dma_addr_t cr_dma;
+	dma_addr_t new_dma;
+	dma_addr_t leds_dma;
+};
+
+static void usb_kbd_irq(struct urb *urb)
+{
+	struct usb_kbd *kbd = urb->context;
+	int i;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		goto resubmit;
+	}
+
+	for (i = 0; i < 8; i++)
+		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
+
+	for (i = 2; i < 8; i++) {
+
+		if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
+			if (usb_kbd_keycode[kbd->old[i]])
+				input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
+			else
+				info("Unknown key (scancode %#x) released.", kbd->old[i]);
+		}
+
+		if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
+			if (usb_kbd_keycode[kbd->new[i]])
+				input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
+			else
+				info("Unknown key (scancode %#x) pressed.", kbd->new[i]);
+		}
+	}
+
+	input_sync(kbd->dev);
+
+	memcpy(kbd->old, kbd->new, 8);
+
+resubmit:
+	i = usb_submit_urb (urb, GFP_ATOMIC);
+	if (i)
+		err ("can't resubmit intr, %s-%s/input0, status %d",
+				kbd->usbdev->bus->bus_name,
+				kbd->usbdev->devpath, i);
+}
+
+static int usb_kbd_event(struct input_dev *dev, unsigned int type,
+			 unsigned int code, int value)
+{
+	struct usb_kbd *kbd = dev->private;
+
+	if (type != EV_LED)
+		return -1;
+
+
+	kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
+		       (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |
+		       (!!test_bit(LED_NUML,    dev->led));
+
+	if (kbd->led->status == -EINPROGRESS)
+		return 0;
+
+	if (*(kbd->leds) == kbd->newleds)
+		return 0;
+
+	*(kbd->leds) = kbd->newleds;
+	kbd->led->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
+		err("usb_submit_urb(leds) failed");
+
+	return 0;
+}
+
+static void usb_kbd_led(struct urb *urb)
+{
+	struct usb_kbd *kbd = urb->context;
+
+	if (urb->status)
+		warn("led urb status %d received", urb->status);
+
+	if (*(kbd->leds) == kbd->newleds)
+		return;
+
+	*(kbd->leds) = kbd->newleds;
+	kbd->led->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
+		err("usb_submit_urb(leds) failed");
+}
+
+static int usb_kbd_open(struct input_dev *dev)
+{
+	struct usb_kbd *kbd = dev->private;
+
+	kbd->irq->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->irq, GFP_KERNEL))
+		return -EIO;
+
+	return 0;
+}
+
+static void usb_kbd_close(struct input_dev *dev)
+{
+	struct usb_kbd *kbd = dev->private;
+
+	usb_kill_urb(kbd->irq);
+}
+
+static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
+{
+	if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
+		return -1;
+	if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))
+		return -1;
+	if (!(kbd->new = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
+		return -1;
+	if (!(kbd->cr = usb_buffer_alloc(dev, sizeof(struct usb_ctrlrequest), GFP_ATOMIC, &kbd->cr_dma)))
+		return -1;
+	if (!(kbd->leds = usb_buffer_alloc(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))
+		return -1;
+
+	return 0;
+}
+
+static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd)
+{
+	usb_free_urb(kbd->irq);
+	usb_free_urb(kbd->led);
+	if (kbd->new)
+		usb_buffer_free(dev, 8, kbd->new, kbd->new_dma);
+	if (kbd->cr)
+		usb_buffer_free(dev, sizeof(struct usb_ctrlrequest), kbd->cr, kbd->cr_dma);
+	if (kbd->leds)
+		usb_buffer_free(dev, 1, kbd->leds, kbd->leds_dma);
+}
+
+static int usb_kbd_probe(struct usb_interface *iface,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *dev = interface_to_usbdev(iface);
+	struct usb_host_interface *interface;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_kbd *kbd;
+	struct input_dev *input_dev;
+	int i, pipe, maxp;
+	int error = -ENOMEM;
+
+	interface = iface->cur_altsetting;
+
+	if (interface->desc.bNumEndpoints != 1)
+		return -ENODEV;
+
+	endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(endpoint))
+		return -ENODEV;
+
+	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+	kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!kbd || !input_dev)
+		goto fail1;
+
+	if (usb_kbd_alloc_mem(dev, kbd))
+		goto fail2;
+
+	kbd->usbdev = dev;
+	kbd->dev = input_dev;
+
+	if (dev->manufacturer)
+		strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(kbd->name, " ", sizeof(kbd->name));
+		strlcat(kbd->name, dev->product, sizeof(kbd->name));
+	}
+
+	if (!strlen(kbd->name))
+		snprintf(kbd->name, sizeof(kbd->name),
+			 "USB HIDBP Keyboard %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
+	strlcpy(kbd->phys, "/input0", sizeof(kbd->phys));
+
+	input_dev->name = kbd->name;
+	input_dev->phys = kbd->phys;
+	usb_to_input_id(dev, &input_dev->id);
+	input_dev->cdev.dev = &iface->dev;
+	input_dev->private = kbd;
+
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_LED) | BIT(EV_REP);
+	input_dev->ledbit[0] = BIT(LED_NUML) | BIT(LED_CAPSL) | BIT(LED_SCROLLL) | BIT(LED_COMPOSE) | BIT(LED_KANA);
+
+	for (i = 0; i < 255; i++)
+		set_bit(usb_kbd_keycode[i], input_dev->keybit);
+	clear_bit(0, input_dev->keybit);
+
+	input_dev->event = usb_kbd_event;
+	input_dev->open = usb_kbd_open;
+	input_dev->close = usb_kbd_close;
+
+	usb_fill_int_urb(kbd->irq, dev, pipe,
+			 kbd->new, (maxp > 8 ? 8 : maxp),
+			 usb_kbd_irq, kbd, endpoint->bInterval);
+	kbd->irq->transfer_dma = kbd->new_dma;
+	kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+	kbd->cr->bRequest = 0x09;
+	kbd->cr->wValue = cpu_to_le16(0x200);
+	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+	kbd->cr->wLength = cpu_to_le16(1);
+
+	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
+			     (void *) kbd->cr, kbd->leds, 1,
+			     usb_kbd_led, kbd);
+	kbd->led->setup_dma = kbd->cr_dma;
+	kbd->led->transfer_dma = kbd->leds_dma;
+	kbd->led->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);
+
+	error = input_register_device(kbd->dev);
+	if (error)
+		goto fail2;
+
+	usb_set_intfdata(iface, kbd);
+	return 0;
+
+fail2:	
+	usb_kbd_free_mem(dev, kbd);
+fail1:	
+	input_free_device(input_dev);
+	kfree(kbd);
+	return error;
+}
+
+static void usb_kbd_disconnect(struct usb_interface *intf)
+{
+	struct usb_kbd *kbd = usb_get_intfdata (intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (kbd) {
+		usb_kill_urb(kbd->irq);
+		input_unregister_device(kbd->dev);
+		usb_kbd_free_mem(interface_to_usbdev(intf), kbd);
+		kfree(kbd);
+	}
+}
+
+static struct usb_device_id usb_kbd_id_table [] = {
+	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
+		USB_INTERFACE_PROTOCOL_KEYBOARD) },
+	{ }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);
+
+static struct usb_driver usb_kbd_driver = {
+	.name =		"usbkbd",
+	.probe =	usb_kbd_probe,
+	.disconnect =	usb_kbd_disconnect,
+	.id_table =	usb_kbd_id_table,
+};
+
+static int __init usb_kbd_init(void)
+{
+	int result = usb_register(&usb_kbd_driver);
+	if (result == 0)
+		info(DRIVER_VERSION ":" DRIVER_DESC);
+	return result;
+}
+
+static void __exit usb_kbd_exit(void)
+{
+	usb_deregister(&usb_kbd_driver);
+}
+
+module_init(usb_kbd_init);
+module_exit(usb_kbd_exit);
diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c
new file mode 100644
index 000000000000..573776d865e1
--- /dev/null
+++ b/drivers/hid/usbhid/usbmouse.c
@@ -0,0 +1,252 @@
+/*
+ * $Id: usbmouse.c,v 1.15 2001/12/27 10:37:41 vojtech Exp $
+ *
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ *
+ *  USB HIDBP Mouse support
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb/input.h>
+#include <linux/hid.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v1.6"
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
+#define DRIVER_DESC "USB HID Boot Protocol mouse driver"
+#define DRIVER_LICENSE "GPL"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+struct usb_mouse {
+	char name[128];
+	char phys[64];
+	struct usb_device *usbdev;
+	struct input_dev *dev;
+	struct urb *irq;
+
+	signed char *data;
+	dma_addr_t data_dma;
+};
+
+static void usb_mouse_irq(struct urb *urb)
+{
+	struct usb_mouse *mouse = urb->context;
+	signed char *data = mouse->data;
+	struct input_dev *dev = mouse->dev;
+	int status;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		goto resubmit;
+	}
+
+	input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
+	input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
+	input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
+	input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
+	input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);
+
+	input_report_rel(dev, REL_X,     data[1]);
+	input_report_rel(dev, REL_Y,     data[2]);
+	input_report_rel(dev, REL_WHEEL, data[3]);
+
+	input_sync(dev);
+resubmit:
+	status = usb_submit_urb (urb, GFP_ATOMIC);
+	if (status)
+		err ("can't resubmit intr, %s-%s/input0, status %d",
+				mouse->usbdev->bus->bus_name,
+				mouse->usbdev->devpath, status);
+}
+
+static int usb_mouse_open(struct input_dev *dev)
+{
+	struct usb_mouse *mouse = dev->private;
+
+	mouse->irq->dev = mouse->usbdev;
+	if (usb_submit_urb(mouse->irq, GFP_KERNEL))
+		return -EIO;
+
+	return 0;
+}
+
+static void usb_mouse_close(struct input_dev *dev)
+{
+	struct usb_mouse *mouse = dev->private;
+
+	usb_kill_urb(mouse->irq);
+}
+
+static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct usb_host_interface *interface;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_mouse *mouse;
+	struct input_dev *input_dev;
+	int pipe, maxp;
+	int error = -ENOMEM;
+
+	interface = intf->cur_altsetting;
+
+	if (interface->desc.bNumEndpoints != 1)
+		return -ENODEV;
+
+	endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(endpoint))
+		return -ENODEV;
+
+	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+	mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!mouse || !input_dev)
+		goto fail1;
+
+	mouse->data = usb_buffer_alloc(dev, 8, GFP_ATOMIC, &mouse->data_dma);
+	if (!mouse->data)
+		goto fail1;
+
+	mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mouse->irq)
+		goto fail2;
+
+	mouse->usbdev = dev;
+	mouse->dev = input_dev;
+
+	if (dev->manufacturer)
+		strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(mouse->name, " ", sizeof(mouse->name));
+		strlcat(mouse->name, dev->product, sizeof(mouse->name));
+	}
+
+	if (!strlen(mouse->name))
+		snprintf(mouse->name, sizeof(mouse->name),
+			 "USB HIDBP Mouse %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
+	strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
+
+	input_dev->name = mouse->name;
+	input_dev->phys = mouse->phys;
+	usb_to_input_id(dev, &input_dev->id);
+	input_dev->cdev.dev = &intf->dev;
+
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+	input_dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT) | BIT(BTN_RIGHT) | BIT(BTN_MIDDLE);
+	input_dev->relbit[0] = BIT(REL_X) | BIT(REL_Y);
+	input_dev->keybit[LONG(BTN_MOUSE)] |= BIT(BTN_SIDE) | BIT(BTN_EXTRA);
+	input_dev->relbit[0] |= BIT(REL_WHEEL);
+
+	input_dev->private = mouse;
+	input_dev->open = usb_mouse_open;
+	input_dev->close = usb_mouse_close;
+
+	usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
+			 (maxp > 8 ? 8 : maxp),
+			 usb_mouse_irq, mouse, endpoint->bInterval);
+	mouse->irq->transfer_dma = mouse->data_dma;
+	mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	error = input_register_device(mouse->dev);
+	if (error)
+		goto fail3;
+
+	usb_set_intfdata(intf, mouse);
+	return 0;
+
+fail3:	
+	usb_free_urb(mouse->irq);
+fail2:	
+	usb_buffer_free(dev, 8, mouse->data, mouse->data_dma);
+fail1:	
+	input_free_device(input_dev);
+	kfree(mouse);
+	return error;
+}
+
+static void usb_mouse_disconnect(struct usb_interface *intf)
+{
+	struct usb_mouse *mouse = usb_get_intfdata (intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (mouse) {
+		usb_kill_urb(mouse->irq);
+		input_unregister_device(mouse->dev);
+		usb_free_urb(mouse->irq);
+		usb_buffer_free(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
+		kfree(mouse);
+	}
+}
+
+static struct usb_device_id usb_mouse_id_table [] = {
+	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
+		USB_INTERFACE_PROTOCOL_MOUSE) },
+	{ }	/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
+
+static struct usb_driver usb_mouse_driver = {
+	.name		= "usbmouse",
+	.probe		= usb_mouse_probe,
+	.disconnect	= usb_mouse_disconnect,
+	.id_table	= usb_mouse_id_table,
+};
+
+static int __init usb_mouse_init(void)
+{
+	int retval = usb_register(&usb_mouse_driver);
+	if (retval == 0)
+		info(DRIVER_VERSION ":" DRIVER_DESC);
+	return retval;
+}
+
+static void __exit usb_mouse_exit(void)
+{
+	usb_deregister(&usb_mouse_driver);
+}
+
+module_init(usb_mouse_init);
+module_exit(usb_mouse_exit);