Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid

Pull HID updates from Jiri Kosina:
 "The list of changes worth pointing out explicitly:

  - We are getting 'UHID', which is a new framework for implementing HID
    transport drivers in userspace (this is different from HIDRAW, which
    is transport-independent and provides report parsing facilities;
    uhid is for the other (transport) part of the pipeline).

    It's needed for (and currently being used by) Bluetooth-LowEnergy,
    as its specification mandates things we don't want in the kernel.

    Written by David Herrmann.

  - there have been quite a few bugs in runtime suspend/resume paths
    (probably never reported to actually happen in the wild, but still).
    Alan Stern fixed those.

  - a few other driver updates and fixes and random new device support."

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid: (45 commits)
  HID: add ASUS AIO keyboard model AK1D
  HID: add support for Cypress barcode scanner 04B4:ED81
  HID: Allow drivers to be their own listener
  HID: usbhid: fix error paths in suspend
  HID: usbhid: check for suspend or reset before restarting
  HID: usbhid: replace HID_REPORTED_IDLE with HID_SUSPENDED
  HID: usbhid: inline some simple routines
  HID: usbhid: fix autosuspend calls
  HID: usbhid: fix use-after-free bug
  HID: hid-core: optimize in case of hidraw
  HID: hidraw: fix list->buffer memleak
  HID: uhid: Fix sending events with invalid data
  HID: roccat: added sensor sysfs attribute for Savu
  HID: Add driver for Holtek based keyboards with broken HID
  HID: Add suport for the brightness control keys on HP keyboards
  HID: magicmouse: Implement Multi-touch Protocol B (MT-B)
  HID: magicmouse: Removing report_touches switch
  HID: roccat: rename roccat_common functions to roccat_common2
  HID: roccat: fix wrong hid_err usage on struct usb_device
  HID: roccat: move functionality to roccat-common
  ...
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
new file mode 100644
index 0000000..57b92cb
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
@@ -0,0 +1,38 @@
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
+		is being controlled by press_speed.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
+		a left or right mouse button click.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This file contains the trackpoint sensitivity.
+		Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
+		Values are decimal integers from 1 (slowest) to 255 (fastest).
+
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu b/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu
new file mode 100644
index 0000000..b42922c
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-roccat-savu
@@ -0,0 +1,77 @@
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/buttons
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	The mouse can store 5 profiles which can be switched by the
+		press of a button. A profile is split into general settings and
+		button settings. buttons holds informations about button layout.
+		When written, this file lets one write the respective profile
+		buttons to the mouse. The data has to be 47 bytes long.
+		The mouse will reject invalid data.
+		Which profile to write is determined by the profile number
+		contained in the data.
+		Before reading this file, control has to be written to select
+		which profile to read.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/control
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	When written, this file lets one select which data from which
+		profile will be	read next. The data has to be 3 bytes long.
+		This file is writeonly.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/general
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	The mouse can store 5 profiles which can be switched by the
+		press of a button. A profile is split into general settings and
+		button settings. profile holds informations like resolution, sensitivity
+		and light effects.
+		When written, this file lets one write the respective profile
+		settings back to the mouse. The data has to be 43 bytes long.
+		The mouse will reject invalid data.
+		Which profile to write is determined by the profile number
+		contained in the data.
+		This file is writeonly.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/info
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	When read, this file returns general data like firmware version.
+		The data is 8 bytes long.
+		This file is readonly.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/macro
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	When written, this file lets one store macros with max 500
+		keystrokes for a specific button for a specific profile.
+		Button and profile numbers are included in written data.
+		The data has to be 2083 bytes long.
+		Before reading this file, control has to be written to select
+		which profile and key to read.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/profile
+Date:		Mai 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	The mouse can store 5 profiles which can be switched by the
+		press of a button. profile holds number of actual profile.
+		This value is persistent, so its value determines the profile
+		that's active when the mouse is powered on next time.
+		When written, the mouse activates the set profile immediately.
+		The data has to be 3 bytes long.
+		The mouse will reject invalid data.
+Users:		http://roccat.sourceforge.net
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/savu/roccatsavu<minor>/sensor
+Date:		July 2012
+Contact:	Stefan Achatz <erazor_de@users.sourceforge.net>
+Description:	The mouse has a Avago ADNS-3090 sensor.
+		This file allows reading and writing of the mouse sensors registers.
+		The data has to be 4 bytes long.
+Users:		http://roccat.sourceforge.net
+
diff --git a/Documentation/hid/uhid.txt b/Documentation/hid/uhid.txt
new file mode 100644
index 0000000..4627c42
--- /dev/null
+++ b/Documentation/hid/uhid.txt
@@ -0,0 +1,169 @@
+      UHID - User-space I/O driver support for HID subsystem
+     ========================================================
+
+The HID subsystem needs two kinds of drivers. In this document we call them:
+
+ 1. The "HID I/O Driver" is the driver that performs raw data I/O to the
+    low-level device. Internally, they register an hid_ll_driver structure with
+    the HID core. They perform device setup, read raw data from the device and
+    push it into the HID subsystem and they provide a callback so the HID
+    subsystem can send data to the device.
+
+ 2. The "HID Device Driver" is the driver that parses HID reports and reacts on
+    them. There are generic drivers like "generic-usb" and "generic-bluetooth"
+    which adhere to the HID specification and provide the standardizes features.
+    But there may be special drivers and quirks for each non-standard device out
+    there. Internally, they use the hid_driver structure.
+
+Historically, the USB stack was the first subsystem to provide an HID I/O
+Driver. However, other standards like Bluetooth have adopted the HID specs and
+may provide HID I/O Drivers, too. The UHID driver allows to implement HID I/O
+Drivers in user-space and feed the data into the kernel HID-subsystem.
+
+This allows user-space to operate on the same level as USB-HID, Bluetooth-HID
+and similar. It does not provide a way to write HID Device Drivers, though. Use
+hidraw for this purpose.
+
+There is an example user-space application in ./samples/uhid/uhid-example.c
+
+The UHID API
+------------
+
+UHID is accessed through a character misc-device. The minor-number is allocated
+dynamically so you need to rely on udev (or similar) to create the device node.
+This is /dev/uhid by default.
+
+If a new device is detected by your HID I/O Driver and you want to register this
+device with the HID subsystem, then you need to open /dev/uhid once for each
+device you want to register. All further communication is done by read()'ing or
+write()'ing "struct uhid_event" objects. Non-blocking operations are supported
+by setting O_NONBLOCK.
+
+struct uhid_event {
+        __u32 type;
+        union {
+                struct uhid_create_req create;
+                struct uhid_data_req data;
+                ...
+        } u;
+};
+
+The "type" field contains the ID of the event. Depending on the ID different
+payloads are sent. You must not split a single event across multiple read()'s or
+multiple write()'s. A single event must always be sent as a whole. Furthermore,
+only a single event can be sent per read() or write(). Pending data is ignored.
+If you want to handle multiple events in a single syscall, then use vectored
+I/O with readv()/writev().
+
+The first thing you should do is sending an UHID_CREATE event. This will
+register the device. UHID will respond with an UHID_START event. You can now
+start sending data to and reading data from UHID. However, unless UHID sends the
+UHID_OPEN event, the internally attached HID Device Driver has no user attached.
+That is, you might put your device asleep unless you receive the UHID_OPEN
+event. If you receive the UHID_OPEN event, you should start I/O. If the last
+user closes the HID device, you will receive an UHID_CLOSE event. This may be
+followed by an UHID_OPEN event again and so on. There is no need to perform
+reference-counting in user-space. That is, you will never receive multiple
+UHID_OPEN events without an UHID_CLOSE event. The HID subsystem performs
+ref-counting for you.
+You may decide to ignore UHID_OPEN/UHID_CLOSE, though. I/O is allowed even
+though the device may have no users.
+
+If you want to send data to the HID subsystem, you send an HID_INPUT event with
+your raw data payload. If the kernel wants to send data to the device, you will
+read an UHID_OUTPUT or UHID_OUTPUT_EV event.
+
+If your device disconnects, you should send an UHID_DESTROY event. This will
+unregister the device. You can now send UHID_CREATE again to register a new
+device.
+If you close() the fd, the device is automatically unregistered and destroyed
+internally.
+
+write()
+-------
+write() allows you to modify the state of the device and feed input data into
+the kernel. The following types are supported: UHID_CREATE, UHID_DESTROY and
+UHID_INPUT. The kernel will parse the event immediately and if the event ID is
+not supported, it will return -EOPNOTSUPP. If the payload is invalid, then
+-EINVAL is returned, otherwise, the amount of data that was read is returned and
+the request was handled successfully.
+
+  UHID_CREATE:
+  This creates the internal HID device. No I/O is possible until you send this
+  event to the kernel. The payload is of type struct uhid_create_req and
+  contains information about your device. You can start I/O now.
+
+  UHID_DESTROY:
+  This destroys the internal HID device. No further I/O will be accepted. There
+  may still be pending messages that you can receive with read() but no further
+  UHID_INPUT events can be sent to the kernel.
+  You can create a new device by sending UHID_CREATE again. There is no need to
+  reopen the character device.
+
+  UHID_INPUT:
+  You must send UHID_CREATE before sending input to the kernel! This event
+  contains a data-payload. This is the raw data that you read from your device.
+  The kernel will parse the HID reports and react on it.
+
+  UHID_FEATURE_ANSWER:
+  If you receive a UHID_FEATURE request you must answer with this request. You
+  must copy the "id" field from the request into the answer. Set the "err" field
+  to 0 if no error occured or to EIO if an I/O error occurred.
+  If "err" is 0 then you should fill the buffer of the answer with the results
+  of the feature request and set "size" correspondingly.
+
+read()
+------
+read() will return a queued ouput report. These output reports can be of type
+UHID_START, UHID_STOP, UHID_OPEN, UHID_CLOSE, UHID_OUTPUT or UHID_OUTPUT_EV. No
+reaction is required to any of them but you should handle them according to your
+needs. Only UHID_OUTPUT and UHID_OUTPUT_EV have payloads.
+
+  UHID_START:
+  This is sent when the HID device is started. Consider this as an answer to
+  UHID_CREATE. This is always the first event that is sent.
+
+  UHID_STOP:
+  This is sent when the HID device is stopped. Consider this as an answer to
+  UHID_DESTROY.
+  If the kernel HID device driver closes the device manually (that is, you
+  didn't send UHID_DESTROY) then you should consider this device closed and send
+  an UHID_DESTROY event. You may want to reregister your device, though. This is
+  always the last message that is sent to you unless you reopen the device with
+  UHID_CREATE.
+
+  UHID_OPEN:
+  This is sent when the HID device is opened. That is, the data that the HID
+  device provides is read by some other process. You may ignore this event but
+  it is useful for power-management. As long as you haven't received this event
+  there is actually no other process that reads your data so there is no need to
+  send UHID_INPUT events to the kernel.
+
+  UHID_CLOSE:
+  This is sent when there are no more processes which read the HID data. It is
+  the counterpart of UHID_OPEN and you may as well ignore this event.
+
+  UHID_OUTPUT:
+  This is sent if the HID device driver wants to send raw data to the I/O
+  device. You should read the payload and forward it to the device. The payload
+  is of type "struct uhid_data_req".
+  This may be received even though you haven't received UHID_OPEN, yet.
+
+  UHID_OUTPUT_EV:
+  Same as UHID_OUTPUT but this contains a "struct input_event" as payload. This
+  is called for force-feedback, LED or similar events which are received through
+  an input device by the HID subsystem. You should convert this into raw reports
+  and send them to your device similar to events of type UHID_OUTPUT.
+
+  UHID_FEATURE:
+  This event is sent if the kernel driver wants to perform a feature request as
+  described in the HID specs. The report-type and report-number are available in
+  the payload.
+  The kernel serializes feature requests so there will never be two in parallel.
+  However, if you fail to respond with a UHID_FEATURE_ANSWER in a time-span of 5
+  seconds, then the requests will be dropped and a new one might be sent.
+  Therefore, the payload also contains an "id" field that identifies every
+  request.
+
+Document by:
+  David Herrmann <dh.herrmann@googlemail.com>
diff --git a/MAINTAINERS b/MAINTAINERS
index cda0453..04ea1f2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6965,6 +6965,13 @@
 F:	Documentation/filesystems/ufs.txt
 F:	fs/ufs/
 
+UHID USERSPACE HID IO DRIVER:
+M:	David Herrmann <dh.herrmann@googlemail.com>
+L:	linux-input@vger.kernel.org
+S:	Maintained
+F:	drivers/hid/uhid.c
+F:	include/linux/uhid.h
+
 ULTRA-WIDEBAND (UWB) SUBSYSTEM:
 L:	linux-usb@vger.kernel.org
 S:	Orphan
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 3fda8c8..fbf4950 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -53,6 +53,27 @@
 
 	If unsure, say Y.
 
+config UHID
+	tristate "User-space I/O driver support for HID subsystem"
+	depends on HID
+	default n
+	---help---
+	Say Y here if you want to provide HID I/O Drivers from user-space.
+	This allows to write I/O drivers in user-space and feed the data from
+	the device into the kernel. The kernel parses the HID reports, loads the
+	corresponding HID Device Driver or provides input devices on top of your
+	user-space device.
+
+	This driver cannot be used to parse HID-reports in user-space and write
+	special HID-drivers. You should use hidraw for that.
+	Instead, this driver allows to write the transport-layer driver in
+	user-space like USB-HID and Bluetooth-HID do in kernel-space.
+
+	If unsure, say N.
+
+	To compile this driver as a module, choose M here: the
+	module will be called uhid.
+
 config HID_GENERIC
 	tristate "Generic HID driver"
 	depends on HID
@@ -193,10 +214,12 @@
 	Support for Ezkey BTC 8193 keyboard.
 
 config HID_HOLTEK
-	tristate "Holtek On Line Grip based game controller support"
+	tristate "Holtek HID devices"
 	depends on USB_HID
 	---help---
-	  Say Y here if you have a Holtek On Line Grip based game controller.
+	Support for Holtek based devices:
+	  - Holtek On Line Grip based game controller
+	  - Trust GXT 18 Gaming Keyboard
 
 config HOLTEK_FF
 	bool "Holtek On Line Grip force feedback support"
@@ -261,6 +284,19 @@
 	---help---
 	Support for LC-Power RC1000MCE RF remote control.
 
+config HID_LENOVO_TPKBD
+	tristate "Lenovo ThinkPad USB Keyboard with TrackPoint"
+	depends on USB_HID
+	select NEW_LEDS
+	select LEDS_CLASS
+	---help---
+	Support for the Lenovo ThinkPad USB Keyboard with TrackPoint.
+
+	Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint
+	and would like to use device-specific features like changing the
+	sensitivity of the trackpoint, using the microphone mute button or
+	controlling the mute and microphone mute LEDs.
+
 config HID_LOGITECH
 	tristate "Logitech devices" if EXPERT
 	depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index ca6cc9f..f975485 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -8,6 +8,7 @@
 endif
 
 obj-$(CONFIG_HID)		+= hid.o
+obj-$(CONFIG_UHID)		+= uhid.o
 
 obj-$(CONFIG_HID_GENERIC)	+= hid-generic.o
 
@@ -48,12 +49,14 @@
 obj-$(CONFIG_HID_ELECOM)	+= hid-elecom.o
 obj-$(CONFIG_HID_EZKEY)		+= hid-ezkey.o
 obj-$(CONFIG_HID_GYRATION)	+= hid-gyration.o
+obj-$(CONFIG_HID_HOLTEK)	+= hid-holtek-kbd.o
 obj-$(CONFIG_HID_HOLTEK)	+= hid-holtekff.o
 obj-$(CONFIG_HID_HYPERV_MOUSE)	+= hid-hyperv.o
 obj-$(CONFIG_HID_KENSINGTON)	+= hid-kensington.o
 obj-$(CONFIG_HID_KEYTOUCH)	+= hid-keytouch.o
 obj-$(CONFIG_HID_KYE)		+= hid-kye.o
 obj-$(CONFIG_HID_LCPOWER)       += hid-lcpower.o
+obj-$(CONFIG_HID_LENOVO_TPKBD)	+= hid-lenovo-tpkbd.o
 obj-$(CONFIG_HID_LOGITECH)	+= hid-logitech.o
 obj-$(CONFIG_HID_LOGITECH_DJ)	+= hid-logitech-dj.o
 obj-$(CONFIG_HID_MAGICMOUSE)    += hid-magicmouse.o
@@ -69,7 +72,8 @@
 obj-$(CONFIG_HID_PRIMAX)	+= hid-primax.o
 obj-$(CONFIG_HID_ROCCAT)	+= hid-roccat.o hid-roccat-common.o \
 	hid-roccat-arvo.o hid-roccat-isku.o hid-roccat-kone.o \
-	hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o
+	hid-roccat-koneplus.o hid-roccat-kovaplus.o hid-roccat-pyra.o \
+	hid-roccat-savu.o
 obj-$(CONFIG_HID_SAITEK)	+= hid-saitek.o
 obj-$(CONFIG_HID_SAMSUNG)	+= hid-samsung.o
 obj-$(CONFIG_HID_SMARTJOYPLUS)	+= hid-sjoy.o
diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c
index b99af34..a2abb8e 100644
--- a/drivers/hid/hid-chicony.c
+++ b/drivers/hid/hid-chicony.c
@@ -60,6 +60,7 @@
 static const struct hid_device_id ch_devices[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, ch_devices);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 4c87276..500844f 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1194,8 +1194,10 @@
 			goto out;
 	}
 
-	for (a = 0; a < report->maxfield; a++)
-		hid_input_field(hid, report->field[a], cdata, interrupt);
+	if (hid->claimed != HID_CLAIMED_HIDRAW) {
+		for (a = 0; a < report->maxfield; a++)
+			hid_input_field(hid, report->field[a], cdata, interrupt);
+	}
 
 	if (hid->claimed & HID_CLAIMED_INPUT)
 		hidinput_report_event(hid, report);
@@ -1243,6 +1245,10 @@
 		goto unlock;
 	}
 
+	/* Avoid unnecessary overhead if debugfs is disabled */
+	if (list_empty(&hid->debug_list))
+		goto nomem;
+
 	buf = kmalloc(sizeof(char) * HID_DEBUG_BUFSIZE, GFP_ATOMIC);
 
 	if (!buf)
@@ -1373,8 +1379,10 @@
 	if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))
 		hdev->claimed |= HID_CLAIMED_HIDRAW;
 
-	if (!hdev->claimed) {
-		hid_err(hdev, "claimed by neither input, hiddev nor hidraw\n");
+	/* Drivers with the ->raw_event callback set are not required to connect
+	 * to any other listener. */
+	if (!hdev->claimed && !hdev->driver->raw_event) {
+		hid_err(hdev, "device has no listeners, quitting\n");
 		return -ENODEV;
 	}
 
@@ -1521,10 +1529,12 @@
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_TACTICAL_PAD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_AK1D) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) },
@@ -1539,6 +1549,7 @@
 	{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
@@ -1547,6 +1558,7 @@
 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
+ 	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
@@ -1620,6 +1632,7 @@
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c
index 2f0be4c..9e43aac 100644
--- a/drivers/hid/hid-cypress.c
+++ b/drivers/hid/hid-cypress.c
@@ -129,6 +129,8 @@
 		.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_3),
 		.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_4),
+		.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_MOUSE),
 		.driver_data = CP_2WHEEL_MOUSE_HACK },
 	{ }
diff --git a/drivers/hid/hid-holtek-kbd.c b/drivers/hid/hid-holtek-kbd.c
new file mode 100644
index 0000000..e0a5d17
--- /dev/null
+++ b/drivers/hid/hid-holtek-kbd.c
@@ -0,0 +1,183 @@
+/*
+ * HID driver for Holtek keyboard
+ * Copyright (c) 2012 Tom Harwood
+*/
+
+/*
+ * 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/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+#include "usbhid/usbhid.h"
+
+/* Holtek based keyboards (USB ID 04d9:a055) have the following issues:
+ * - The report descriptor specifies an excessively large number of consumer
+ *   usages (2^15), which is more than HID_MAX_USAGES. This prevents proper
+ *   parsing of the report descriptor.
+ * - The report descriptor reports on caps/scroll/num lock key presses, but
+ *   doesn't have an LED output usage block.
+ *
+ * The replacement descriptor below fixes the number of consumer usages,
+ * and provides an LED output usage block. LED output events are redirected
+ * to the boot interface.
+ */
+
+static __u8 holtek_kbd_rdesc_fixed[] = {
+	/* Original report descriptor, with reduced number of consumer usages */
+	0x05, 0x01,         /*  Usage Page (Desktop),                         */
+	0x09, 0x80,         /*  Usage (Sys Control),                          */
+	0xA1, 0x01,         /*  Collection (Application),                     */
+	0x85, 0x01,         /*      Report ID (1),                            */
+	0x19, 0x81,         /*      Usage Minimum (Sys Power Down),           */
+	0x29, 0x83,         /*      Usage Maximum (Sys Wake Up),              */
+	0x15, 0x00,         /*      Logical Minimum (0),                      */
+	0x25, 0x01,         /*      Logical Maximum (1),                      */
+	0x95, 0x03,         /*      Report Count (3),                         */
+	0x75, 0x01,         /*      Report Size (1),                          */
+	0x81, 0x02,         /*      Input (Variable),                         */
+	0x95, 0x01,         /*      Report Count (1),                         */
+	0x75, 0x05,         /*      Report Size (5),                          */
+	0x81, 0x01,         /*      Input (Constant),                         */
+	0xC0,               /*  End Collection,                               */
+	0x05, 0x0C,         /*  Usage Page (Consumer),                        */
+	0x09, 0x01,         /*  Usage (Consumer Control),                     */
+	0xA1, 0x01,         /*  Collection (Application),                     */
+	0x85, 0x02,         /*      Report ID (2),                            */
+	0x19, 0x00,         /*      Usage Minimum (00h),                      */
+	0x2A, 0xFF, 0x2F,   /*      Usage Maximum (0x2FFF), previously 0x7FFF */
+	0x15, 0x00,         /*      Logical Minimum (0),                      */
+	0x26, 0xFF, 0x2F,   /*      Logical Maximum (0x2FFF),previously 0x7FFF*/
+	0x95, 0x01,         /*      Report Count (1),                         */
+	0x75, 0x10,         /*      Report Size (16),                         */
+	0x81, 0x00,         /*      Input,                                    */
+	0xC0,               /*  End Collection,                               */
+	0x05, 0x01,         /*  Usage Page (Desktop),                         */
+	0x09, 0x06,         /*  Usage (Keyboard),                             */
+	0xA1, 0x01,         /*  Collection (Application),                     */
+	0x85, 0x03,         /*      Report ID (3),                            */
+	0x95, 0x38,         /*      Report Count (56),                        */
+	0x75, 0x01,         /*      Report Size (1),                          */
+	0x15, 0x00,         /*      Logical Minimum (0),                      */
+	0x25, 0x01,         /*      Logical Maximum (1),                      */
+	0x05, 0x07,         /*      Usage Page (Keyboard),                    */
+	0x19, 0xE0,         /*      Usage Minimum (KB Leftcontrol),           */
+	0x29, 0xE7,         /*      Usage Maximum (KB Right GUI),             */
+	0x19, 0x00,         /*      Usage Minimum (None),                     */
+	0x29, 0x2F,         /*      Usage Maximum (KB Lboxbracket And Lbrace),*/
+	0x81, 0x02,         /*      Input (Variable),                         */
+	0xC0,               /*  End Collection,                               */
+	0x05, 0x01,         /*  Usage Page (Desktop),                         */
+	0x09, 0x06,         /*  Usage (Keyboard),                             */
+	0xA1, 0x01,         /*  Collection (Application),                     */
+	0x85, 0x04,         /*      Report ID (4),                            */
+	0x95, 0x38,         /*      Report Count (56),                        */
+	0x75, 0x01,         /*      Report Size (1),                          */
+	0x15, 0x00,         /*      Logical Minimum (0),                      */
+	0x25, 0x01,         /*      Logical Maximum (1),                      */
+	0x05, 0x07,         /*      Usage Page (Keyboard),                    */
+	0x19, 0x30,         /*      Usage Minimum (KB Rboxbracket And Rbrace),*/
+	0x29, 0x67,         /*      Usage Maximum (KP Equals),                */
+	0x81, 0x02,         /*      Input (Variable),                         */
+	0xC0,               /*  End Collection                                */
+
+	/* LED usage for the boot protocol interface */
+	0x05, 0x01,         /*  Usage Page (Desktop),                         */
+	0x09, 0x06,         /*  Usage (Keyboard),                             */
+	0xA1, 0x01,         /*  Collection (Application),                     */
+	0x05, 0x08,         /*      Usage Page (LED),                         */
+	0x19, 0x01,         /*      Usage Minimum (01h),                      */
+	0x29, 0x03,         /*      Usage Maximum (03h),                      */
+	0x15, 0x00,         /*      Logical Minimum (0),                      */
+	0x25, 0x01,         /*      Logical Maximum (1),                      */
+	0x75, 0x01,         /*      Report Size (1),                          */
+	0x95, 0x03,         /*      Report Count (3),                         */
+	0x91, 0x02,         /*      Output (Variable),                        */
+	0x95, 0x05,         /*      Report Count (5),                         */
+	0x91, 0x01,         /*      Output (Constant),                        */
+	0xC0,               /*  End Collection                                */
+};
+
+static __u8 *holtek_kbd_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+	if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
+		rdesc = holtek_kbd_rdesc_fixed;
+		*rsize = sizeof(holtek_kbd_rdesc_fixed);
+	}
+	return rdesc;
+}
+
+static int holtek_kbd_input_event(struct input_dev *dev, unsigned int type,
+		unsigned int code,
+		int value)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct usb_device *usb_dev = hid_to_usb_dev(hid);
+
+	/* Locate the boot interface, to receive the LED change events */
+	struct usb_interface *boot_interface = usb_ifnum_to_if(usb_dev, 0);
+
+	struct hid_device *boot_hid = usb_get_intfdata(boot_interface);
+	struct hid_input *boot_hid_input = list_first_entry(&boot_hid->inputs,
+		struct hid_input, list);
+
+	return boot_hid_input->input->event(boot_hid_input->input, type, code,
+			value);
+}
+
+static int holtek_kbd_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	int ret = hid_parse(hdev);
+
+	if (!ret)
+		ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+	if (!ret && intf->cur_altsetting->desc.bInterfaceNumber == 1) {
+		struct hid_input *hidinput;
+		list_for_each_entry(hidinput, &hdev->inputs, list) {
+			hidinput->input->event = holtek_kbd_input_event;
+		}
+	}
+
+	return ret;
+}
+
+static const struct hid_device_id holtek_kbd_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, holtek_kbd_devices);
+
+static struct hid_driver holtek_kbd_driver = {
+	.name = "holtek_kbd",
+	.id_table = holtek_kbd_devices,
+	.report_fixup = holtek_kbd_report_fixup,
+	.probe = holtek_kbd_probe
+};
+
+static int __init holtek_kbd_init(void)
+{
+	return hid_register_driver(&holtek_kbd_driver);
+}
+
+static void __exit holtek_kbd_exit(void)
+{
+	hid_unregister_driver(&holtek_kbd_driver);
+}
+
+module_exit(holtek_kbd_exit);
+module_init(holtek_kbd_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 3203923..41c34f2 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -208,6 +208,7 @@
 #define USB_DEVICE_ID_CHICONY_MULTI_TOUCH	0xb19d
 #define USB_DEVICE_ID_CHICONY_WIRELESS	0x0618
 #define USB_DEVICE_ID_CHICONY_WIRELESS2	0x1123
+#define USB_DEVICE_ID_CHICONY_AK1D	0x1125
 
 #define USB_VENDOR_ID_CHUNGHWAT		0x2247
 #define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH	0x0001
@@ -237,6 +238,7 @@
 #define USB_DEVICE_ID_CYPRESS_BARCODE_1	0xde61
 #define USB_DEVICE_ID_CYPRESS_BARCODE_2	0xde64
 #define USB_DEVICE_ID_CYPRESS_BARCODE_3	0xbca1
+#define USB_DEVICE_ID_CYPRESS_BARCODE_4	0xed81
 #define USB_DEVICE_ID_CYPRESS_TRUETOUCH	0xc001
 
 #define USB_VENDOR_ID_DEALEXTREAME	0x10c5
@@ -410,6 +412,9 @@
 #define USB_VENDOR_ID_HOLTEK		0x1241
 #define USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP	0x5015
 
+#define USB_VENDOR_ID_HOLTEK_ALT		0x04d9
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD	0xa055
+
 #define USB_VENDOR_ID_IMATION		0x0718
 #define USB_DEVICE_ID_DISC_STAKKA	0xd000
 
@@ -479,6 +484,9 @@
 #define USB_DEVICE_ID_LD_HYBRID		0x2090
 #define USB_DEVICE_ID_LD_HEATCONTROL	0x20A0
 
+#define USB_VENDOR_ID_LENOVO		0x17ef
+#define USB_DEVICE_ID_LENOVO_TPKBD	0x6009
+
 #define USB_VENDOR_ID_LG		0x1fd2
 #define USB_DEVICE_ID_LG_MULTITOUCH	0x0064
 
@@ -573,6 +581,9 @@
 #define USB_VENDOR_ID_NINTENDO		0x057e
 #define USB_DEVICE_ID_NINTENDO_WIIMOTE	0x0306
 
+#define USB_VENDOR_ID_NOVATEK		0x0603
+#define USB_DEVICE_ID_NOVATEK_PCT	0x0600
+
 #define USB_VENDOR_ID_NTRIG		0x1b96
 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN   0x0001
 #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1   0x0003
@@ -650,6 +661,7 @@
 #define USB_DEVICE_ID_ROCCAT_KOVAPLUS	0x2d50
 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRED	0x2c24
 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS	0x2cf6
+#define USB_DEVICE_ID_ROCCAT_SAVU	0x2d5a
 
 #define USB_VENDOR_ID_SAITEK		0x06a3
 #define USB_DEVICE_ID_SAITEK_RUMBLEPAD	0xff17
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 5301006..811bfad 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -837,6 +837,15 @@
 		}
 		break;
 
+	case HID_UP_HPVENDOR2:
+		set_bit(EV_REP, input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x003: map_key_clear(KEY_BRIGHTNESSDOWN);	break;
+		case 0x004: map_key_clear(KEY_BRIGHTNESSUP);	break;
+		default:    goto ignore;
+		}
+		break;
+
 	case HID_UP_MSVENDOR:
 		goto ignore;
 
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
new file mode 100644
index 0000000..77d2df0
--- /dev/null
+++ b/drivers/hid/hid-lenovo-tpkbd.c
@@ -0,0 +1,564 @@
+/*
+ *  HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
+ *
+ *  Copyright (c) 2012 Bernhard Seibold
+ */
+
+/*
+ * 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/sysfs.h>
+#include <linux/device.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include "usbhid/usbhid.h"
+
+#include "hid-ids.h"
+
+/* This is only used for the trackpoint part of the driver, hence _tp */
+struct tpkbd_data_pointer {
+	int led_state;
+	struct led_classdev led_mute;
+	struct led_classdev led_micmute;
+	int press_to_select;
+	int dragging;
+	int release_to_select;
+	int select_right;
+	int sensitivity;
+	int press_speed;
+};
+
+#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static int tpkbd_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	struct usbhid_device *uhdev;
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+	if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) {
+		map_key_clear(KEY_MICMUTE);
+		return 1;
+	}
+	return 0;
+}
+
+#undef map_key_clear
+
+static int tpkbd_features_set(struct hid_device *hdev)
+{
+	struct hid_report *report;
+	struct tpkbd_data_pointer *data_pointer;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+	report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
+
+	report->field[0]->value[0]  = data_pointer->press_to_select   ? 0x01 : 0x02;
+	report->field[0]->value[0] |= data_pointer->dragging          ? 0x04 : 0x08;
+	report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
+	report->field[0]->value[0] |= data_pointer->select_right      ? 0x80 : 0x40;
+	report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
+	report->field[2]->value[0] = data_pointer->sensitivity;
+	report->field[3]->value[0] = data_pointer->press_speed;
+
+	usbhid_submit_report(hdev, report, USB_DIR_OUT);
+	return 0;
+}
+
+static ssize_t pointer_press_to_select_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
+}
+
+static ssize_t pointer_press_to_select_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->press_to_select = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_dragging_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
+}
+
+static ssize_t pointer_dragging_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->dragging = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_release_to_select_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
+}
+
+static ssize_t pointer_release_to_select_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->release_to_select = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_select_right_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
+}
+
+static ssize_t pointer_select_right_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->select_right = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_sensitivity_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->sensitivity);
+}
+
+static ssize_t pointer_sensitivity_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->sensitivity = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_press_speed_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->press_speed);
+}
+
+static ssize_t pointer_press_speed_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->press_speed = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static struct device_attribute dev_attr_pointer_press_to_select =
+	__ATTR(press_to_select, S_IWUSR | S_IRUGO,
+			pointer_press_to_select_show,
+			pointer_press_to_select_store);
+
+static struct device_attribute dev_attr_pointer_dragging =
+	__ATTR(dragging, S_IWUSR | S_IRUGO,
+			pointer_dragging_show,
+			pointer_dragging_store);
+
+static struct device_attribute dev_attr_pointer_release_to_select =
+	__ATTR(release_to_select, S_IWUSR | S_IRUGO,
+			pointer_release_to_select_show,
+			pointer_release_to_select_store);
+
+static struct device_attribute dev_attr_pointer_select_right =
+	__ATTR(select_right, S_IWUSR | S_IRUGO,
+			pointer_select_right_show,
+			pointer_select_right_store);
+
+static struct device_attribute dev_attr_pointer_sensitivity =
+	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
+			pointer_sensitivity_show,
+			pointer_sensitivity_store);
+
+static struct device_attribute dev_attr_pointer_press_speed =
+	__ATTR(press_speed, S_IWUSR | S_IRUGO,
+			pointer_press_speed_show,
+			pointer_press_speed_store);
+
+static struct attribute *tpkbd_attributes_pointer[] = {
+	&dev_attr_pointer_press_to_select.attr,
+	&dev_attr_pointer_dragging.attr,
+	&dev_attr_pointer_release_to_select.attr,
+	&dev_attr_pointer_select_right.attr,
+	&dev_attr_pointer_sensitivity.attr,
+	&dev_attr_pointer_press_speed.attr,
+	NULL
+};
+
+static const struct attribute_group tpkbd_attr_group_pointer = {
+	.attrs = tpkbd_attributes_pointer,
+};
+
+static enum led_brightness tpkbd_led_brightness_get(
+			struct led_classdev *led_cdev)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int led_nr = 0;
+
+	dev = led_cdev->dev->parent;
+	hdev = container_of(dev, struct hid_device, dev);
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (led_cdev == &data_pointer->led_micmute)
+		led_nr = 1;
+
+	return data_pointer->led_state & (1 << led_nr)
+				? LED_FULL
+				: LED_OFF;
+}
+
+static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct hid_report *report;
+	struct tpkbd_data_pointer *data_pointer;
+	int led_nr = 0;
+
+	dev = led_cdev->dev->parent;
+	hdev = container_of(dev, struct hid_device, dev);
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (led_cdev == &data_pointer->led_micmute)
+		led_nr = 1;
+
+	if (value == LED_OFF)
+		data_pointer->led_state &= ~(1 << led_nr);
+	else
+		data_pointer->led_state |= 1 << led_nr;
+
+	report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
+	report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
+	report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
+	usbhid_submit_report(hdev, report, USB_DIR_OUT);
+}
+
+static int tpkbd_probe_tp(struct hid_device *hdev)
+{
+	struct device *dev = &hdev->dev;
+	struct tpkbd_data_pointer *data_pointer;
+	size_t name_sz = strlen(dev_name(dev)) + 16;
+	char *name_mute, *name_micmute;
+	int ret;
+
+	if (sysfs_create_group(&hdev->dev.kobj,
+				&tpkbd_attr_group_pointer)) {
+		hid_warn(hdev, "Could not create sysfs group\n");
+	}
+
+	data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL);
+	if (data_pointer == NULL) {
+		hid_err(hdev, "Could not allocate memory for driver data\n");
+		return -ENOMEM;
+	}
+
+	// set same default values as windows driver
+	data_pointer->sensitivity = 0xa0;
+	data_pointer->press_speed = 0x38;
+
+	name_mute = kzalloc(name_sz, GFP_KERNEL);
+	if (name_mute == NULL) {
+		hid_err(hdev, "Could not allocate memory for led data\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
+
+	name_micmute = kzalloc(name_sz, GFP_KERNEL);
+	if (name_micmute == NULL) {
+		hid_err(hdev, "Could not allocate memory for led data\n");
+		ret = -ENOMEM;
+		goto err2;
+	}
+	snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
+
+	hid_set_drvdata(hdev, data_pointer);
+
+	data_pointer->led_mute.name = name_mute;
+	data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get;
+	data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set;
+	data_pointer->led_mute.dev = dev;
+	led_classdev_register(dev, &data_pointer->led_mute);
+
+	data_pointer->led_micmute.name = name_micmute;
+	data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get;
+	data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set;
+	data_pointer->led_micmute.dev = dev;
+	led_classdev_register(dev, &data_pointer->led_micmute);
+
+	tpkbd_features_set(hdev);
+
+	return 0;
+
+err2:
+	kfree(name_mute);
+err:
+	kfree(data_pointer);
+	return ret;
+}
+
+static int tpkbd_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret;
+	struct usbhid_device *uhdev;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid_parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hid_hw_start failed\n");
+		goto err_free;
+	}
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+
+	if (uhdev->ifnum == 1)
+		return tpkbd_probe_tp(hdev);
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static void tpkbd_remove_tp(struct hid_device *hdev)
+{
+	struct tpkbd_data_pointer *data_pointer;
+
+	sysfs_remove_group(&hdev->dev.kobj,
+			&tpkbd_attr_group_pointer);
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	led_classdev_unregister(&data_pointer->led_micmute);
+	led_classdev_unregister(&data_pointer->led_mute);
+
+	hid_set_drvdata(hdev, NULL);
+	kfree(data_pointer);
+}
+
+static void tpkbd_remove(struct hid_device *hdev)
+{
+	struct usbhid_device *uhdev;
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+	if (uhdev->ifnum == 1)
+		tpkbd_remove_tp(hdev);
+
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id tpkbd_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, tpkbd_devices);
+
+static struct hid_driver tpkbd_driver = {
+	.name = "lenovo_tpkbd",
+	.id_table = tpkbd_devices,
+	.input_mapping = tpkbd_input_mapping,
+	.probe = tpkbd_probe,
+	.remove = tpkbd_remove,
+};
+
+static int __init tpkbd_init(void)
+{
+	return hid_register_driver(&tpkbd_driver);
+}
+
+static void __exit tpkbd_exit(void)
+{
+	hid_unregister_driver(&tpkbd_driver);
+}
+
+module_init(tpkbd_init);
+module_exit(tpkbd_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 40ac665..7364726 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -16,6 +16,7 @@
 
 #include <linux/device.h>
 #include <linux/hid.h>
+#include <linux/input/mt.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/usb.h>
@@ -48,10 +49,6 @@
 module_param(scroll_acceleration, bool, 0644);
 MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events");
 
-static bool report_touches = true;
-module_param(report_touches, bool, 0644);
-MODULE_PARM_DESC(report_touches, "Emit touch records (otherwise, only use them for emulation)");
-
 static bool report_undeciphered;
 module_param(report_undeciphered, bool, 0644);
 MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state field using a MSC_RAW event");
@@ -72,15 +69,6 @@
 
 #define SCROLL_ACCEL_DEFAULT 7
 
-/* Single touch emulation should only begin when no touches are currently down.
- * This is true when single_touch_id is equal to NO_TOUCHES. If multiple touches
- * are down and the touch providing for single touch emulation is lifted,
- * single_touch_id is equal to SINGLE_TOUCH_UP. While single touch emulation is
- * occurring, single_touch_id corresponds with the tracking id of the touch used.
- */
-#define NO_TOUCHES -1
-#define SINGLE_TOUCH_UP -2
-
 /* Touch surface information. Dimension is in hundredths of a mm, min and max
  * are in units. */
 #define MOUSE_DIMENSION_X (float)9056
@@ -129,7 +117,6 @@
 		u8 size;
 	} touches[16];
 	int tracking_ids[16];
-	int single_touch_id;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -268,16 +255,14 @@
 		}
 	}
 
-	if (down) {
+	if (down)
 		msc->ntouches++;
-		if (msc->single_touch_id == NO_TOUCHES)
-			msc->single_touch_id = id;
-	} else if (msc->single_touch_id == id)
-		msc->single_touch_id = SINGLE_TOUCH_UP;
+
+	input_mt_slot(input, id);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER, down);
 
 	/* Generate the input events for this touch. */
-	if (report_touches && down) {
-		input_report_abs(input, ABS_MT_TRACKING_ID, id);
+	if (down) {
 		input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major << 2);
 		input_report_abs(input, ABS_MT_TOUCH_MINOR, touch_minor << 2);
 		input_report_abs(input, ABS_MT_ORIENTATION, -orientation);
@@ -290,8 +275,6 @@
 			else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 				input_event(input, EV_MSC, MSC_RAW, tdata[8]);
 		}
-
-		input_mt_sync(input);
 	}
 }
 
@@ -312,12 +295,6 @@
 		for (ii = 0; ii < npoints; ii++)
 			magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
 
-		/* We don't need an MT sync here because trackpad emits a
-		 * BTN_TOUCH event in a new frame when all touches are released.
-		 */
-		if (msc->ntouches == 0)
-			msc->single_touch_id = NO_TOUCHES;
-
 		clicks = data[1];
 
 		/* The following bits provide a device specific timestamp. They
@@ -335,9 +312,6 @@
 		for (ii = 0; ii < npoints; ii++)
 			magicmouse_emit_touch(msc, ii, data + ii * 8 + 6);
 
-		if (report_touches && msc->ntouches == 0)
-			input_mt_sync(input);
-
 		/* When emulating three-button mode, it is important
 		 * to have the current touch information before
 		 * generating a click event.
@@ -370,25 +344,17 @@
 		input_report_rel(input, REL_Y, y);
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		input_report_key(input, BTN_MOUSE, clicks & 1);
-		input_report_key(input, BTN_TOUCH, msc->ntouches > 0);
-		input_report_key(input, BTN_TOOL_FINGER, msc->ntouches == 1);
-		input_report_key(input, BTN_TOOL_DOUBLETAP, msc->ntouches == 2);
-		input_report_key(input, BTN_TOOL_TRIPLETAP, msc->ntouches == 3);
-		input_report_key(input, BTN_TOOL_QUADTAP, msc->ntouches == 4);
-		if (msc->single_touch_id >= 0) {
-			input_report_abs(input, ABS_X,
-				msc->touches[msc->single_touch_id].x);
-			input_report_abs(input, ABS_Y,
-				msc->touches[msc->single_touch_id].y);
-		}
+		input_mt_report_pointer_emulation(input, true);
 	}
 
 	input_sync(input);
 	return 1;
 }
 
-static void magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
+static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
 {
+	int error;
+
 	__set_bit(EV_KEY, input->evbit);
 
 	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
@@ -417,62 +383,66 @@
 		__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
 		__set_bit(BTN_TOOL_TRIPLETAP, input->keybit);
 		__set_bit(BTN_TOOL_QUADTAP, input->keybit);
+		__set_bit(BTN_TOOL_QUINTTAP, input->keybit);
 		__set_bit(BTN_TOUCH, input->keybit);
 		__set_bit(INPUT_PROP_POINTER, input->propbit);
 		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
 	}
 
-	if (report_touches) {
-		__set_bit(EV_ABS, input->evbit);
 
-		input_set_abs_params(input, ABS_MT_TRACKING_ID, 0, 15, 0, 0);
-		input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
-				     4, 0);
-		input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
-				     4, 0);
-		input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
+	__set_bit(EV_ABS, input->evbit);
 
-		/* Note: Touch Y position from the device is inverted relative
-		 * to how pointer motion is reported (and relative to how USB
-		 * HID recommends the coordinates work).  This driver keeps
-		 * the origin at the same position, and just uses the additive
-		 * inverse of the reported Y.
-		 */
-		if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
-			input_set_abs_params(input, ABS_MT_POSITION_X,
-				MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
-			input_set_abs_params(input, ABS_MT_POSITION_Y,
-				MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0);
+	error = input_mt_init_slots(input, 16);
+	if (error)
+		return error;
+	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
+			     4, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
+			     4, 0);
+	input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
 
-			input_abs_set_res(input, ABS_MT_POSITION_X,
-				MOUSE_RES_X);
-			input_abs_set_res(input, ABS_MT_POSITION_Y,
-				MOUSE_RES_Y);
-		} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
-			input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
-				TRACKPAD_MAX_X, 4, 0);
-			input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
-				TRACKPAD_MAX_Y, 4, 0);
-			input_set_abs_params(input, ABS_MT_POSITION_X,
-				TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0);
-			input_set_abs_params(input, ABS_MT_POSITION_Y,
-				TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0);
+	/* Note: Touch Y position from the device is inverted relative
+	 * to how pointer motion is reported (and relative to how USB
+	 * HID recommends the coordinates work).  This driver keeps
+	 * the origin at the same position, and just uses the additive
+	 * inverse of the reported Y.
+	 */
+	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+		input_set_abs_params(input, ABS_MT_POSITION_X,
+				     MOUSE_MIN_X, MOUSE_MAX_X, 4, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_Y,
+				     MOUSE_MIN_Y, MOUSE_MAX_Y, 4, 0);
 
-			input_abs_set_res(input, ABS_X, TRACKPAD_RES_X);
-			input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y);
-			input_abs_set_res(input, ABS_MT_POSITION_X,
-				TRACKPAD_RES_X);
-			input_abs_set_res(input, ABS_MT_POSITION_Y,
-				TRACKPAD_RES_Y);
-		}
+		input_abs_set_res(input, ABS_MT_POSITION_X,
+				  MOUSE_RES_X);
+		input_abs_set_res(input, ABS_MT_POSITION_Y,
+				  MOUSE_RES_Y);
+	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		input_set_abs_params(input, ABS_X, TRACKPAD_MIN_X,
+				     TRACKPAD_MAX_X, 4, 0);
+		input_set_abs_params(input, ABS_Y, TRACKPAD_MIN_Y,
+				     TRACKPAD_MAX_Y, 4, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_X,
+				     TRACKPAD_MIN_X, TRACKPAD_MAX_X, 4, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_Y,
+				     TRACKPAD_MIN_Y, TRACKPAD_MAX_Y, 4, 0);
 
-		input_set_events_per_packet(input, 60);
+		input_abs_set_res(input, ABS_X, TRACKPAD_RES_X);
+		input_abs_set_res(input, ABS_Y, TRACKPAD_RES_Y);
+		input_abs_set_res(input, ABS_MT_POSITION_X,
+				  TRACKPAD_RES_X);
+		input_abs_set_res(input, ABS_MT_POSITION_Y,
+				  TRACKPAD_RES_Y);
 	}
 
+	input_set_events_per_packet(input, 60);
+
 	if (report_undeciphered) {
 		__set_bit(EV_MSC, input->evbit);
 		__set_bit(MSC_RAW, input->mscbit);
 	}
+
+	return 0;
 }
 
 static int magicmouse_input_mapping(struct hid_device *hdev,
@@ -511,8 +481,6 @@
 	msc->quirks = id->driver_data;
 	hid_set_drvdata(hdev, msc);
 
-	msc->single_touch_id = NO_TOUCHES;
-
 	ret = hid_parse(hdev);
 	if (ret) {
 		hid_err(hdev, "magicmouse hid parse failed\n");
@@ -528,8 +496,13 @@
 	/* We do this after hid-input is done parsing reports so that
 	 * hid-input uses the most natural button and axis IDs.
 	 */
-	if (msc->input)
-		magicmouse_setup_input(msc->input, hdev);
+	if (msc->input) {
+		ret = magicmouse_setup_input(msc->input, hdev);
+		if (ret) {
+			hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
+			goto err_stop_hw;
+		}
+	}
 
 	if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 7647924..59c8b5c 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -83,6 +83,7 @@
 	unsigned last_field_index;	/* last field index of the report */
 	unsigned last_slot_field;	/* the last field of a slot */
 	__s8 inputmode;		/* InputMode HID feature, -1 if non-existent */
+	__s8 inputmode_index;	/* InputMode HID feature index in the report */
 	__s8 maxcontact_report_id;	/* Maximum Contact Number HID feature,
 				   -1 if non-existent */
 	__u8 num_received;	/* how many contacts we received */
@@ -260,10 +261,20 @@
 		struct hid_field *field, struct hid_usage *usage)
 {
 	struct mt_device *td = hid_get_drvdata(hdev);
+	int i;
 
 	switch (usage->hid) {
 	case HID_DG_INPUTMODE:
 		td->inputmode = field->report->id;
+		td->inputmode_index = 0; /* has to be updated below */
+
+		for (i=0; i < field->maxusage; i++) {
+			if (field->usage[i].hid == usage->hid) {
+				td->inputmode_index = i;
+				break;
+			}
+		}
+
 		break;
 	case HID_DG_CONTACTMAX:
 		td->maxcontact_report_id = field->report->id;
@@ -618,7 +629,7 @@
 	re = &(hdev->report_enum[HID_FEATURE_REPORT]);
 	r = re->report_id_hash[td->inputmode];
 	if (r) {
-		r->field[0]->value[0] = 0x02;
+		r->field[0]->value[td->inputmode_index] = 0x02;
 		usbhid_submit_report(hdev, r, USB_DIR_OUT);
 	}
 }
@@ -951,6 +962,11 @@
 		MT_USB_DEVICE(USB_VENDOR_ID_PANASONIC,
 			USB_DEVICE_ID_PANABOARD_UBT880) },
 
+	/* Novatek Panel */
+	{ .driver_data = MT_CLS_DEFAULT,
+		MT_USB_DEVICE(USB_VENDOR_ID_NOVATEK,
+			USB_DEVICE_ID_NOVATEK_PCT) },
+
 	/* PenMount panels */
 	{ .driver_data = MT_CLS_CONFIDENCE,
 		MT_USB_DEVICE(USB_VENDOR_ID_PENMOUNT,
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index 45c3433..27c8ebd 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -1846,7 +1846,7 @@
 #define BUFF_SZ 256
 
 	/* Avoid unnecessary overhead if debugfs is disabled */
-	if (!hdev->debug_events)
+	if (list_empty(&hdev->debug_list))
 		return;
 
 	buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
@@ -2613,11 +2613,7 @@
 		goto err_cleanup_data;
 	}
 
-	/* We don't use hidinput but hid_hw_start() fails if nothing is
-	 * claimed. So spoof claimed input. */
-	hdev->claimed = HID_CLAIMED_INPUT;
 	error = hid_hw_start(hdev, 0);
-	hdev->claimed = 0;
 	if (error) {
 		hid_err(hdev, "hardware start failed\n");
 		goto err_cleanup_data;
diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c
index 093bfad..327f9b8 100644
--- a/drivers/hid/hid-roccat-arvo.c
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -39,7 +39,7 @@
 	int retval;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
 			&temp_buf, sizeof(struct arvo_mode_key));
 	mutex_unlock(&arvo->arvo_lock);
 	if (retval)
@@ -67,7 +67,7 @@
 	temp_buf.state = state;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_send(usb_dev, ARVO_COMMAND_MODE_KEY,
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_MODE_KEY,
 			&temp_buf, sizeof(struct arvo_mode_key));
 	mutex_unlock(&arvo->arvo_lock);
 	if (retval)
@@ -87,7 +87,7 @@
 	int retval;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
 			&temp_buf, sizeof(struct arvo_key_mask));
 	mutex_unlock(&arvo->arvo_lock);
 	if (retval)
@@ -115,7 +115,7 @@
 	temp_buf.key_mask = key_mask;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_send(usb_dev, ARVO_COMMAND_KEY_MASK,
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_KEY_MASK,
 			&temp_buf, sizeof(struct arvo_key_mask));
 	mutex_unlock(&arvo->arvo_lock);
 	if (retval)
@@ -130,7 +130,7 @@
 	struct arvo_actual_profile temp_buf;
 	int retval;
 
-	retval = roccat_common_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
 			&temp_buf, sizeof(struct arvo_actual_profile));
 
 	if (retval)
@@ -170,7 +170,7 @@
 	temp_buf.actual_profile = profile;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
 			&temp_buf, sizeof(struct arvo_actual_profile));
 	if (!retval) {
 		arvo->actual_profile = profile;
@@ -194,7 +194,7 @@
 		return -EINVAL;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_send(usb_dev, command, buf, real_size);
+	retval = roccat_common2_send(usb_dev, command, buf, real_size);
 	mutex_unlock(&arvo->arvo_lock);
 
 	return (retval ? retval : real_size);
@@ -217,7 +217,7 @@
 		return -EINVAL;
 
 	mutex_lock(&arvo->arvo_lock);
-	retval = roccat_common_receive(usb_dev, command, buf, real_size);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
 	mutex_unlock(&arvo->arvo_lock);
 
 	return (retval ? retval : real_size);
diff --git a/drivers/hid/hid-roccat-common.c b/drivers/hid/hid-roccat-common.c
index a6d9399..74f7040 100644
--- a/drivers/hid/hid-roccat-common.c
+++ b/drivers/hid/hid-roccat-common.c
@@ -16,12 +16,12 @@
 #include <linux/module.h>
 #include "hid-roccat-common.h"
 
-static inline uint16_t roccat_common_feature_report(uint8_t report_id)
+static inline uint16_t roccat_common2_feature_report(uint8_t report_id)
 {
 	return 0x300 | report_id;
 }
 
-int roccat_common_receive(struct usb_device *usb_dev, uint report_id,
+int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
 		void *data, uint size)
 {
 	char *buf;
@@ -34,16 +34,16 @@
 	len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
 			HID_REQ_GET_REPORT,
 			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
-			roccat_common_feature_report(report_id),
+			roccat_common2_feature_report(report_id),
 			0, buf, size, USB_CTRL_SET_TIMEOUT);
 
 	memcpy(data, buf, size);
 	kfree(buf);
 	return ((len < 0) ? len : ((len != size) ? -EIO : 0));
 }
-EXPORT_SYMBOL_GPL(roccat_common_receive);
+EXPORT_SYMBOL_GPL(roccat_common2_receive);
 
-int roccat_common_send(struct usb_device *usb_dev, uint report_id,
+int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
 		void const *data, uint size)
 {
 	char *buf;
@@ -56,13 +56,71 @@
 	len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
 			HID_REQ_SET_REPORT,
 			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
-			roccat_common_feature_report(report_id),
+			roccat_common2_feature_report(report_id),
 			0, buf, size, USB_CTRL_SET_TIMEOUT);
 
 	kfree(buf);
 	return ((len < 0) ? len : ((len != size) ? -EIO : 0));
 }
-EXPORT_SYMBOL_GPL(roccat_common_send);
+EXPORT_SYMBOL_GPL(roccat_common2_send);
+
+enum roccat_common2_control_states {
+	ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD = 0,
+	ROCCAT_COMMON_CONTROL_STATUS_OK = 1,
+	ROCCAT_COMMON_CONTROL_STATUS_INVALID = 2,
+	ROCCAT_COMMON_CONTROL_STATUS_WAIT = 3,
+};
+
+static int roccat_common2_receive_control_status(struct usb_device *usb_dev)
+{
+	int retval;
+	struct roccat_common2_control control;
+
+	do {
+		msleep(50);
+		retval = roccat_common2_receive(usb_dev,
+				ROCCAT_COMMON_COMMAND_CONTROL,
+				&control, sizeof(struct roccat_common2_control));
+
+		if (retval)
+			return retval;
+
+		switch (control.value) {
+		case ROCCAT_COMMON_CONTROL_STATUS_OK:
+			return 0;
+		case ROCCAT_COMMON_CONTROL_STATUS_WAIT:
+			msleep(500);
+			continue;
+		case ROCCAT_COMMON_CONTROL_STATUS_INVALID:
+
+		case ROCCAT_COMMON_CONTROL_STATUS_OVERLOAD:
+			/* seems to be critical - replug necessary */
+			return -EINVAL;
+		default:
+			dev_err(&usb_dev->dev,
+					"roccat_common2_receive_control_status: "
+					"unknown response value 0x%x\n",
+					control.value);
+			return -EINVAL;
+		}
+
+	} while (1);
+}
+
+int roccat_common2_send_with_status(struct usb_device *usb_dev,
+		uint command, void const *buf, uint size)
+{
+	int retval;
+
+	retval = roccat_common2_send(usb_dev, command, buf, size);
+	if (retval)
+		return retval;
+
+	msleep(100);
+
+	return roccat_common2_receive_control_status(usb_dev);
+}
+EXPORT_SYMBOL_GPL(roccat_common2_send_with_status);
 
 MODULE_AUTHOR("Stefan Achatz");
 MODULE_DESCRIPTION("USB Roccat common driver");
diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h
index 9a5bc61..a97746a 100644
--- a/drivers/hid/hid-roccat-common.h
+++ b/drivers/hid/hid-roccat-common.h
@@ -15,9 +15,21 @@
 #include <linux/usb.h>
 #include <linux/types.h>
 
-int roccat_common_receive(struct usb_device *usb_dev, uint report_id,
+enum roccat_common2_commands {
+	ROCCAT_COMMON_COMMAND_CONTROL = 0x4,
+};
+
+struct roccat_common2_control {
+	uint8_t command;
+	uint8_t value;
+	uint8_t request; /* always 0 on requesting write check */
+} __packed;
+
+int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
 		void *data, uint size);
-int roccat_common_send(struct usb_device *usb_dev, uint report_id,
+int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
 		void const *data, uint size);
+int roccat_common2_send_with_status(struct usb_device *usb_dev,
+		uint command, void const *buf, uint size);
 
 #endif
diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c
index 0e4a0ab..5669916 100644
--- a/drivers/hid/hid-roccat-isku.c
+++ b/drivers/hid/hid-roccat-isku.c
@@ -36,51 +36,7 @@
 static int isku_receive(struct usb_device *usb_dev, uint command,
 		void *buf, uint size)
 {
-	return roccat_common_receive(usb_dev, command, buf, size);
-}
-
-static int isku_receive_control_status(struct usb_device *usb_dev)
-{
-	int retval;
-	struct isku_control control;
-
-	do {
-		msleep(50);
-		retval = isku_receive(usb_dev, ISKU_COMMAND_CONTROL,
-				&control, sizeof(struct isku_control));
-
-		if (retval)
-			return retval;
-
-		switch (control.value) {
-		case ISKU_CONTROL_VALUE_STATUS_OK:
-			return 0;
-		case ISKU_CONTROL_VALUE_STATUS_WAIT:
-			continue;
-		case ISKU_CONTROL_VALUE_STATUS_INVALID:
-		/* seems to be critical - replug necessary */
-		case ISKU_CONTROL_VALUE_STATUS_OVERLOAD:
-			return -EINVAL;
-		default:
-			hid_err(usb_dev, "isku_receive_control_status: "
-					"unknown response value 0x%x\n",
-					control.value);
-			return -EINVAL;
-		}
-
-	} while (1);
-}
-
-static int isku_send(struct usb_device *usb_dev, uint command,
-		void const *buf, uint size)
-{
-	int retval;
-
-	retval = roccat_common_send(usb_dev, command, buf, size);
-	if (retval)
-		return retval;
-
-	return isku_receive_control_status(usb_dev);
+	return roccat_common2_receive(usb_dev, command, buf, size);
 }
 
 static int isku_get_actual_profile(struct usb_device *usb_dev)
@@ -100,7 +56,8 @@
 	buf.command = ISKU_COMMAND_ACTUAL_PROFILE;
 	buf.size = sizeof(struct isku_actual_profile);
 	buf.actual_profile = new_profile;
-	return isku_send(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE, &buf,
+	return roccat_common2_send_with_status(usb_dev,
+			ISKU_COMMAND_ACTUAL_PROFILE, &buf,
 			sizeof(struct isku_actual_profile));
 }
 
@@ -197,7 +154,8 @@
 		return -EINVAL;
 
 	mutex_lock(&isku->isku_lock);
-	retval = isku_send(usb_dev, command, (void *)buf, real_size);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			(void *)buf, real_size);
 	mutex_unlock(&isku->isku_lock);
 
 	return retval ? retval : real_size;
diff --git a/drivers/hid/hid-roccat-isku.h b/drivers/hid/hid-roccat-isku.h
index 075f6ef..605b3ce 100644
--- a/drivers/hid/hid-roccat-isku.h
+++ b/drivers/hid/hid-roccat-isku.h
@@ -25,13 +25,6 @@
 	uint8_t request;
 } __packed;
 
-enum isku_control_values {
-	ISKU_CONTROL_VALUE_STATUS_OVERLOAD = 0,
-	ISKU_CONTROL_VALUE_STATUS_OK = 1,
-	ISKU_CONTROL_VALUE_STATUS_INVALID = 2,
-	ISKU_CONTROL_VALUE_STATUS_WAIT = 3,
-};
-
 struct isku_actual_profile {
 	uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */
 	uint8_t size; /* always 3 */
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
index 40090d6..9ce2d0b 100644
--- a/drivers/hid/hid-roccat-kone.c
+++ b/drivers/hid/hid-roccat-kone.c
@@ -138,7 +138,7 @@
 		return 0;
 
 	/* unknown answer */
-	hid_err(usb_dev, "got retval %d when checking write\n", data);
+	dev_err(&usb_dev->dev, "got retval %d when checking write\n", data);
 	return -EIO;
 }
 
@@ -503,7 +503,7 @@
 
 		retval = kone_set_settings(usb_dev, &kone->settings);
 		if (retval) {
-			hid_err(usb_dev, "couldn't set tcu state\n");
+			dev_err(&usb_dev->dev, "couldn't set tcu state\n");
 			/*
 			 * try to reread valid settings into buffer overwriting
 			 * first error code
@@ -519,7 +519,7 @@
 
 	retval = size;
 exit_no_settings:
-	hid_err(usb_dev, "couldn't read settings\n");
+	dev_err(&usb_dev->dev, "couldn't read settings\n");
 exit_unlock:
 	mutex_unlock(&kone->kone_lock);
 	return retval;
diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c
index 59e4777..f5602fe 100644
--- a/drivers/hid/hid-roccat-koneplus.c
+++ b/drivers/hid/hid-roccat-koneplus.c
@@ -39,88 +39,26 @@
 static int koneplus_send_control(struct usb_device *usb_dev, uint value,
 		enum koneplus_control_requests request)
 {
-	struct koneplus_control control;
+	struct roccat_common2_control control;
 
 	if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
 			request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
 			value > 4)
 		return -EINVAL;
 
-	control.command = KONEPLUS_COMMAND_CONTROL;
+	control.command = ROCCAT_COMMON_COMMAND_CONTROL;
 	control.value = value;
 	control.request = request;
 
-	return roccat_common_send(usb_dev, KONEPLUS_COMMAND_CONTROL,
-			&control, sizeof(struct koneplus_control));
-}
-
-static int koneplus_receive_control_status(struct usb_device *usb_dev)
-{
-	int retval;
-	struct koneplus_control control;
-
-	do {
-		retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_CONTROL,
-				&control, sizeof(struct koneplus_control));
-
-		/* check if we get a completely wrong answer */
-		if (retval)
-			return retval;
-
-		if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OK)
-			return 0;
-
-		/* indicates that hardware needs some more time to complete action */
-		if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) {
-			msleep(500); /* windows driver uses 1000 */
-			continue;
-		}
-
-		/* seems to be critical - replug necessary */
-		if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD)
-			return -EINVAL;
-
-		hid_err(usb_dev, "koneplus_receive_control_status: "
-				"unknown response value 0x%x\n", control.value);
-		return -EINVAL;
-	} while (1);
-}
-
-static int koneplus_send(struct usb_device *usb_dev, uint command,
-		void const *buf, uint size)
-{
-	int retval;
-
-	retval = roccat_common_send(usb_dev, command, buf, size);
-	if (retval)
-		return retval;
-
-	return koneplus_receive_control_status(usb_dev);
-}
-
-static int koneplus_select_profile(struct usb_device *usb_dev, uint number,
-		enum koneplus_control_requests request)
-{
-	int retval;
-
-	retval = koneplus_send_control(usb_dev, number, request);
-	if (retval)
-		return retval;
-
-	/* allow time to settle things - windows driver uses 500 */
-	msleep(100);
-
-	retval = koneplus_receive_control_status(usb_dev);
-	if (retval)
-		return retval;
-
-	return 0;
+	return roccat_common2_send_with_status(usb_dev,
+			ROCCAT_COMMON_COMMAND_CONTROL,
+			&control, sizeof(struct roccat_common2_control));
 }
 
 static int koneplus_get_info(struct usb_device *usb_dev,
 		struct koneplus_info *buf)
 {
-	return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_INFO,
+	return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_INFO,
 			buf, sizeof(struct koneplus_info));
 }
 
@@ -129,19 +67,20 @@
 {
 	int retval;
 
-	retval = koneplus_select_profile(usb_dev, number,
+	retval = koneplus_send_control(usb_dev, number,
 			KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
 	if (retval)
 		return retval;
 
-	return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
+	return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
 			buf, sizeof(struct koneplus_profile_settings));
 }
 
 static int koneplus_set_profile_settings(struct usb_device *usb_dev,
 		struct koneplus_profile_settings const *settings)
 {
-	return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_SETTINGS,
+	return roccat_common2_send_with_status(usb_dev,
+			KONEPLUS_COMMAND_PROFILE_SETTINGS,
 			settings, sizeof(struct koneplus_profile_settings));
 }
 
@@ -150,19 +89,20 @@
 {
 	int retval;
 
-	retval = koneplus_select_profile(usb_dev, number,
+	retval = koneplus_send_control(usb_dev, number,
 			KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
 	if (retval)
 		return retval;
 
-	return roccat_common_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
+	return roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
 			buf, sizeof(struct koneplus_profile_buttons));
 }
 
 static int koneplus_set_profile_buttons(struct usb_device *usb_dev,
 		struct koneplus_profile_buttons const *buttons)
 {
-	return koneplus_send(usb_dev, KONEPLUS_COMMAND_PROFILE_BUTTONS,
+	return roccat_common2_send_with_status(usb_dev,
+			KONEPLUS_COMMAND_PROFILE_BUTTONS,
 			buttons, sizeof(struct koneplus_profile_buttons));
 }
 
@@ -172,7 +112,7 @@
 	struct koneplus_actual_profile buf;
 	int retval;
 
-	retval = roccat_common_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
+	retval = roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
 			&buf, sizeof(struct koneplus_actual_profile));
 
 	return retval ? retval : buf.actual_profile;
@@ -187,7 +127,8 @@
 	buf.size = sizeof(struct koneplus_actual_profile);
 	buf.actual_profile = new_profile;
 
-	return koneplus_send(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
+	return roccat_common2_send_with_status(usb_dev,
+			KONEPLUS_COMMAND_ACTUAL_PROFILE,
 			&buf, sizeof(struct koneplus_actual_profile));
 }
 
@@ -208,7 +149,7 @@
 		return -EINVAL;
 
 	mutex_lock(&koneplus->koneplus_lock);
-	retval = roccat_common_receive(usb_dev, command, buf, real_size);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
 	mutex_unlock(&koneplus->koneplus_lock);
 
 	if (retval)
@@ -231,7 +172,8 @@
 		return -EINVAL;
 
 	mutex_lock(&koneplus->koneplus_lock);
-	retval = koneplus_send(usb_dev, command, buf, real_size);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			buf, real_size);
 	mutex_unlock(&koneplus->koneplus_lock);
 
 	if (retval)
diff --git a/drivers/hid/hid-roccat-koneplus.h b/drivers/hid/hid-roccat-koneplus.h
index c03332a..7074b2a 100644
--- a/drivers/hid/hid-roccat-koneplus.h
+++ b/drivers/hid/hid-roccat-koneplus.h
@@ -20,32 +20,11 @@
 	uint8_t data[14];
 } __packed;
 
-/*
- * case 1: writes request 80 and reads value 1
- *
- */
-struct koneplus_control {
-	uint8_t command; /* KONEPLUS_COMMAND_CONTROL */
-	/*
-	 * value is profile number in range 0-4 for requesting settings and buttons
-	 * 1 if status ok for requesting status
-	 */
-	uint8_t value;
-	uint8_t request;
-} __attribute__ ((__packed__));
-
 enum koneplus_control_requests {
-	KONEPLUS_CONTROL_REQUEST_STATUS = 0x00,
 	KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80,
 	KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90,
 };
 
-enum koneplus_control_values {
-	KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0,
-	KONEPLUS_CONTROL_REQUEST_STATUS_OK = 1,
-	KONEPLUS_CONTROL_REQUEST_STATUS_WAIT = 3,
-};
-
 struct koneplus_actual_profile {
 	uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */
 	uint8_t size; /* always 3 */
@@ -137,7 +116,6 @@
 } __attribute__ ((__packed__));
 
 enum koneplus_commands {
-	KONEPLUS_COMMAND_CONTROL = 0x4,
 	KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
 	KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
 	KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c
index 112d934..ca6527a 100644
--- a/drivers/hid/hid-roccat-kovaplus.c
+++ b/drivers/hid/hid-roccat-kovaplus.c
@@ -47,69 +47,23 @@
 		enum kovaplus_control_requests request)
 {
 	int retval;
-	struct kovaplus_control control;
+	struct roccat_common2_control control;
 
 	if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
 			request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
 			value > 4)
 		return -EINVAL;
 
-	control.command = KOVAPLUS_COMMAND_CONTROL;
+	control.command = ROCCAT_COMMON_COMMAND_CONTROL;
 	control.value = value;
 	control.request = request;
 
-	retval = roccat_common_send(usb_dev, KOVAPLUS_COMMAND_CONTROL,
-			&control, sizeof(struct kovaplus_control));
+	retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
+			&control, sizeof(struct roccat_common2_control));
 
 	return retval;
 }
 
-static int kovaplus_receive_control_status(struct usb_device *usb_dev)
-{
-	int retval;
-	struct kovaplus_control control;
-
-	do {
-		retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_CONTROL,
-				&control, sizeof(struct kovaplus_control));
-
-		/* check if we get a completely wrong answer */
-		if (retval)
-			return retval;
-
-		if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK)
-			return 0;
-
-		/* indicates that hardware needs some more time to complete action */
-		if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) {
-			msleep(500); /* windows driver uses 1000 */
-			continue;
-		}
-
-		/* seems to be critical - replug necessary */
-		if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD)
-			return -EINVAL;
-
-		hid_err(usb_dev, "roccat_common_receive_control_status: "
-				"unknown response value 0x%x\n", control.value);
-		return -EINVAL;
-	} while (1);
-}
-
-static int kovaplus_send(struct usb_device *usb_dev, uint command,
-		void const *buf, uint size)
-{
-	int retval;
-
-	retval = roccat_common_send(usb_dev, command, buf, size);
-	if (retval)
-		return retval;
-
-	msleep(100);
-
-	return kovaplus_receive_control_status(usb_dev);
-}
-
 static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,
 		enum kovaplus_control_requests request)
 {
@@ -119,7 +73,7 @@
 static int kovaplus_get_info(struct usb_device *usb_dev,
 		struct kovaplus_info *buf)
 {
-	return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
+	return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
 			buf, sizeof(struct kovaplus_info));
 }
 
@@ -133,14 +87,15 @@
 	if (retval)
 		return retval;
 
-	return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
+	return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
 			buf, sizeof(struct kovaplus_profile_settings));
 }
 
 static int kovaplus_set_profile_settings(struct usb_device *usb_dev,
 		struct kovaplus_profile_settings const *settings)
 {
-	return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
+	return roccat_common2_send_with_status(usb_dev,
+			KOVAPLUS_COMMAND_PROFILE_SETTINGS,
 			settings, sizeof(struct kovaplus_profile_settings));
 }
 
@@ -154,14 +109,15 @@
 	if (retval)
 		return retval;
 
-	return roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
+	return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
 			buf, sizeof(struct kovaplus_profile_buttons));
 }
 
 static int kovaplus_set_profile_buttons(struct usb_device *usb_dev,
 		struct kovaplus_profile_buttons const *buttons)
 {
-	return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
+	return roccat_common2_send_with_status(usb_dev,
+			KOVAPLUS_COMMAND_PROFILE_BUTTONS,
 			buttons, sizeof(struct kovaplus_profile_buttons));
 }
 
@@ -171,7 +127,7 @@
 	struct kovaplus_actual_profile buf;
 	int retval;
 
-	retval = roccat_common_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
+	retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
 			&buf, sizeof(struct kovaplus_actual_profile));
 
 	return retval ? retval : buf.actual_profile;
@@ -186,7 +142,8 @@
 	buf.size = sizeof(struct kovaplus_actual_profile);
 	buf.actual_profile = new_profile;
 
-	return kovaplus_send(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
+	return roccat_common2_send_with_status(usb_dev,
+			KOVAPLUS_COMMAND_ACTUAL_PROFILE,
 			&buf, sizeof(struct kovaplus_actual_profile));
 }
 
diff --git a/drivers/hid/hid-roccat-kovaplus.h b/drivers/hid/hid-roccat-kovaplus.h
index fb2aed4..f82daa1 100644
--- a/drivers/hid/hid-roccat-kovaplus.h
+++ b/drivers/hid/hid-roccat-kovaplus.h
@@ -14,27 +14,13 @@
 
 #include <linux/types.h>
 
-struct kovaplus_control {
-	uint8_t command; /* KOVAPLUS_COMMAND_CONTROL */
-	uint8_t value;
-	uint8_t request;
-} __packed;
-
 enum kovaplus_control_requests {
-	/* read after write; value = 1 */
-	KOVAPLUS_CONTROL_REQUEST_STATUS = 0x0,
 	/* write; value = profile number range 0-4 */
 	KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
 	/* write; value = profile number range 0-4 */
 	KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20,
 };
 
-enum kovaplus_control_values {
-	KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, /* supposed */
-	KOVAPLUS_CONTROL_REQUEST_STATUS_OK = 1,
-	KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, /* supposed */
-};
-
 struct kovaplus_actual_profile {
 	uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */
 	uint8_t size; /* always 3 */
@@ -75,7 +61,6 @@
 } __packed;
 
 enum kovaplus_commands {
-	KOVAPLUS_COMMAND_CONTROL = 0x4,
 	KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
 	KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
 	KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c
index df05c1b1..1317c17 100644
--- a/drivers/hid/hid-roccat-pyra.c
+++ b/drivers/hid/hid-roccat-pyra.c
@@ -42,43 +42,19 @@
 static int pyra_send_control(struct usb_device *usb_dev, int value,
 		enum pyra_control_requests request)
 {
-	struct pyra_control control;
+	struct roccat_common2_control control;
 
 	if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS ||
 			request == PYRA_CONTROL_REQUEST_PROFILE_BUTTONS) &&
 			(value < 0 || value > 4))
 		return -EINVAL;
 
-	control.command = PYRA_COMMAND_CONTROL;
+	control.command = ROCCAT_COMMON_COMMAND_CONTROL;
 	control.value = value;
 	control.request = request;
 
-	return roccat_common_send(usb_dev, PYRA_COMMAND_CONTROL,
-			&control, sizeof(struct pyra_control));
-}
-
-static int pyra_receive_control_status(struct usb_device *usb_dev)
-{
-	int retval;
-	struct pyra_control control;
-
-	do {
-		msleep(10);
-		retval = roccat_common_receive(usb_dev, PYRA_COMMAND_CONTROL,
-				&control, sizeof(struct pyra_control));
-
-		/* requested too early, try again */
-	} while (retval == -EPROTO);
-
-	if (!retval && control.command == PYRA_COMMAND_CONTROL &&
-			control.request == PYRA_CONTROL_REQUEST_STATUS &&
-			control.value == 1)
-		return 0;
-	else {
-		hid_err(usb_dev, "receive control status: unknown response 0x%x 0x%x\n",
-			control.request, control.value);
-		return retval ? retval : -EINVAL;
-	}
+	return roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
+			&control, sizeof(struct roccat_common2_control));
 }
 
 static int pyra_get_profile_settings(struct usb_device *usb_dev,
@@ -89,7 +65,7 @@
 			PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
 	if (retval)
 		return retval;
-	return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
 			buf, sizeof(struct pyra_profile_settings));
 }
 
@@ -101,51 +77,44 @@
 			PYRA_CONTROL_REQUEST_PROFILE_BUTTONS);
 	if (retval)
 		return retval;
-	return roccat_common_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS,
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS,
 			buf, sizeof(struct pyra_profile_buttons));
 }
 
 static int pyra_get_settings(struct usb_device *usb_dev,
 		struct pyra_settings *buf)
 {
-	return roccat_common_receive(usb_dev, PYRA_COMMAND_SETTINGS,
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
 			buf, sizeof(struct pyra_settings));
 }
 
 static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf)
 {
-	return roccat_common_receive(usb_dev, PYRA_COMMAND_INFO,
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_INFO,
 			buf, sizeof(struct pyra_info));
 }
 
-static int pyra_send(struct usb_device *usb_dev, uint command,
-		void const *buf, uint size)
-{
-	int retval;
-	retval = roccat_common_send(usb_dev, command, buf, size);
-	if (retval)
-		return retval;
-	return pyra_receive_control_status(usb_dev);
-}
-
 static int pyra_set_profile_settings(struct usb_device *usb_dev,
 		struct pyra_profile_settings const *settings)
 {
-	return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS, settings,
+	return roccat_common2_send_with_status(usb_dev,
+			PYRA_COMMAND_PROFILE_SETTINGS, settings,
 			sizeof(struct pyra_profile_settings));
 }
 
 static int pyra_set_profile_buttons(struct usb_device *usb_dev,
 		struct pyra_profile_buttons const *buttons)
 {
-	return pyra_send(usb_dev, PYRA_COMMAND_PROFILE_BUTTONS, buttons,
+	return roccat_common2_send_with_status(usb_dev,
+			PYRA_COMMAND_PROFILE_BUTTONS, buttons,
 			sizeof(struct pyra_profile_buttons));
 }
 
 static int pyra_set_settings(struct usb_device *usb_dev,
 		struct pyra_settings const *settings)
 {
-	return pyra_send(usb_dev, PYRA_COMMAND_SETTINGS, settings,
+	return roccat_common2_send_with_status(usb_dev,
+			PYRA_COMMAND_SETTINGS, settings,
 			sizeof(struct pyra_settings));
 }
 
diff --git a/drivers/hid/hid-roccat-pyra.h b/drivers/hid/hid-roccat-pyra.h
index 0442d7f..eada783 100644
--- a/drivers/hid/hid-roccat-pyra.h
+++ b/drivers/hid/hid-roccat-pyra.h
@@ -20,18 +20,7 @@
 	uint8_t unknown; /* 1 */
 } __attribute__ ((__packed__));
 
-struct pyra_control {
-	uint8_t command; /* PYRA_COMMAND_CONTROL */
-	/*
-	 * value is profile number for request_settings and request_buttons
-	 * 1 if status ok for request_status
-	 */
-	uint8_t value; /* Range 0-4 */
-	uint8_t request;
-} __attribute__ ((__packed__));
-
 enum pyra_control_requests {
-	PYRA_CONTROL_REQUEST_STATUS = 0x00,
 	PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
 	PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20
 };
@@ -75,7 +64,6 @@
 } __attribute__ ((__packed__));
 
 enum pyra_commands {
-	PYRA_COMMAND_CONTROL = 0x4,
 	PYRA_COMMAND_SETTINGS = 0x5,
 	PYRA_COMMAND_PROFILE_SETTINGS = 0x6,
 	PYRA_COMMAND_PROFILE_BUTTONS = 0x7,
diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c
new file mode 100644
index 0000000..014afba
--- /dev/null
+++ b/drivers/hid/hid-roccat-savu.c
@@ -0,0 +1,316 @@
+/*
+ * Roccat Savu driver for Linux
+ *
+ * Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
+ */
+
+/*
+ * 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.
+ */
+
+/* Roccat Savu is a gamer mouse with macro keys that can be configured in
+ * 5 profiles.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/hid-roccat.h>
+#include "hid-ids.h"
+#include "hid-roccat-common.h"
+#include "hid-roccat-savu.h"
+
+static struct class *savu_class;
+
+static ssize_t savu_sysfs_read(struct file *fp, struct kobject *kobj,
+		char *buf, loff_t off, size_t count,
+		size_t real_size, uint command)
+{
+	struct device *dev =
+			container_of(kobj, struct device, kobj)->parent->parent;
+	struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	int retval;
+
+	if (off >= real_size)
+		return 0;
+
+	if (off != 0 || count != real_size)
+		return -EINVAL;
+
+	mutex_lock(&savu->savu_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&savu->savu_lock);
+
+	return retval ? retval : real_size;
+}
+
+static ssize_t savu_sysfs_write(struct file *fp, struct kobject *kobj,
+		void const *buf, loff_t off, size_t count,
+		size_t real_size, uint command)
+{
+	struct device *dev =
+			container_of(kobj, struct device, kobj)->parent->parent;
+	struct savu_device *savu = hid_get_drvdata(dev_get_drvdata(dev));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	int retval;
+
+	if (off != 0 || count != real_size)
+		return -EINVAL;
+
+	mutex_lock(&savu->savu_lock);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			(void *)buf, real_size);
+	mutex_unlock(&savu->savu_lock);
+
+	return retval ? retval : real_size;
+}
+
+#define SAVU_SYSFS_W(thingy, THINGY) \
+static ssize_t savu_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return savu_sysfs_write(fp, kobj, buf, off, count, \
+			SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \
+}
+
+#define SAVU_SYSFS_R(thingy, THINGY) \
+static ssize_t savu_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return savu_sysfs_read(fp, kobj, buf, off, count, \
+			SAVU_SIZE_ ## THINGY, SAVU_COMMAND_ ## THINGY); \
+}
+
+#define SAVU_SYSFS_RW(thingy, THINGY) \
+SAVU_SYSFS_W(thingy, THINGY) \
+SAVU_SYSFS_R(thingy, THINGY)
+
+#define SAVU_BIN_ATTRIBUTE_RW(thingy, THINGY) \
+{ \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = SAVU_SIZE_ ## THINGY, \
+	.read = savu_sysfs_read_ ## thingy, \
+	.write = savu_sysfs_write_ ## thingy \
+}
+
+#define SAVU_BIN_ATTRIBUTE_R(thingy, THINGY) \
+{ \
+	.attr = { .name = #thingy, .mode = 0440 }, \
+	.size = SAVU_SIZE_ ## THINGY, \
+	.read = savu_sysfs_read_ ## thingy, \
+}
+
+#define SAVU_BIN_ATTRIBUTE_W(thingy, THINGY) \
+{ \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = SAVU_SIZE_ ## THINGY, \
+	.write = savu_sysfs_write_ ## thingy \
+}
+
+SAVU_SYSFS_W(control, CONTROL)
+SAVU_SYSFS_RW(profile, PROFILE)
+SAVU_SYSFS_RW(general, GENERAL)
+SAVU_SYSFS_RW(buttons, BUTTONS)
+SAVU_SYSFS_RW(macro, MACRO)
+SAVU_SYSFS_R(info, INFO)
+SAVU_SYSFS_RW(sensor, SENSOR)
+
+static struct bin_attribute savu_bin_attributes[] = {
+	SAVU_BIN_ATTRIBUTE_W(control, CONTROL),
+	SAVU_BIN_ATTRIBUTE_RW(profile, PROFILE),
+	SAVU_BIN_ATTRIBUTE_RW(general, GENERAL),
+	SAVU_BIN_ATTRIBUTE_RW(buttons, BUTTONS),
+	SAVU_BIN_ATTRIBUTE_RW(macro, MACRO),
+	SAVU_BIN_ATTRIBUTE_R(info, INFO),
+	SAVU_BIN_ATTRIBUTE_RW(sensor, SENSOR),
+	__ATTR_NULL
+};
+
+static int savu_init_savu_device_struct(struct usb_device *usb_dev,
+		struct savu_device *savu)
+{
+	mutex_init(&savu->savu_lock);
+
+	return 0;
+}
+
+static int savu_init_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_device *usb_dev = interface_to_usbdev(intf);
+	struct savu_device *savu;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE) {
+		hid_set_drvdata(hdev, NULL);
+		return 0;
+	}
+
+	savu = kzalloc(sizeof(*savu), GFP_KERNEL);
+	if (!savu) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, savu);
+
+	retval = savu_init_savu_device_struct(usb_dev, savu);
+	if (retval) {
+		hid_err(hdev, "couldn't init struct savu_device\n");
+		goto exit_free;
+	}
+
+	retval = roccat_connect(savu_class, hdev,
+			sizeof(struct savu_roccat_report));
+	if (retval < 0) {
+		hid_err(hdev, "couldn't init char dev\n");
+	} else {
+		savu->chrdev_minor = retval;
+		savu->roccat_claimed = 1;
+	}
+
+	return 0;
+exit_free:
+	kfree(savu);
+	return retval;
+}
+
+static void savu_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct savu_device *savu;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return;
+
+	savu = hid_get_drvdata(hdev);
+	if (savu->roccat_claimed)
+		roccat_disconnect(savu->chrdev_minor);
+	kfree(savu);
+}
+
+static int savu_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int retval;
+
+	retval = hid_parse(hdev);
+	if (retval) {
+		hid_err(hdev, "parse failed\n");
+		goto exit;
+	}
+
+	retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (retval) {
+		hid_err(hdev, "hw start failed\n");
+		goto exit;
+	}
+
+	retval = savu_init_specials(hdev);
+	if (retval) {
+		hid_err(hdev, "couldn't install mouse\n");
+		goto exit_stop;
+	}
+
+	return 0;
+
+exit_stop:
+	hid_hw_stop(hdev);
+exit:
+	return retval;
+}
+
+static void savu_remove(struct hid_device *hdev)
+{
+	savu_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void savu_report_to_chrdev(struct savu_device const *savu,
+		u8 const *data)
+{
+	struct savu_roccat_report roccat_report;
+	struct savu_mouse_report_special const *special_report;
+
+	if (data[0] != SAVU_MOUSE_REPORT_NUMBER_SPECIAL)
+		return;
+
+	special_report = (struct savu_mouse_report_special const *)data;
+
+	roccat_report.type = special_report->type;
+	roccat_report.data[0] = special_report->data[0];
+	roccat_report.data[1] = special_report->data[1];
+	roccat_report_event(savu->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+}
+
+static int savu_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct savu_device *savu = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return 0;
+
+	if (savu == NULL)
+		return 0;
+
+	if (savu->roccat_claimed)
+		savu_report_to_chrdev(savu, data);
+
+	return 0;
+}
+
+static const struct hid_device_id savu_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, savu_devices);
+
+static struct hid_driver savu_driver = {
+		.name = "savu",
+		.id_table = savu_devices,
+		.probe = savu_probe,
+		.remove = savu_remove,
+		.raw_event = savu_raw_event
+};
+
+static int __init savu_init(void)
+{
+	int retval;
+
+	savu_class = class_create(THIS_MODULE, "savu");
+	if (IS_ERR(savu_class))
+		return PTR_ERR(savu_class);
+	savu_class->dev_bin_attrs = savu_bin_attributes;
+
+	retval = hid_register_driver(&savu_driver);
+	if (retval)
+		class_destroy(savu_class);
+	return retval;
+}
+
+static void __exit savu_exit(void)
+{
+	hid_unregister_driver(&savu_driver);
+	class_destroy(savu_class);
+}
+
+module_init(savu_init);
+module_exit(savu_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Savu driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-savu.h b/drivers/hid/hid-roccat-savu.h
new file mode 100644
index 0000000..9120ba7
--- /dev/null
+++ b/drivers/hid/hid-roccat-savu.h
@@ -0,0 +1,87 @@
+#ifndef __HID_ROCCAT_SAVU_H
+#define __HID_ROCCAT_SAVU_H
+
+/*
+ * Copyright (c) 2012 Stefan Achatz <erazor_de@users.sourceforge.net>
+ */
+
+/*
+ * 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/types.h>
+
+enum {
+	SAVU_SIZE_CONTROL = 0x03,
+	SAVU_SIZE_PROFILE = 0x03,
+	SAVU_SIZE_GENERAL = 0x10,
+	SAVU_SIZE_BUTTONS = 0x2f,
+	SAVU_SIZE_MACRO = 0x0823,
+	SAVU_SIZE_INFO = 0x08,
+	SAVU_SIZE_SENSOR = 0x04,
+};
+
+enum savu_control_requests {
+	SAVU_CONTROL_REQUEST_GENERAL = 0x80,
+	SAVU_CONTROL_REQUEST_BUTTONS = 0x90,
+};
+
+enum savu_commands {
+	SAVU_COMMAND_CONTROL = 0x4,
+	SAVU_COMMAND_PROFILE = 0x5,
+	SAVU_COMMAND_GENERAL = 0x6,
+	SAVU_COMMAND_BUTTONS = 0x7,
+	SAVU_COMMAND_MACRO = 0x8,
+	SAVU_COMMAND_INFO = 0x9,
+	SAVU_COMMAND_SENSOR = 0xc,
+};
+
+struct savu_mouse_report_special {
+	uint8_t report_number; /* always 3 */
+	uint8_t zero;
+	uint8_t type;
+	uint8_t data[2];
+} __packed;
+
+enum {
+	SAVU_MOUSE_REPORT_NUMBER_SPECIAL = 3,
+};
+
+enum savu_mouse_report_button_types {
+	/* data1 = new profile range 1-5 */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20,
+
+	/* data1 = button number range 1-24; data2 = action */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
+
+	/* data1 = button number range 1-24; data2 = action */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
+
+	/* data1 = setting number range 1-5 */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
+
+	/* data1 and data2 = range 0x1-0xb */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
+
+	/* data1 = 22 = next track...
+	 * data2 = action
+	 */
+	SAVU_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
+};
+
+struct savu_roccat_report {
+	uint8_t type;
+	uint8_t data[2];
+} __packed;
+
+struct savu_device {
+	int roccat_claimed;
+	int chrdev_minor;
+
+	struct mutex savu_lock;
+};
+
+#endif
diff --git a/drivers/hid/hid-wiimote-ext.c b/drivers/hid/hid-wiimote-ext.c
index aa95870..0a1805c 100644
--- a/drivers/hid/hid-wiimote-ext.c
+++ b/drivers/hid/hid-wiimote-ext.c
@@ -77,7 +77,7 @@
 	BTN_TR,		/* WIIEXT_KEY_RT */
 };
 
-/* diable all extensions */
+/* disable all extensions */
 static void ext_disable(struct wiimote_ext *ext)
 {
 	unsigned long flags;
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
index 36fa77b..3b6f7bf 100644
--- a/drivers/hid/hidraw.c
+++ b/drivers/hid/hidraw.c
@@ -96,6 +96,7 @@
 		}
 
 		kfree(list->buffer[list->tail].value);
+		list->buffer[list->tail].value = NULL;
 		list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
 	}
 out:
@@ -300,6 +301,7 @@
 	struct hidraw *dev;
 	struct hidraw_list *list = file->private_data;
 	int ret;
+	int i;
 
 	mutex_lock(&minors_lock);
 	if (!hidraw_table[minor]) {
@@ -317,6 +319,9 @@
 			kfree(list->hidraw);
 		}
 	}
+
+	for (i = 0; i < HIDRAW_BUFFER_SIZE; ++i)
+		kfree(list->buffer[i].value);
 	kfree(list);
 	ret = 0;
 unlock:
@@ -446,12 +451,17 @@
 	int ret = 0;
 
 	list_for_each_entry(list, &dev->list, node) {
+		int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
+
+		if (new_head == list->tail)
+			continue;
+
 		if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
 			ret = -ENOMEM;
 			break;
 		}
 		list->buffer[list->head].len = len;
-		list->head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
+		list->head = new_head;
 		kill_fasync(&list->fasync, SIGIO, POLL_IN);
 	}
 
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
new file mode 100644
index 0000000..714cd8c
--- /dev/null
+++ b/drivers/hid/uhid.c
@@ -0,0 +1,572 @@
+/*
+ * User-space I/O driver support for HID subsystem
+ * Copyright (c) 2012 David Herrmann
+ */
+
+/*
+ * 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/atomic.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/uhid.h>
+#include <linux/wait.h>
+
+#define UHID_NAME	"uhid"
+#define UHID_BUFSIZE	32
+
+struct uhid_device {
+	struct mutex devlock;
+	bool running;
+
+	__u8 *rd_data;
+	uint rd_size;
+
+	struct hid_device *hid;
+	struct uhid_event input_buf;
+
+	wait_queue_head_t waitq;
+	spinlock_t qlock;
+	__u8 head;
+	__u8 tail;
+	struct uhid_event *outq[UHID_BUFSIZE];
+
+	struct mutex report_lock;
+	wait_queue_head_t report_wait;
+	atomic_t report_done;
+	atomic_t report_id;
+	struct uhid_event report_buf;
+};
+
+static struct miscdevice uhid_misc;
+
+static void uhid_queue(struct uhid_device *uhid, struct uhid_event *ev)
+{
+	__u8 newhead;
+
+	newhead = (uhid->head + 1) % UHID_BUFSIZE;
+
+	if (newhead != uhid->tail) {
+		uhid->outq[uhid->head] = ev;
+		uhid->head = newhead;
+		wake_up_interruptible(&uhid->waitq);
+	} else {
+		hid_warn(uhid->hid, "Output queue is full\n");
+		kfree(ev);
+	}
+}
+
+static int uhid_queue_event(struct uhid_device *uhid, __u32 event)
+{
+	unsigned long flags;
+	struct uhid_event *ev;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = event;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	return 0;
+}
+
+static int uhid_hid_start(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	return uhid_queue_event(uhid, UHID_START);
+}
+
+static void uhid_hid_stop(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	hid->claimed = 0;
+	uhid_queue_event(uhid, UHID_STOP);
+}
+
+static int uhid_hid_open(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	return uhid_queue_event(uhid, UHID_OPEN);
+}
+
+static void uhid_hid_close(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	uhid_queue_event(uhid, UHID_CLOSE);
+}
+
+static int uhid_hid_input(struct input_dev *input, unsigned int type,
+			  unsigned int code, int value)
+{
+	struct hid_device *hid = input_get_drvdata(input);
+	struct uhid_device *uhid = hid->driver_data;
+	unsigned long flags;
+	struct uhid_event *ev;
+
+	ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = UHID_OUTPUT_EV;
+	ev->u.output_ev.type = type;
+	ev->u.output_ev.code = code;
+	ev->u.output_ev.value = value;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	return 0;
+}
+
+static int uhid_hid_parse(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
+}
+
+static int uhid_hid_get_raw(struct hid_device *hid, unsigned char rnum,
+			    __u8 *buf, size_t count, unsigned char rtype)
+{
+	struct uhid_device *uhid = hid->driver_data;
+	__u8 report_type;
+	struct uhid_event *ev;
+	unsigned long flags;
+	int ret;
+	size_t uninitialized_var(len);
+	struct uhid_feature_answer_req *req;
+
+	if (!uhid->running)
+		return -EIO;
+
+	switch (rtype) {
+	case HID_FEATURE_REPORT:
+		report_type = UHID_FEATURE_REPORT;
+		break;
+	case HID_OUTPUT_REPORT:
+		report_type = UHID_OUTPUT_REPORT;
+		break;
+	case HID_INPUT_REPORT:
+		report_type = UHID_INPUT_REPORT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = mutex_lock_interruptible(&uhid->report_lock);
+	if (ret)
+		return ret;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	ev->type = UHID_FEATURE;
+	ev->u.feature.id = atomic_inc_return(&uhid->report_id);
+	ev->u.feature.rnum = rnum;
+	ev->u.feature.rtype = report_type;
+
+	atomic_set(&uhid->report_done, 0);
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	ret = wait_event_interruptible_timeout(uhid->report_wait,
+				atomic_read(&uhid->report_done), 5 * HZ);
+
+	/*
+	 * Make sure "uhid->running" is cleared on shutdown before
+	 * "uhid->report_done" is set.
+	 */
+	smp_rmb();
+	if (!ret || !uhid->running) {
+		ret = -EIO;
+	} else if (ret < 0) {
+		ret = -ERESTARTSYS;
+	} else {
+		spin_lock_irqsave(&uhid->qlock, flags);
+		req = &uhid->report_buf.u.feature_answer;
+
+		if (req->err) {
+			ret = -EIO;
+		} else {
+			ret = 0;
+			len = min(count,
+				min_t(size_t, req->size, UHID_DATA_MAX));
+			memcpy(buf, req->data, len);
+		}
+
+		spin_unlock_irqrestore(&uhid->qlock, flags);
+	}
+
+	atomic_set(&uhid->report_done, 1);
+
+unlock:
+	mutex_unlock(&uhid->report_lock);
+	return ret ? ret : len;
+}
+
+static int uhid_hid_output_raw(struct hid_device *hid, __u8 *buf, size_t count,
+			       unsigned char report_type)
+{
+	struct uhid_device *uhid = hid->driver_data;
+	__u8 rtype;
+	unsigned long flags;
+	struct uhid_event *ev;
+
+	switch (report_type) {
+	case HID_FEATURE_REPORT:
+		rtype = UHID_FEATURE_REPORT;
+		break;
+	case HID_OUTPUT_REPORT:
+		rtype = UHID_OUTPUT_REPORT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (count < 1 || count > UHID_DATA_MAX)
+		return -EINVAL;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = UHID_OUTPUT;
+	ev->u.output.size = count;
+	ev->u.output.rtype = rtype;
+	memcpy(ev->u.output.data, buf, count);
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	return count;
+}
+
+static struct hid_ll_driver uhid_hid_driver = {
+	.start = uhid_hid_start,
+	.stop = uhid_hid_stop,
+	.open = uhid_hid_open,
+	.close = uhid_hid_close,
+	.hidinput_input_event = uhid_hid_input,
+	.parse = uhid_hid_parse,
+};
+
+static int uhid_dev_create(struct uhid_device *uhid,
+			   const struct uhid_event *ev)
+{
+	struct hid_device *hid;
+	int ret;
+
+	if (uhid->running)
+		return -EALREADY;
+
+	uhid->rd_size = ev->u.create.rd_size;
+	if (uhid->rd_size <= 0 || uhid->rd_size > HID_MAX_DESCRIPTOR_SIZE)
+		return -EINVAL;
+
+	uhid->rd_data = kmalloc(uhid->rd_size, GFP_KERNEL);
+	if (!uhid->rd_data)
+		return -ENOMEM;
+
+	if (copy_from_user(uhid->rd_data, ev->u.create.rd_data,
+			   uhid->rd_size)) {
+		ret = -EFAULT;
+		goto err_free;
+	}
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		ret = PTR_ERR(hid);
+		goto err_free;
+	}
+
+	strncpy(hid->name, ev->u.create.name, 127);
+	hid->name[127] = 0;
+	strncpy(hid->phys, ev->u.create.phys, 63);
+	hid->phys[63] = 0;
+	strncpy(hid->uniq, ev->u.create.uniq, 63);
+	hid->uniq[63] = 0;
+
+	hid->ll_driver = &uhid_hid_driver;
+	hid->hid_get_raw_report = uhid_hid_get_raw;
+	hid->hid_output_raw_report = uhid_hid_output_raw;
+	hid->bus = ev->u.create.bus;
+	hid->vendor = ev->u.create.vendor;
+	hid->product = ev->u.create.product;
+	hid->version = ev->u.create.version;
+	hid->country = ev->u.create.country;
+	hid->driver_data = uhid;
+	hid->dev.parent = uhid_misc.this_device;
+
+	uhid->hid = hid;
+	uhid->running = true;
+
+	ret = hid_add_device(hid);
+	if (ret) {
+		hid_err(hid, "Cannot register HID device\n");
+		goto err_hid;
+	}
+
+	return 0;
+
+err_hid:
+	hid_destroy_device(hid);
+	uhid->hid = NULL;
+	uhid->running = false;
+err_free:
+	kfree(uhid->rd_data);
+	return ret;
+}
+
+static int uhid_dev_destroy(struct uhid_device *uhid)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	/* clear "running" before setting "report_done" */
+	uhid->running = false;
+	smp_wmb();
+	atomic_set(&uhid->report_done, 1);
+	wake_up_interruptible(&uhid->report_wait);
+
+	hid_destroy_device(uhid->hid);
+	kfree(uhid->rd_data);
+
+	return 0;
+}
+
+static int uhid_dev_input(struct uhid_device *uhid, struct uhid_event *ev)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input.data,
+			 min_t(size_t, ev->u.input.size, UHID_DATA_MAX), 0);
+
+	return 0;
+}
+
+static int uhid_dev_feature_answer(struct uhid_device *uhid,
+				   struct uhid_event *ev)
+{
+	unsigned long flags;
+
+	if (!uhid->running)
+		return -EINVAL;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+
+	/* id for old report; drop it silently */
+	if (atomic_read(&uhid->report_id) != ev->u.feature_answer.id)
+		goto unlock;
+	if (atomic_read(&uhid->report_done))
+		goto unlock;
+
+	memcpy(&uhid->report_buf, ev, sizeof(*ev));
+	atomic_set(&uhid->report_done, 1);
+	wake_up_interruptible(&uhid->report_wait);
+
+unlock:
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+	return 0;
+}
+
+static int uhid_char_open(struct inode *inode, struct file *file)
+{
+	struct uhid_device *uhid;
+
+	uhid = kzalloc(sizeof(*uhid), GFP_KERNEL);
+	if (!uhid)
+		return -ENOMEM;
+
+	mutex_init(&uhid->devlock);
+	mutex_init(&uhid->report_lock);
+	spin_lock_init(&uhid->qlock);
+	init_waitqueue_head(&uhid->waitq);
+	init_waitqueue_head(&uhid->report_wait);
+	uhid->running = false;
+	atomic_set(&uhid->report_done, 1);
+
+	file->private_data = uhid;
+	nonseekable_open(inode, file);
+
+	return 0;
+}
+
+static int uhid_char_release(struct inode *inode, struct file *file)
+{
+	struct uhid_device *uhid = file->private_data;
+	unsigned int i;
+
+	uhid_dev_destroy(uhid);
+
+	for (i = 0; i < UHID_BUFSIZE; ++i)
+		kfree(uhid->outq[i]);
+
+	kfree(uhid);
+
+	return 0;
+}
+
+static ssize_t uhid_char_read(struct file *file, char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	struct uhid_device *uhid = file->private_data;
+	int ret;
+	unsigned long flags;
+	size_t len;
+
+	/* they need at least the "type" member of uhid_event */
+	if (count < sizeof(__u32))
+		return -EINVAL;
+
+try_again:
+	if (file->f_flags & O_NONBLOCK) {
+		if (uhid->head == uhid->tail)
+			return -EAGAIN;
+	} else {
+		ret = wait_event_interruptible(uhid->waitq,
+						uhid->head != uhid->tail);
+		if (ret)
+			return ret;
+	}
+
+	ret = mutex_lock_interruptible(&uhid->devlock);
+	if (ret)
+		return ret;
+
+	if (uhid->head == uhid->tail) {
+		mutex_unlock(&uhid->devlock);
+		goto try_again;
+	} else {
+		len = min(count, sizeof(**uhid->outq));
+		if (copy_to_user(buffer, uhid->outq[uhid->tail], len)) {
+			ret = -EFAULT;
+		} else {
+			kfree(uhid->outq[uhid->tail]);
+			uhid->outq[uhid->tail] = NULL;
+
+			spin_lock_irqsave(&uhid->qlock, flags);
+			uhid->tail = (uhid->tail + 1) % UHID_BUFSIZE;
+			spin_unlock_irqrestore(&uhid->qlock, flags);
+		}
+	}
+
+	mutex_unlock(&uhid->devlock);
+	return ret ? ret : len;
+}
+
+static ssize_t uhid_char_write(struct file *file, const char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	struct uhid_device *uhid = file->private_data;
+	int ret;
+	size_t len;
+
+	/* we need at least the "type" member of uhid_event */
+	if (count < sizeof(__u32))
+		return -EINVAL;
+
+	ret = mutex_lock_interruptible(&uhid->devlock);
+	if (ret)
+		return ret;
+
+	memset(&uhid->input_buf, 0, sizeof(uhid->input_buf));
+	len = min(count, sizeof(uhid->input_buf));
+	if (copy_from_user(&uhid->input_buf, buffer, len)) {
+		ret = -EFAULT;
+		goto unlock;
+	}
+
+	switch (uhid->input_buf.type) {
+	case UHID_CREATE:
+		ret = uhid_dev_create(uhid, &uhid->input_buf);
+		break;
+	case UHID_DESTROY:
+		ret = uhid_dev_destroy(uhid);
+		break;
+	case UHID_INPUT:
+		ret = uhid_dev_input(uhid, &uhid->input_buf);
+		break;
+	case UHID_FEATURE_ANSWER:
+		ret = uhid_dev_feature_answer(uhid, &uhid->input_buf);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+	}
+
+unlock:
+	mutex_unlock(&uhid->devlock);
+
+	/* return "count" not "len" to not confuse the caller */
+	return ret ? ret : count;
+}
+
+static unsigned int uhid_char_poll(struct file *file, poll_table *wait)
+{
+	struct uhid_device *uhid = file->private_data;
+
+	poll_wait(file, &uhid->waitq, wait);
+
+	if (uhid->head != uhid->tail)
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static const struct file_operations uhid_fops = {
+	.owner		= THIS_MODULE,
+	.open		= uhid_char_open,
+	.release	= uhid_char_release,
+	.read		= uhid_char_read,
+	.write		= uhid_char_write,
+	.poll		= uhid_char_poll,
+	.llseek		= no_llseek,
+};
+
+static struct miscdevice uhid_misc = {
+	.fops		= &uhid_fops,
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= UHID_NAME,
+};
+
+static int __init uhid_init(void)
+{
+	return misc_register(&uhid_misc);
+}
+
+static void __exit uhid_exit(void)
+{
+	misc_deregister(&uhid_misc);
+}
+
+module_init(uhid_init);
+module_exit(uhid_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 482f936..dedd8e4 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -84,7 +84,7 @@
 	spin_lock_irqsave(&usbhid->lock, flags);
 	if (hid->open > 0 &&
 			!test_bit(HID_DISCONNECTED, &usbhid->iofl) &&
-			!test_bit(HID_REPORTED_IDLE, &usbhid->iofl) &&
+			!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) {
@@ -207,15 +207,27 @@
 	int kicked;
 	int r;
 
-	if (!hid)
+	if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) ||
+			test_bit(HID_SUSPENDED, &usbhid->iofl))
 		return 0;
 
 	if ((kicked = (usbhid->outhead != usbhid->outtail))) {
 		hid_dbg(hid, "Kicking head %d tail %d", usbhid->outhead, usbhid->outtail);
 
+		/* Try to wake up from autosuspend... */
 		r = usb_autopm_get_interface_async(usbhid->intf);
 		if (r < 0)
 			return r;
+
+		/*
+		 * If still suspended, don't submit.  Submission will
+		 * occur if/when resume drains the queue.
+		 */
+		if (test_bit(HID_SUSPENDED, &usbhid->iofl)) {
+			usb_autopm_put_interface_no_suspend(usbhid->intf);
+			return r;
+		}
+
 		/* Asynchronously flush queue. */
 		set_bit(HID_OUT_RUNNING, &usbhid->iofl);
 		if (hid_submit_out(hid)) {
@@ -234,15 +246,27 @@
 	int r;
 
 	WARN_ON(hid == NULL);
-	if (!hid)
+	if (!hid || test_bit(HID_RESET_PENDING, &usbhid->iofl) ||
+			test_bit(HID_SUSPENDED, &usbhid->iofl))
 		return 0;
 
 	if ((kicked = (usbhid->ctrlhead != usbhid->ctrltail))) {
 		hid_dbg(hid, "Kicking head %d tail %d", usbhid->ctrlhead, usbhid->ctrltail);
 
+		/* Try to wake up from autosuspend... */
 		r = usb_autopm_get_interface_async(usbhid->intf);
 		if (r < 0)
 			return r;
+
+		/*
+		 * If still suspended, don't submit.  Submission will
+		 * occur if/when resume drains the queue.
+		 */
+		if (test_bit(HID_SUSPENDED, &usbhid->iofl)) {
+			usb_autopm_put_interface_no_suspend(usbhid->intf);
+			return r;
+		}
+
 		/* Asynchronously flush queue. */
 		set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
 		if (hid_submit_ctrl(hid)) {
@@ -331,9 +355,12 @@
 	usbhid->urbout->transfer_buffer_length = ((report->size - 1) >> 3) +
 						 1 + (report->id > 0);
 	usbhid->urbout->dev = hid_to_usb_dev(hid);
-	memcpy(usbhid->outbuf, raw_report,
-	       usbhid->urbout->transfer_buffer_length);
-	kfree(raw_report);
+	if (raw_report) {
+		memcpy(usbhid->outbuf, raw_report,
+				usbhid->urbout->transfer_buffer_length);
+		kfree(raw_report);
+		usbhid->out[usbhid->outtail].raw_report = NULL;
+	}
 
 	dbg_hid("submitting out urb\n");
 
@@ -362,8 +389,11 @@
 	if (dir == USB_DIR_OUT) {
 		usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
 		usbhid->urbctrl->transfer_buffer_length = len;
-		memcpy(usbhid->ctrlbuf, raw_report, len);
-		kfree(raw_report);
+		if (raw_report) {
+			memcpy(usbhid->ctrlbuf, raw_report, len);
+			kfree(raw_report);
+			usbhid->ctrl[usbhid->ctrltail].raw_report = NULL;
+		}
 	} else {
 		int maxpacket, padlen;
 
@@ -407,16 +437,6 @@
  * Output interrupt completion handler.
  */
 
-static int irq_out_pump_restart(struct hid_device *hid)
-{
-	struct usbhid_device *usbhid = hid->driver_data;
-
-	if (usbhid->outhead != usbhid->outtail)
-		return hid_submit_out(hid);
-	else
-		return -1;
-}
-
 static void hid_irq_out(struct urb *urb)
 {
 	struct hid_device *hid = urb->context;
@@ -441,15 +461,17 @@
 
 	spin_lock_irqsave(&usbhid->lock, flags);
 
-	if (unplug)
+	if (unplug) {
 		usbhid->outtail = usbhid->outhead;
-	else
+	} else {
 		usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
 
-	if (!irq_out_pump_restart(hid)) {
-		/* Successfully submitted next urb in queue */
-		spin_unlock_irqrestore(&usbhid->lock, flags);
-		return;
+		if (usbhid->outhead != usbhid->outtail &&
+				hid_submit_out(hid) == 0) {
+			/* Successfully submitted next urb in queue */
+			spin_unlock_irqrestore(&usbhid->lock, flags);
+			return;
+		}
 	}
 
 	clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
@@ -461,15 +483,6 @@
 /*
  * Control pipe completion handler.
  */
-static int ctrl_pump_restart(struct hid_device *hid)
-{
-	struct usbhid_device *usbhid = hid->driver_data;
-
-	if (usbhid->ctrlhead != usbhid->ctrltail)
-		return hid_submit_ctrl(hid);
-	else
-		return -1;
-}
 
 static void hid_ctrl(struct urb *urb)
 {
@@ -498,15 +511,17 @@
 		hid_warn(urb->dev, "ctrl urb status %d received\n", status);
 	}
 
-	if (unplug)
+	if (unplug) {
 		usbhid->ctrltail = usbhid->ctrlhead;
-	else
+	} else {
 		usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
 
-	if (!ctrl_pump_restart(hid)) {
-		/* Successfully submitted next urb in queue */
-		spin_unlock(&usbhid->lock);
-		return;
+		if (usbhid->ctrlhead != usbhid->ctrltail &&
+				hid_submit_ctrl(hid) == 0) {
+			/* Successfully submitted next urb in queue */
+			spin_unlock(&usbhid->lock);
+			return;
+		}
 	}
 
 	clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
@@ -540,49 +555,36 @@
 		usbhid->out[usbhid->outhead].report = report;
 		usbhid->outhead = head;
 
-		/* Try to awake from autosuspend... */
-		if (usb_autopm_get_interface_async(usbhid->intf) < 0)
-			return;
+		/* If the queue isn't running, restart it */
+		if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
+			usbhid_restart_out_queue(usbhid);
 
-		/*
-		 * But if still suspended, leave urb enqueued, don't submit.
-		 * Submission will occur if/when resume() drains the queue.
-		 */
-		if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
-			return;
+		/* Otherwise see if an earlier request has timed out */
+		} else if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
 
-		if (!test_and_set_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
-			if (hid_submit_out(hid)) {
-				clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
-				usb_autopm_put_interface_async(usbhid->intf);
-			}
-			wake_up(&usbhid->wait);
-		} else {
+			/* Prevent autosuspend following the unlink */
+			usb_autopm_get_interface_no_resume(usbhid->intf);
+
 			/*
-			 * the queue is known to run
-			 * but an earlier request may be stuck
-			 * we may need to time out
-			 * no race because the URB is blocked under
-			 * spinlock
+			 * Prevent resubmission in case the URB completes
+			 * before we can unlink it.  We don't want to cancel
+			 * the wrong transfer!
 			 */
-			if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
-				usb_block_urb(usbhid->urbout);
-				/* drop lock to not deadlock if the callback is called */
-				spin_unlock(&usbhid->lock);
-				usb_unlink_urb(usbhid->urbout);
-				spin_lock(&usbhid->lock);
-				usb_unblock_urb(usbhid->urbout);
-				/*
-				 * if the unlinking has already completed
-				 * the pump will have been stopped
-				 * it must be restarted now
-				 */
-				if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
-					if (!irq_out_pump_restart(hid))
-						set_bit(HID_OUT_RUNNING, &usbhid->iofl);
+			usb_block_urb(usbhid->urbout);
 
+			/* Drop lock to avoid deadlock if the callback runs */
+			spin_unlock(&usbhid->lock);
 
-			}
+			usb_unlink_urb(usbhid->urbout);
+			spin_lock(&usbhid->lock);
+			usb_unblock_urb(usbhid->urbout);
+
+			/* Unlink might have stopped the queue */
+			if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl))
+				usbhid_restart_out_queue(usbhid);
+
+			/* Now we can allow autosuspend again */
+			usb_autopm_put_interface_async(usbhid->intf);
 		}
 		return;
 	}
@@ -604,47 +606,36 @@
 	usbhid->ctrl[usbhid->ctrlhead].dir = dir;
 	usbhid->ctrlhead = head;
 
-	/* Try to awake from autosuspend... */
-	if (usb_autopm_get_interface_async(usbhid->intf) < 0)
-		return;
+	/* If the queue isn't running, restart it */
+	if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
+		usbhid_restart_ctrl_queue(usbhid);
 
-	/*
-	 * If already suspended, leave urb enqueued, but don't submit.
-	 * Submission will occur if/when resume() drains the queue.
-	 */
-	if (test_bit(HID_REPORTED_IDLE, &usbhid->iofl))
-		return;
+	/* Otherwise see if an earlier request has timed out */
+	} else if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
 
-	if (!test_and_set_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
-		if (hid_submit_ctrl(hid)) {
-			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-			usb_autopm_put_interface_async(usbhid->intf);
-		}
-		wake_up(&usbhid->wait);
-	} else {
+		/* Prevent autosuspend following the unlink */
+		usb_autopm_get_interface_no_resume(usbhid->intf);
+
 		/*
-		 * the queue is known to run
-		 * but an earlier request may be stuck
-		 * we may need to time out
-		 * no race because the URB is blocked under
-		 * spinlock
+		 * Prevent resubmission in case the URB completes
+		 * before we can unlink it.  We don't want to cancel
+		 * the wrong transfer!
 		 */
-		if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
-			usb_block_urb(usbhid->urbctrl);
-			/* drop lock to not deadlock if the callback is called */
-			spin_unlock(&usbhid->lock);
-			usb_unlink_urb(usbhid->urbctrl);
-			spin_lock(&usbhid->lock);
-			usb_unblock_urb(usbhid->urbctrl);
-			/*
-			 * if the unlinking has already completed
-			 * the pump will have been stopped
-			 * it must be restarted now
-			 */
-			if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
-				if (!ctrl_pump_restart(hid))
-					set_bit(HID_CTRL_RUNNING, &usbhid->iofl);
-		}
+		usb_block_urb(usbhid->urbctrl);
+
+		/* Drop lock to avoid deadlock if the callback runs */
+		spin_unlock(&usbhid->lock);
+
+		usb_unlink_urb(usbhid->urbctrl);
+		spin_lock(&usbhid->lock);
+		usb_unblock_urb(usbhid->urbctrl);
+
+		/* Unlink might have stopped the queue */
+		if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+			usbhid_restart_ctrl_queue(usbhid);
+
+		/* Now we can allow autosuspend again */
+		usb_autopm_put_interface_async(usbhid->intf);
 	}
 }
 
@@ -1002,9 +993,10 @@
 
 static void usbhid_restart_queues(struct usbhid_device *usbhid)
 {
-	if (usbhid->urbout)
+	if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl))
 		usbhid_restart_out_queue(usbhid);
-	usbhid_restart_ctrl_queue(usbhid);
+	if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+		usbhid_restart_ctrl_queue(usbhid);
 }
 
 static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
@@ -1471,11 +1463,38 @@
 
 
 #ifdef CONFIG_PM
+static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int status;
+
+	spin_lock_irq(&usbhid->lock);
+	clear_bit(HID_SUSPENDED, &usbhid->iofl);
+	usbhid_mark_busy(usbhid);
+
+	if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
+			test_bit(HID_RESET_PENDING, &usbhid->iofl))
+		schedule_work(&usbhid->reset_work);
+	usbhid->retry_delay = 0;
+
+	usbhid_restart_queues(usbhid);
+	spin_unlock_irq(&usbhid->lock);
+
+	status = hid_start_in(hid);
+	if (status < 0)
+		hid_io_error(hid);
+
+	if (driver_suspended && hid->driver && hid->driver->resume)
+		status = hid->driver->resume(hid);
+	return status;
+}
+
 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;
 	int status;
+	bool driver_suspended = false;
 
 	if (PMSG_IS_AUTO(message)) {
 		spin_lock_irq(&usbhid->lock);	/* Sync with error handler */
@@ -1486,13 +1505,14 @@
 		    && !test_bit(HID_KEYS_PRESSED, &usbhid->iofl)
 		    && (!usbhid->ledcount || ignoreled))
 		{
-			set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
+			set_bit(HID_SUSPENDED, &usbhid->iofl);
 			spin_unlock_irq(&usbhid->lock);
 			if (hid->driver && hid->driver->suspend) {
 				status = hid->driver->suspend(hid, message);
 				if (status < 0)
-					return status;
+					goto failed;
 			}
+			driver_suspended = true;
 		} else {
 			usbhid_mark_busy(usbhid);
 			spin_unlock_irq(&usbhid->lock);
@@ -1505,11 +1525,14 @@
 			if (status < 0)
 				return status;
 		}
+		driver_suspended = true;
 		spin_lock_irq(&usbhid->lock);
-		set_bit(HID_REPORTED_IDLE, &usbhid->iofl);
+		set_bit(HID_SUSPENDED, &usbhid->iofl);
 		spin_unlock_irq(&usbhid->lock);
-		if (usbhid_wait_io(hid) < 0)
-			return -EIO;
+		if (usbhid_wait_io(hid) < 0) {
+			status = -EIO;
+			goto failed;
+		}
 	}
 
 	hid_cancel_delayed_stuff(usbhid);
@@ -1517,14 +1540,15 @@
 
 	if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) {
 		/* lost race against keypresses */
-		status = hid_start_in(hid);
-		if (status < 0)
-			hid_io_error(hid);
-		usbhid_mark_busy(usbhid);
-		return -EBUSY;
+		status = -EBUSY;
+		goto failed;
 	}
 	dev_dbg(&intf->dev, "suspend\n");
 	return 0;
+
+ failed:
+	hid_resume_common(hid, driver_suspended);
+	return status;
 }
 
 static int hid_resume(struct usb_interface *intf)
@@ -1536,23 +1560,7 @@
 	if (!test_bit(HID_STARTED, &usbhid->iofl))
 		return 0;
 
-	clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
-	usbhid_mark_busy(usbhid);
-
-	if (test_bit(HID_CLEAR_HALT, &usbhid->iofl) ||
-	    test_bit(HID_RESET_PENDING, &usbhid->iofl))
-		schedule_work(&usbhid->reset_work);
-	usbhid->retry_delay = 0;
-	status = hid_start_in(hid);
-	if (status < 0)
-		hid_io_error(hid);
-	usbhid_restart_queues(usbhid);
-
-	if (status >= 0 && hid->driver && hid->driver->resume) {
-		int ret = hid->driver->resume(hid);
-		if (ret < 0)
-			status = ret;
-	}
+	status = hid_resume_common(hid, true);
 	dev_dbg(&intf->dev, "resume status %d\n", status);
 	return 0;
 }
@@ -1563,7 +1571,7 @@
 	struct usbhid_device *usbhid = hid->driver_data;
 	int status;
 
-	clear_bit(HID_REPORTED_IDLE, &usbhid->iofl);
+	clear_bit(HID_SUSPENDED, &usbhid->iofl);
 	status = hid_post_reset(intf);
 	if (status >= 0 && hid->driver && hid->driver->reset_resume) {
 		int ret = hid->driver->reset_resume(hid);
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h
index 1883d7b..bd87a61 100644
--- a/drivers/hid/usbhid/usbhid.h
+++ b/drivers/hid/usbhid/usbhid.h
@@ -53,7 +53,6 @@
 #define HID_CLEAR_HALT		6
 #define HID_DISCONNECTED	7
 #define HID_STARTED		8
-#define HID_REPORTED_IDLE	9
 #define HID_KEYS_PRESSED	10
 #define HID_NO_BANDWIDTH	11
 
diff --git a/include/linux/Kbuild b/include/linux/Kbuild
index 8760be3..cb2a7d1 100644
--- a/include/linux/Kbuild
+++ b/include/linux/Kbuild
@@ -376,6 +376,7 @@
 header-y += types.h
 header-y += udf_fs_i.h
 header-y += udp.h
+header-y += uhid.h
 header-y += uinput.h
 header-y += uio.h
 header-y += ultrasound.h
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 449fa38..42970de 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -200,6 +200,7 @@
 #define HID_UP_DIGITIZER	0x000d0000
 #define HID_UP_PID		0x000f0000
 #define HID_UP_HPVENDOR         0xff7f0000
+#define HID_UP_HPVENDOR2        0xff010000
 #define HID_UP_MSVENDOR		0xff000000
 #define HID_UP_CUSTOM		0x00ff0000
 #define HID_UP_LOGIVENDOR	0xffbc0000
diff --git a/include/linux/uhid.h b/include/linux/uhid.h
new file mode 100644
index 0000000..9c6974f
--- /dev/null
+++ b/include/linux/uhid.h
@@ -0,0 +1,104 @@
+#ifndef __UHID_H_
+#define __UHID_H_
+
+/*
+ * User-space I/O driver support for HID subsystem
+ * Copyright (c) 2012 David Herrmann
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * Public header for user-space communication. We try to keep every structure
+ * aligned but to be safe we also use __attribute__((__packed__)). Therefore,
+ * the communication should be ABI compatible even between architectures.
+ */
+
+#include <linux/input.h>
+#include <linux/types.h>
+
+enum uhid_event_type {
+	UHID_CREATE,
+	UHID_DESTROY,
+	UHID_START,
+	UHID_STOP,
+	UHID_OPEN,
+	UHID_CLOSE,
+	UHID_OUTPUT,
+	UHID_OUTPUT_EV,
+	UHID_INPUT,
+	UHID_FEATURE,
+	UHID_FEATURE_ANSWER,
+};
+
+struct uhid_create_req {
+	__u8 name[128];
+	__u8 phys[64];
+	__u8 uniq[64];
+	__u8 __user *rd_data;
+	__u16 rd_size;
+
+	__u16 bus;
+	__u32 vendor;
+	__u32 product;
+	__u32 version;
+	__u32 country;
+} __attribute__((__packed__));
+
+#define UHID_DATA_MAX 4096
+
+enum uhid_report_type {
+	UHID_FEATURE_REPORT,
+	UHID_OUTPUT_REPORT,
+	UHID_INPUT_REPORT,
+};
+
+struct uhid_input_req {
+	__u8 data[UHID_DATA_MAX];
+	__u16 size;
+} __attribute__((__packed__));
+
+struct uhid_output_req {
+	__u8 data[UHID_DATA_MAX];
+	__u16 size;
+	__u8 rtype;
+} __attribute__((__packed__));
+
+struct uhid_output_ev_req {
+	__u16 type;
+	__u16 code;
+	__s32 value;
+} __attribute__((__packed__));
+
+struct uhid_feature_req {
+	__u32 id;
+	__u8 rnum;
+	__u8 rtype;
+} __attribute__((__packed__));
+
+struct uhid_feature_answer_req {
+	__u32 id;
+	__u16 err;
+	__u16 size;
+	__u8 data[UHID_DATA_MAX];
+};
+
+struct uhid_event {
+	__u32 type;
+
+	union {
+		struct uhid_create_req create;
+		struct uhid_input_req input;
+		struct uhid_output_req output;
+		struct uhid_output_ev_req output_ev;
+		struct uhid_feature_req feature;
+		struct uhid_feature_answer_req feature_answer;
+	} u;
+} __attribute__((__packed__));
+
+#endif /* __UHID_H_ */
diff --git a/samples/uhid/Makefile b/samples/uhid/Makefile
new file mode 100644
index 0000000..c95a696
--- /dev/null
+++ b/samples/uhid/Makefile
@@ -0,0 +1,10 @@
+# kbuild trick to avoid linker error. Can be omitted if a module is built.
+obj- := dummy.o
+
+# List of programs to build
+hostprogs-y := uhid-example
+
+# Tell kbuild to always build the programs
+always := $(hostprogs-y)
+
+HOSTCFLAGS_uhid-example.o += -I$(objtree)/usr/include
diff --git a/samples/uhid/uhid-example.c b/samples/uhid/uhid-example.c
new file mode 100644
index 0000000..03ce3c0
--- /dev/null
+++ b/samples/uhid/uhid-example.c
@@ -0,0 +1,381 @@
+/*
+ * UHID Example
+ *
+ * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.com>
+ *
+ * The code may be used by anyone for any purpose,
+ * and can serve as a starting point for developing
+ * applications using uhid.
+ */
+
+/* UHID Example
+ * This example emulates a basic 3 buttons mouse with wheel over UHID. Run this
+ * program as root and then use the following keys to control the mouse:
+ *   q: Quit the application
+ *   1: Toggle left button (down, up, ...)
+ *   2: Toggle right button
+ *   3: Toggle middle button
+ *   a: Move mouse left
+ *   d: Move mouse right
+ *   w: Move mouse up
+ *   s: Move mouse down
+ *   r: Move wheel up
+ *   f: Move wheel down
+ *
+ * If uhid is not available as /dev/uhid, then you can pass a different path as
+ * first argument.
+ * If <linux/uhid.h> is not installed in /usr, then compile this with:
+ *   gcc -o ./uhid_test -Wall -I./include ./samples/uhid/uhid-example.c
+ * And ignore the warning about kernel headers. However, it is recommended to
+ * use the installed uhid.h if available.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <linux/uhid.h>
+
+/* HID Report Desciptor
+ * We emulate a basic 3 button mouse with wheel. This is the report-descriptor
+ * as the kernel will parse it:
+ *
+ * INPUT[INPUT]
+ *   Field(0)
+ *     Physical(GenericDesktop.Pointer)
+ *     Application(GenericDesktop.Mouse)
+ *     Usage(3)
+ *       Button.0001
+ *       Button.0002
+ *       Button.0003
+ *     Logical Minimum(0)
+ *     Logical Maximum(1)
+ *     Report Size(1)
+ *     Report Count(3)
+ *     Report Offset(0)
+ *     Flags( Variable Absolute )
+ *   Field(1)
+ *     Physical(GenericDesktop.Pointer)
+ *     Application(GenericDesktop.Mouse)
+ *     Usage(3)
+ *       GenericDesktop.X
+ *       GenericDesktop.Y
+ *       GenericDesktop.Wheel
+ *     Logical Minimum(-128)
+ *     Logical Maximum(127)
+ *     Report Size(8)
+ *     Report Count(3)
+ *     Report Offset(8)
+ *     Flags( Variable Relative )
+ *
+ * This is the mapping that we expect:
+ *   Button.0001 ---> Key.LeftBtn
+ *   Button.0002 ---> Key.RightBtn
+ *   Button.0003 ---> Key.MiddleBtn
+ *   GenericDesktop.X ---> Relative.X
+ *   GenericDesktop.Y ---> Relative.Y
+ *   GenericDesktop.Wheel ---> Relative.Wheel
+ *
+ * This information can be verified by reading /sys/kernel/debug/hid/<dev>/rdesc
+ * This file should print the same information as showed above.
+ */
+
+static unsigned char rdesc[] = {
+	0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
+	0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
+	0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
+	0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01,
+	0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
+	0x15, 0x80, 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03,
+	0x81, 0x06, 0xc0, 0xc0,
+};
+
+static int uhid_write(int fd, const struct uhid_event *ev)
+{
+	ssize_t ret;
+
+	ret = write(fd, ev, sizeof(*ev));
+	if (ret < 0) {
+		fprintf(stderr, "Cannot write to uhid: %m\n");
+		return -errno;
+	} else if (ret != sizeof(*ev)) {
+		fprintf(stderr, "Wrong size written to uhid: %ld != %lu\n",
+			ret, sizeof(ev));
+		return -EFAULT;
+	} else {
+		return 0;
+	}
+}
+
+static int create(int fd)
+{
+	struct uhid_event ev;
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = UHID_CREATE;
+	strcpy((char*)ev.u.create.name, "test-uhid-device");
+	ev.u.create.rd_data = rdesc;
+	ev.u.create.rd_size = sizeof(rdesc);
+	ev.u.create.bus = BUS_USB;
+	ev.u.create.vendor = 0x15d9;
+	ev.u.create.product = 0x0a37;
+	ev.u.create.version = 0;
+	ev.u.create.country = 0;
+
+	return uhid_write(fd, &ev);
+}
+
+static void destroy(int fd)
+{
+	struct uhid_event ev;
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = UHID_DESTROY;
+
+	uhid_write(fd, &ev);
+}
+
+static int event(int fd)
+{
+	struct uhid_event ev;
+	ssize_t ret;
+
+	memset(&ev, 0, sizeof(ev));
+	ret = read(fd, &ev, sizeof(ev));
+	if (ret == 0) {
+		fprintf(stderr, "Read HUP on uhid-cdev\n");
+		return -EFAULT;
+	} else if (ret < 0) {
+		fprintf(stderr, "Cannot read uhid-cdev: %m\n");
+		return -errno;
+	} else if (ret != sizeof(ev)) {
+		fprintf(stderr, "Invalid size read from uhid-dev: %ld != %lu\n",
+			ret, sizeof(ev));
+		return -EFAULT;
+	}
+
+	switch (ev.type) {
+	case UHID_START:
+		fprintf(stderr, "UHID_START from uhid-dev\n");
+		break;
+	case UHID_STOP:
+		fprintf(stderr, "UHID_STOP from uhid-dev\n");
+		break;
+	case UHID_OPEN:
+		fprintf(stderr, "UHID_OPEN from uhid-dev\n");
+		break;
+	case UHID_CLOSE:
+		fprintf(stderr, "UHID_CLOSE from uhid-dev\n");
+		break;
+	case UHID_OUTPUT:
+		fprintf(stderr, "UHID_OUTPUT from uhid-dev\n");
+		break;
+	case UHID_OUTPUT_EV:
+		fprintf(stderr, "UHID_OUTPUT_EV from uhid-dev\n");
+		break;
+	default:
+		fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
+	}
+
+	return 0;
+}
+
+static bool btn1_down;
+static bool btn2_down;
+static bool btn3_down;
+static signed char abs_hor;
+static signed char abs_ver;
+static signed char wheel;
+
+static int send_event(int fd)
+{
+	struct uhid_event ev;
+
+	memset(&ev, 0, sizeof(ev));
+	ev.type = UHID_INPUT;
+	ev.u.input.size = 4;
+
+	if (btn1_down)
+		ev.u.input.data[0] |= 0x1;
+	if (btn2_down)
+		ev.u.input.data[0] |= 0x2;
+	if (btn3_down)
+		ev.u.input.data[0] |= 0x4;
+
+	ev.u.input.data[1] = abs_hor;
+	ev.u.input.data[2] = abs_ver;
+	ev.u.input.data[3] = wheel;
+
+	return uhid_write(fd, &ev);
+}
+
+static int keyboard(int fd)
+{
+	char buf[128];
+	ssize_t ret, i;
+
+	ret = read(STDIN_FILENO, buf, sizeof(buf));
+	if (ret == 0) {
+		fprintf(stderr, "Read HUP on stdin\n");
+		return -EFAULT;
+	} else if (ret < 0) {
+		fprintf(stderr, "Cannot read stdin: %m\n");
+		return -errno;
+	}
+
+	for (i = 0; i < ret; ++i) {
+		switch (buf[i]) {
+		case '1':
+			btn1_down = !btn1_down;
+			ret = send_event(fd);
+			if (ret)
+				return ret;
+			break;
+		case '2':
+			btn2_down = !btn2_down;
+			ret = send_event(fd);
+			if (ret)
+				return ret;
+			break;
+		case '3':
+			btn3_down = !btn3_down;
+			ret = send_event(fd);
+			if (ret)
+				return ret;
+			break;
+		case 'a':
+			abs_hor = -20;
+			ret = send_event(fd);
+			abs_hor = 0;
+			if (ret)
+				return ret;
+			break;
+		case 'd':
+			abs_hor = 20;
+			ret = send_event(fd);
+			abs_hor = 0;
+			if (ret)
+				return ret;
+			break;
+		case 'w':
+			abs_ver = -20;
+			ret = send_event(fd);
+			abs_ver = 0;
+			if (ret)
+				return ret;
+			break;
+		case 's':
+			abs_ver = 20;
+			ret = send_event(fd);
+			abs_ver = 0;
+			if (ret)
+				return ret;
+			break;
+		case 'r':
+			wheel = 1;
+			ret = send_event(fd);
+			wheel = 0;
+			if (ret)
+				return ret;
+			break;
+		case 'f':
+			wheel = -1;
+			ret = send_event(fd);
+			wheel = 0;
+			if (ret)
+				return ret;
+			break;
+		case 'q':
+			return -ECANCELED;
+		default:
+			fprintf(stderr, "Invalid input: %c\n", buf[i]);
+		}
+	}
+
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int fd;
+	const char *path = "/dev/uhid";
+	struct pollfd pfds[2];
+	int ret;
+	struct termios state;
+
+	ret = tcgetattr(STDIN_FILENO, &state);
+	if (ret) {
+		fprintf(stderr, "Cannot get tty state\n");
+	} else {
+		state.c_lflag &= ~ICANON;
+		state.c_cc[VMIN] = 1;
+		ret = tcsetattr(STDIN_FILENO, TCSANOW, &state);
+		if (ret)
+			fprintf(stderr, "Cannot set tty state\n");
+	}
+
+	if (argc >= 2) {
+		if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+			fprintf(stderr, "Usage: %s [%s]\n", argv[0], path);
+			return EXIT_SUCCESS;
+		} else {
+			path = argv[1];
+		}
+	}
+
+	fprintf(stderr, "Open uhid-cdev %s\n", path);
+	fd = open(path, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open uhid-cdev %s: %m\n", path);
+		return EXIT_FAILURE;
+	}
+
+	fprintf(stderr, "Create uhid device\n");
+	ret = create(fd);
+	if (ret) {
+		close(fd);
+		return EXIT_FAILURE;
+	}
+
+	pfds[0].fd = STDIN_FILENO;
+	pfds[0].events = POLLIN;
+	pfds[1].fd = fd;
+	pfds[1].events = POLLIN;
+
+	fprintf(stderr, "Press 'q' to quit...\n");
+	while (1) {
+		ret = poll(pfds, 2, -1);
+		if (ret < 0) {
+			fprintf(stderr, "Cannot poll for fds: %m\n");
+			break;
+		}
+		if (pfds[0].revents & POLLHUP) {
+			fprintf(stderr, "Received HUP on stdin\n");
+			break;
+		}
+		if (pfds[1].revents & POLLHUP) {
+			fprintf(stderr, "Received HUP on uhid-cdev\n");
+			break;
+		}
+
+		if (pfds[0].revents & POLLIN) {
+			ret = keyboard(fd);
+			if (ret)
+				break;
+		}
+		if (pfds[1].revents & POLLIN) {
+			ret = event(fd);
+			if (ret)
+				break;
+		}
+	}
+
+	fprintf(stderr, "Destroy uhid device\n");
+	destroy(fd);
+	return EXIT_SUCCESS;
+}