v4.19.13 snapshot.
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
new file mode 100644
index 0000000..61e1953
--- /dev/null
+++ b/drivers/hid/Kconfig
@@ -0,0 +1,1092 @@
+#
+# HID driver configuration
+#
+menu "HID support"
+     depends on INPUT
+
+config HID
+	tristate "HID bus support"
+	depends on INPUT
+	default y
+	---help---
+	  A human interface device (HID) is a type of computer device that
+	  interacts directly with and takes input from humans. The term "HID"
+	  most commonly used to refer to the USB-HID specification, but other
+	  devices (such as, but not strictly limited to, Bluetooth) are
+	  designed using HID specification (this involves certain keyboards,
+	  mice, tablets, etc). This option adds the HID bus to the kernel,
+	  together with generic HID layer code. The HID devices are added and
+	  removed from the HID bus by the transport-layer drivers, such as
+	  usbhid (USB_HID) and hidp (BT_HIDP).
+
+	  For docs and specs, see http://www.usb.org/developers/hidpage/
+
+	  If unsure, say Y.
+
+if HID
+
+config HID_BATTERY_STRENGTH
+	bool "Battery level reporting for HID devices"
+	depends on HID
+	select POWER_SUPPLY
+	default n
+	---help---
+	This option adds support of reporting battery strength (for HID devices
+	that support this feature) through power_supply class so that userspace
+	tools, such as upower, can display it.
+
+config HIDRAW
+	bool "/dev/hidraw raw HID device support"
+	depends on HID
+	---help---
+	Say Y here if you want to support HID devices (from the USB
+	specification standpoint) that aren't strictly user interface
+	devices, like monitor controls and Uninterruptable Power Supplies.
+
+	This module supports these devices separately using a separate
+	event interface on /dev/hidraw.
+
+	There is also a /dev/hiddev configuration option in the USB HID
+	configuration menu. In comparison to hiddev, this device does not process
+	the hid events at all (no parsing, no lookups). This lets applications
+	to work on raw hid events when they want to, and avoid using transport-specific
+	userspace libhid/libusb libraries.
+
+	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
+	default HID
+	---help---
+	Support for generic devices on the HID bus. This includes most
+	keyboards and mice, joysticks, tablets and digitizers.
+
+	To compile this driver as a module, choose M here: the module
+	will be called hid-generic.
+
+	If unsure, say Y.
+
+menu "Special HID drivers"
+	depends on HID
+
+config HID_A4TECH
+	tristate "A4 tech mice"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for A4 tech X5 and WOP-35 / Trust 450L mice.
+
+config HID_ACCUTOUCH
+	tristate "Accutouch touch device"
+	depends on USB_HID
+	---help---
+	  This selects a driver for the Accutouch 2216 touch controller.
+
+	  The driver works around a problem in the reported device capabilities
+	  which causes userspace to detect the device as a mouse rather than
+          a touchscreen.
+
+	  Say Y here if you have a Accutouch 2216 touch controller.
+
+config HID_ACRUX
+	tristate "ACRUX game controller support"
+	depends on HID
+	---help---
+	Say Y here if you want to enable support for ACRUX game controllers.
+
+config HID_ACRUX_FF
+	bool "ACRUX force feedback support"
+	depends on HID_ACRUX
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you want to enable force feedback support for ACRUX
+	game controllers.
+
+config HID_APPLE
+	tristate "Apple {i,Power,Mac}Books"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for some Apple devices which less or more break
+	HID specification.
+
+	Say Y here if you want support for keyboards of	Apple iBooks, PowerBooks,
+	MacBooks, MacBook Pros and Apple Aluminum.
+
+config HID_APPLEIR
+	tristate "Apple infrared receiver"
+	depends on (USB_HID)
+	---help---
+	Support for Apple infrared remote control. All the Apple computers from
+	  2005 onwards include such a port, except the unibody Macbook (2009),
+	  and Mac Pros. This receiver is also used in the Apple TV set-top box
+	  prior to the 2010 model.
+
+	Say Y here if you want support for Apple infrared remote control.
+
+config HID_ASUS
+	tristate "Asus"
+	depends on LEDS_CLASS
+	---help---
+	Support for Asus notebook built-in keyboard and touchpad via i2c, and
+	the Asus Republic of Gamers laptop keyboard special keys.
+
+	Supported devices:
+	- EeeBook X205TA
+	- VivoBook E200HA
+	- GL553V series
+	- GL753V series
+
+config HID_AUREAL
+	tristate "Aureal"
+	depends on HID
+	---help---
+	Support for Aureal Cy se W-01RN Remote Controller and other Aureal derived remotes.
+
+config HID_BELKIN
+	tristate "Belkin Flip KVM and Wireless keyboard"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Belkin Flip KVM and Wireless keyboard.
+
+config HID_BETOP_FF
+	tristate "Betop Production Inc. force feedback support"
+	depends on USB_HID
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you want to enable force feedback support for devices by
+	BETOP Production Ltd.
+	Currently the following devices are known to be supported:
+	 - BETOP 2185 PC & BFM MODE
+
+config HID_CHERRY
+	tristate "Cherry Cymotion keyboard"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Cherry Cymotion keyboard.
+
+config HID_CHICONY
+	tristate "Chicony devices"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Chicony Tactical pad and special keys on Chicony keyboards.
+
+config HID_CORSAIR
+	tristate "Corsair devices"
+	depends on HID && USB && LEDS_CLASS
+	---help---
+	Support for Corsair devices that are not fully compliant with the
+	HID standard.
+
+	Supported devices:
+	- Vengeance K90
+	- Scimitar PRO RGB
+
+config HID_COUGAR
+	tristate "Cougar devices"
+	depends on HID
+	help
+	Support for Cougar devices that are not fully compliant with the
+	HID standard.
+
+	Supported devices:
+	- Cougar 500k Gaming Keyboard
+
+config HID_PRODIKEYS
+	tristate "Prodikeys PC-MIDI Keyboard support"
+	depends on HID && SND
+	select SND_RAWMIDI
+	---help---
+	Support for Prodikeys PC-MIDI Keyboard device support.
+	Say Y here to enable support for this device.
+	- Prodikeys PC-MIDI keyboard.
+	  The Prodikeys PC-MIDI acts as a USB Audio device, with one MIDI
+	  input and one MIDI output. These MIDI jacks appear as
+	  a sound "card" in the ALSA sound system.
+	  Note: if you say N here, this device will still function as a basic
+	  multimedia keyboard, but will lack support for the musical keyboard
+	  and some additional multimedia keys.
+
+config HID_CMEDIA
+	tristate "CMedia CM6533 HID audio jack controls"
+	depends on HID
+	---help---
+	Support for CMedia CM6533 HID audio jack controls.
+
+config HID_CP2112
+	tristate "Silicon Labs CP2112 HID USB-to-SMBus Bridge support"
+	depends on USB_HID && HIDRAW && I2C && GPIOLIB
+	select GPIOLIB_IRQCHIP
+	---help---
+	Support for Silicon Labs CP2112 HID USB to SMBus Master Bridge.
+	This is a HID device driver which registers as an i2c adapter
+	and gpiochip to expose these functions of the CP2112. The
+	customizable USB descriptor fields are exposed as sysfs attributes.
+
+config HID_CYPRESS
+	tristate "Cypress mouse and barcode readers"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for cypress mouse and barcode readers.
+
+config HID_DRAGONRISE
+	tristate "DragonRise Inc. game controller"
+	depends on HID
+	---help---
+	Say Y here if you have DragonRise Inc. game controllers.
+	These might be branded as:
+	- Tesun USB-703
+	- Media-tech MT1504 "Rogue"
+	- DVTech JS19 "Gear"
+	- Defender Game Master
+
+config DRAGONRISE_FF
+	bool "DragonRise Inc. force feedback"
+	depends on HID_DRAGONRISE
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you want to enable force feedback support for DragonRise Inc.
+	game controllers.
+
+config HID_EMS_FF
+	tristate "EMS Production Inc. force feedback support"
+	depends on HID
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you want to enable force feedback support for devices by
+	EMS Production Ltd.
+	Currently the following devices are known to be supported:
+	 - Trio Linker Plus II
+
+config HID_ELAN
+	tristate "ELAN USB Touchpad Support"
+	depends on LEDS_CLASS && USB_HID
+	---help---
+	Say Y to enable support for the USB ELAN touchpad
+	Currently the following devices are known to be supported:
+	 - HP Pavilion X2 10-p0XX.
+
+config HID_ELECOM
+	tristate "ELECOM HID devices"
+	depends on HID
+	---help---
+	Support for ELECOM devices:
+	  - BM084 Bluetooth Mouse
+	  - EX-G Trackballs (M-XT3DRBK, M-XT3URBK)
+	  - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
+	  - HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
+
+config HID_ELO
+	tristate "ELO USB 4000/4500 touchscreen"
+	depends on USB_HID
+	---help---
+	Support for the ELO USB 4000/4500 touchscreens. Note that this is for
+	different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
+
+config HID_EZKEY
+	tristate "Ezkey BTC 8193 keyboard"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Ezkey BTC 8193 keyboard.
+
+config HID_GEMBIRD
+	tristate "Gembird Joypad"
+	depends on HID
+	---help---
+	Support for Gembird JPD-DualForce 2.
+
+config HID_GFRM
+	tristate "Google Fiber TV Box remote control support"
+	depends on HID
+	---help---
+	Support for Google Fiber TV Box remote controls
+
+config HID_HOLTEK
+	tristate "Holtek HID devices"
+	depends on USB_HID
+	---help---
+	Support for Holtek based devices:
+	  - Holtek On Line Grip based game controller
+	  - Trust GXT 18 Gaming Keyboard
+	  - Sharkoon Drakonia / Perixx MX-2000 gaming mice
+	  - Tracer Sniper TRM-503 / NOVA Gaming Slider X200 /
+	    Zalman ZM-GM1
+	  - SHARKOON DarkGlider Gaming mouse
+	  - LEETGION Hellion Gaming Mouse
+
+config HOLTEK_FF
+	bool "Holtek On Line Grip force feedback support"
+	depends on HID_HOLTEK
+	select INPUT_FF_MEMLESS
+	---help---
+	  Say Y here if you have a Holtek On Line Grip based game controller
+	  and want to have force feedback support for it.
+
+config HID_GOOGLE_HAMMER
+	tristate "Google Hammer Keyboard"
+	depends on USB_HID && LEDS_CLASS
+	---help---
+	Say Y here if you have a Google Hammer device.
+
+config HID_GT683R
+	tristate "MSI GT68xR LED support"
+	depends on LEDS_CLASS && USB_HID
+	---help---
+	Say Y here if you want to enable support for the three MSI GT68xR LEDs
+
+	This driver support following modes:
+	  - Normal: LEDs are fully on when enabled
+	  - Audio:  LEDs brightness depends on sound level
+	  - Breathing: LEDs brightness varies at human breathing rate
+
+	Currently the following devices are know to be supported:
+	  - MSI GT683R
+
+config HID_KEYTOUCH
+	tristate "Keytouch HID devices"
+	depends on HID
+	---help---
+	Support for Keytouch HID devices not fully compliant with
+	the specification. Currently supported:
+		- Keytouch IEC 60945
+
+config HID_KYE
+	tristate "KYE/Genius devices"
+	depends on HID
+	---help---
+	Support for KYE/Genius devices not fully compliant with HID standard:
+	- Ergo Mouse
+	- EasyPen i405X tablet
+	- MousePen i608X tablet
+	- EasyPen M610X tablet
+
+config HID_UCLOGIC
+	tristate "UC-Logic"
+	depends on USB_HID
+	---help---
+	Support for UC-Logic and Huion tablets.
+
+config HID_WALTOP
+	tristate "Waltop"
+	depends on HID
+	---help---
+	Support for Waltop tablets.
+
+config HID_GYRATION
+	tristate "Gyration remote control"
+	depends on HID
+	---help---
+	Support for Gyration remote control.
+
+config HID_ICADE
+	tristate "ION iCade arcade controller"
+	depends on HID
+	---help---
+	Support for the ION iCade arcade controller to work as a joystick.
+
+	To compile this driver as a module, choose M here: the
+	module will be called hid-icade.
+
+config HID_ITE
+	tristate "ITE devices"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for ITE devices not fully compliant with HID standard.
+
+config HID_JABRA
+	tristate "Jabra USB HID Driver"
+	depends on HID
+	---help---
+	Support for Jabra USB HID devices.
+
+	Prevents mapping of vendor defined HID usages to input events. Without
+	this driver HID	reports from Jabra devices may incorrectly be seen as
+	mouse button events.
+	Say M here if you may ever plug in a Jabra USB device.
+
+config HID_TWINHAN
+	tristate "Twinhan IR remote control"
+	depends on HID
+	---help---
+	Support for Twinhan IR remote control.
+
+config HID_KENSINGTON
+	tristate "Kensington Slimblade Trackball"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Kensington Slimblade Trackball.
+
+config HID_LCPOWER
+	tristate "LC-Power"
+	depends on HID
+	---help---
+	Support for LC-Power RC1000MCE RF remote control.
+
+config HID_LED
+	tristate "Simple RGB LED support"
+	depends on HID
+	depends on LEDS_CLASS
+	---help---
+	Support for simple RGB LED devices. Currently supported are:
+	- Riso Kagaku Webmail Notifier
+	- Dream Cheeky Webmail Notifier and Friends Alert
+	- ThingM blink(1)
+	- Delcom Visual Signal Indicator Generation 2
+	- Greynut Luxafor
+
+	To compile this driver as a module, choose M here: the
+	module will be called hid-led.
+
+config HID_LENOVO
+	tristate "Lenovo / Thinkpad devices"
+	depends on HID
+	select NEW_LEDS
+	select LEDS_CLASS
+	---help---
+	Support for IBM/Lenovo devices that are not fully compliant with HID standard.
+
+	Say Y if you want support for horizontal scrolling of the IBM/Lenovo
+	Scrollpoint mice or the non-compliant features of the Lenovo Thinkpad
+	standalone keyboards, e.g:
+	- ThinkPad USB Keyboard with TrackPoint (supports extra LEDs and trackpoint
+	  configuration)
+	- ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
+	- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
+
+config HID_LOGITECH
+	tristate "Logitech devices"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Logitech devices that are not fully compliant with HID standard.
+
+config HID_LOGITECH_DJ
+	tristate "Logitech Unifying receivers full support"
+	depends on HIDRAW
+	depends on HID_LOGITECH
+	select HID_LOGITECH_HIDPP
+	---help---
+	Say Y if you want support for Logitech Unifying receivers and devices.
+	Unifying receivers are capable of pairing up to 6 Logitech compliant
+	devices to the same receiver. Without this driver it will be handled by
+	generic USB_HID driver and all incoming events will be multiplexed
+	into a single mouse and a single keyboard device.
+
+config HID_LOGITECH_HIDPP
+	tristate "Logitech HID++ devices support"
+	depends on HID_LOGITECH
+	select POWER_SUPPLY
+	---help---
+	Support for Logitech devices relyingon the HID++ Logitech specification
+
+	Say Y if you want support for Logitech devices relying on the HID++
+	specification. Such devices are the various Logitech Touchpads (T650,
+	T651, TK820), some mice (Zone Touch mouse), or even keyboards (Solar
+	Keyboard).
+
+config LOGITECH_FF
+	bool "Logitech force feedback support"
+	depends on HID_LOGITECH
+	select INPUT_FF_MEMLESS
+	help
+	  Say Y here if you have one of these devices:
+	  - Logitech WingMan Cordless RumblePad
+	  - Logitech WingMan Cordless RumblePad 2
+	  - Logitech WingMan Force 3D
+
+	  and if you want to enable force feedback for them.
+	  Note: if you say N here, this device will still be supported, but without
+	  force feedback.
+
+config LOGIRUMBLEPAD2_FF
+	bool "Logitech force feedback support (variant 2)"
+	depends on HID_LOGITECH
+	select INPUT_FF_MEMLESS
+	help
+	  Say Y here if you want to enable force feedback support for:
+	  - Logitech RumblePad
+	  - Logitech Rumblepad 2
+	  - Logitech Formula Vibration Feedback Wheel
+
+config LOGIG940_FF
+	bool "Logitech Flight System G940 force feedback support"
+	depends on HID_LOGITECH
+	select INPUT_FF_MEMLESS
+	help
+	  Say Y here if you want to enable force feedback support for Logitech
+	  Flight System G940 devices.
+
+config LOGIWHEELS_FF
+	bool "Logitech wheels configuration and force feedback support"
+	depends on HID_LOGITECH
+	select INPUT_FF_MEMLESS
+	default LOGITECH_FF
+	help
+	  Say Y here if you want to enable force feedback and range setting(*)
+	  support for following Logitech wheels:
+	  - Logitech G25 (*)
+	  - Logitech G27 (*)
+	  - Logitech G29 (*)
+	  - Logitech Driving Force
+	  - Logitech Driving Force Pro (*)
+	  - Logitech Driving Force GT (*)
+	  - Logitech Driving Force EX/RX
+	  - Logitech Driving Force Wireless
+	  - Logitech Speed Force Wireless
+	  - Logitech MOMO Force
+	  - Logitech MOMO Racing Force
+	  - Logitech Formula Force GP
+	  - Logitech Formula Force EX/RX
+	  - Logitech Wingman Formula Force GP
+
+config HID_MAGICMOUSE
+	tristate "Apple Magic Mouse/Trackpad multi-touch support"
+	depends on HID
+	---help---
+	Support for the Apple Magic Mouse/Trackpad multi-touch.
+
+	Say Y here if you want support for the multi-touch features of the
+	Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
+
+config HID_MAYFLASH
+	tristate "Mayflash game controller adapter force feedback"
+	depends on HID
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you have HJZ Mayflash PS3 game controller adapters
+	and want to enable force feedback support.
+
+config HID_REDRAGON
+	tristate "Redragon keyboards"
+	depends on HID
+	default !EXPERT
+	---help---
+    Support for Redragon keyboards that need fix-ups to work properly.
+
+config HID_MICROSOFT
+	tristate "Microsoft non-fully HID-compliant devices"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Microsoft devices that are not fully compliant with HID standard.
+
+config HID_MONTEREY
+	tristate "Monterey Genius KB29E keyboard"
+	depends on HID
+	default !EXPERT
+	---help---
+	Support for Monterey Genius KB29E.
+
+config HID_MULTITOUCH
+	tristate "HID Multitouch panels"
+	depends on HID
+	---help---
+	  Generic support for HID multitouch panels.
+
+	  Say Y here if you have one of the following devices:
+	  - 3M PCT touch screens
+	  - ActionStar dual touch panels
+	  - Atmel panels
+	  - Cando dual touch panels
+	  - Chunghwa panels
+	  - CJTouch panels
+	  - CVTouch panels
+	  - Cypress TrueTouch panels
+	  - Elan Microelectronics touch panels
+	  - Elo TouchSystems IntelliTouch Plus panels
+	  - GeneralTouch 'Sensing Win7-TwoFinger' panels
+	  - GoodTouch panels
+	  - Hanvon dual touch panels
+	  - Ilitek dual touch panels
+	  - IrTouch Infrared USB panels
+	  - LG Display panels (Dell ST2220Tc)
+	  - Lumio CrystalTouch panels
+	  - MosArt dual-touch panels
+	  - Panasonic multitouch panels
+	  - PenMount dual touch panels
+	  - Perixx Peripad 701 touchpad
+	  - PixArt optical touch screen
+	  - Pixcir dual touch panels
+	  - Quanta panels
+	  - eGalax dual-touch panels, including the Joojoo and Wetab tablets
+	  - SiS multitouch panels
+	  - Stantum multitouch panels
+	  - Touch International Panels
+	  - Unitec Panels
+	  - Wistron optical touch panels
+	  - XAT optical touch panels
+	  - Xiroku optical touch panels
+	  - Zytronic touch panels
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hid-multitouch.
+
+config HID_NTI
+	tristate "NTI keyboard adapters"
+	---help---
+	Support for the "extra" Sun keyboard keys on keyboards attached
+	through Network Technologies USB-SUN keyboard adapters.
+
+config HID_NTRIG
+	tristate "N-Trig touch screen"
+	depends on USB_HID
+	---help---
+	Support for N-Trig touch screen.
+
+config HID_ORTEK
+	tristate "Ortek PKB-1700/WKB-2000/Skycable wireless keyboard and mouse trackpad"
+	depends on HID
+	---help---
+	There are certain devices which have LogicalMaximum wrong in the keyboard
+	usage page of their report descriptor. The most prevailing ones so far
+	are manufactured by Ortek, thus the name of the driver. Currently
+	supported devices by this driver are
+
+	   - Ortek PKB-1700
+	   - Ortek WKB-2000
+	   - Skycable wireless presenter
+
+config HID_PANTHERLORD
+	tristate "Pantherlord/GreenAsia game controller"
+	depends on HID
+	---help---
+	  Say Y here if you have a PantherLord/GreenAsia based game controller
+	  or adapter.
+
+config PANTHERLORD_FF
+	bool "Pantherlord force feedback support"
+	depends on HID_PANTHERLORD
+	select INPUT_FF_MEMLESS
+	---help---
+	  Say Y here if you have a PantherLord/GreenAsia based game controller
+	  or adapter and want to enable force feedback support for it.
+
+config HID_PENMOUNT
+	tristate "Penmount touch device"
+	depends on USB_HID
+	---help---
+	  This selects a driver for the PenMount 6000 touch controller.
+
+	  The driver works around a problem in the report descript allowing
+	  the userspace to touch events instead of mouse events.
+
+	  Say Y here if you have a Penmount based touch controller.
+
+config HID_PETALYNX
+	tristate "Petalynx Maxter remote control"
+	depends on HID
+	---help---
+	Support for Petalynx Maxter remote control.
+
+config HID_PICOLCD
+	tristate "PicoLCD (graphic version)"
+	depends on HID
+	---help---
+	  This provides support for Minibox PicoLCD devices, currently
+	  only the graphical ones are supported.
+
+	  This includes support for the following device features:
+	  - Keypad
+	  - Switching between Firmware and Flash mode
+	  - EEProm / Flash access     (via debugfs)
+	  Features selectively enabled:
+	  - Framebuffer for monochrome 256x64 display
+	  - Backlight control
+	  - Contrast control
+	  - General purpose outputs
+	  Features that are not (yet) supported:
+	  - IR
+
+config HID_PICOLCD_FB
+	bool "Framebuffer support" if EXPERT
+	default !EXPERT
+	depends on HID_PICOLCD
+	depends on HID_PICOLCD=FB || FB=y
+	select FB_DEFERRED_IO
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select FB_SYS_FOPS
+	---help---
+	  Provide access to PicoLCD's 256x64 monochrome display via a
+	  framebuffer device.
+
+config HID_PICOLCD_BACKLIGHT
+	bool "Backlight control" if EXPERT
+	default !EXPERT
+	depends on HID_PICOLCD
+	depends on HID_PICOLCD=BACKLIGHT_CLASS_DEVICE || BACKLIGHT_CLASS_DEVICE=y
+	---help---
+	  Provide access to PicoLCD's backlight control via backlight
+	  class.
+
+config HID_PICOLCD_LCD
+	bool "Contrast control" if EXPERT
+	default !EXPERT
+	depends on HID_PICOLCD
+	depends on HID_PICOLCD=LCD_CLASS_DEVICE || LCD_CLASS_DEVICE=y
+	---help---
+	  Provide access to PicoLCD's LCD contrast via lcd class.
+
+config HID_PICOLCD_LEDS
+	bool "GPO via leds class" if EXPERT
+	default !EXPERT
+	depends on HID_PICOLCD
+	depends on HID_PICOLCD=LEDS_CLASS || LEDS_CLASS=y
+	---help---
+	  Provide access to PicoLCD's GPO pins via leds class.
+
+config HID_PICOLCD_CIR
+	bool "CIR via RC class" if EXPERT
+	default !EXPERT
+	depends on HID_PICOLCD
+	depends on HID_PICOLCD=RC_CORE || RC_CORE=y
+	---help---
+	  Provide access to PicoLCD's CIR interface via remote control (LIRC).
+
+config HID_PLANTRONICS
+	tristate "Plantronics USB HID Driver"
+	depends on HID
+	---help---
+	  Provides HID support for Plantronics USB audio devices.
+	  Correctly maps vendor unique volume up/down HID usages to
+	  KEY_VOLUMEUP and KEY_VOLUMEDOWN events and prevents core mapping
+	  of other vendor unique HID usages to random mouse events.
+
+	  Say M here if you may ever plug in a Plantronics USB audio device.
+
+config HID_PRIMAX
+	tristate "Primax non-fully HID-compliant devices"
+	depends on HID
+	---help---
+	Support for Primax devices that are not fully compliant with the
+	HID standard.
+
+config HID_RETRODE
+	tristate "Retrode 2 USB adapter for vintage video games"
+	depends on USB_HID
+	---help---
+	Support for
+	  * Retrode 2 cartridge and controller adapter
+
+config HID_ROCCAT
+	tristate "Roccat device support"
+	depends on USB_HID
+	---help---
+	Support for Roccat devices.
+	Say Y here if you have a Roccat mouse or keyboard and want
+	support for its special functionalities.
+
+config HID_SAITEK
+	tristate "Saitek (Mad Catz) non-fully HID-compliant devices"
+	depends on HID
+	---help---
+	Support for Saitek devices that are not fully compliant with the
+	HID standard.
+
+	Supported devices:
+	- PS1000 Dual Analog Pad
+	- Saitek R.A.T.7, R.A.T.9, M.M.O.7 Gaming Mice
+	- Mad Catz R.A.T.5, R.A.T.9 Gaming Mice
+
+config HID_SAMSUNG
+	tristate "Samsung InfraRed remote control or keyboards"
+	depends on HID
+	---help---
+	Support for Samsung InfraRed remote control or keyboards.
+
+config HID_SONY
+	tristate "Sony PS2/3/4 accessories"
+	depends on USB_HID
+	depends on NEW_LEDS
+	depends on LEDS_CLASS
+	select POWER_SUPPLY
+	---help---
+	Support for
+
+	  * Sony PS3 6-axis controllers
+	  * Sony PS4 DualShock 4 controllers
+	  * Buzz controllers
+	  * Sony PS3 Blue-ray Disk Remote Control (Bluetooth)
+	  * Logitech Harmony adapter for Sony Playstation 3 (Bluetooth)
+
+config SONY_FF
+	bool "Sony PS2/3/4 accessories force feedback support" 
+	depends on HID_SONY
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you have a Sony PS2/3/4 accessory and want to enable
+	force feedback support for it.
+
+config HID_SPEEDLINK
+	tristate "Speedlink VAD Cezanne mouse support"
+	depends on HID
+	---help---
+	Support for Speedlink Vicious and Divine Cezanne mouse.
+
+config HID_STEAM
+	tristate "Steam Controller support"
+	depends on HID
+	select POWER_SUPPLY
+	---help---
+	Say Y here if you have a Steam Controller if you want to use it
+	without running the Steam Client. It supports both the wired and
+	the wireless adaptor.
+
+config HID_STEELSERIES
+	tristate "Steelseries SRW-S1 steering wheel support"
+	depends on HID
+	---help---
+	Support for Steelseries SRW-S1 steering wheel
+
+config HID_SUNPLUS
+	tristate "Sunplus wireless desktop"
+	depends on HID
+	---help---
+	Support for Sunplus wireless desktop.
+
+config HID_RMI
+	tristate "Synaptics RMI4 device support"
+	depends on HID
+	select RMI4_CORE
+	select RMI4_F03
+	select RMI4_F11
+	select RMI4_F12
+	select RMI4_F30
+	---help---
+	Support for Synaptics RMI4 touchpads.
+	Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
+	and want support for its special functionalities.
+
+config HID_GREENASIA
+	tristate "GreenAsia (Product ID 0x12) game controller support"
+	depends on HID
+	---help---
+	  Say Y here if you have a GreenAsia (Product ID 0x12) based game
+	  controller or adapter.
+
+config GREENASIA_FF
+	bool "GreenAsia (Product ID 0x12) force feedback support"
+	depends on HID_GREENASIA
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you have a GreenAsia (Product ID 0x12) based game controller
+	(like MANTA Warrior MM816 and SpeedLink Strike2 SL-6635) or adapter
+	and want to enable force feedback support for it.
+
+config HID_HYPERV_MOUSE
+	tristate "Microsoft Hyper-V mouse driver"
+	depends on HYPERV
+	---help---
+	Select this option to enable the Hyper-V mouse driver.
+
+config HID_SMARTJOYPLUS
+	tristate "SmartJoy PLUS PS2/USB adapter support"
+	depends on HID
+	---help---
+	Support for SmartJoy PLUS PS2/USB adapter, Super Dual Box,
+	Super Joy Box 3 Pro, Super Dual Box Pro, and Super Joy Box 5 Pro.
+
+	Note that DDR (Dance Dance Revolution) mode is not supported, nor
+	is pressure sensitive buttons on the pro models.
+
+config SMARTJOYPLUS_FF
+	bool "SmartJoy PLUS PS2/USB adapter force feedback support"
+	depends on HID_SMARTJOYPLUS
+	select INPUT_FF_MEMLESS
+	---help---
+	Say Y here if you have a SmartJoy PLUS PS2/USB adapter and want to
+	enable force feedback support for it.
+
+config HID_TIVO
+	tristate "TiVo Slide Bluetooth remote control support"
+	depends on HID
+	---help---
+	Say Y if you have a TiVo Slide Bluetooth remote control.
+
+config HID_TOPSEED
+	tristate "TopSeed Cyberlink, BTC Emprex, Conceptronic remote control support"
+	depends on HID
+	---help---
+	Say Y if you have a TopSeed Cyberlink or BTC Emprex or Conceptronic
+	CLLRCMCE remote control.
+
+config HID_THINGM
+	tristate "ThingM blink(1) USB RGB LED"
+	depends on HID
+	depends on LEDS_CLASS
+	select HID_LED
+	---help---
+	Support for the ThingM blink(1) USB RGB LED. This driver has been
+	merged into the generic hid led driver. Config symbol HID_THINGM
+	just selects HID_LED and will be removed soon.
+
+config HID_THRUSTMASTER
+	tristate "ThrustMaster devices support"
+	depends on HID
+	---help---
+	  Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or
+	  a THRUSTMASTER Ferrari GT Rumble Wheel.
+
+config THRUSTMASTER_FF
+	bool "ThrustMaster devices force feedback support"
+	depends on HID_THRUSTMASTER
+	select INPUT_FF_MEMLESS
+	---help---
+	  Say Y here if you have a THRUSTMASTER FireStore Dual Power 2 or 3,
+	  a THRUSTMASTER Dual Trigger 3-in-1 or a THRUSTMASTER Ferrari GT
+	  Rumble Force or Force Feedback Wheel.
+
+config HID_UDRAW_PS3
+	tristate "THQ PS3 uDraw tablet"
+	depends on HID
+	---help---
+	  Say Y here if you want to use the THQ uDraw gaming tablet for
+	  the PS3.
+
+config HID_WACOM
+	tristate "Wacom Intuos/Graphire tablet support (USB)"
+	depends on USB_HID
+	select POWER_SUPPLY
+	select NEW_LEDS
+	select LEDS_CLASS
+	select LEDS_TRIGGERS
+	help
+	  Say Y here if you want to use the USB or BT version of the Wacom Intuos
+	  or Graphire tablet.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wacom.
+
+config HID_WIIMOTE
+	tristate "Nintendo Wii / Wii U peripherals"
+	depends on HID
+	depends on LEDS_CLASS
+	select POWER_SUPPLY
+	select INPUT_FF_MEMLESS
+	---help---
+	Support for Nintendo Wii and Wii U Bluetooth peripherals. Supported
+	devices are the Wii Remote and its extension devices, but also devices
+	based on the Wii Remote like the Wii U Pro Controller or the
+	Wii Balance Board.
+
+	Support for all official Nintendo extensions is available, however, 3rd
+	party extensions might not be supported. Please report these devices to:
+	  http://github.com/dvdhrm/xwiimote/issues
+
+	Other Nintendo Wii U peripherals that are IEEE 802.11 based (including
+	the Wii U Gamepad) might be supported in the future. But currently
+	support is limited to Bluetooth based devices.
+
+	If unsure, say N.
+
+	To compile this driver as a module, choose M here: the
+	module will be called hid-wiimote.
+
+config HID_XINMO
+	tristate "Xin-Mo non-fully compliant devices"
+	depends on HID
+	---help---
+	Support for Xin-Mo devices that are not fully compliant with the HID
+	standard. Currently only supports the Xin-Mo Dual Arcade. Say Y here
+	if you have a Xin-Mo Dual Arcade controller.
+
+config HID_ZEROPLUS
+	tristate "Zeroplus based game controller support"
+	depends on HID
+	---help---
+	  Say Y here if you have a Zeroplus based game controller.
+
+config ZEROPLUS_FF
+	bool "Zeroplus based game controller force feedback support"
+	depends on HID_ZEROPLUS
+	select INPUT_FF_MEMLESS
+	---help---
+	  Say Y here if you have a Zeroplus based game controller and want
+	  to have force feedback support for it.
+
+config HID_ZYDACRON
+	tristate "Zydacron remote control support"
+	depends on HID
+	---help---
+	Support for Zydacron remote control.
+
+config HID_SENSOR_HUB
+	tristate "HID Sensors framework support"
+	depends on HID && HAS_IOMEM
+	select MFD_CORE
+	default n
+	---help---
+	  Support for HID Sensor framework. This creates a MFD instance
+	  for a sensor hub and identifies all the sensors connected to it.
+	  Each sensor is registered as a MFD cell, so that sensor specific
+	  processing can be done in a separate driver. Each sensor
+	  drivers can use the service provided by this driver to register
+	  for events and handle data streams. Each sensor driver can format
+	  data and present to user mode using input or IIO interface.
+
+config HID_SENSOR_CUSTOM_SENSOR
+	tristate "HID Sensors hub custom sensor support"
+	depends on HID_SENSOR_HUB
+	default n
+	---help---
+	  HID Sensor hub specification allows definition of some custom and
+	  generic sensors. Unlike other HID sensors, they can't be exported
+	  via Linux IIO because of custom fields. This is up to the manufacturer
+	  to decide how to interpret these special sensor ids and process in
+	  the user space. Currently some manufacturers are using these ids for
+	  sensor calibration and debugging other sensors. Manufacturers
+	  should't use these special custom sensor ids to export any of the
+	  standard sensors.
+	  Select this config option for custom/generic sensor support.
+
+config HID_ALPS
+	tristate "Alps HID device support"
+	depends on HID
+	---help---
+	Support for Alps I2C HID touchpads and StickPointer.
+	Say Y here if you have a Alps touchpads over i2c-hid or usbhid
+	and want support for its special functionalities.
+
+endmenu
+
+endif # HID
+
+source "drivers/hid/usbhid/Kconfig"
+
+source "drivers/hid/i2c-hid/Kconfig"
+
+source "drivers/hid/intel-ish-hid/Kconfig"
+
+endmenu
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
new file mode 100644
index 0000000..bd7ac53
--- /dev/null
+++ b/drivers/hid/Makefile
@@ -0,0 +1,130 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the HID driver
+#
+hid-y			:= hid-core.o hid-input.o hid-quirks.o
+hid-$(CONFIG_DEBUG_FS)		+= hid-debug.o
+
+obj-$(CONFIG_HID)		+= hid.o
+obj-$(CONFIG_UHID)		+= uhid.o
+
+obj-$(CONFIG_HID_GENERIC)	+= hid-generic.o
+
+hid-$(CONFIG_HIDRAW)		+= hidraw.o
+
+hid-logitech-y		:= hid-lg.o
+hid-logitech-$(CONFIG_LOGITECH_FF)	+= hid-lgff.o
+hid-logitech-$(CONFIG_LOGIRUMBLEPAD2_FF)	+= hid-lg2ff.o
+hid-logitech-$(CONFIG_LOGIG940_FF)	+= hid-lg3ff.o
+hid-logitech-$(CONFIG_LOGIWHEELS_FF)	+= hid-lg4ff.o
+
+hid-wiimote-y		:= hid-wiimote-core.o hid-wiimote-modules.o
+hid-wiimote-$(CONFIG_DEBUG_FS)	+= hid-wiimote-debug.o
+
+obj-$(CONFIG_HID_A4TECH)	+= hid-a4tech.o
+obj-$(CONFIG_HID_ACCUTOUCH)	+= hid-accutouch.o
+obj-$(CONFIG_HID_ALPS)		+= hid-alps.o
+obj-$(CONFIG_HID_ACRUX)		+= hid-axff.o
+obj-$(CONFIG_HID_APPLE)		+= hid-apple.o
+obj-$(CONFIG_HID_APPLEIR)	+= hid-appleir.o
+obj-$(CONFIG_HID_ASUS)		+= hid-asus.o
+obj-$(CONFIG_HID_AUREAL)	+= hid-aureal.o
+obj-$(CONFIG_HID_BELKIN)	+= hid-belkin.o
+obj-$(CONFIG_HID_BETOP_FF)	+= hid-betopff.o
+obj-$(CONFIG_HID_CHERRY)	+= hid-cherry.o
+obj-$(CONFIG_HID_CHICONY)	+= hid-chicony.o
+obj-$(CONFIG_HID_CMEDIA)	+= hid-cmedia.o
+obj-$(CONFIG_HID_CORSAIR)	+= hid-corsair.o
+obj-$(CONFIG_HID_COUGAR)	+= hid-cougar.o
+obj-$(CONFIG_HID_CP2112)	+= hid-cp2112.o
+obj-$(CONFIG_HID_CYPRESS)	+= hid-cypress.o
+obj-$(CONFIG_HID_DRAGONRISE)	+= hid-dr.o
+obj-$(CONFIG_HID_EMS_FF)	+= hid-emsff.o
+obj-$(CONFIG_HID_ELAN)		+= hid-elan.o
+obj-$(CONFIG_HID_ELECOM)	+= hid-elecom.o
+obj-$(CONFIG_HID_ELO)		+= hid-elo.o
+obj-$(CONFIG_HID_EZKEY)		+= hid-ezkey.o
+obj-$(CONFIG_HID_GEMBIRD)	+= hid-gembird.o
+obj-$(CONFIG_HID_GFRM)		+= hid-gfrm.o
+obj-$(CONFIG_HID_GOOGLE_HAMMER)	+= hid-google-hammer.o
+obj-$(CONFIG_HID_GT683R)	+= hid-gt683r.o
+obj-$(CONFIG_HID_GYRATION)	+= hid-gyration.o
+obj-$(CONFIG_HID_HOLTEK)	+= hid-holtek-kbd.o
+obj-$(CONFIG_HID_HOLTEK)	+= hid-holtek-mouse.o
+obj-$(CONFIG_HID_HOLTEK)	+= hid-holtekff.o
+obj-$(CONFIG_HID_HYPERV_MOUSE)	+= hid-hyperv.o
+obj-$(CONFIG_HID_ICADE)		+= hid-icade.o
+obj-$(CONFIG_HID_ITE)		+= hid-ite.o
+obj-$(CONFIG_HID_JABRA)		+= hid-jabra.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)	+= hid-lenovo.o
+obj-$(CONFIG_HID_LOGITECH)	+= hid-logitech.o
+obj-$(CONFIG_HID_LOGITECH_DJ)	+= hid-logitech-dj.o
+obj-$(CONFIG_HID_LOGITECH_HIDPP)	+= hid-logitech-hidpp.o
+obj-$(CONFIG_HID_MAGICMOUSE)	+= hid-magicmouse.o
+obj-$(CONFIG_HID_MAYFLASH)	+= hid-mf.o
+obj-$(CONFIG_HID_MICROSOFT)	+= hid-microsoft.o
+obj-$(CONFIG_HID_MONTEREY)	+= hid-monterey.o
+obj-$(CONFIG_HID_MULTITOUCH)	+= hid-multitouch.o
+obj-$(CONFIG_HID_NTI)			+= hid-nti.o
+obj-$(CONFIG_HID_NTRIG)		+= hid-ntrig.o
+obj-$(CONFIG_HID_ORTEK)		+= hid-ortek.o
+obj-$(CONFIG_HID_PRODIKEYS)	+= hid-prodikeys.o
+obj-$(CONFIG_HID_PANTHERLORD)	+= hid-pl.o
+obj-$(CONFIG_HID_PENMOUNT)	+= hid-penmount.o
+obj-$(CONFIG_HID_PETALYNX)	+= hid-petalynx.o
+obj-$(CONFIG_HID_PICOLCD)	+= hid-picolcd.o
+hid-picolcd-y			+= hid-picolcd_core.o
+hid-picolcd-$(CONFIG_HID_PICOLCD_FB)	+= hid-picolcd_fb.o
+hid-picolcd-$(CONFIG_HID_PICOLCD_BACKLIGHT)	+= hid-picolcd_backlight.o
+hid-picolcd-$(CONFIG_HID_PICOLCD_LCD)	+= hid-picolcd_lcd.o
+hid-picolcd-$(CONFIG_HID_PICOLCD_LEDS)	+= hid-picolcd_leds.o
+hid-picolcd-$(CONFIG_HID_PICOLCD_CIR)	+= hid-picolcd_cir.o
+hid-picolcd-$(CONFIG_DEBUG_FS)		+= hid-picolcd_debugfs.o
+
+obj-$(CONFIG_HID_PLANTRONICS)	+= hid-plantronics.o
+obj-$(CONFIG_HID_PRIMAX)	+= hid-primax.o
+obj-$(CONFIG_HID_REDRAGON)	+= hid-redragon.o
+obj-$(CONFIG_HID_RETRODE)	+= hid-retrode.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-konepure.o hid-roccat-kovaplus.o \
+	hid-roccat-lua.o hid-roccat-pyra.o hid-roccat-ryos.o hid-roccat-savu.o
+obj-$(CONFIG_HID_RMI)		+= hid-rmi.o
+obj-$(CONFIG_HID_SAITEK)	+= hid-saitek.o
+obj-$(CONFIG_HID_SAMSUNG)	+= hid-samsung.o
+obj-$(CONFIG_HID_SMARTJOYPLUS)	+= hid-sjoy.o
+obj-$(CONFIG_HID_SONY)		+= hid-sony.o
+obj-$(CONFIG_HID_SPEEDLINK)	+= hid-speedlink.o
+obj-$(CONFIG_HID_STEAM)		+= hid-steam.o
+obj-$(CONFIG_HID_STEELSERIES)	+= hid-steelseries.o
+obj-$(CONFIG_HID_SUNPLUS)	+= hid-sunplus.o
+obj-$(CONFIG_HID_GREENASIA)	+= hid-gaff.o
+obj-$(CONFIG_HID_THRUSTMASTER)	+= hid-tmff.o
+obj-$(CONFIG_HID_TIVO)		+= hid-tivo.o
+obj-$(CONFIG_HID_TOPSEED)	+= hid-topseed.o
+obj-$(CONFIG_HID_TWINHAN)	+= hid-twinhan.o
+obj-$(CONFIG_HID_UCLOGIC)	+= hid-uclogic.o
+obj-$(CONFIG_HID_UDRAW_PS3)	+= hid-udraw-ps3.o
+obj-$(CONFIG_HID_LED)		+= hid-led.o
+obj-$(CONFIG_HID_XINMO)		+= hid-xinmo.o
+obj-$(CONFIG_HID_ZEROPLUS)	+= hid-zpff.o
+obj-$(CONFIG_HID_ZYDACRON)	+= hid-zydacron.o
+
+wacom-objs			:= wacom_wac.o wacom_sys.o
+obj-$(CONFIG_HID_WACOM)		+= wacom.o
+obj-$(CONFIG_HID_WALTOP)	+= hid-waltop.o
+obj-$(CONFIG_HID_WIIMOTE)	+= hid-wiimote.o
+obj-$(CONFIG_HID_SENSOR_HUB)	+= hid-sensor-hub.o
+obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR)	+= hid-sensor-custom.o
+
+obj-$(CONFIG_USB_HID)		+= usbhid/
+obj-$(CONFIG_USB_MOUSE)		+= usbhid/
+obj-$(CONFIG_USB_KBD)		+= usbhid/
+
+obj-$(CONFIG_I2C_HID)		+= i2c-hid/
+
+obj-$(CONFIG_INTEL_ISH_HID)	+= intel-ish-hid/
diff --git a/drivers/hid/hid-a4tech.c b/drivers/hid/hid-a4tech.c
new file mode 100644
index 0000000..9428ea7
--- /dev/null
+++ b/drivers/hid/hid-a4tech.c
@@ -0,0 +1,138 @@
+/*
+ *  HID driver for some a4tech "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+#define A4_2WHEEL_MOUSE_HACK_7	0x01
+#define A4_2WHEEL_MOUSE_HACK_B8	0x02
+
+struct a4tech_sc {
+	unsigned long quirks;
+	unsigned int hw_wheel;
+	__s32 delayed_value;
+};
+
+static int a4_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct a4tech_sc *a4 = hid_get_drvdata(hdev);
+
+	if (usage->type == EV_REL && usage->code == REL_WHEEL)
+		set_bit(REL_HWHEEL, *bit);
+
+	if ((a4->quirks & A4_2WHEEL_MOUSE_HACK_7) && usage->hid == 0x00090007)
+		return -1;
+
+	return 0;
+}
+
+static int a4_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct a4tech_sc *a4 = hid_get_drvdata(hdev);
+	struct input_dev *input;
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type)
+		return 0;
+
+	input = field->hidinput->input;
+
+	if (a4->quirks & A4_2WHEEL_MOUSE_HACK_B8) {
+		if (usage->type == EV_REL && usage->code == REL_WHEEL) {
+			a4->delayed_value = value;
+			return 1;
+		}
+
+		if (usage->hid == 0x000100b8) {
+			input_event(input, EV_REL, value ? REL_HWHEEL :
+					REL_WHEEL, a4->delayed_value);
+			return 1;
+		}
+	}
+
+	if ((a4->quirks & A4_2WHEEL_MOUSE_HACK_7) && usage->hid == 0x00090007) {
+		a4->hw_wheel = !!value;
+		return 1;
+	}
+
+	if (usage->code == REL_WHEEL && a4->hw_wheel) {
+		input_event(input, usage->type, REL_HWHEEL, value);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int a4_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct a4tech_sc *a4;
+	int ret;
+
+	a4 = devm_kzalloc(&hdev->dev, sizeof(*a4), GFP_KERNEL);
+	if (a4 == NULL) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+
+	a4->quirks = id->driver_data;
+
+	hid_set_drvdata(hdev, a4);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id a4_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU),
+		.driver_data = A4_2WHEEL_MOUSE_HACK_7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D),
+		.driver_data = A4_2WHEEL_MOUSE_HACK_B8 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_RP_649),
+		.driver_data = A4_2WHEEL_MOUSE_HACK_B8 },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, a4_devices);
+
+static struct hid_driver a4_driver = {
+	.name = "a4tech",
+	.id_table = a4_devices,
+	.input_mapped = a4_input_mapped,
+	.event = a4_event,
+	.probe = a4_probe,
+};
+module_hid_driver(a4_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-accutouch.c b/drivers/hid/hid-accutouch.c
new file mode 100644
index 0000000..4e28716
--- /dev/null
+++ b/drivers/hid/hid-accutouch.c
@@ -0,0 +1,52 @@
+/*
+ * HID driver for Elo Accutouch touchscreens
+ *
+ * Copyright (c) 2016, Collabora Ltd.
+ * Copyright (c) 2016, General Electric Company
+ *
+ * based on hid-penmount.c
+ *  Copyright (c) 2014 Christian Gmeiner <christian.gmeiner <at> gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+static int accutouch_input_mapping(struct hid_device *hdev,
+				   struct hid_input *hi,
+				   struct hid_field *field,
+				   struct hid_usage *usage,
+				   unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id accutouch_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_ACCUTOUCH_2216) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, accutouch_devices);
+
+static struct hid_driver accutouch_driver = {
+	.name = "hid-accutouch",
+	.id_table = accutouch_devices,
+	.input_mapping = accutouch_input_mapping,
+};
+
+module_hid_driver(accutouch_driver);
+
+MODULE_AUTHOR("Martyn Welch <martyn.welch@collabora.co.uk");
+MODULE_DESCRIPTION("Elo Accutouch HID TouchScreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c
new file mode 100644
index 0000000..3cd7229
--- /dev/null
+++ b/drivers/hid/hid-alps.c
@@ -0,0 +1,858 @@
+/*
+ *  Copyright (c) 2016 Masaki Ota <masaki.ota@jp.alps.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/module.h>
+#include <asm/unaligned.h>
+#include "hid-ids.h"
+
+/* ALPS Device Product ID */
+#define HID_PRODUCT_ID_T3_BTNLESS	0xD0C0
+#define HID_PRODUCT_ID_COSMO		0x1202
+#define HID_PRODUCT_ID_U1_PTP_1		0x1207
+#define HID_PRODUCT_ID_U1			0x1209
+#define HID_PRODUCT_ID_U1_PTP_2		0x120A
+#define HID_PRODUCT_ID_U1_DUAL		0x120B
+#define HID_PRODUCT_ID_T4_BTNLESS	0x120C
+
+#define DEV_SINGLEPOINT				0x01
+#define DEV_DUALPOINT				0x02
+
+#define U1_MOUSE_REPORT_ID			0x01 /* Mouse data ReportID */
+#define U1_ABSOLUTE_REPORT_ID		0x03 /* Absolute data ReportID */
+#define U1_FEATURE_REPORT_ID		0x05 /* Feature ReportID */
+#define U1_SP_ABSOLUTE_REPORT_ID	0x06 /* Feature ReportID */
+
+#define U1_FEATURE_REPORT_LEN		0x08 /* Feature Report Length */
+#define U1_FEATURE_REPORT_LEN_ALL	0x0A
+#define U1_CMD_REGISTER_READ		0xD1
+#define U1_CMD_REGISTER_WRITE		0xD2
+
+#define	U1_DEVTYPE_SP_SUPPORT		0x10 /* SP Support */
+#define	U1_DISABLE_DEV				0x01
+#define U1_TP_ABS_MODE				0x02
+#define	U1_SP_ABS_MODE				0x80
+
+#define ADDRESS_U1_DEV_CTRL_1	0x00800040
+#define ADDRESS_U1_DEVICE_TYP	0x00800043
+#define ADDRESS_U1_NUM_SENS_X	0x00800047
+#define ADDRESS_U1_NUM_SENS_Y	0x00800048
+#define ADDRESS_U1_PITCH_SENS_X	0x00800049
+#define ADDRESS_U1_PITCH_SENS_Y	0x0080004A
+#define ADDRESS_U1_RESO_DWN_ABS 0x0080004E
+#define ADDRESS_U1_PAD_BTN		0x00800052
+#define ADDRESS_U1_SP_BTN		0x0080009F
+
+#define T4_INPUT_REPORT_LEN			sizeof(struct t4_input_report)
+#define T4_FEATURE_REPORT_LEN		T4_INPUT_REPORT_LEN
+#define T4_FEATURE_REPORT_ID		7
+#define T4_CMD_REGISTER_READ			0x08
+#define T4_CMD_REGISTER_WRITE			0x07
+
+#define T4_ADDRESS_BASE				0xC2C0
+#define PRM_SYS_CONFIG_1			(T4_ADDRESS_BASE + 0x0002)
+#define T4_PRM_FEED_CONFIG_1		(T4_ADDRESS_BASE + 0x0004)
+#define T4_PRM_FEED_CONFIG_4		(T4_ADDRESS_BASE + 0x001A)
+#define T4_PRM_ID_CONFIG_3			(T4_ADDRESS_BASE + 0x00B0)
+
+
+#define T4_FEEDCFG4_ADVANCED_ABS_ENABLE			0x01
+#define T4_I2C_ABS	0x78
+
+#define T4_COUNT_PER_ELECTRODE		256
+#define MAX_TOUCHES	5
+
+enum dev_num {
+	U1,
+	T4,
+	UNKNOWN,
+};
+/**
+ * struct u1_data
+ *
+ * @input: pointer to the kernel input device
+ * @input2: pointer to the kernel input2 device
+ * @hdev: pointer to the struct hid_device
+ *
+ * @dev_type: device type
+ * @max_fingers: total number of fingers
+ * @has_sp: boolean of sp existense
+ * @sp_btn_info: button information
+ * @x_active_len_mm: active area length of X (mm)
+ * @y_active_len_mm: active area length of Y (mm)
+ * @x_max: maximum x coordinate value
+ * @y_max: maximum y coordinate value
+ * @x_min: minimum x coordinate value
+ * @y_min: minimum y coordinate value
+ * @btn_cnt: number of buttons
+ * @sp_btn_cnt: number of stick buttons
+ */
+struct alps_dev {
+	struct input_dev *input;
+	struct input_dev *input2;
+	struct hid_device *hdev;
+
+	enum dev_num dev_type;
+	u8  max_fingers;
+	u8  has_sp;
+	u8	sp_btn_info;
+	u32	x_active_len_mm;
+	u32	y_active_len_mm;
+	u32	x_max;
+	u32	y_max;
+	u32	x_min;
+	u32	y_min;
+	u32	btn_cnt;
+	u32	sp_btn_cnt;
+};
+
+struct t4_contact_data {
+	u8  palm;
+	u8	x_lo;
+	u8	x_hi;
+	u8	y_lo;
+	u8	y_hi;
+};
+
+struct t4_input_report {
+	u8  reportID;
+	u8  numContacts;
+	struct t4_contact_data contact[5];
+	u8  button;
+	u8  track[5];
+	u8  zx[5], zy[5];
+	u8  palmTime[5];
+	u8  kilroy;
+	u16 timeStamp;
+};
+
+static u16 t4_calc_check_sum(u8 *buffer,
+		unsigned long offset, unsigned long length)
+{
+	u16 sum1 = 0xFF, sum2 = 0xFF;
+	unsigned long i = 0;
+
+	if (offset + length >= 50)
+		return 0;
+
+	while (length > 0) {
+		u32 tlen = length > 20 ? 20 : length;
+
+		length -= tlen;
+
+		do {
+			sum1 += buffer[offset + i];
+			sum2 += sum1;
+			i++;
+		} while (--tlen > 0);
+
+		sum1 = (sum1 & 0xFF) + (sum1 >> 8);
+		sum2 = (sum2 & 0xFF) + (sum2 >> 8);
+	}
+
+	sum1 = (sum1 & 0xFF) + (sum1 >> 8);
+	sum2 = (sum2 & 0xFF) + (sum2 >> 8);
+
+	return(sum2 << 8 | sum1);
+}
+
+static int t4_read_write_register(struct hid_device *hdev, u32 address,
+	u8 *read_val, u8 write_val, bool read_flag)
+{
+	int ret;
+	u16 check_sum;
+	u8 *input;
+	u8 *readbuf = NULL;
+
+	input = kzalloc(T4_FEATURE_REPORT_LEN, GFP_KERNEL);
+	if (!input)
+		return -ENOMEM;
+
+	input[0] = T4_FEATURE_REPORT_ID;
+	if (read_flag) {
+		input[1] = T4_CMD_REGISTER_READ;
+		input[8] = 0x00;
+	} else {
+		input[1] = T4_CMD_REGISTER_WRITE;
+		input[8] = write_val;
+	}
+	put_unaligned_le32(address, input + 2);
+	input[6] = 1;
+	input[7] = 0;
+
+	/* Calculate the checksum */
+	check_sum = t4_calc_check_sum(input, 1, 8);
+	input[9] = (u8)check_sum;
+	input[10] = (u8)(check_sum >> 8);
+	input[11] = 0;
+
+	ret = hid_hw_raw_request(hdev, T4_FEATURE_REPORT_ID, input,
+			T4_FEATURE_REPORT_LEN,
+			HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to read command (%d)\n", ret);
+		goto exit;
+	}
+
+	if (read_flag) {
+		readbuf = kzalloc(T4_FEATURE_REPORT_LEN, GFP_KERNEL);
+		if (!readbuf) {
+			ret = -ENOMEM;
+			goto exit;
+		}
+
+		ret = hid_hw_raw_request(hdev, T4_FEATURE_REPORT_ID, readbuf,
+				T4_FEATURE_REPORT_LEN,
+				HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed read register (%d)\n", ret);
+			goto exit_readbuf;
+		}
+
+		ret = -EINVAL;
+
+		if (*(u32 *)&readbuf[6] != address) {
+			dev_err(&hdev->dev, "read register address error (%x,%x)\n",
+				*(u32 *)&readbuf[6], address);
+			goto exit_readbuf;
+		}
+
+		if (*(u16 *)&readbuf[10] != 1) {
+			dev_err(&hdev->dev, "read register size error (%x)\n",
+				*(u16 *)&readbuf[10]);
+			goto exit_readbuf;
+		}
+
+		check_sum = t4_calc_check_sum(readbuf, 6, 7);
+		if (*(u16 *)&readbuf[13] != check_sum) {
+			dev_err(&hdev->dev, "read register checksum error (%x,%x)\n",
+				*(u16 *)&readbuf[13], check_sum);
+			goto exit_readbuf;
+		}
+
+		*read_val = readbuf[12];
+	}
+
+	ret = 0;
+
+exit_readbuf:
+	kfree(readbuf);
+exit:
+	kfree(input);
+	return ret;
+}
+
+static int u1_read_write_register(struct hid_device *hdev, u32 address,
+	u8 *read_val, u8 write_val, bool read_flag)
+{
+	int ret, i;
+	u8 check_sum;
+	u8 *input;
+	u8 *readbuf;
+
+	input = kzalloc(U1_FEATURE_REPORT_LEN, GFP_KERNEL);
+	if (!input)
+		return -ENOMEM;
+
+	input[0] = U1_FEATURE_REPORT_ID;
+	if (read_flag) {
+		input[1] = U1_CMD_REGISTER_READ;
+		input[6] = 0x00;
+	} else {
+		input[1] = U1_CMD_REGISTER_WRITE;
+		input[6] = write_val;
+	}
+
+	put_unaligned_le32(address, input + 2);
+
+	/* Calculate the checksum */
+	check_sum = U1_FEATURE_REPORT_LEN_ALL;
+	for (i = 0; i < U1_FEATURE_REPORT_LEN - 1; i++)
+		check_sum += input[i];
+
+	input[7] = check_sum;
+	ret = hid_hw_raw_request(hdev, U1_FEATURE_REPORT_ID, input,
+			U1_FEATURE_REPORT_LEN,
+			HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to read command (%d)\n", ret);
+		goto exit;
+	}
+
+	if (read_flag) {
+		readbuf = kzalloc(U1_FEATURE_REPORT_LEN, GFP_KERNEL);
+		if (!readbuf) {
+			ret = -ENOMEM;
+			goto exit;
+		}
+
+		ret = hid_hw_raw_request(hdev, U1_FEATURE_REPORT_ID, readbuf,
+				U1_FEATURE_REPORT_LEN,
+				HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed read register (%d)\n", ret);
+			kfree(readbuf);
+			goto exit;
+		}
+
+		*read_val = readbuf[6];
+
+		kfree(readbuf);
+	}
+
+	ret = 0;
+
+exit:
+	kfree(input);
+	return ret;
+}
+
+static int t4_raw_event(struct alps_dev *hdata, u8 *data, int size)
+{
+	unsigned int x, y, z;
+	int i;
+	struct t4_input_report *p_report = (struct t4_input_report *)data;
+
+	if (!data)
+		return 0;
+	for (i = 0; i < hdata->max_fingers; i++) {
+		x = p_report->contact[i].x_hi << 8 | p_report->contact[i].x_lo;
+		y = p_report->contact[i].y_hi << 8 | p_report->contact[i].y_lo;
+		y = hdata->y_max - y + hdata->y_min;
+		z = (p_report->contact[i].palm < 0x80 &&
+			p_report->contact[i].palm > 0) * 62;
+		if (x == 0xffff) {
+			x = 0;
+			y = 0;
+			z = 0;
+		}
+		input_mt_slot(hdata->input, i);
+
+		input_mt_report_slot_state(hdata->input,
+			MT_TOOL_FINGER, z != 0);
+
+		if (!z)
+			continue;
+
+		input_report_abs(hdata->input, ABS_MT_POSITION_X, x);
+		input_report_abs(hdata->input, ABS_MT_POSITION_Y, y);
+		input_report_abs(hdata->input, ABS_MT_PRESSURE, z);
+	}
+	input_mt_sync_frame(hdata->input);
+
+	input_report_key(hdata->input, BTN_LEFT, p_report->button);
+
+	input_sync(hdata->input);
+	return 1;
+}
+
+static int u1_raw_event(struct alps_dev *hdata, u8 *data, int size)
+{
+	unsigned int x, y, z;
+	int i;
+	short sp_x, sp_y;
+
+	if (!data)
+		return 0;
+	switch (data[0]) {
+	case U1_MOUSE_REPORT_ID:
+		break;
+	case U1_FEATURE_REPORT_ID:
+		break;
+	case U1_ABSOLUTE_REPORT_ID:
+		for (i = 0; i < hdata->max_fingers; i++) {
+			u8 *contact = &data[i * 5];
+
+			x = get_unaligned_le16(contact + 3);
+			y = get_unaligned_le16(contact + 5);
+			z = contact[7] & 0x7F;
+
+			input_mt_slot(hdata->input, i);
+
+			if (z != 0) {
+				input_mt_report_slot_state(hdata->input,
+					MT_TOOL_FINGER, 1);
+				input_report_abs(hdata->input,
+					ABS_MT_POSITION_X, x);
+				input_report_abs(hdata->input,
+					ABS_MT_POSITION_Y, y);
+				input_report_abs(hdata->input,
+					ABS_MT_PRESSURE, z);
+			} else {
+				input_mt_report_slot_state(hdata->input,
+					MT_TOOL_FINGER, 0);
+			}
+		}
+
+		input_mt_sync_frame(hdata->input);
+
+		input_report_key(hdata->input, BTN_LEFT,
+			data[1] & 0x1);
+		input_report_key(hdata->input, BTN_RIGHT,
+			(data[1] & 0x2));
+		input_report_key(hdata->input, BTN_MIDDLE,
+			(data[1] & 0x4));
+
+		input_sync(hdata->input);
+
+		return 1;
+
+	case U1_SP_ABSOLUTE_REPORT_ID:
+		sp_x = get_unaligned_le16(data+2);
+		sp_y = get_unaligned_le16(data+4);
+
+		sp_x = sp_x / 8;
+		sp_y = sp_y / 8;
+
+		input_report_rel(hdata->input2, REL_X, sp_x);
+		input_report_rel(hdata->input2, REL_Y, sp_y);
+
+		input_report_key(hdata->input2, BTN_LEFT,
+			data[1] & 0x1);
+		input_report_key(hdata->input2, BTN_RIGHT,
+			(data[1] & 0x2));
+		input_report_key(hdata->input2, BTN_MIDDLE,
+			(data[1] & 0x4));
+
+		input_sync(hdata->input2);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int alps_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	int ret = 0;
+	struct alps_dev *hdata = hid_get_drvdata(hdev);
+
+	switch (hdev->product) {
+	case HID_PRODUCT_ID_T4_BTNLESS:
+		ret = t4_raw_event(hdata, data, size);
+		break;
+	default:
+		ret = u1_raw_event(hdata, data, size);
+		break;
+	}
+	return ret;
+}
+
+static int __maybe_unused alps_post_reset(struct hid_device *hdev)
+{
+	int ret = -1;
+	struct alps_dev *data = hid_get_drvdata(hdev);
+
+	switch (data->dev_type) {
+	case T4:
+		ret = t4_read_write_register(hdev, T4_PRM_FEED_CONFIG_1,
+			NULL, T4_I2C_ABS, false);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed T4_PRM_FEED_CONFIG_1 (%d)\n",
+				ret);
+			goto exit;
+		}
+
+		ret = t4_read_write_register(hdev, T4_PRM_FEED_CONFIG_4,
+			NULL, T4_FEEDCFG4_ADVANCED_ABS_ENABLE, false);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed T4_PRM_FEED_CONFIG_4 (%d)\n",
+				ret);
+			goto exit;
+		}
+		break;
+	case U1:
+		ret = u1_read_write_register(hdev,
+			ADDRESS_U1_DEV_CTRL_1, NULL,
+			U1_TP_ABS_MODE | U1_SP_ABS_MODE, false);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed to change TP mode (%d)\n",
+				ret);
+			goto exit;
+		}
+		break;
+	default:
+		break;
+	}
+
+exit:
+	return ret;
+}
+
+static int __maybe_unused alps_post_resume(struct hid_device *hdev)
+{
+	return alps_post_reset(hdev);
+}
+
+static int u1_init(struct hid_device *hdev, struct alps_dev *pri_data)
+{
+	int ret;
+	u8 tmp, dev_ctrl, sen_line_num_x, sen_line_num_y;
+	u8 pitch_x, pitch_y, resolution;
+
+	/* Device initialization */
+	ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
+			&dev_ctrl, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_DEV_CTRL_1 (%d)\n", ret);
+		goto exit;
+	}
+
+	dev_ctrl &= ~U1_DISABLE_DEV;
+	dev_ctrl |= U1_TP_ABS_MODE;
+	ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
+			NULL, dev_ctrl, false);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to change TP mode (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_NUM_SENS_X,
+			&sen_line_num_x, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_NUM_SENS_X (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_NUM_SENS_Y,
+			&sen_line_num_y, 0, true);
+		if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_NUM_SENS_Y (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_PITCH_SENS_X,
+			&pitch_x, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_PITCH_SENS_X (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_PITCH_SENS_Y,
+			&pitch_y, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_PITCH_SENS_Y (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_RESO_DWN_ABS,
+		&resolution, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_RESO_DWN_ABS (%d)\n", ret);
+		goto exit;
+	}
+	pri_data->x_active_len_mm =
+		(pitch_x * (sen_line_num_x - 1)) / 10;
+	pri_data->y_active_len_mm =
+		(pitch_y * (sen_line_num_y - 1)) / 10;
+
+	pri_data->x_max =
+		(resolution << 2) * (sen_line_num_x - 1);
+	pri_data->x_min = 1;
+	pri_data->y_max =
+		(resolution << 2) * (sen_line_num_y - 1);
+	pri_data->y_min = 1;
+
+	ret = u1_read_write_register(hdev, ADDRESS_U1_PAD_BTN,
+			&tmp, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_PAD_BTN (%d)\n", ret);
+		goto exit;
+	}
+	if ((tmp & 0x0F) == (tmp & 0xF0) >> 4) {
+		pri_data->btn_cnt = (tmp & 0x0F);
+	} else {
+		/* Button pad */
+		pri_data->btn_cnt = 1;
+	}
+
+	pri_data->has_sp = 0;
+	/* Check StickPointer device */
+	ret = u1_read_write_register(hdev, ADDRESS_U1_DEVICE_TYP,
+			&tmp, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed U1_DEVICE_TYP (%d)\n", ret);
+		goto exit;
+	}
+	if (tmp & U1_DEVTYPE_SP_SUPPORT) {
+		dev_ctrl |= U1_SP_ABS_MODE;
+		ret = u1_read_write_register(hdev, ADDRESS_U1_DEV_CTRL_1,
+			NULL, dev_ctrl, false);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed SP mode (%d)\n", ret);
+			goto exit;
+		}
+
+		ret = u1_read_write_register(hdev, ADDRESS_U1_SP_BTN,
+			&pri_data->sp_btn_info, 0, true);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "failed U1_SP_BTN (%d)\n", ret);
+			goto exit;
+		}
+		pri_data->has_sp = 1;
+	}
+	pri_data->max_fingers = 5;
+exit:
+	return ret;
+}
+
+static int T4_init(struct hid_device *hdev, struct alps_dev *pri_data)
+{
+	int ret;
+	u8 tmp, sen_line_num_x, sen_line_num_y;
+
+	ret = t4_read_write_register(hdev, T4_PRM_ID_CONFIG_3, &tmp, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed T4_PRM_ID_CONFIG_3 (%d)\n", ret);
+		goto exit;
+	}
+	sen_line_num_x = 16 + ((tmp & 0x0F)  | (tmp & 0x08 ? 0xF0 : 0));
+	sen_line_num_y = 12 + (((tmp & 0xF0) >> 4)  | (tmp & 0x80 ? 0xF0 : 0));
+
+	pri_data->x_max = sen_line_num_x * T4_COUNT_PER_ELECTRODE;
+	pri_data->x_min = T4_COUNT_PER_ELECTRODE;
+	pri_data->y_max = sen_line_num_y * T4_COUNT_PER_ELECTRODE;
+	pri_data->y_min = T4_COUNT_PER_ELECTRODE;
+	pri_data->x_active_len_mm = pri_data->y_active_len_mm = 0;
+	pri_data->btn_cnt = 1;
+
+	ret = t4_read_write_register(hdev, PRM_SYS_CONFIG_1, &tmp, 0, true);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed PRM_SYS_CONFIG_1 (%d)\n", ret);
+		goto exit;
+	}
+	tmp |= 0x02;
+	ret = t4_read_write_register(hdev, PRM_SYS_CONFIG_1, NULL, tmp, false);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed PRM_SYS_CONFIG_1 (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = t4_read_write_register(hdev, T4_PRM_FEED_CONFIG_1,
+					NULL, T4_I2C_ABS, false);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed T4_PRM_FEED_CONFIG_1 (%d)\n", ret);
+		goto exit;
+	}
+
+	ret = t4_read_write_register(hdev, T4_PRM_FEED_CONFIG_4, NULL,
+				T4_FEEDCFG4_ADVANCED_ABS_ENABLE, false);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed T4_PRM_FEED_CONFIG_4 (%d)\n", ret);
+		goto exit;
+	}
+	pri_data->max_fingers = 5;
+	pri_data->has_sp = 0;
+exit:
+	return ret;
+}
+
+static int alps_sp_open(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	return hid_hw_open(hid);
+}
+
+static void alps_sp_close(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	hid_hw_close(hid);
+}
+
+static int alps_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	struct alps_dev *data = hid_get_drvdata(hdev);
+	struct input_dev *input = hi->input, *input2;
+	int ret;
+	int res_x, res_y, i;
+
+	data->input = input;
+
+	hid_dbg(hdev, "Opening low level driver\n");
+	ret = hid_hw_open(hdev);
+	if (ret)
+		return ret;
+
+	/* Allow incoming hid reports */
+	hid_device_io_start(hdev);
+	switch (data->dev_type) {
+	case T4:
+		ret = T4_init(hdev, data);
+		break;
+	case U1:
+		ret = u1_init(hdev, data);
+		break;
+	default:
+		break;
+	}
+
+	if (ret)
+		goto exit;
+
+	__set_bit(EV_ABS, input->evbit);
+	input_set_abs_params(input, ABS_MT_POSITION_X,
+						data->x_min, data->x_max, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y,
+						data->y_min, data->y_max, 0, 0);
+
+	if (data->x_active_len_mm && data->y_active_len_mm) {
+		res_x = (data->x_max - 1) / data->x_active_len_mm;
+		res_y = (data->y_max - 1) / data->y_active_len_mm;
+
+		input_abs_set_res(input, ABS_MT_POSITION_X, res_x);
+		input_abs_set_res(input, ABS_MT_POSITION_Y, res_y);
+	}
+
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0, 64, 0, 0);
+
+	input_mt_init_slots(input, data->max_fingers, INPUT_MT_POINTER);
+
+	__set_bit(EV_KEY, input->evbit);
+
+	if (data->btn_cnt == 1)
+		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
+	for (i = 0; i < data->btn_cnt; i++)
+		__set_bit(BTN_LEFT + i, input->keybit);
+
+	/* Stick device initialization */
+	if (data->has_sp) {
+		input2 = input_allocate_device();
+		if (!input2) {
+			input_free_device(input2);
+			goto exit;
+		}
+
+		data->input2 = input2;
+		input2->phys = input->phys;
+		input2->name = "DualPoint Stick";
+		input2->id.bustype = BUS_I2C;
+		input2->id.vendor  = input->id.vendor;
+		input2->id.product = input->id.product;
+		input2->id.version = input->id.version;
+		input2->dev.parent = input->dev.parent;
+
+		input_set_drvdata(input2, hdev);
+		input2->open = alps_sp_open;
+		input2->close = alps_sp_close;
+
+		__set_bit(EV_KEY, input2->evbit);
+		data->sp_btn_cnt = (data->sp_btn_info & 0x0F);
+		for (i = 0; i < data->sp_btn_cnt; i++)
+			__set_bit(BTN_LEFT + i, input2->keybit);
+
+		__set_bit(EV_REL, input2->evbit);
+		__set_bit(REL_X, input2->relbit);
+		__set_bit(REL_Y, input2->relbit);
+		__set_bit(INPUT_PROP_POINTER, input2->propbit);
+		__set_bit(INPUT_PROP_POINTING_STICK, input2->propbit);
+
+		if (input_register_device(data->input2)) {
+			input_free_device(input2);
+			goto exit;
+		}
+	}
+
+exit:
+	hid_device_io_stop(hdev);
+	hid_hw_close(hdev);
+	return ret;
+}
+
+static int alps_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct alps_dev *data = NULL;
+	int ret;
+	data = devm_kzalloc(&hdev->dev, sizeof(struct alps_dev), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->hdev = hdev;
+	hid_set_drvdata(hdev, data);
+
+	hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	switch (hdev->product) {
+	case HID_DEVICE_ID_ALPS_T4_BTNLESS:
+		data->dev_type = T4;
+		break;
+	case HID_DEVICE_ID_ALPS_U1_DUAL:
+	case HID_DEVICE_ID_ALPS_U1:
+		data->dev_type = U1;
+		break;
+	default:
+		data->dev_type = UNKNOWN;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void alps_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id alps_id[] = {
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
+		USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
+		USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1) },
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
+		USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_T4_BTNLESS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, alps_id);
+
+static struct hid_driver alps_driver = {
+	.name = "hid-alps",
+	.id_table		= alps_id,
+	.probe			= alps_probe,
+	.remove			= alps_remove,
+	.raw_event		= alps_raw_event,
+	.input_mapping		= alps_input_mapping,
+	.input_configured	= alps_input_configured,
+#ifdef CONFIG_PM
+	.resume			= alps_post_resume,
+	.reset_resume		= alps_post_reset,
+#endif
+};
+
+module_hid_driver(alps_driver);
+
+MODULE_AUTHOR("Masaki Ota <masaki.ota@jp.alps.com>");
+MODULE_DESCRIPTION("ALPS HID driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
new file mode 100644
index 0000000..1cb4199
--- /dev/null
+++ b/drivers/hid/hid-apple.c
@@ -0,0 +1,586 @@
+/*
+ *  USB HID quirks support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+#define APPLE_RDESC_JIS		0x0001
+#define APPLE_IGNORE_MOUSE	0x0002
+#define APPLE_HAS_FN		0x0004
+#define APPLE_HIDDEV		0x0008
+/* 0x0010 reserved, was: APPLE_ISO_KEYBOARD */
+#define APPLE_MIGHTYMOUSE	0x0020
+#define APPLE_INVERT_HWHEEL	0x0040
+#define APPLE_IGNORE_HIDINPUT	0x0080
+#define APPLE_NUMLOCK_EMULATION	0x0100
+
+#define APPLE_FLAG_FKEY		0x01
+
+#define HID_COUNTRY_INTERNATIONAL_ISO	13
+
+static unsigned int fnmode = 1;
+module_param(fnmode, uint, 0644);
+MODULE_PARM_DESC(fnmode, "Mode of fn key on Apple keyboards (0 = disabled, "
+		"[1] = fkeyslast, 2 = fkeysfirst)");
+
+static unsigned int iso_layout = 1;
+module_param(iso_layout, uint, 0644);
+MODULE_PARM_DESC(iso_layout, "Enable/Disable hardcoded ISO-layout of the keyboard. "
+		"(0 = disabled, [1] = enabled)");
+
+static unsigned int swap_opt_cmd;
+module_param(swap_opt_cmd, uint, 0644);
+MODULE_PARM_DESC(swap_opt_cmd, "Swap the Option (\"Alt\") and Command (\"Flag\") keys. "
+		"(For people who want to keep Windows PC keyboard muscle memory. "
+		"[0] = as-is, Mac layout. 1 = swapped, Windows layout.)");
+
+struct apple_sc {
+	unsigned long quirks;
+	unsigned int fn_on;
+	DECLARE_BITMAP(pressed_fn, KEY_CNT);
+	DECLARE_BITMAP(pressed_numlock, KEY_CNT);
+};
+
+struct apple_key_translation {
+	u16 from;
+	u16 to;
+	u8 flags;
+};
+
+static const struct apple_key_translation macbookair_fn_keys[] = {
+	{ KEY_BACKSPACE, KEY_DELETE },
+	{ KEY_ENTER,	KEY_INSERT },
+	{ KEY_F1,	KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+	{ KEY_F2,	KEY_BRIGHTNESSUP,   APPLE_FLAG_FKEY },
+	{ KEY_F3,	KEY_SCALE,          APPLE_FLAG_FKEY },
+	{ KEY_F4,	KEY_DASHBOARD,      APPLE_FLAG_FKEY },
+	{ KEY_F6,	KEY_PREVIOUSSONG,   APPLE_FLAG_FKEY },
+	{ KEY_F7,	KEY_PLAYPAUSE,      APPLE_FLAG_FKEY },
+	{ KEY_F8,	KEY_NEXTSONG,       APPLE_FLAG_FKEY },
+	{ KEY_F9,	KEY_MUTE,           APPLE_FLAG_FKEY },
+	{ KEY_F10,	KEY_VOLUMEDOWN,     APPLE_FLAG_FKEY },
+	{ KEY_F11,	KEY_VOLUMEUP,       APPLE_FLAG_FKEY },
+	{ KEY_F12,	KEY_EJECTCD,        APPLE_FLAG_FKEY },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_LEFT,	KEY_HOME },
+	{ KEY_RIGHT,	KEY_END },
+	{ }
+};
+
+static const struct apple_key_translation apple_fn_keys[] = {
+	{ KEY_BACKSPACE, KEY_DELETE },
+	{ KEY_ENTER,	KEY_INSERT },
+	{ KEY_F1,	KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+	{ KEY_F2,	KEY_BRIGHTNESSUP,   APPLE_FLAG_FKEY },
+	{ KEY_F3,	KEY_SCALE,          APPLE_FLAG_FKEY },
+	{ KEY_F4,	KEY_DASHBOARD,      APPLE_FLAG_FKEY },
+	{ KEY_F5,	KEY_KBDILLUMDOWN,   APPLE_FLAG_FKEY },
+	{ KEY_F6,	KEY_KBDILLUMUP,     APPLE_FLAG_FKEY },
+	{ KEY_F7,	KEY_PREVIOUSSONG,   APPLE_FLAG_FKEY },
+	{ KEY_F8,	KEY_PLAYPAUSE,      APPLE_FLAG_FKEY },
+	{ KEY_F9,	KEY_NEXTSONG,       APPLE_FLAG_FKEY },
+	{ KEY_F10,	KEY_MUTE,           APPLE_FLAG_FKEY },
+	{ KEY_F11,	KEY_VOLUMEDOWN,     APPLE_FLAG_FKEY },
+	{ KEY_F12,	KEY_VOLUMEUP,       APPLE_FLAG_FKEY },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_LEFT,	KEY_HOME },
+	{ KEY_RIGHT,	KEY_END },
+	{ }
+};
+
+static const struct apple_key_translation powerbook_fn_keys[] = {
+	{ KEY_BACKSPACE, KEY_DELETE },
+	{ KEY_F1,	KEY_BRIGHTNESSDOWN,     APPLE_FLAG_FKEY },
+	{ KEY_F2,	KEY_BRIGHTNESSUP,       APPLE_FLAG_FKEY },
+	{ KEY_F3,	KEY_MUTE,               APPLE_FLAG_FKEY },
+	{ KEY_F4,	KEY_VOLUMEDOWN,         APPLE_FLAG_FKEY },
+	{ KEY_F5,	KEY_VOLUMEUP,           APPLE_FLAG_FKEY },
+	{ KEY_F6,	KEY_NUMLOCK,            APPLE_FLAG_FKEY },
+	{ KEY_F7,	KEY_SWITCHVIDEOMODE,    APPLE_FLAG_FKEY },
+	{ KEY_F8,	KEY_KBDILLUMTOGGLE,     APPLE_FLAG_FKEY },
+	{ KEY_F9,	KEY_KBDILLUMDOWN,       APPLE_FLAG_FKEY },
+	{ KEY_F10,	KEY_KBDILLUMUP,         APPLE_FLAG_FKEY },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_LEFT,	KEY_HOME },
+	{ KEY_RIGHT,	KEY_END },
+	{ }
+};
+
+static const struct apple_key_translation powerbook_numlock_keys[] = {
+	{ KEY_J,	KEY_KP1 },
+	{ KEY_K,	KEY_KP2 },
+	{ KEY_L,	KEY_KP3 },
+	{ KEY_U,	KEY_KP4 },
+	{ KEY_I,	KEY_KP5 },
+	{ KEY_O,	KEY_KP6 },
+	{ KEY_7,	KEY_KP7 },
+	{ KEY_8,	KEY_KP8 },
+	{ KEY_9,	KEY_KP9 },
+	{ KEY_M,	KEY_KP0 },
+	{ KEY_DOT,	KEY_KPDOT },
+	{ KEY_SLASH,	KEY_KPPLUS },
+	{ KEY_SEMICOLON, KEY_KPMINUS },
+	{ KEY_P,	KEY_KPASTERISK },
+	{ KEY_MINUS,	KEY_KPEQUAL },
+	{ KEY_0,	KEY_KPSLASH },
+	{ KEY_F6,	KEY_NUMLOCK },
+	{ KEY_KPENTER,	KEY_KPENTER },
+	{ KEY_BACKSPACE, KEY_BACKSPACE },
+	{ }
+};
+
+static const struct apple_key_translation apple_iso_keyboard[] = {
+	{ KEY_GRAVE,	KEY_102ND },
+	{ KEY_102ND,	KEY_GRAVE },
+	{ }
+};
+
+static const struct apple_key_translation swapped_option_cmd_keys[] = {
+	{ KEY_LEFTALT,	KEY_LEFTMETA },
+	{ KEY_LEFTMETA,	KEY_LEFTALT },
+	{ KEY_RIGHTALT,	KEY_RIGHTMETA },
+	{ KEY_RIGHTMETA,KEY_RIGHTALT },
+	{ }
+};
+
+static const struct apple_key_translation *apple_find_translation(
+		const struct apple_key_translation *table, u16 from)
+{
+	const struct apple_key_translation *trans;
+
+	/* Look for the translation */
+	for (trans = table; trans->from; trans++)
+		if (trans->from == from)
+			return trans;
+
+	return NULL;
+}
+
+static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
+		struct hid_usage *usage, __s32 value)
+{
+	struct apple_sc *asc = hid_get_drvdata(hid);
+	const struct apple_key_translation *trans, *table;
+
+	if (usage->code == KEY_FN) {
+		asc->fn_on = !!value;
+		input_event(input, usage->type, usage->code, value);
+		return 1;
+	}
+
+	if (fnmode) {
+		int do_translate;
+
+		if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
+				hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
+			table = macbookair_fn_keys;
+		else if (hid->product < 0x21d || hid->product >= 0x300)
+			table = powerbook_fn_keys;
+		else
+			table = apple_fn_keys;
+
+		trans = apple_find_translation (table, usage->code);
+
+		if (trans) {
+			if (test_bit(usage->code, asc->pressed_fn))
+				do_translate = 1;
+			else if (trans->flags & APPLE_FLAG_FKEY)
+				do_translate = (fnmode == 2 && asc->fn_on) ||
+					(fnmode == 1 && !asc->fn_on);
+			else
+				do_translate = asc->fn_on;
+
+			if (do_translate) {
+				if (value)
+					set_bit(usage->code, asc->pressed_fn);
+				else
+					clear_bit(usage->code, asc->pressed_fn);
+
+				input_event(input, usage->type, trans->to,
+						value);
+
+				return 1;
+			}
+		}
+
+		if (asc->quirks & APPLE_NUMLOCK_EMULATION &&
+				(test_bit(usage->code, asc->pressed_numlock) ||
+				test_bit(LED_NUML, input->led))) {
+			trans = apple_find_translation(powerbook_numlock_keys,
+					usage->code);
+
+			if (trans) {
+				if (value)
+					set_bit(usage->code,
+							asc->pressed_numlock);
+				else
+					clear_bit(usage->code,
+							asc->pressed_numlock);
+
+				input_event(input, usage->type, trans->to,
+						value);
+			}
+
+			return 1;
+		}
+	}
+
+	if (iso_layout) {
+		if (hid->country == HID_COUNTRY_INTERNATIONAL_ISO) {
+			trans = apple_find_translation(apple_iso_keyboard, usage->code);
+			if (trans) {
+				input_event(input, usage->type, trans->to, value);
+				return 1;
+			}
+		}
+	}
+
+	if (swap_opt_cmd) {
+		trans = apple_find_translation(swapped_option_cmd_keys, usage->code);
+		if (trans) {
+			input_event(input, usage->type, trans->to, value);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int apple_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct apple_sc *asc = hid_get_drvdata(hdev);
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type)
+		return 0;
+
+	if ((asc->quirks & APPLE_INVERT_HWHEEL) &&
+			usage->code == REL_HWHEEL) {
+		input_event(field->hidinput->input, usage->type, usage->code,
+				-value);
+		return 1;
+	}
+
+	if ((asc->quirks & APPLE_HAS_FN) &&
+			hidinput_apple_event(hdev, field->hidinput->input,
+				usage, value))
+		return 1;
+
+
+	return 0;
+}
+
+/*
+ * MacBook JIS keyboard has wrong logical maximum
+ */
+static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct apple_sc *asc = hid_get_drvdata(hdev);
+
+	if ((asc->quirks & APPLE_RDESC_JIS) && *rsize >= 60 &&
+			rdesc[53] == 0x65 && rdesc[59] == 0x65) {
+		hid_info(hdev,
+			 "fixing up MacBook JIS keyboard report descriptor\n");
+		rdesc[53] = rdesc[59] = 0xe7;
+	}
+	return rdesc;
+}
+
+static void apple_setup_input(struct input_dev *input)
+{
+	const struct apple_key_translation *trans;
+
+	set_bit(KEY_NUMLOCK, input->keybit);
+
+	/* Enable all needed keys */
+	for (trans = apple_fn_keys; trans->from; trans++)
+		set_bit(trans->to, input->keybit);
+
+	for (trans = powerbook_fn_keys; trans->from; trans++)
+		set_bit(trans->to, input->keybit);
+
+	for (trans = powerbook_numlock_keys; trans->from; trans++)
+		set_bit(trans->to, input->keybit);
+
+	for (trans = apple_iso_keyboard; trans->from; trans++)
+		set_bit(trans->to, input->keybit);
+}
+
+static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if (usage->hid == (HID_UP_CUSTOM | 0x0003) ||
+			usage->hid == (HID_UP_MSVENDOR | 0x0003)) {
+		/* The fn key on Apple USB keyboards */
+		set_bit(EV_REP, hi->input->evbit);
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_FN);
+		apple_setup_input(hi->input);
+		return 1;
+	}
+
+	/* we want the hid layer to go through standard path (set and ignore) */
+	return 0;
+}
+
+static int apple_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct apple_sc *asc = hid_get_drvdata(hdev);
+
+	if (asc->quirks & APPLE_MIGHTYMOUSE) {
+		if (usage->hid == HID_GD_Z)
+			hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
+		else if (usage->code == BTN_1)
+			hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_2);
+		else if (usage->code == BTN_2)
+			hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_1);
+	}
+
+	return 0;
+}
+
+static int apple_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	unsigned long quirks = id->driver_data;
+	struct apple_sc *asc;
+	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+	int ret;
+
+	asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL);
+	if (asc == NULL) {
+		hid_err(hdev, "can't alloc apple descriptor\n");
+		return -ENOMEM;
+	}
+
+	asc->quirks = quirks;
+
+	hid_set_drvdata(hdev, asc);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	if (quirks & APPLE_HIDDEV)
+		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
+	if (quirks & APPLE_IGNORE_HIDINPUT)
+		connect_mask &= ~HID_CONNECT_HIDINPUT;
+
+	ret = hid_hw_start(hdev, connect_mask);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id apple_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE),
+		.driver_data = APPLE_MIGHTYMOUSE | APPLE_INVERT_HWHEEL },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+			APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+			APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_JIS),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN |
+			APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_JIS),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+				USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+				USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO),
+		.driver_data = APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
+		.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY),
+		.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
+
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, apple_devices);
+
+static struct hid_driver apple_driver = {
+	.name = "apple",
+	.id_table = apple_devices,
+	.report_fixup = apple_report_fixup,
+	.probe = apple_probe,
+	.event = apple_event,
+	.input_mapping = apple_input_mapping,
+	.input_mapped = apple_input_mapped,
+};
+module_hid_driver(apple_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-appleir.c b/drivers/hid/hid-appleir.c
new file mode 100644
index 0000000..eae7d52
--- /dev/null
+++ b/drivers/hid/hid-appleir.c
@@ -0,0 +1,356 @@
+/*
+ * HID driver for the apple ir device
+ *
+ * Original driver written by James McKenzie
+ * Ported to recent 2.6 kernel versions by Greg Kroah-Hartman <gregkh@suse.de>
+ * Updated to support newer remotes by Bastien Nocera <hadess@hadess.net>
+ * Ported to HID subsystem by Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *
+ * Copyright (C) 2006 James McKenzie
+ * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2008 Novell Inc.
+ * Copyright (C) 2010, 2012 Bastien Nocera <hadess@hadess.net>
+ * Copyright (C) 2013 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (C) 2013 Red Hat Inc. All Rights Reserved
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+MODULE_AUTHOR("James McKenzie");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>");
+MODULE_DESCRIPTION("HID Apple IR remote controls");
+MODULE_LICENSE("GPL");
+
+#define KEY_MASK		0x0F
+#define TWO_PACKETS_MASK	0x40
+
+/*
+ * James McKenzie has two devices both of which report the following
+ * 25 87 ee 83 0a	+
+ * 25 87 ee 83 0c	-
+ * 25 87 ee 83 09	<<
+ * 25 87 ee 83 06	>>
+ * 25 87 ee 83 05	>"
+ * 25 87 ee 83 03	menu
+ * 26 00 00 00 00	for key repeat
+ */
+
+/*
+ * Thomas Glanzmann reports the following responses
+ * 25 87 ee ca 0b	+
+ * 25 87 ee ca 0d	-
+ * 25 87 ee ca 08	<<
+ * 25 87 ee ca 07	>>
+ * 25 87 ee ca 04	>"
+ * 25 87 ee ca 02	menu
+ * 26 00 00 00 00       for key repeat
+ *
+ * He also observes the following event sometimes
+ * sent after a key is release, which I interpret
+ * as a flat battery message
+ * 25 87 e0 ca 06	flat battery
+ */
+
+/*
+ * Alexandre Karpenko reports the following responses for Device ID 0x8242
+ * 25 87 ee 47 0b	+
+ * 25 87 ee 47 0d	-
+ * 25 87 ee 47 08	<<
+ * 25 87 ee 47 07	>>
+ * 25 87 ee 47 04	>"
+ * 25 87 ee 47 02	menu
+ * 26 87 ee 47 **	for key repeat (** is the code of the key being held)
+ */
+
+/*
+ * Bastien Nocera's remote
+ * 25 87 ee 91 5f	followed by
+ * 25 87 ee 91 05	gives you >"
+ *
+ * 25 87 ee 91 5c	followed by
+ * 25 87 ee 91 05	gives you the middle button
+ */
+
+/*
+ * Fabien Andre's remote
+ * 25 87 ee a3 5e	followed by
+ * 25 87 ee a3 04	gives you >"
+ *
+ * 25 87 ee a3 5d	followed by
+ * 25 87 ee a3 04	gives you the middle button
+ */
+
+static const unsigned short appleir_key_table[] = {
+	KEY_RESERVED,
+	KEY_MENU,
+	KEY_PLAYPAUSE,
+	KEY_FORWARD,
+	KEY_BACK,
+	KEY_VOLUMEUP,
+	KEY_VOLUMEDOWN,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_RESERVED,
+	KEY_ENTER,
+	KEY_PLAYPAUSE,
+	KEY_RESERVED,
+};
+
+struct appleir {
+	struct input_dev *input_dev;
+	struct hid_device *hid;
+	unsigned short keymap[ARRAY_SIZE(appleir_key_table)];
+	struct timer_list key_up_timer;	/* timer for key up */
+	spinlock_t lock;		/* protects .current_key */
+	int current_key;		/* the currently pressed key */
+	int prev_key_idx;		/* key index in a 2 packets message */
+};
+
+static int get_key(int data)
+{
+	/*
+	 * The key is coded accross bits 2..9:
+	 *
+	 * 0x00 or 0x01 (        )	key:  0		-> KEY_RESERVED
+	 * 0x02 or 0x03 (  menu  )	key:  1		-> KEY_MENU
+	 * 0x04 or 0x05 (   >"   )	key:  2		-> KEY_PLAYPAUSE
+	 * 0x06 or 0x07 (   >>   )	key:  3		-> KEY_FORWARD
+	 * 0x08 or 0x09 (   <<   )	key:  4		-> KEY_BACK
+	 * 0x0a or 0x0b (    +   )	key:  5		-> KEY_VOLUMEUP
+	 * 0x0c or 0x0d (    -   )	key:  6		-> KEY_VOLUMEDOWN
+	 * 0x0e or 0x0f (        )	key:  7		-> KEY_RESERVED
+	 * 0x50 or 0x51 (        )	key:  8		-> KEY_RESERVED
+	 * 0x52 or 0x53 (        )	key:  9		-> KEY_RESERVED
+	 * 0x54 or 0x55 (        )	key: 10		-> KEY_RESERVED
+	 * 0x56 or 0x57 (        )	key: 11		-> KEY_RESERVED
+	 * 0x58 or 0x59 (        )	key: 12		-> KEY_RESERVED
+	 * 0x5a or 0x5b (        )	key: 13		-> KEY_RESERVED
+	 * 0x5c or 0x5d ( middle )	key: 14		-> KEY_ENTER
+	 * 0x5e or 0x5f (   >"   )	key: 15		-> KEY_PLAYPAUSE
+	 *
+	 * Packets starting with 0x5 are part of a two-packets message,
+	 * we notify the caller by sending a negative value.
+	 */
+	int key = (data >> 1) & KEY_MASK;
+
+	if ((data & TWO_PACKETS_MASK))
+		/* Part of a 2 packets-command */
+		key = -key;
+
+	return key;
+}
+
+static void key_up(struct hid_device *hid, struct appleir *appleir, int key)
+{
+	input_report_key(appleir->input_dev, key, 0);
+	input_sync(appleir->input_dev);
+}
+
+static void key_down(struct hid_device *hid, struct appleir *appleir, int key)
+{
+	input_report_key(appleir->input_dev, key, 1);
+	input_sync(appleir->input_dev);
+}
+
+static void battery_flat(struct appleir *appleir)
+{
+	dev_err(&appleir->input_dev->dev, "possible flat battery?\n");
+}
+
+static void key_up_tick(struct timer_list *t)
+{
+	struct appleir *appleir = from_timer(appleir, t, key_up_timer);
+	struct hid_device *hid = appleir->hid;
+	unsigned long flags;
+
+	spin_lock_irqsave(&appleir->lock, flags);
+	if (appleir->current_key) {
+		key_up(hid, appleir, appleir->current_key);
+		appleir->current_key = 0;
+	}
+	spin_unlock_irqrestore(&appleir->lock, flags);
+}
+
+static int appleir_raw_event(struct hid_device *hid, struct hid_report *report,
+	 u8 *data, int len)
+{
+	struct appleir *appleir = hid_get_drvdata(hid);
+	static const u8 keydown[] = { 0x25, 0x87, 0xee };
+	static const u8 keyrepeat[] = { 0x26, };
+	static const u8 flatbattery[] = { 0x25, 0x87, 0xe0 };
+	unsigned long flags;
+
+	if (len != 5)
+		goto out;
+
+	if (!memcmp(data, keydown, sizeof(keydown))) {
+		int index;
+
+		spin_lock_irqsave(&appleir->lock, flags);
+		/*
+		 * If we already have a key down, take it up before marking
+		 * this one down
+		 */
+		if (appleir->current_key)
+			key_up(hid, appleir, appleir->current_key);
+
+		/* Handle dual packet commands */
+		if (appleir->prev_key_idx > 0)
+			index = appleir->prev_key_idx;
+		else
+			index = get_key(data[4]);
+
+		if (index >= 0) {
+			appleir->current_key = appleir->keymap[index];
+
+			key_down(hid, appleir, appleir->current_key);
+			/*
+			 * Remote doesn't do key up, either pull them up, in
+			 * the test above, or here set a timer which pulls
+			 * them up after 1/8 s
+			 */
+			mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
+			appleir->prev_key_idx = 0;
+		} else
+			/* Remember key for next packet */
+			appleir->prev_key_idx = -index;
+		spin_unlock_irqrestore(&appleir->lock, flags);
+		goto out;
+	}
+
+	appleir->prev_key_idx = 0;
+
+	if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) {
+		key_down(hid, appleir, appleir->current_key);
+		/*
+		 * Remote doesn't do key up, either pull them up, in the test
+		 * above, or here set a timer which pulls them up after 1/8 s
+		 */
+		mod_timer(&appleir->key_up_timer, jiffies + HZ / 8);
+		goto out;
+	}
+
+	if (!memcmp(data, flatbattery, sizeof(flatbattery))) {
+		battery_flat(appleir);
+		/* Fall through */
+	}
+
+out:
+	/* let hidraw and hiddev handle the report */
+	return 0;
+}
+
+static int appleir_input_configured(struct hid_device *hid,
+		struct hid_input *hidinput)
+{
+	struct input_dev *input_dev = hidinput->input;
+	struct appleir *appleir = hid_get_drvdata(hid);
+	int i;
+
+	appleir->input_dev = input_dev;
+
+	input_dev->keycode = appleir->keymap;
+	input_dev->keycodesize = sizeof(unsigned short);
+	input_dev->keycodemax = ARRAY_SIZE(appleir->keymap);
+
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+
+	memcpy(appleir->keymap, appleir_key_table, sizeof(appleir->keymap));
+	for (i = 0; i < ARRAY_SIZE(appleir_key_table); i++)
+		set_bit(appleir->keymap[i], input_dev->keybit);
+	clear_bit(KEY_RESERVED, input_dev->keybit);
+
+	return 0;
+}
+
+static int appleir_input_mapping(struct hid_device *hid,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+static int appleir_probe(struct hid_device *hid, const struct hid_device_id *id)
+{
+	int ret;
+	struct appleir *appleir;
+
+	appleir = kzalloc(sizeof(struct appleir), GFP_KERNEL);
+	if (!appleir) {
+		ret = -ENOMEM;
+		goto allocfail;
+	}
+
+	appleir->hid = hid;
+
+	/* force input as some remotes bypass the input registration */
+	hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
+
+	spin_lock_init(&appleir->lock);
+	timer_setup(&appleir->key_up_timer, key_up_tick, 0);
+
+	hid_set_drvdata(hid, appleir);
+
+	ret = hid_parse(hid);
+	if (ret) {
+		hid_err(hid, "parse failed\n");
+		goto fail;
+	}
+
+	ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE);
+	if (ret) {
+		hid_err(hid, "hw start failed\n");
+		goto fail;
+	}
+
+	return 0;
+fail:
+	kfree(appleir);
+allocfail:
+	return ret;
+}
+
+static void appleir_remove(struct hid_device *hid)
+{
+	struct appleir *appleir = hid_get_drvdata(hid);
+	hid_hw_stop(hid);
+	del_timer_sync(&appleir->key_up_timer);
+	kfree(appleir);
+}
+
+static const struct hid_device_id appleir_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL5) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, appleir_devices);
+
+static struct hid_driver appleir_driver = {
+	.name = "appleir",
+	.id_table = appleir_devices,
+	.raw_event = appleir_raw_event,
+	.input_configured = appleir_input_configured,
+	.probe = appleir_probe,
+	.remove = appleir_remove,
+	.input_mapping = appleir_input_mapping,
+};
+module_hid_driver(appleir_driver);
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
new file mode 100644
index 0000000..88a5672
--- /dev/null
+++ b/drivers/hid/hid-asus.c
@@ -0,0 +1,815 @@
+/*
+ *  HID driver for Asus notebook built-in keyboard.
+ *  Fixes small logical maximum to match usage maximum.
+ *
+ *  Currently supported devices are:
+ *    EeeBook X205TA
+ *    VivoBook E200HA
+ *
+ *  Copyright (c) 2016 Yusuke Fujimaki <usk.fujimaki@gmail.com>
+ *
+ *  This module based on hid-ortek by
+ *  Copyright (c) 2010 Johnathon Harris <jmharris@gmail.com>
+ *  Copyright (c) 2011 Jiri Kosina
+ *
+ *  This module has been updated to add support for Asus i2c touchpad.
+ *
+ *  Copyright (c) 2016 Brendan McGrath <redmcg@redmandi.dyndns.org>
+ *  Copyright (c) 2016 Victor Vlasenko <victor.vlasenko@sysgears.com>
+ *  Copyright (c) 2016 Frederik Wenigwieser <frederik.wenigwieser@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/dmi.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/input/mt.h>
+#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
+
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Yusuke Fujimaki <usk.fujimaki@gmail.com>");
+MODULE_AUTHOR("Brendan McGrath <redmcg@redmandi.dyndns.org>");
+MODULE_AUTHOR("Victor Vlasenko <victor.vlasenko@sysgears.com>");
+MODULE_AUTHOR("Frederik Wenigwieser <frederik.wenigwieser@gmail.com>");
+MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
+
+#define T100_TPAD_INTF 2
+
+#define T100CHI_MOUSE_REPORT_ID 0x06
+#define FEATURE_REPORT_ID 0x0d
+#define INPUT_REPORT_ID 0x5d
+#define FEATURE_KBD_REPORT_ID 0x5a
+#define FEATURE_KBD_REPORT_SIZE 16
+
+#define SUPPORT_KBD_BACKLIGHT BIT(0)
+
+#define MAX_TOUCH_MAJOR 8
+#define MAX_PRESSURE 128
+
+#define BTN_LEFT_MASK 0x01
+#define CONTACT_TOOL_TYPE_MASK 0x80
+#define CONTACT_X_MSB_MASK 0xf0
+#define CONTACT_Y_MSB_MASK 0x0f
+#define CONTACT_TOUCH_MAJOR_MASK 0x07
+#define CONTACT_PRESSURE_MASK 0x7f
+
+#define QUIRK_FIX_NOTEBOOK_REPORT	BIT(0)
+#define QUIRK_NO_INIT_REPORTS		BIT(1)
+#define QUIRK_SKIP_INPUT_MAPPING	BIT(2)
+#define QUIRK_IS_MULTITOUCH		BIT(3)
+#define QUIRK_NO_CONSUMER_USAGES	BIT(4)
+#define QUIRK_USE_KBD_BACKLIGHT		BIT(5)
+#define QUIRK_T100_KEYBOARD		BIT(6)
+#define QUIRK_T100CHI			BIT(7)
+#define QUIRK_G752_KEYBOARD		BIT(8)
+
+#define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
+						 QUIRK_NO_INIT_REPORTS | \
+						 QUIRK_NO_CONSUMER_USAGES)
+#define I2C_TOUCHPAD_QUIRKS			(QUIRK_NO_INIT_REPORTS | \
+						 QUIRK_SKIP_INPUT_MAPPING | \
+						 QUIRK_IS_MULTITOUCH)
+
+#define TRKID_SGN       ((TRKID_MAX + 1) >> 1)
+
+struct asus_kbd_leds {
+	struct led_classdev cdev;
+	struct hid_device *hdev;
+	struct work_struct work;
+	unsigned int brightness;
+	bool removed;
+};
+
+struct asus_touchpad_info {
+	int max_x;
+	int max_y;
+	int res_x;
+	int res_y;
+	int contact_size;
+	int max_contacts;
+};
+
+struct asus_drvdata {
+	unsigned long quirks;
+	struct input_dev *input;
+	struct asus_kbd_leds *kbd_backlight;
+	const struct asus_touchpad_info *tp;
+	bool enable_backlight;
+};
+
+static const struct asus_touchpad_info asus_i2c_tp = {
+	.max_x = 2794,
+	.max_y = 1758,
+	.contact_size = 5,
+	.max_contacts = 5,
+};
+
+static const struct asus_touchpad_info asus_t100ta_tp = {
+	.max_x = 2240,
+	.max_y = 1120,
+	.res_x = 30, /* units/mm */
+	.res_y = 27, /* units/mm */
+	.contact_size = 5,
+	.max_contacts = 5,
+};
+
+static const struct asus_touchpad_info asus_t100ha_tp = {
+	.max_x = 2640,
+	.max_y = 1320,
+	.res_x = 30, /* units/mm */
+	.res_y = 29, /* units/mm */
+	.contact_size = 5,
+	.max_contacts = 5,
+};
+
+static const struct asus_touchpad_info asus_t200ta_tp = {
+	.max_x = 3120,
+	.max_y = 1716,
+	.res_x = 30, /* units/mm */
+	.res_y = 28, /* units/mm */
+	.contact_size = 5,
+	.max_contacts = 5,
+};
+
+static const struct asus_touchpad_info asus_t100chi_tp = {
+	.max_x = 2640,
+	.max_y = 1320,
+	.res_x = 31, /* units/mm */
+	.res_y = 29, /* units/mm */
+	.contact_size = 3,
+	.max_contacts = 4,
+};
+
+static void asus_report_contact_down(struct asus_drvdata *drvdat,
+		int toolType, u8 *data)
+{
+	struct input_dev *input = drvdat->input;
+	int touch_major, pressure, x, y;
+
+	x = (data[0] & CONTACT_X_MSB_MASK) << 4 | data[1];
+	y = drvdat->tp->max_y - ((data[0] & CONTACT_Y_MSB_MASK) << 8 | data[2]);
+
+	input_report_abs(input, ABS_MT_POSITION_X, x);
+	input_report_abs(input, ABS_MT_POSITION_Y, y);
+
+	if (drvdat->tp->contact_size < 5)
+		return;
+
+	if (toolType == MT_TOOL_PALM) {
+		touch_major = MAX_TOUCH_MAJOR;
+		pressure = MAX_PRESSURE;
+	} else {
+		touch_major = (data[3] >> 4) & CONTACT_TOUCH_MAJOR_MASK;
+		pressure = data[4] & CONTACT_PRESSURE_MASK;
+	}
+
+	input_report_abs(input, ABS_MT_TOUCH_MAJOR, touch_major);
+	input_report_abs(input, ABS_MT_PRESSURE, pressure);
+}
+
+/* Required for Synaptics Palm Detection */
+static void asus_report_tool_width(struct asus_drvdata *drvdat)
+{
+	struct input_mt *mt = drvdat->input->mt;
+	struct input_mt_slot *oldest;
+	int oldid, count, i;
+
+	if (drvdat->tp->contact_size < 5)
+		return;
+
+	oldest = NULL;
+	oldid = mt->trkid;
+	count = 0;
+
+	for (i = 0; i < mt->num_slots; ++i) {
+		struct input_mt_slot *ps = &mt->slots[i];
+		int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
+
+		if (id < 0)
+			continue;
+		if ((id - oldid) & TRKID_SGN) {
+			oldest = ps;
+			oldid = id;
+		}
+		count++;
+	}
+
+	if (oldest) {
+		input_report_abs(drvdat->input, ABS_TOOL_WIDTH,
+			input_mt_get_value(oldest, ABS_MT_TOUCH_MAJOR));
+	}
+}
+
+static int asus_report_input(struct asus_drvdata *drvdat, u8 *data, int size)
+{
+	int i, toolType = MT_TOOL_FINGER;
+	u8 *contactData = data + 2;
+
+	if (size != 3 + drvdat->tp->contact_size * drvdat->tp->max_contacts)
+		return 0;
+
+	for (i = 0; i < drvdat->tp->max_contacts; i++) {
+		bool down = !!(data[1] & BIT(i+3));
+
+		if (drvdat->tp->contact_size >= 5)
+			toolType = contactData[3] & CONTACT_TOOL_TYPE_MASK ?
+						MT_TOOL_PALM : MT_TOOL_FINGER;
+
+		input_mt_slot(drvdat->input, i);
+		input_mt_report_slot_state(drvdat->input, toolType, down);
+
+		if (down) {
+			asus_report_contact_down(drvdat, toolType, contactData);
+			contactData += drvdat->tp->contact_size;
+		}
+	}
+
+	input_report_key(drvdat->input, BTN_LEFT, data[1] & BTN_LEFT_MASK);
+	asus_report_tool_width(drvdat);
+
+	input_mt_sync_frame(drvdat->input);
+	input_sync(drvdat->input);
+
+	return 1;
+}
+
+static int asus_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->tp && data[0] == INPUT_REPORT_ID)
+		return asus_report_input(drvdata, data, size);
+
+	return 0;
+}
+
+static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size)
+{
+	unsigned char *dmabuf;
+	int ret;
+
+	dmabuf = kmemdup(buf, buf_size, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, dmabuf,
+				 buf_size, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	kfree(dmabuf);
+
+	return ret;
+}
+
+static int asus_kbd_init(struct hid_device *hdev)
+{
+	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
+		     0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
+	int ret;
+
+	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
+	if (ret < 0)
+		hid_err(hdev, "Asus failed to send init command: %d\n", ret);
+
+	return ret;
+}
+
+static int asus_kbd_get_functions(struct hid_device *hdev,
+				  unsigned char *kbd_func)
+{
+	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 };
+	u8 *readbuf;
+	int ret;
+
+	ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
+	if (ret < 0) {
+		hid_err(hdev, "Asus failed to send configuration command: %d\n", ret);
+		return ret;
+	}
+
+	readbuf = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
+	if (!readbuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
+				 FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		hid_err(hdev, "Asus failed to request functions: %d\n", ret);
+		kfree(readbuf);
+		return ret;
+	}
+
+	*kbd_func = readbuf[6];
+
+	kfree(readbuf);
+	return ret;
+}
+
+static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
+				   enum led_brightness brightness)
+{
+	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
+						 cdev);
+	if (led->brightness == brightness)
+		return;
+
+	led->brightness = brightness;
+	schedule_work(&led->work);
+}
+
+static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
+{
+	struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
+						 cdev);
+
+	return led->brightness;
+}
+
+static void asus_kbd_backlight_work(struct work_struct *work)
+{
+	struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
+	u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
+	int ret;
+
+	if (led->removed)
+		return;
+
+	buf[4] = led->brightness;
+
+	ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
+	if (ret < 0)
+		hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
+}
+
+static int asus_kbd_register_leds(struct hid_device *hdev)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+	unsigned char kbd_func;
+	int ret;
+
+	/* Initialize keyboard */
+	ret = asus_kbd_init(hdev);
+	if (ret < 0)
+		return ret;
+
+	/* Get keyboard functions */
+	ret = asus_kbd_get_functions(hdev, &kbd_func);
+	if (ret < 0)
+		return ret;
+
+	/* Check for backlight support */
+	if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
+		return -ENODEV;
+
+	drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
+					      sizeof(struct asus_kbd_leds),
+					      GFP_KERNEL);
+	if (!drvdata->kbd_backlight)
+		return -ENOMEM;
+
+	drvdata->kbd_backlight->removed = false;
+	drvdata->kbd_backlight->brightness = 0;
+	drvdata->kbd_backlight->hdev = hdev;
+	drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
+	drvdata->kbd_backlight->cdev.max_brightness = 3;
+	drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
+	drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
+	INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
+
+	ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
+	if (ret < 0) {
+		/* No need to have this still around */
+		devm_kfree(&hdev->dev, drvdata->kbd_backlight);
+	}
+
+	return ret;
+}
+
+static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	struct input_dev *input = hi->input;
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	/* T100CHI uses MULTI_INPUT, bind the touchpad to the mouse hid_input */
+	if (drvdata->quirks & QUIRK_T100CHI &&
+	    hi->report->id != T100CHI_MOUSE_REPORT_ID)
+		return 0;
+
+	if (drvdata->tp) {
+		int ret;
+
+		input_set_abs_params(input, ABS_MT_POSITION_X, 0,
+				     drvdata->tp->max_x, 0, 0);
+		input_set_abs_params(input, ABS_MT_POSITION_Y, 0,
+				     drvdata->tp->max_y, 0, 0);
+		input_abs_set_res(input, ABS_MT_POSITION_X, drvdata->tp->res_x);
+		input_abs_set_res(input, ABS_MT_POSITION_Y, drvdata->tp->res_y);
+
+		if (drvdata->tp->contact_size >= 5) {
+			input_set_abs_params(input, ABS_TOOL_WIDTH, 0,
+					     MAX_TOUCH_MAJOR, 0, 0);
+			input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0,
+					     MAX_TOUCH_MAJOR, 0, 0);
+			input_set_abs_params(input, ABS_MT_PRESSURE, 0,
+					      MAX_PRESSURE, 0, 0);
+		}
+
+		__set_bit(BTN_LEFT, input->keybit);
+		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
+		ret = input_mt_init_slots(input, drvdata->tp->max_contacts,
+					  INPUT_MT_POINTER);
+
+		if (ret) {
+			hid_err(hdev, "Asus input mt init slots failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	drvdata->input = input;
+
+	if (drvdata->enable_backlight && asus_kbd_register_leds(hdev))
+		hid_warn(hdev, "Failed to initialize backlight.\n");
+
+	return 0;
+}
+
+#define asus_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, \
+						    max, EV_KEY, (c))
+static int asus_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit,
+		int *max)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->quirks & QUIRK_SKIP_INPUT_MAPPING) {
+		/* Don't map anything from the HID report.
+		 * We do it all manually in asus_input_configured
+		 */
+		return -1;
+	}
+
+	/*
+	 * Ignore a bunch of bogus collections in the T100CHI descriptor.
+	 * This avoids a bunch of non-functional hid_input devices getting
+	 * created because of the T100CHI using HID_QUIRK_MULTI_INPUT.
+	 */
+	if (drvdata->quirks & QUIRK_T100CHI) {
+		if (field->application == (HID_UP_GENDESK | 0x0080) ||
+		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0024) ||
+		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0025) ||
+		    usage->hid == (HID_UP_GENDEVCTRLS | 0x0026))
+			return -1;
+		/*
+		 * We use the hid_input for the mouse report for the touchpad,
+		 * keep the left button, to avoid the core removing it.
+		 */
+		if (field->application == HID_GD_MOUSE &&
+		    usage->hid != (HID_UP_BUTTON | 1))
+			return -1;
+	}
+
+	/* ASUS-specific keyboard hotkeys */
+	if ((usage->hid & HID_USAGE_PAGE) == 0xff310000) {
+		set_bit(EV_REP, hi->input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x10: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
+		case 0x20: asus_map_key_clear(KEY_BRIGHTNESSUP);		break;
+		case 0x35: asus_map_key_clear(KEY_DISPLAY_OFF);		break;
+		case 0x6c: asus_map_key_clear(KEY_SLEEP);		break;
+		case 0x82: asus_map_key_clear(KEY_CAMERA);		break;
+		case 0x88: asus_map_key_clear(KEY_RFKILL);			break;
+		case 0xb5: asus_map_key_clear(KEY_CALC);			break;
+		case 0xc4: asus_map_key_clear(KEY_KBDILLUMUP);		break;
+		case 0xc5: asus_map_key_clear(KEY_KBDILLUMDOWN);		break;
+
+		/* ASUS touchpad toggle */
+		case 0x6b: asus_map_key_clear(KEY_F21);			break;
+
+		/* ROG key */
+		case 0x38: asus_map_key_clear(KEY_PROG1);		break;
+
+		/* Fn+C ASUS Splendid */
+		case 0xba: asus_map_key_clear(KEY_PROG2);		break;
+
+		/* Fn+Space Power4Gear Hybrid */
+		case 0x5c: asus_map_key_clear(KEY_PROG3);		break;
+
+		default:
+			/* ASUS lazily declares 256 usages, ignore the rest,
+			 * as some make the keyboard appear as a pointer device. */
+			return -1;
+		}
+
+		/*
+		 * Check and enable backlight only on devices with UsagePage ==
+		 * 0xff31 to avoid initializing the keyboard firmware multiple
+		 * times on devices with multiple HID descriptors but same
+		 * PID/VID.
+		 */
+		if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
+			drvdata->enable_backlight = true;
+
+		return 1;
+	}
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR) {
+		set_bit(EV_REP, hi->input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0xff01: asus_map_key_clear(BTN_1);	break;
+		case 0xff02: asus_map_key_clear(BTN_2);	break;
+		case 0xff03: asus_map_key_clear(BTN_3);	break;
+		case 0xff04: asus_map_key_clear(BTN_4);	break;
+		case 0xff05: asus_map_key_clear(BTN_5);	break;
+		case 0xff06: asus_map_key_clear(BTN_6);	break;
+		case 0xff07: asus_map_key_clear(BTN_7);	break;
+		case 0xff08: asus_map_key_clear(BTN_8);	break;
+		case 0xff09: asus_map_key_clear(BTN_9);	break;
+		case 0xff0a: asus_map_key_clear(BTN_A);	break;
+		case 0xff0b: asus_map_key_clear(BTN_B);	break;
+		case 0x00f1: asus_map_key_clear(KEY_WLAN);	break;
+		case 0x00f2: asus_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
+		case 0x00f3: asus_map_key_clear(KEY_BRIGHTNESSUP);	break;
+		case 0x00f4: asus_map_key_clear(KEY_DISPLAY_OFF);	break;
+		case 0x00f7: asus_map_key_clear(KEY_CAMERA);	break;
+		case 0x00f8: asus_map_key_clear(KEY_PROG1);	break;
+		default:
+			return 0;
+		}
+
+		return 1;
+	}
+
+	if (drvdata->quirks & QUIRK_NO_CONSUMER_USAGES &&
+		(usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		switch (usage->hid & HID_USAGE) {
+		case 0xe2: /* Mute */
+		case 0xe9: /* Volume up */
+		case 0xea: /* Volume down */
+			return 0;
+		default:
+			/* Ignore dummy Consumer usages which make the
+			 * keyboard incorrectly appear as a pointer device.
+			 */
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int asus_start_multitouch(struct hid_device *hdev)
+{
+	int ret;
+	static const unsigned char buf[] = {
+		FEATURE_REPORT_ID, 0x00, 0x03, 0x01, 0x00
+	};
+	unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
+
+	if (!dmabuf) {
+		ret = -ENOMEM;
+		hid_err(hdev, "Asus failed to alloc dma buf: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, sizeof(buf),
+					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	kfree(dmabuf);
+
+	if (ret != sizeof(buf)) {
+		hid_err(hdev, "Asus failed to start multitouch: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->tp)
+		return asus_start_multitouch(hdev);
+
+	return 0;
+}
+
+static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct asus_drvdata *drvdata;
+
+	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL) {
+		hid_err(hdev, "Can't alloc Asus descriptor\n");
+		return -ENOMEM;
+	}
+
+	hid_set_drvdata(hdev, drvdata);
+
+	drvdata->quirks = id->driver_data;
+
+	if (drvdata->quirks & QUIRK_IS_MULTITOUCH)
+		drvdata->tp = &asus_i2c_tp;
+
+	if (drvdata->quirks & QUIRK_T100_KEYBOARD) {
+		struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+		if (intf->altsetting->desc.bInterfaceNumber == T100_TPAD_INTF) {
+			drvdata->quirks = QUIRK_SKIP_INPUT_MAPPING;
+			/*
+			 * The T100HA uses the same USB-ids as the T100TAF and
+			 * the T200TA uses the same USB-ids as the T100TA, while
+			 * both have different max x/y values as the T100TA[F].
+			 */
+			if (dmi_match(DMI_PRODUCT_NAME, "T100HAN"))
+				drvdata->tp = &asus_t100ha_tp;
+			else if (dmi_match(DMI_PRODUCT_NAME, "T200TA"))
+				drvdata->tp = &asus_t200ta_tp;
+			else
+				drvdata->tp = &asus_t100ta_tp;
+		}
+	}
+
+	if (drvdata->quirks & QUIRK_T100CHI) {
+		/*
+		 * All functionality is on a single HID interface and for
+		 * userspace the touchpad must be a separate input_dev.
+		 */
+		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+		drvdata->tp = &asus_t100chi_tp;
+	}
+
+	if (drvdata->quirks & QUIRK_NO_INIT_REPORTS)
+		hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "Asus hid parse failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "Asus hw start failed: %d\n", ret);
+		return ret;
+	}
+
+	if (!drvdata->input) {
+		hid_err(hdev, "Asus input not registered\n");
+		ret = -ENOMEM;
+		goto err_stop_hw;
+	}
+
+	if (drvdata->tp) {
+		drvdata->input->name = "Asus TouchPad";
+	} else {
+		drvdata->input->name = "Asus Keyboard";
+	}
+
+	if (drvdata->tp) {
+		ret = asus_start_multitouch(hdev);
+		if (ret)
+			goto err_stop_hw;
+	}
+
+	return 0;
+err_stop_hw:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void asus_remove(struct hid_device *hdev)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->kbd_backlight) {
+		drvdata->kbd_backlight->removed = true;
+		cancel_work_sync(&drvdata->kbd_backlight->work);
+	}
+
+	hid_hw_stop(hdev);
+}
+
+static const __u8 asus_g752_fixed_rdesc[] = {
+        0x19, 0x00,			/*   Usage Minimum (0x00)       */
+        0x2A, 0xFF, 0x00,		/*   Usage Maximum (0xFF)       */
+};
+
+static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->quirks & QUIRK_FIX_NOTEBOOK_REPORT &&
+			*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x65) {
+		hid_info(hdev, "Fixing up Asus notebook report descriptor\n");
+		rdesc[55] = 0xdd;
+	}
+	/* For the T100TA/T200TA keyboard dock */
+	if (drvdata->quirks & QUIRK_T100_KEYBOARD &&
+		 (*rsize == 76 || *rsize == 101) &&
+		 rdesc[73] == 0x81 && rdesc[74] == 0x01) {
+		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
+		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
+	}
+	/* For the T100CHI keyboard dock */
+	if (drvdata->quirks & QUIRK_T100CHI &&
+		 *rsize == 403 && rdesc[388] == 0x09 && rdesc[389] == 0x76) {
+		/*
+		 * Change Usage (76h) to Usage Minimum (00h), Usage Maximum
+		 * (FFh) and clear the flags in the Input() byte.
+		 * Note the descriptor has a bogus 0 byte at the end so we
+		 * only need 1 extra byte.
+		 */
+		*rsize = 404;
+		rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
+		if (!rdesc)
+			return NULL;
+
+		hid_info(hdev, "Fixing up T100CHI keyb report descriptor\n");
+		memmove(rdesc + 392, rdesc + 390, 12);
+		rdesc[388] = 0x19;
+		rdesc[389] = 0x00;
+		rdesc[390] = 0x29;
+		rdesc[391] = 0xff;
+		rdesc[402] = 0x00;
+	}
+	if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
+		 *rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
+		/* report is missing usage mninum and maximum */
+		__u8 *new_rdesc;
+		size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);
+
+		new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
+		if (new_rdesc == NULL)
+			return rdesc;
+
+		hid_info(hdev, "Fixing up Asus G752 keyb report descriptor\n");
+		/* copy the valid part */
+		memcpy(new_rdesc, rdesc, 61);
+		/* insert missing part */
+		memcpy(new_rdesc + 61, asus_g752_fixed_rdesc, sizeof(asus_g752_fixed_rdesc));
+		/* copy remaining data */
+		memcpy(new_rdesc + 61 + sizeof(asus_g752_fixed_rdesc), rdesc + 61, *rsize - 61);
+
+		*rsize = new_size;
+		rdesc = new_rdesc;
+	}
+
+	return rdesc;
+}
+
+static const struct hid_device_id asus_devices[] = {
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD), I2C_KEYBOARD_QUIRKS},
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD), I2C_TOUCHPAD_QUIRKS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1), QUIRK_USE_KBD_BACKLIGHT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2), QUIRK_USE_KBD_BACKLIGHT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3), QUIRK_G752_KEYBOARD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD),
+	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD),
+	  QUIRK_T100_KEYBOARD | QUIRK_NO_CONSUMER_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_ASUS_AK1D) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_ASUS_MD_5110) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_ASUS_MD_5112) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK,
+		USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD), QUIRK_T100CHI },
+
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, asus_devices);
+
+static struct hid_driver asus_driver = {
+	.name			= "asus",
+	.id_table		= asus_devices,
+	.report_fixup		= asus_report_fixup,
+	.probe                  = asus_probe,
+	.remove			= asus_remove,
+	.input_mapping          = asus_input_mapping,
+	.input_configured       = asus_input_configured,
+#ifdef CONFIG_PM
+	.reset_resume           = asus_reset_resume,
+#endif
+	.raw_event		= asus_raw_event
+};
+module_hid_driver(asus_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-aureal.c b/drivers/hid/hid-aureal.c
new file mode 100644
index 0000000..3280aff
--- /dev/null
+++ b/drivers/hid/hid-aureal.c
@@ -0,0 +1,43 @@
+/*
+ *  HID driver for Aureal Cy se W-01RN USB_V3.1 devices
+ *
+ *  Copyright (c) 2010 Franco Catrin <fcatrin@gmail.com>
+ *  Copyright (c) 2010 Ben Cropley <bcropley@internode.on.net>
+ *
+ *  Based on HID sunplus driver by
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static __u8 *aureal_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
+		dev_info(&hdev->dev, "fixing Aureal Cy se W-01RN USB_V3.1 report descriptor.\n");
+		rdesc[53] = 0x65;
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id aureal_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, aureal_devices);
+
+static struct hid_driver aureal_driver = {
+	.name = "aureal",
+	.id_table = aureal_devices,
+	.report_fixup = aureal_report_fixup,
+};
+module_hid_driver(aureal_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-axff.c b/drivers/hid/hid-axff.c
new file mode 100644
index 0000000..a594e47
--- /dev/null
+++ b/drivers/hid/hid-axff.c
@@ -0,0 +1,198 @@
+/*
+ * Force feedback support for ACRUX game controllers
+ *
+ * From what I have gathered, these devices are mass produced in China
+ * by several vendors. They often share the same design as the original
+ * Xbox 360 controller.
+ *
+ * 1a34:0802 "ACRUX USB GAMEPAD 8116"
+ *  - tested with an EXEQ EQ-PCU-02090 game controller.
+ *
+ * Copyright (c) 2010 Sergei Kolzun <x0r@dv-life.ru>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#ifdef CONFIG_HID_ACRUX_FF
+
+struct axff_device {
+	struct hid_report *report;
+};
+
+static int axff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct axff_device *axff = data;
+	struct hid_report *report = axff->report;
+	int field_count = 0;
+	int left, right;
+	int i, j;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+
+	dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+	left = left * 0xff / 0xffff;
+	right = right * 0xff / 0xffff;
+
+	for (i = 0; i < report->maxfield; i++) {
+		for (j = 0; j < report->field[i]->report_count; j++) {
+			report->field[i]->value[j] =
+				field_count % 2 ? right : left;
+			field_count++;
+		}
+	}
+
+	dbg_hid("running with 0x%02x 0x%02x", left, right);
+	hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int axff_init(struct hid_device *hid)
+{
+	struct axff_device *axff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_first_entry(&hid->inputs, struct hid_input, list);
+	struct list_head *report_list =&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int field_count = 0;
+	int i, j;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	report = list_first_entry(report_list, struct hid_report, list);
+	for (i = 0; i < report->maxfield; i++) {
+		for (j = 0; j < report->field[i]->report_count; j++) {
+			report->field[i]->value[j] = 0x00;
+			field_count++;
+		}
+	}
+
+	if (field_count < 4 && hid->product != 0xf705) {
+		hid_err(hid, "not enough fields in the report: %d\n",
+			field_count);
+		return -ENODEV;
+	}
+
+	axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL);
+	if (!axff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, axff, axff_play);
+	if (error)
+		goto err_free_mem;
+
+	axff->report = report;
+	hid_hw_request(hid, axff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "Force Feedback for ACRUX game controllers by Sergei Kolzun <x0r@dv-life.ru>\n");
+
+	return 0;
+
+err_free_mem:
+	kfree(axff);
+	return error;
+}
+#else
+static inline int axff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+static int ax_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int error;
+
+	dev_dbg(&hdev->dev, "ACRUX HID hardware probe...\n");
+
+	error = hid_parse(hdev);
+	if (error) {
+		hid_err(hdev, "parse failed\n");
+		return error;
+	}
+
+	error = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (error) {
+		hid_err(hdev, "hw start failed\n");
+		return error;
+	}
+
+	error = axff_init(hdev);
+	if (error) {
+		/*
+		 * Do not fail device initialization completely as device
+		 * may still be partially operable, just warn.
+		 */
+		hid_warn(hdev,
+			 "Failed to enable force feedback support, error: %d\n",
+			 error);
+	}
+
+	/*
+	 * We need to start polling device right away, otherwise
+	 * it will go into a coma.
+	 */
+	error = hid_hw_open(hdev);
+	if (error) {
+		dev_err(&hdev->dev, "hw open failed\n");
+		hid_hw_stop(hdev);
+		return error;
+	}
+
+	return 0;
+}
+
+static void ax_remove(struct hid_device *hdev)
+{
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id ax_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802), },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705), },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ax_devices);
+
+static struct hid_driver ax_driver = {
+	.name		= "acrux",
+	.id_table	= ax_devices,
+	.probe		= ax_probe,
+	.remove		= ax_remove,
+};
+module_hid_driver(ax_driver);
+
+MODULE_AUTHOR("Sergei Kolzun");
+MODULE_DESCRIPTION("Force feedback support for ACRUX game controllers");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-belkin.c b/drivers/hid/hid-belkin.c
new file mode 100644
index 0000000..cc4cf13
--- /dev/null
+++ b/drivers/hid/hid-belkin.c
@@ -0,0 +1,91 @@
+/*
+ *  HID driver for some belkin "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#define BELKIN_HIDDEV	0x01
+#define BELKIN_WKBD	0x02
+
+#define belkin_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int belkin_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER ||
+			!(quirks & BELKIN_WKBD))
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x03a: belkin_map_key_clear(KEY_SOUND);		break;
+	case 0x03b: belkin_map_key_clear(KEY_CAMERA);		break;
+	case 0x03c: belkin_map_key_clear(KEY_DOCUMENTS);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int belkin_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	unsigned long quirks = id->driver_data;
+	int ret;
+
+	hid_set_drvdata(hdev, (void *)quirks);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
+		((quirks & BELKIN_HIDDEV) ? HID_CONNECT_HIDDEV_FORCE : 0));
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static const struct hid_device_id belkin_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM),
+		.driver_data = BELKIN_HIDDEV },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD),
+		.driver_data = BELKIN_WKBD },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, belkin_devices);
+
+static struct hid_driver belkin_driver = {
+	.name = "belkin",
+	.id_table = belkin_devices,
+	.input_mapping = belkin_input_mapping,
+	.probe = belkin_probe,
+};
+module_hid_driver(belkin_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c
new file mode 100644
index 0000000..69cfc8d
--- /dev/null
+++ b/drivers/hid/hid-betopff.c
@@ -0,0 +1,160 @@
+/*
+ *  Force feedback support for Betop based devices
+ *
+ *  The devices are distributed under various names and the same USB device ID
+ *  can be used in both adapters and actual game controllers.
+ *
+ *  0x11c2:0x2208 "BTP2185 BFM mode Joystick"
+ *   - tested with BTP2185 BFM Mode.
+ *
+ *  0x11C0:0x5506 "BTP2185 PC mode Joystick"
+ *   - tested with BTP2185 PC Mode.
+ *
+ *  0x8380:0x1850 "BTP2185 V2 PC mode USB Gamepad"
+ *   - tested with BTP2185 PC Mode with another version.
+ *
+ *  0x20bc:0x5500 "BTP2185 V2 BFM mode Joystick"
+ *   - tested with BTP2171s.
+ *  Copyright (c) 2014 Huang Bo <huangbobupt@163.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/hid.h>
+
+#include "hid-ids.h"
+
+struct betopff_device {
+	struct hid_report *report;
+};
+
+static int hid_betopff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct betopff_device *betopff = data;
+	__u16 left, right;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+
+	betopff->report->field[2]->value[0] = left / 256;
+	betopff->report->field[3]->value[0] = right / 256;
+
+	hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int betopff_init(struct hid_device *hid)
+{
+	struct betopff_device *betopff;
+	struct hid_report *report;
+	struct hid_input *hidinput =
+			list_first_entry(&hid->inputs, struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int field_count = 0;
+	int error;
+	int i, j;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	report = list_first_entry(report_list, struct hid_report, list);
+	/*
+	 * Actually there are 4 fields for 4 Bytes as below:
+	 * -----------------------------------------
+	 * Byte0  Byte1  Byte2	  Byte3
+	 * 0x00   0x00   left_motor right_motor
+	 * -----------------------------------------
+	 * Do init them with default value.
+	 */
+	for (i = 0; i < report->maxfield; i++) {
+		for (j = 0; j < report->field[i]->report_count; j++) {
+			report->field[i]->value[j] = 0x00;
+			field_count++;
+		}
+	}
+
+	if (field_count < 4) {
+		hid_err(hid, "not enough fields in the report: %d\n",
+				field_count);
+		return -ENODEV;
+	}
+
+	betopff = kzalloc(sizeof(*betopff), GFP_KERNEL);
+	if (!betopff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, betopff, hid_betopff_play);
+	if (error) {
+		kfree(betopff);
+		return error;
+	}
+
+	betopff->report = report;
+	hid_hw_request(hid, betopff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "Force feedback for betop devices by huangbo <huangbobupt@163.com>\n");
+
+	return 0;
+}
+
+static int betop_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	if (id->driver_data)
+		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	betopff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id betop_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185BFM, 0x2208) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185PC, 0x5506) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2PC, 0x1850) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2BFM, 0x5500) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, betop_devices);
+
+static struct hid_driver betop_driver = {
+	.name = "betop",
+	.id_table = betop_devices,
+	.probe = betop_probe,
+};
+module_hid_driver(betop_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-cherry.c b/drivers/hid/hid-cherry.c
new file mode 100644
index 0000000..f745d2c
--- /dev/null
+++ b/drivers/hid/hid-cherry.c
@@ -0,0 +1,74 @@
+/*
+ *  HID driver for some cherry "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+/*
+ * Cherry Cymotion keyboard have an invalid HID report descriptor,
+ * that needs fixing before we can parse it.
+ */
+static __u8 *ch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 18 && rdesc[11] == 0x3c && rdesc[12] == 0x02) {
+		hid_info(hdev, "fixing up Cherry Cymotion report descriptor\n");
+		rdesc[11] = rdesc[16] = 0xff;
+		rdesc[12] = rdesc[17] = 0x03;
+	}
+	return rdesc;
+}
+
+#define ch_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x301: ch_map_key_clear(KEY_PROG1);	break;
+	case 0x302: ch_map_key_clear(KEY_PROG2);	break;
+	case 0x303: ch_map_key_clear(KEY_PROG3);	break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static const struct hid_device_id ch_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ch_devices);
+
+static struct hid_driver ch_driver = {
+	.name = "cherry",
+	.id_table = ch_devices,
+	.report_fixup = ch_report_fixup,
+	.input_mapping = ch_input_mapping,
+};
+module_hid_driver(ch_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-chicony.c b/drivers/hid/hid-chicony.c
new file mode 100644
index 0000000..397a789
--- /dev/null
+++ b/drivers/hid/hid-chicony.c
@@ -0,0 +1,100 @@
+/*
+ *  HID driver for some chicony "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2007 Paul Walmsley
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+#define ch_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int ch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
+		return 0;
+
+	set_bit(EV_REP, hi->input->evbit);
+	switch (usage->hid & HID_USAGE) {
+	case 0xff01: ch_map_key_clear(BTN_1);	break;
+	case 0xff02: ch_map_key_clear(BTN_2);	break;
+	case 0xff03: ch_map_key_clear(BTN_3);	break;
+	case 0xff04: ch_map_key_clear(BTN_4);	break;
+	case 0xff05: ch_map_key_clear(BTN_5);	break;
+	case 0xff06: ch_map_key_clear(BTN_6);	break;
+	case 0xff07: ch_map_key_clear(BTN_7);	break;
+	case 0xff08: ch_map_key_clear(BTN_8);	break;
+	case 0xff09: ch_map_key_clear(BTN_9);	break;
+	case 0xff0a: ch_map_key_clear(BTN_A);	break;
+	case 0xff0b: ch_map_key_clear(BTN_B);	break;
+	case 0x00f1: ch_map_key_clear(KEY_WLAN);	break;
+	case 0x00f2: ch_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
+	case 0x00f3: ch_map_key_clear(KEY_BRIGHTNESSUP);	break;
+	case 0x00f4: ch_map_key_clear(KEY_DISPLAY_OFF);	break;
+	case 0x00f7: ch_map_key_clear(KEY_CAMERA);	break;
+	case 0x00f8: ch_map_key_clear(KEY_PROG1);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static __u8 *ch_switch12_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) {
+		/* Change usage maximum and logical maximum from 0x7fff to
+		 * 0x2fff, so they don't exceed HID_MAX_USAGES */
+		switch (hdev->product) {
+		case USB_DEVICE_ID_CHICONY_ACER_SWITCH12:
+			if (*rsize >= 128 && rdesc[64] == 0xff && rdesc[65] == 0x7f
+					&& rdesc[69] == 0xff && rdesc[70] == 0x7f) {
+				hid_info(hdev, "Fixing up report descriptor\n");
+				rdesc[65] = rdesc[70] = 0x2f;
+			}
+			break;
+		}
+
+	}
+	return rdesc;
+}
+
+
+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_ACER_SWITCH12) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ch_devices);
+
+static struct hid_driver ch_driver = {
+	.name = "chicony",
+	.id_table = ch_devices,
+	.report_fixup = ch_switch12_report_fixup,
+	.input_mapping = ch_input_mapping,
+};
+module_hid_driver(ch_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-cmedia.c b/drivers/hid/hid-cmedia.c
new file mode 100644
index 0000000..7230f85
--- /dev/null
+++ b/drivers/hid/hid-cmedia.c
@@ -0,0 +1,168 @@
+/*
+ * HID driver for CMedia CM6533 audio jack controls
+ *
+ * Copyright (C) 2015 Ben Chen <ben_chen@bizlinktech.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Ben Chen");
+MODULE_DESCRIPTION("CM6533 HID jack controls");
+MODULE_LICENSE("GPL");
+
+#define CM6533_JD_TYPE_COUNT      1
+#define CM6533_JD_RAWEV_LEN	 16
+#define CM6533_JD_SFX_OFFSET	  8
+
+/*
+*
+*CM6533 audio jack HID raw events:
+*
+*Plug in:
+*01000600 002083xx 080008c0 10000000
+*about 3 seconds later...
+*01000a00 002083xx 08000380 10000000
+*01000600 002083xx 08000380 10000000
+*
+*Plug out:
+*01000400 002083xx 080008c0 x0000000
+*/
+
+static const u8 ji_sfx[] = { 0x08, 0x00, 0x08, 0xc0 };
+static const u8 ji_in[]  = { 0x01, 0x00, 0x06, 0x00 };
+static const u8 ji_out[] = { 0x01, 0x00, 0x04, 0x00 };
+
+static int jack_switch_types[CM6533_JD_TYPE_COUNT] = {
+	SW_HEADPHONE_INSERT,
+};
+
+struct cmhid {
+	struct input_dev *input_dev;
+	struct hid_device *hid;
+	unsigned short switch_map[CM6533_JD_TYPE_COUNT];
+};
+
+static void hp_ev(struct hid_device *hid, struct cmhid *cm, int value)
+{
+	input_report_switch(cm->input_dev, SW_HEADPHONE_INSERT, value);
+	input_sync(cm->input_dev);
+}
+
+static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report,
+	 u8 *data, int len)
+{
+	struct cmhid *cm = hid_get_drvdata(hid);
+
+	if (len != CM6533_JD_RAWEV_LEN)
+		goto out;
+	if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx)))
+		goto out;
+
+	if (!memcmp(data, ji_out, sizeof(ji_out))) {
+		hp_ev(hid, cm, 0);
+		goto out;
+	}
+	if (!memcmp(data, ji_in, sizeof(ji_in))) {
+		hp_ev(hid, cm, 1);
+		goto out;
+	}
+
+out:
+	return 0;
+}
+
+static int cmhid_input_configured(struct hid_device *hid,
+		struct hid_input *hidinput)
+{
+	struct input_dev *input_dev = hidinput->input;
+	struct cmhid *cm = hid_get_drvdata(hid);
+	int i;
+
+	cm->input_dev = input_dev;
+	memcpy(cm->switch_map, jack_switch_types, sizeof(cm->switch_map));
+	input_dev->evbit[0] = BIT(EV_SW);
+	for (i = 0; i < CM6533_JD_TYPE_COUNT; i++)
+		input_set_capability(cm->input_dev,
+				EV_SW, jack_switch_types[i]);
+	return 0;
+}
+
+static int cmhid_input_mapping(struct hid_device *hid,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id)
+{
+	int ret;
+	struct cmhid *cm;
+
+	cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL);
+	if (!cm) {
+		ret = -ENOMEM;
+		goto allocfail;
+	}
+
+	cm->hid = hid;
+
+	hid->quirks |= HID_QUIRK_HIDINPUT_FORCE;
+	hid_set_drvdata(hid, cm);
+
+	ret = hid_parse(hid);
+	if (ret) {
+		hid_err(hid, "parse failed\n");
+		goto fail;
+	}
+
+	ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE);
+	if (ret) {
+		hid_err(hid, "hw start failed\n");
+		goto fail;
+	}
+
+	return 0;
+fail:
+	kfree(cm);
+allocfail:
+	return ret;
+}
+
+static void cmhid_remove(struct hid_device *hid)
+{
+	struct cmhid *cm = hid_get_drvdata(hid);
+
+	hid_hw_stop(hid);
+	kfree(cm);
+}
+
+static const struct hid_device_id cmhid_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM6533) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, cmhid_devices);
+
+static struct hid_driver cmhid_driver = {
+	.name = "cm6533_jd",
+	.id_table = cmhid_devices,
+	.raw_event = cmhid_raw_event,
+	.input_configured = cmhid_input_configured,
+	.probe = cmhid_probe,
+	.remove = cmhid_remove,
+	.input_mapping = cmhid_input_mapping,
+};
+module_hid_driver(cmhid_driver);
+
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
new file mode 100644
index 0000000..44564f6
--- /dev/null
+++ b/drivers/hid/hid-core.c
@@ -0,0 +1,2373 @@
+/*
+ *  HID support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2012 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+#include <linux/input.h>
+#include <linux/wait.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/hid-debug.h>
+#include <linux/hidraw.h>
+
+#include "hid-ids.h"
+
+/*
+ * Version Information
+ */
+
+#define DRIVER_DESC "HID core driver"
+
+int hid_debug = 0;
+module_param_named(debug, hid_debug, int, 0600);
+MODULE_PARM_DESC(debug, "toggle HID debugging messages");
+EXPORT_SYMBOL_GPL(hid_debug);
+
+static int hid_ignore_special_drivers = 0;
+module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600);
+MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
+
+/*
+ * Register a new report for a device.
+ */
+
+struct hid_report *hid_register_report(struct hid_device *device,
+				       unsigned int type, unsigned int id,
+				       unsigned int application)
+{
+	struct hid_report_enum *report_enum = device->report_enum + type;
+	struct hid_report *report;
+
+	if (id >= HID_MAX_IDS)
+		return NULL;
+	if (report_enum->report_id_hash[id])
+		return report_enum->report_id_hash[id];
+
+	report = kzalloc(sizeof(struct hid_report), GFP_KERNEL);
+	if (!report)
+		return NULL;
+
+	if (id != 0)
+		report_enum->numbered = 1;
+
+	report->id = id;
+	report->type = type;
+	report->size = 0;
+	report->device = device;
+	report->application = application;
+	report_enum->report_id_hash[id] = report;
+
+	list_add_tail(&report->list, &report_enum->report_list);
+
+	return report;
+}
+EXPORT_SYMBOL_GPL(hid_register_report);
+
+/*
+ * Register a new field for this report.
+ */
+
+static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages, unsigned values)
+{
+	struct hid_field *field;
+
+	if (report->maxfield == HID_MAX_FIELDS) {
+		hid_err(report->device, "too many fields in report\n");
+		return NULL;
+	}
+
+	field = kzalloc((sizeof(struct hid_field) +
+			 usages * sizeof(struct hid_usage) +
+			 values * sizeof(unsigned)), GFP_KERNEL);
+	if (!field)
+		return NULL;
+
+	field->index = report->maxfield++;
+	report->field[field->index] = field;
+	field->usage = (struct hid_usage *)(field + 1);
+	field->value = (s32 *)(field->usage + usages);
+	field->report = report;
+
+	return field;
+}
+
+/*
+ * Open a collection. The type/usage is pushed on the stack.
+ */
+
+static int open_collection(struct hid_parser *parser, unsigned type)
+{
+	struct hid_collection *collection;
+	unsigned usage;
+
+	usage = parser->local.usage[0];
+
+	if (parser->collection_stack_ptr == parser->collection_stack_size) {
+		unsigned int *collection_stack;
+		unsigned int new_size = parser->collection_stack_size +
+					HID_COLLECTION_STACK_SIZE;
+
+		collection_stack = krealloc(parser->collection_stack,
+					    new_size * sizeof(unsigned int),
+					    GFP_KERNEL);
+		if (!collection_stack)
+			return -ENOMEM;
+
+		parser->collection_stack = collection_stack;
+		parser->collection_stack_size = new_size;
+	}
+
+	if (parser->device->maxcollection == parser->device->collection_size) {
+		collection = kmalloc(
+				array3_size(sizeof(struct hid_collection),
+					    parser->device->collection_size,
+					    2),
+				GFP_KERNEL);
+		if (collection == NULL) {
+			hid_err(parser->device, "failed to reallocate collection array\n");
+			return -ENOMEM;
+		}
+		memcpy(collection, parser->device->collection,
+			sizeof(struct hid_collection) *
+			parser->device->collection_size);
+		memset(collection + parser->device->collection_size, 0,
+			sizeof(struct hid_collection) *
+			parser->device->collection_size);
+		kfree(parser->device->collection);
+		parser->device->collection = collection;
+		parser->device->collection_size *= 2;
+	}
+
+	parser->collection_stack[parser->collection_stack_ptr++] =
+		parser->device->maxcollection;
+
+	collection = parser->device->collection +
+		parser->device->maxcollection++;
+	collection->type = type;
+	collection->usage = usage;
+	collection->level = parser->collection_stack_ptr - 1;
+
+	if (type == HID_COLLECTION_APPLICATION)
+		parser->device->maxapplication++;
+
+	return 0;
+}
+
+/*
+ * Close a collection.
+ */
+
+static int close_collection(struct hid_parser *parser)
+{
+	if (!parser->collection_stack_ptr) {
+		hid_err(parser->device, "collection stack underflow\n");
+		return -EINVAL;
+	}
+	parser->collection_stack_ptr--;
+	return 0;
+}
+
+/*
+ * Climb up the stack, search for the specified collection type
+ * and return the usage.
+ */
+
+static unsigned hid_lookup_collection(struct hid_parser *parser, unsigned type)
+{
+	struct hid_collection *collection = parser->device->collection;
+	int n;
+
+	for (n = parser->collection_stack_ptr - 1; n >= 0; n--) {
+		unsigned index = parser->collection_stack[n];
+		if (collection[index].type == type)
+			return collection[index].usage;
+	}
+	return 0; /* we know nothing about this usage type */
+}
+
+/*
+ * Add a usage to the temporary parser table.
+ */
+
+static int hid_add_usage(struct hid_parser *parser, unsigned usage)
+{
+	if (parser->local.usage_index >= HID_MAX_USAGES) {
+		hid_err(parser->device, "usage index exceeded\n");
+		return -1;
+	}
+	parser->local.usage[parser->local.usage_index] = usage;
+	parser->local.collection_index[parser->local.usage_index] =
+		parser->collection_stack_ptr ?
+		parser->collection_stack[parser->collection_stack_ptr - 1] : 0;
+	parser->local.usage_index++;
+	return 0;
+}
+
+/*
+ * Register a new field for this report.
+ */
+
+static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	unsigned int usages;
+	unsigned int offset;
+	unsigned int i;
+	unsigned int application;
+
+	application = hid_lookup_collection(parser, HID_COLLECTION_APPLICATION);
+
+	report = hid_register_report(parser->device, report_type,
+				     parser->global.report_id, application);
+	if (!report) {
+		hid_err(parser->device, "hid_register_report failed\n");
+		return -1;
+	}
+
+	/* Handle both signed and unsigned cases properly */
+	if ((parser->global.logical_minimum < 0 &&
+		parser->global.logical_maximum <
+		parser->global.logical_minimum) ||
+		(parser->global.logical_minimum >= 0 &&
+		(__u32)parser->global.logical_maximum <
+		(__u32)parser->global.logical_minimum)) {
+		dbg_hid("logical range invalid 0x%x 0x%x\n",
+			parser->global.logical_minimum,
+			parser->global.logical_maximum);
+		return -1;
+	}
+
+	offset = report->size;
+	report->size += parser->global.report_size * parser->global.report_count;
+
+	if (!parser->local.usage_index) /* Ignore padding fields */
+		return 0;
+
+	usages = max_t(unsigned, parser->local.usage_index,
+				 parser->global.report_count);
+
+	field = hid_register_field(report, usages, parser->global.report_count);
+	if (!field)
+		return 0;
+
+	field->physical = hid_lookup_collection(parser, HID_COLLECTION_PHYSICAL);
+	field->logical = hid_lookup_collection(parser, HID_COLLECTION_LOGICAL);
+	field->application = application;
+
+	for (i = 0; i < usages; i++) {
+		unsigned j = i;
+		/* Duplicate the last usage we parsed if we have excess values */
+		if (i >= parser->local.usage_index)
+			j = parser->local.usage_index - 1;
+		field->usage[i].hid = parser->local.usage[j];
+		field->usage[i].collection_index =
+			parser->local.collection_index[j];
+		field->usage[i].usage_index = i;
+	}
+
+	field->maxusage = usages;
+	field->flags = flags;
+	field->report_offset = offset;
+	field->report_type = report_type;
+	field->report_size = parser->global.report_size;
+	field->report_count = parser->global.report_count;
+	field->logical_minimum = parser->global.logical_minimum;
+	field->logical_maximum = parser->global.logical_maximum;
+	field->physical_minimum = parser->global.physical_minimum;
+	field->physical_maximum = parser->global.physical_maximum;
+	field->unit_exponent = parser->global.unit_exponent;
+	field->unit = parser->global.unit;
+
+	return 0;
+}
+
+/*
+ * Read data value from item.
+ */
+
+static u32 item_udata(struct hid_item *item)
+{
+	switch (item->size) {
+	case 1: return item->data.u8;
+	case 2: return item->data.u16;
+	case 4: return item->data.u32;
+	}
+	return 0;
+}
+
+static s32 item_sdata(struct hid_item *item)
+{
+	switch (item->size) {
+	case 1: return item->data.s8;
+	case 2: return item->data.s16;
+	case 4: return item->data.s32;
+	}
+	return 0;
+}
+
+/*
+ * Process a global item.
+ */
+
+static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
+{
+	__s32 raw_value;
+	switch (item->tag) {
+	case HID_GLOBAL_ITEM_TAG_PUSH:
+
+		if (parser->global_stack_ptr == HID_GLOBAL_STACK_SIZE) {
+			hid_err(parser->device, "global environment stack overflow\n");
+			return -1;
+		}
+
+		memcpy(parser->global_stack + parser->global_stack_ptr++,
+			&parser->global, sizeof(struct hid_global));
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_POP:
+
+		if (!parser->global_stack_ptr) {
+			hid_err(parser->device, "global environment stack underflow\n");
+			return -1;
+		}
+
+		memcpy(&parser->global, parser->global_stack +
+			--parser->global_stack_ptr, sizeof(struct hid_global));
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_USAGE_PAGE:
+		parser->global.usage_page = item_udata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM:
+		parser->global.logical_minimum = item_sdata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM:
+		if (parser->global.logical_minimum < 0)
+			parser->global.logical_maximum = item_sdata(item);
+		else
+			parser->global.logical_maximum = item_udata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM:
+		parser->global.physical_minimum = item_sdata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM:
+		if (parser->global.physical_minimum < 0)
+			parser->global.physical_maximum = item_sdata(item);
+		else
+			parser->global.physical_maximum = item_udata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:
+		/* Many devices provide unit exponent as a two's complement
+		 * nibble due to the common misunderstanding of HID
+		 * specification 1.11, 6.2.2.7 Global Items. Attempt to handle
+		 * both this and the standard encoding. */
+		raw_value = item_sdata(item);
+		if (!(raw_value & 0xfffffff0))
+			parser->global.unit_exponent = hid_snto32(raw_value, 4);
+		else
+			parser->global.unit_exponent = raw_value;
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_UNIT:
+		parser->global.unit = item_udata(item);
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:
+		parser->global.report_size = item_udata(item);
+		if (parser->global.report_size > 128) {
+			hid_err(parser->device, "invalid report_size %d\n",
+					parser->global.report_size);
+			return -1;
+		}
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_REPORT_COUNT:
+		parser->global.report_count = item_udata(item);
+		if (parser->global.report_count > HID_MAX_USAGES) {
+			hid_err(parser->device, "invalid report_count %d\n",
+					parser->global.report_count);
+			return -1;
+		}
+		return 0;
+
+	case HID_GLOBAL_ITEM_TAG_REPORT_ID:
+		parser->global.report_id = item_udata(item);
+		if (parser->global.report_id == 0 ||
+		    parser->global.report_id >= HID_MAX_IDS) {
+			hid_err(parser->device, "report_id %u is invalid\n",
+				parser->global.report_id);
+			return -1;
+		}
+		return 0;
+
+	default:
+		hid_err(parser->device, "unknown global tag 0x%x\n", item->tag);
+		return -1;
+	}
+}
+
+/*
+ * Process a local item.
+ */
+
+static int hid_parser_local(struct hid_parser *parser, struct hid_item *item)
+{
+	__u32 data;
+	unsigned n;
+	__u32 count;
+
+	data = item_udata(item);
+
+	switch (item->tag) {
+	case HID_LOCAL_ITEM_TAG_DELIMITER:
+
+		if (data) {
+			/*
+			 * We treat items before the first delimiter
+			 * as global to all usage sets (branch 0).
+			 * In the moment we process only these global
+			 * items and the first delimiter set.
+			 */
+			if (parser->local.delimiter_depth != 0) {
+				hid_err(parser->device, "nested delimiters\n");
+				return -1;
+			}
+			parser->local.delimiter_depth++;
+			parser->local.delimiter_branch++;
+		} else {
+			if (parser->local.delimiter_depth < 1) {
+				hid_err(parser->device, "bogus close delimiter\n");
+				return -1;
+			}
+			parser->local.delimiter_depth--;
+		}
+		return 0;
+
+	case HID_LOCAL_ITEM_TAG_USAGE:
+
+		if (parser->local.delimiter_branch > 1) {
+			dbg_hid("alternative usage ignored\n");
+			return 0;
+		}
+
+		if (item->size <= 2)
+			data = (parser->global.usage_page << 16) + data;
+
+		return hid_add_usage(parser, data);
+
+	case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:
+
+		if (parser->local.delimiter_branch > 1) {
+			dbg_hid("alternative usage ignored\n");
+			return 0;
+		}
+
+		if (item->size <= 2)
+			data = (parser->global.usage_page << 16) + data;
+
+		parser->local.usage_minimum = data;
+		return 0;
+
+	case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM:
+
+		if (parser->local.delimiter_branch > 1) {
+			dbg_hid("alternative usage ignored\n");
+			return 0;
+		}
+
+		if (item->size <= 2)
+			data = (parser->global.usage_page << 16) + data;
+
+		count = data - parser->local.usage_minimum;
+		if (count + parser->local.usage_index >= HID_MAX_USAGES) {
+			/*
+			 * We do not warn if the name is not set, we are
+			 * actually pre-scanning the device.
+			 */
+			if (dev_name(&parser->device->dev))
+				hid_warn(parser->device,
+					 "ignoring exceeding usage max\n");
+			data = HID_MAX_USAGES - parser->local.usage_index +
+				parser->local.usage_minimum - 1;
+			if (data <= 0) {
+				hid_err(parser->device,
+					"no more usage index available\n");
+				return -1;
+			}
+		}
+
+		for (n = parser->local.usage_minimum; n <= data; n++)
+			if (hid_add_usage(parser, n)) {
+				dbg_hid("hid_add_usage failed\n");
+				return -1;
+			}
+		return 0;
+
+	default:
+
+		dbg_hid("unknown local item tag 0x%x\n", item->tag);
+		return 0;
+	}
+	return 0;
+}
+
+/*
+ * Process a main item.
+ */
+
+static int hid_parser_main(struct hid_parser *parser, struct hid_item *item)
+{
+	__u32 data;
+	int ret;
+
+	data = item_udata(item);
+
+	switch (item->tag) {
+	case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
+		ret = open_collection(parser, data & 0xff);
+		break;
+	case HID_MAIN_ITEM_TAG_END_COLLECTION:
+		ret = close_collection(parser);
+		break;
+	case HID_MAIN_ITEM_TAG_INPUT:
+		ret = hid_add_field(parser, HID_INPUT_REPORT, data);
+		break;
+	case HID_MAIN_ITEM_TAG_OUTPUT:
+		ret = hid_add_field(parser, HID_OUTPUT_REPORT, data);
+		break;
+	case HID_MAIN_ITEM_TAG_FEATURE:
+		ret = hid_add_field(parser, HID_FEATURE_REPORT, data);
+		break;
+	default:
+		hid_warn(parser->device, "unknown main item tag 0x%x\n", item->tag);
+		ret = 0;
+	}
+
+	memset(&parser->local, 0, sizeof(parser->local));	/* Reset the local parser environment */
+
+	return ret;
+}
+
+/*
+ * Process a reserved item.
+ */
+
+static int hid_parser_reserved(struct hid_parser *parser, struct hid_item *item)
+{
+	dbg_hid("reserved item type, tag 0x%x\n", item->tag);
+	return 0;
+}
+
+/*
+ * Free a report and all registered fields. The field->usage and
+ * field->value table's are allocated behind the field, so we need
+ * only to free(field) itself.
+ */
+
+static void hid_free_report(struct hid_report *report)
+{
+	unsigned n;
+
+	for (n = 0; n < report->maxfield; n++)
+		kfree(report->field[n]);
+	kfree(report);
+}
+
+/*
+ * Close report. This function returns the device
+ * state to the point prior to hid_open_report().
+ */
+static void hid_close_report(struct hid_device *device)
+{
+	unsigned i, j;
+
+	for (i = 0; i < HID_REPORT_TYPES; i++) {
+		struct hid_report_enum *report_enum = device->report_enum + i;
+
+		for (j = 0; j < HID_MAX_IDS; j++) {
+			struct hid_report *report = report_enum->report_id_hash[j];
+			if (report)
+				hid_free_report(report);
+		}
+		memset(report_enum, 0, sizeof(*report_enum));
+		INIT_LIST_HEAD(&report_enum->report_list);
+	}
+
+	kfree(device->rdesc);
+	device->rdesc = NULL;
+	device->rsize = 0;
+
+	kfree(device->collection);
+	device->collection = NULL;
+	device->collection_size = 0;
+	device->maxcollection = 0;
+	device->maxapplication = 0;
+
+	device->status &= ~HID_STAT_PARSED;
+}
+
+/*
+ * Free a device structure, all reports, and all fields.
+ */
+
+static void hid_device_release(struct device *dev)
+{
+	struct hid_device *hid = to_hid_device(dev);
+
+	hid_close_report(hid);
+	kfree(hid->dev_rdesc);
+	kfree(hid);
+}
+
+/*
+ * Fetch a report description item from the data stream. We support long
+ * items, though they are not used yet.
+ */
+
+static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item)
+{
+	u8 b;
+
+	if ((end - start) <= 0)
+		return NULL;
+
+	b = *start++;
+
+	item->type = (b >> 2) & 3;
+	item->tag  = (b >> 4) & 15;
+
+	if (item->tag == HID_ITEM_TAG_LONG) {
+
+		item->format = HID_ITEM_FORMAT_LONG;
+
+		if ((end - start) < 2)
+			return NULL;
+
+		item->size = *start++;
+		item->tag  = *start++;
+
+		if ((end - start) < item->size)
+			return NULL;
+
+		item->data.longdata = start;
+		start += item->size;
+		return start;
+	}
+
+	item->format = HID_ITEM_FORMAT_SHORT;
+	item->size = b & 3;
+
+	switch (item->size) {
+	case 0:
+		return start;
+
+	case 1:
+		if ((end - start) < 1)
+			return NULL;
+		item->data.u8 = *start++;
+		return start;
+
+	case 2:
+		if ((end - start) < 2)
+			return NULL;
+		item->data.u16 = get_unaligned_le16(start);
+		start = (__u8 *)((__le16 *)start + 1);
+		return start;
+
+	case 3:
+		item->size++;
+		if ((end - start) < 4)
+			return NULL;
+		item->data.u32 = get_unaligned_le32(start);
+		start = (__u8 *)((__le32 *)start + 1);
+		return start;
+	}
+
+	return NULL;
+}
+
+static void hid_scan_input_usage(struct hid_parser *parser, u32 usage)
+{
+	struct hid_device *hid = parser->device;
+
+	if (usage == HID_DG_CONTACTID)
+		hid->group = HID_GROUP_MULTITOUCH;
+}
+
+static void hid_scan_feature_usage(struct hid_parser *parser, u32 usage)
+{
+	if (usage == 0xff0000c5 && parser->global.report_count == 256 &&
+	    parser->global.report_size == 8)
+		parser->scan_flags |= HID_SCAN_FLAG_MT_WIN_8;
+}
+
+static void hid_scan_collection(struct hid_parser *parser, unsigned type)
+{
+	struct hid_device *hid = parser->device;
+	int i;
+
+	if (((parser->global.usage_page << 16) == HID_UP_SENSOR) &&
+	    type == HID_COLLECTION_PHYSICAL)
+		hid->group = HID_GROUP_SENSOR_HUB;
+
+	if (hid->vendor == USB_VENDOR_ID_MICROSOFT &&
+	    hid->product == USB_DEVICE_ID_MS_POWER_COVER &&
+	    hid->group == HID_GROUP_MULTITOUCH)
+		hid->group = HID_GROUP_GENERIC;
+
+	if ((parser->global.usage_page << 16) == HID_UP_GENDESK)
+		for (i = 0; i < parser->local.usage_index; i++)
+			if (parser->local.usage[i] == HID_GD_POINTER)
+				parser->scan_flags |= HID_SCAN_FLAG_GD_POINTER;
+
+	if ((parser->global.usage_page << 16) >= HID_UP_MSVENDOR)
+		parser->scan_flags |= HID_SCAN_FLAG_VENDOR_SPECIFIC;
+}
+
+static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
+{
+	__u32 data;
+	int i;
+
+	data = item_udata(item);
+
+	switch (item->tag) {
+	case HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:
+		hid_scan_collection(parser, data & 0xff);
+		break;
+	case HID_MAIN_ITEM_TAG_END_COLLECTION:
+		break;
+	case HID_MAIN_ITEM_TAG_INPUT:
+		/* ignore constant inputs, they will be ignored by hid-input */
+		if (data & HID_MAIN_ITEM_CONSTANT)
+			break;
+		for (i = 0; i < parser->local.usage_index; i++)
+			hid_scan_input_usage(parser, parser->local.usage[i]);
+		break;
+	case HID_MAIN_ITEM_TAG_OUTPUT:
+		break;
+	case HID_MAIN_ITEM_TAG_FEATURE:
+		for (i = 0; i < parser->local.usage_index; i++)
+			hid_scan_feature_usage(parser, parser->local.usage[i]);
+		break;
+	}
+
+	/* Reset the local parser environment */
+	memset(&parser->local, 0, sizeof(parser->local));
+
+	return 0;
+}
+
+/*
+ * Scan a report descriptor before the device is added to the bus.
+ * Sets device groups and other properties that determine what driver
+ * to load.
+ */
+static int hid_scan_report(struct hid_device *hid)
+{
+	struct hid_parser *parser;
+	struct hid_item item;
+	__u8 *start = hid->dev_rdesc;
+	__u8 *end = start + hid->dev_rsize;
+	static int (*dispatch_type[])(struct hid_parser *parser,
+				      struct hid_item *item) = {
+		hid_scan_main,
+		hid_parser_global,
+		hid_parser_local,
+		hid_parser_reserved
+	};
+
+	parser = vzalloc(sizeof(struct hid_parser));
+	if (!parser)
+		return -ENOMEM;
+
+	parser->device = hid;
+	hid->group = HID_GROUP_GENERIC;
+
+	/*
+	 * The parsing is simpler than the one in hid_open_report() as we should
+	 * be robust against hid errors. Those errors will be raised by
+	 * hid_open_report() anyway.
+	 */
+	while ((start = fetch_item(start, end, &item)) != NULL)
+		dispatch_type[item.type](parser, &item);
+
+	/*
+	 * Handle special flags set during scanning.
+	 */
+	if ((parser->scan_flags & HID_SCAN_FLAG_MT_WIN_8) &&
+	    (hid->group == HID_GROUP_MULTITOUCH))
+		hid->group = HID_GROUP_MULTITOUCH_WIN_8;
+
+	/*
+	 * Vendor specific handlings
+	 */
+	switch (hid->vendor) {
+	case USB_VENDOR_ID_WACOM:
+		hid->group = HID_GROUP_WACOM;
+		break;
+	case USB_VENDOR_ID_SYNAPTICS:
+		if (hid->group == HID_GROUP_GENERIC)
+			if ((parser->scan_flags & HID_SCAN_FLAG_VENDOR_SPECIFIC)
+			    && (parser->scan_flags & HID_SCAN_FLAG_GD_POINTER))
+				/*
+				 * hid-rmi should take care of them,
+				 * not hid-generic
+				 */
+				hid->group = HID_GROUP_RMI;
+		break;
+	}
+
+	kfree(parser->collection_stack);
+	vfree(parser);
+	return 0;
+}
+
+/**
+ * hid_parse_report - parse device report
+ *
+ * @device: hid device
+ * @start: report start
+ * @size: report size
+ *
+ * Allocate the device report as read by the bus driver. This function should
+ * only be called from parse() in ll drivers.
+ */
+int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size)
+{
+	hid->dev_rdesc = kmemdup(start, size, GFP_KERNEL);
+	if (!hid->dev_rdesc)
+		return -ENOMEM;
+	hid->dev_rsize = size;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_parse_report);
+
+static const char * const hid_report_names[] = {
+	"HID_INPUT_REPORT",
+	"HID_OUTPUT_REPORT",
+	"HID_FEATURE_REPORT",
+};
+/**
+ * hid_validate_values - validate existing device report's value indexes
+ *
+ * @device: hid device
+ * @type: which report type to examine
+ * @id: which report ID to examine (0 for first)
+ * @field_index: which report field to examine
+ * @report_counts: expected number of values
+ *
+ * Validate the number of values in a given field of a given report, after
+ * parsing.
+ */
+struct hid_report *hid_validate_values(struct hid_device *hid,
+				       unsigned int type, unsigned int id,
+				       unsigned int field_index,
+				       unsigned int report_counts)
+{
+	struct hid_report *report;
+
+	if (type > HID_FEATURE_REPORT) {
+		hid_err(hid, "invalid HID report type %u\n", type);
+		return NULL;
+	}
+
+	if (id >= HID_MAX_IDS) {
+		hid_err(hid, "invalid HID report id %u\n", id);
+		return NULL;
+	}
+
+	/*
+	 * Explicitly not using hid_get_report() here since it depends on
+	 * ->numbered being checked, which may not always be the case when
+	 * drivers go to access report values.
+	 */
+	if (id == 0) {
+		/*
+		 * Validating on id 0 means we should examine the first
+		 * report in the list.
+		 */
+		report = list_entry(
+				hid->report_enum[type].report_list.next,
+				struct hid_report, list);
+	} else {
+		report = hid->report_enum[type].report_id_hash[id];
+	}
+	if (!report) {
+		hid_err(hid, "missing %s %u\n", hid_report_names[type], id);
+		return NULL;
+	}
+	if (report->maxfield <= field_index) {
+		hid_err(hid, "not enough fields in %s %u\n",
+			hid_report_names[type], id);
+		return NULL;
+	}
+	if (report->field[field_index]->report_count < report_counts) {
+		hid_err(hid, "not enough values in %s %u field %u\n",
+			hid_report_names[type], id, field_index);
+		return NULL;
+	}
+	return report;
+}
+EXPORT_SYMBOL_GPL(hid_validate_values);
+
+/**
+ * hid_open_report - open a driver-specific device report
+ *
+ * @device: hid device
+ *
+ * Parse a report description into a hid_device structure. Reports are
+ * enumerated, fields are attached to these reports.
+ * 0 returned on success, otherwise nonzero error value.
+ *
+ * This function (or the equivalent hid_parse() macro) should only be
+ * called from probe() in drivers, before starting the device.
+ */
+int hid_open_report(struct hid_device *device)
+{
+	struct hid_parser *parser;
+	struct hid_item item;
+	unsigned int size;
+	__u8 *start;
+	__u8 *buf;
+	__u8 *end;
+	int ret;
+	static int (*dispatch_type[])(struct hid_parser *parser,
+				      struct hid_item *item) = {
+		hid_parser_main,
+		hid_parser_global,
+		hid_parser_local,
+		hid_parser_reserved
+	};
+
+	if (WARN_ON(device->status & HID_STAT_PARSED))
+		return -EBUSY;
+
+	start = device->dev_rdesc;
+	if (WARN_ON(!start))
+		return -ENODEV;
+	size = device->dev_rsize;
+
+	buf = kmemdup(start, size, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	if (device->driver->report_fixup)
+		start = device->driver->report_fixup(device, buf, &size);
+	else
+		start = buf;
+
+	start = kmemdup(start, size, GFP_KERNEL);
+	kfree(buf);
+	if (start == NULL)
+		return -ENOMEM;
+
+	device->rdesc = start;
+	device->rsize = size;
+
+	parser = vzalloc(sizeof(struct hid_parser));
+	if (!parser) {
+		ret = -ENOMEM;
+		goto alloc_err;
+	}
+
+	parser->device = device;
+
+	end = start + size;
+
+	device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS,
+				     sizeof(struct hid_collection), GFP_KERNEL);
+	if (!device->collection) {
+		ret = -ENOMEM;
+		goto err;
+	}
+	device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
+
+	ret = -EINVAL;
+	while ((start = fetch_item(start, end, &item)) != NULL) {
+
+		if (item.format != HID_ITEM_FORMAT_SHORT) {
+			hid_err(device, "unexpected long global item\n");
+			goto err;
+		}
+
+		if (dispatch_type[item.type](parser, &item)) {
+			hid_err(device, "item %u %u %u %u parsing failed\n",
+				item.format, (unsigned)item.size,
+				(unsigned)item.type, (unsigned)item.tag);
+			goto err;
+		}
+
+		if (start == end) {
+			if (parser->collection_stack_ptr) {
+				hid_err(device, "unbalanced collection at end of report description\n");
+				goto err;
+			}
+			if (parser->local.delimiter_depth) {
+				hid_err(device, "unbalanced delimiter at end of report description\n");
+				goto err;
+			}
+			kfree(parser->collection_stack);
+			vfree(parser);
+			device->status |= HID_STAT_PARSED;
+			return 0;
+		}
+	}
+
+	hid_err(device, "item fetching failed at offset %d\n", (int)(end - start));
+err:
+	kfree(parser->collection_stack);
+alloc_err:
+	vfree(parser);
+	hid_close_report(device);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_open_report);
+
+/*
+ * Convert a signed n-bit integer to signed 32-bit integer. Common
+ * cases are done through the compiler, the screwed things has to be
+ * done by hand.
+ */
+
+static s32 snto32(__u32 value, unsigned n)
+{
+	switch (n) {
+	case 8:  return ((__s8)value);
+	case 16: return ((__s16)value);
+	case 32: return ((__s32)value);
+	}
+	return value & (1 << (n - 1)) ? value | (~0U << n) : value;
+}
+
+s32 hid_snto32(__u32 value, unsigned n)
+{
+	return snto32(value, n);
+}
+EXPORT_SYMBOL_GPL(hid_snto32);
+
+/*
+ * Convert a signed 32-bit integer to a signed n-bit integer.
+ */
+
+static u32 s32ton(__s32 value, unsigned n)
+{
+	s32 a = value >> (n - 1);
+	if (a && a != -1)
+		return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
+	return value & ((1 << n) - 1);
+}
+
+/*
+ * Extract/implement a data field from/to a little endian report (bit array).
+ *
+ * Code sort-of follows HID spec:
+ *     http://www.usb.org/developers/hidpage/HID1_11.pdf
+ *
+ * While the USB HID spec allows unlimited length bit fields in "report
+ * descriptors", most devices never use more than 16 bits.
+ * One model of UPS is claimed to report "LINEV" as a 32-bit field.
+ * Search linux-kernel and linux-usb-devel archives for "hid-core extract".
+ */
+
+static u32 __extract(u8 *report, unsigned offset, int n)
+{
+	unsigned int idx = offset / 8;
+	unsigned int bit_nr = 0;
+	unsigned int bit_shift = offset % 8;
+	int bits_to_copy = 8 - bit_shift;
+	u32 value = 0;
+	u32 mask = n < 32 ? (1U << n) - 1 : ~0U;
+
+	while (n > 0) {
+		value |= ((u32)report[idx] >> bit_shift) << bit_nr;
+		n -= bits_to_copy;
+		bit_nr += bits_to_copy;
+		bits_to_copy = 8;
+		bit_shift = 0;
+		idx++;
+	}
+
+	return value & mask;
+}
+
+u32 hid_field_extract(const struct hid_device *hid, u8 *report,
+			unsigned offset, unsigned n)
+{
+	if (n > 32) {
+		hid_warn(hid, "hid_field_extract() called with n (%d) > 32! (%s)\n",
+			 n, current->comm);
+		n = 32;
+	}
+
+	return __extract(report, offset, n);
+}
+EXPORT_SYMBOL_GPL(hid_field_extract);
+
+/*
+ * "implement" : set bits in a little endian bit stream.
+ * Same concepts as "extract" (see comments above).
+ * The data mangled in the bit stream remains in little endian
+ * order the whole time. It make more sense to talk about
+ * endianness of register values by considering a register
+ * a "cached" copy of the little endian bit stream.
+ */
+
+static void __implement(u8 *report, unsigned offset, int n, u32 value)
+{
+	unsigned int idx = offset / 8;
+	unsigned int bit_shift = offset % 8;
+	int bits_to_set = 8 - bit_shift;
+
+	while (n - bits_to_set >= 0) {
+		report[idx] &= ~(0xff << bit_shift);
+		report[idx] |= value << bit_shift;
+		value >>= bits_to_set;
+		n -= bits_to_set;
+		bits_to_set = 8;
+		bit_shift = 0;
+		idx++;
+	}
+
+	/* last nibble */
+	if (n) {
+		u8 bit_mask = ((1U << n) - 1);
+		report[idx] &= ~(bit_mask << bit_shift);
+		report[idx] |= value << bit_shift;
+	}
+}
+
+static void implement(const struct hid_device *hid, u8 *report,
+		      unsigned offset, unsigned n, u32 value)
+{
+	if (unlikely(n > 32)) {
+		hid_warn(hid, "%s() called with n (%d) > 32! (%s)\n",
+			 __func__, n, current->comm);
+		n = 32;
+	} else if (n < 32) {
+		u32 m = (1U << n) - 1;
+
+		if (unlikely(value > m)) {
+			hid_warn(hid,
+				 "%s() called with too large value %d (n: %d)! (%s)\n",
+				 __func__, value, n, current->comm);
+			WARN_ON(1);
+			value &= m;
+		}
+	}
+
+	__implement(report, offset, n, value);
+}
+
+/*
+ * Search an array for a value.
+ */
+
+static int search(__s32 *array, __s32 value, unsigned n)
+{
+	while (n--) {
+		if (*array++ == value)
+			return 0;
+	}
+	return -1;
+}
+
+/**
+ * hid_match_report - check if driver's raw_event should be called
+ *
+ * @hid: hid device
+ * @report_type: type to match against
+ *
+ * compare hid->driver->report_table->report_type to report->type
+ */
+static int hid_match_report(struct hid_device *hid, struct hid_report *report)
+{
+	const struct hid_report_id *id = hid->driver->report_table;
+
+	if (!id) /* NULL means all */
+		return 1;
+
+	for (; id->report_type != HID_TERMINATOR; id++)
+		if (id->report_type == HID_ANY_ID ||
+				id->report_type == report->type)
+			return 1;
+	return 0;
+}
+
+/**
+ * hid_match_usage - check if driver's event should be called
+ *
+ * @hid: hid device
+ * @usage: usage to match against
+ *
+ * compare hid->driver->usage_table->usage_{type,code} to
+ * usage->usage_{type,code}
+ */
+static int hid_match_usage(struct hid_device *hid, struct hid_usage *usage)
+{
+	const struct hid_usage_id *id = hid->driver->usage_table;
+
+	if (!id) /* NULL means all */
+		return 1;
+
+	for (; id->usage_type != HID_ANY_ID - 1; id++)
+		if ((id->usage_hid == HID_ANY_ID ||
+				id->usage_hid == usage->hid) &&
+				(id->usage_type == HID_ANY_ID ||
+				id->usage_type == usage->type) &&
+				(id->usage_code == HID_ANY_ID ||
+				 id->usage_code == usage->code))
+			return 1;
+	return 0;
+}
+
+static void hid_process_event(struct hid_device *hid, struct hid_field *field,
+		struct hid_usage *usage, __s32 value, int interrupt)
+{
+	struct hid_driver *hdrv = hid->driver;
+	int ret;
+
+	if (!list_empty(&hid->debug_list))
+		hid_dump_input(hid, usage, value);
+
+	if (hdrv && hdrv->event && hid_match_usage(hid, usage)) {
+		ret = hdrv->event(hid, field, usage, value);
+		if (ret != 0) {
+			if (ret < 0)
+				hid_err(hid, "%s's event failed with %d\n",
+						hdrv->name, ret);
+			return;
+		}
+	}
+
+	if (hid->claimed & HID_CLAIMED_INPUT)
+		hidinput_hid_event(hid, field, usage, value);
+	if (hid->claimed & HID_CLAIMED_HIDDEV && interrupt && hid->hiddev_hid_event)
+		hid->hiddev_hid_event(hid, field, usage, value);
+}
+
+/*
+ * Analyse a received field, and fetch the data from it. The field
+ * content is stored for next report processing (we do differential
+ * reporting to the layer).
+ */
+
+static void hid_input_field(struct hid_device *hid, struct hid_field *field,
+			    __u8 *data, int interrupt)
+{
+	unsigned n;
+	unsigned count = field->report_count;
+	unsigned offset = field->report_offset;
+	unsigned size = field->report_size;
+	__s32 min = field->logical_minimum;
+	__s32 max = field->logical_maximum;
+	__s32 *value;
+
+	value = kmalloc_array(count, sizeof(__s32), GFP_ATOMIC);
+	if (!value)
+		return;
+
+	for (n = 0; n < count; n++) {
+
+		value[n] = min < 0 ?
+			snto32(hid_field_extract(hid, data, offset + n * size,
+			       size), size) :
+			hid_field_extract(hid, data, offset + n * size, size);
+
+		/* Ignore report if ErrorRollOver */
+		if (!(field->flags & HID_MAIN_ITEM_VARIABLE) &&
+		    value[n] >= min && value[n] <= max &&
+		    value[n] - min < field->maxusage &&
+		    field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1)
+			goto exit;
+	}
+
+	for (n = 0; n < count; n++) {
+
+		if (HID_MAIN_ITEM_VARIABLE & field->flags) {
+			hid_process_event(hid, field, &field->usage[n], value[n], interrupt);
+			continue;
+		}
+
+		if (field->value[n] >= min && field->value[n] <= max
+			&& field->value[n] - min < field->maxusage
+			&& field->usage[field->value[n] - min].hid
+			&& search(value, field->value[n], count))
+				hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt);
+
+		if (value[n] >= min && value[n] <= max
+			&& value[n] - min < field->maxusage
+			&& field->usage[value[n] - min].hid
+			&& search(field->value, value[n], count))
+				hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt);
+	}
+
+	memcpy(field->value, value, count * sizeof(__s32));
+exit:
+	kfree(value);
+}
+
+/*
+ * Output the field into the report.
+ */
+
+static void hid_output_field(const struct hid_device *hid,
+			     struct hid_field *field, __u8 *data)
+{
+	unsigned count = field->report_count;
+	unsigned offset = field->report_offset;
+	unsigned size = field->report_size;
+	unsigned n;
+
+	for (n = 0; n < count; n++) {
+		if (field->logical_minimum < 0)	/* signed values */
+			implement(hid, data, offset + n * size, size,
+				  s32ton(field->value[n], size));
+		else				/* unsigned values */
+			implement(hid, data, offset + n * size, size,
+				  field->value[n]);
+	}
+}
+
+/*
+ * Create a report. 'data' has to be allocated using
+ * hid_alloc_report_buf() so that it has proper size.
+ */
+
+void hid_output_report(struct hid_report *report, __u8 *data)
+{
+	unsigned n;
+
+	if (report->id > 0)
+		*data++ = report->id;
+
+	memset(data, 0, ((report->size - 1) >> 3) + 1);
+	for (n = 0; n < report->maxfield; n++)
+		hid_output_field(report->device, report->field[n], data);
+}
+EXPORT_SYMBOL_GPL(hid_output_report);
+
+/*
+ * Allocator for buffer that is going to be passed to hid_output_report()
+ */
+u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags)
+{
+	/*
+	 * 7 extra bytes are necessary to achieve proper functionality
+	 * of implement() working on 8 byte chunks
+	 */
+
+	u32 len = hid_report_len(report) + 7;
+
+	return kmalloc(len, flags);
+}
+EXPORT_SYMBOL_GPL(hid_alloc_report_buf);
+
+/*
+ * Set a field value. The report this field belongs to has to be
+ * created and transferred to the device, to set this value in the
+ * device.
+ */
+
+int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
+{
+	unsigned size;
+
+	if (!field)
+		return -1;
+
+	size = field->report_size;
+
+	hid_dump_input(field->report->device, field->usage + offset, value);
+
+	if (offset >= field->report_count) {
+		hid_err(field->report->device, "offset (%d) exceeds report_count (%d)\n",
+				offset, field->report_count);
+		return -1;
+	}
+	if (field->logical_minimum < 0) {
+		if (value != snto32(s32ton(value, size), size)) {
+			hid_err(field->report->device, "value %d is out of range\n", value);
+			return -1;
+		}
+	}
+	field->value[offset] = value;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_set_field);
+
+static struct hid_report *hid_get_report(struct hid_report_enum *report_enum,
+		const u8 *data)
+{
+	struct hid_report *report;
+	unsigned int n = 0;	/* Normally report number is 0 */
+
+	/* Device uses numbered reports, data[0] is report number */
+	if (report_enum->numbered)
+		n = *data;
+
+	report = report_enum->report_id_hash[n];
+	if (report == NULL)
+		dbg_hid("undefined report_id %u received\n", n);
+
+	return report;
+}
+
+/*
+ * Implement a generic .request() callback, using .raw_request()
+ * DO NOT USE in hid drivers directly, but through hid_hw_request instead.
+ */
+void __hid_request(struct hid_device *hid, struct hid_report *report,
+		int reqtype)
+{
+	char *buf;
+	int ret;
+	u32 len;
+
+	buf = hid_alloc_report_buf(report, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	len = hid_report_len(report);
+
+	if (reqtype == HID_REQ_SET_REPORT)
+		hid_output_report(report, buf);
+
+	ret = hid->ll_driver->raw_request(hid, report->id, buf, len,
+					  report->type, reqtype);
+	if (ret < 0) {
+		dbg_hid("unable to complete request: %d\n", ret);
+		goto out;
+	}
+
+	if (reqtype == HID_REQ_GET_REPORT)
+		hid_input_report(hid, report->type, buf, ret, 0);
+
+out:
+	kfree(buf);
+}
+EXPORT_SYMBOL_GPL(__hid_request);
+
+int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size,
+		int interrupt)
+{
+	struct hid_report_enum *report_enum = hid->report_enum + type;
+	struct hid_report *report;
+	struct hid_driver *hdrv;
+	unsigned int a;
+	u32 rsize, csize = size;
+	u8 *cdata = data;
+	int ret = 0;
+
+	report = hid_get_report(report_enum, data);
+	if (!report)
+		goto out;
+
+	if (report_enum->numbered) {
+		cdata++;
+		csize--;
+	}
+
+	rsize = ((report->size - 1) >> 3) + 1;
+
+	if (rsize > HID_MAX_BUFFER_SIZE)
+		rsize = HID_MAX_BUFFER_SIZE;
+
+	if (csize < rsize) {
+		dbg_hid("report %d is too short, (%d < %d)\n", report->id,
+				csize, rsize);
+		memset(cdata + csize, 0, rsize - csize);
+	}
+
+	if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_report_event)
+		hid->hiddev_report_event(hid, report);
+	if (hid->claimed & HID_CLAIMED_HIDRAW) {
+		ret = hidraw_report_event(hid, data, size);
+		if (ret)
+			goto out;
+	}
+
+	if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
+		for (a = 0; a < report->maxfield; a++)
+			hid_input_field(hid, report->field[a], cdata, interrupt);
+		hdrv = hid->driver;
+		if (hdrv && hdrv->report)
+			hdrv->report(hid, report);
+	}
+
+	if (hid->claimed & HID_CLAIMED_INPUT)
+		hidinput_report_event(hid, report);
+out:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_report_raw_event);
+
+/**
+ * hid_input_report - report data from lower layer (usb, bt...)
+ *
+ * @hid: hid device
+ * @type: HID report type (HID_*_REPORT)
+ * @data: report contents
+ * @size: size of data parameter
+ * @interrupt: distinguish between interrupt and control transfers
+ *
+ * This is data entry for lower layers.
+ */
+int hid_input_report(struct hid_device *hid, int type, u8 *data, u32 size, int interrupt)
+{
+	struct hid_report_enum *report_enum;
+	struct hid_driver *hdrv;
+	struct hid_report *report;
+	int ret = 0;
+
+	if (!hid)
+		return -ENODEV;
+
+	if (down_trylock(&hid->driver_input_lock))
+		return -EBUSY;
+
+	if (!hid->driver) {
+		ret = -ENODEV;
+		goto unlock;
+	}
+	report_enum = hid->report_enum + type;
+	hdrv = hid->driver;
+
+	if (!size) {
+		dbg_hid("empty report\n");
+		ret = -1;
+		goto unlock;
+	}
+
+	/* Avoid unnecessary overhead if debugfs is disabled */
+	if (!list_empty(&hid->debug_list))
+		hid_dump_report(hid, type, data, size);
+
+	report = hid_get_report(report_enum, data);
+
+	if (!report) {
+		ret = -1;
+		goto unlock;
+	}
+
+	if (hdrv && hdrv->raw_event && hid_match_report(hid, report)) {
+		ret = hdrv->raw_event(hid, report, data, size);
+		if (ret < 0)
+			goto unlock;
+	}
+
+	ret = hid_report_raw_event(hid, type, data, size, interrupt);
+
+unlock:
+	up(&hid->driver_input_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_input_report);
+
+bool hid_match_one_id(const struct hid_device *hdev,
+		      const struct hid_device_id *id)
+{
+	return (id->bus == HID_BUS_ANY || id->bus == hdev->bus) &&
+		(id->group == HID_GROUP_ANY || id->group == hdev->group) &&
+		(id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) &&
+		(id->product == HID_ANY_ID || id->product == hdev->product);
+}
+
+const struct hid_device_id *hid_match_id(const struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	for (; id->bus; id++)
+		if (hid_match_one_id(hdev, id))
+			return id;
+
+	return NULL;
+}
+
+static const struct hid_device_id hid_hiddev_list[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MGE, USB_DEVICE_ID_MGE_UPS1) },
+	{ }
+};
+
+static bool hid_hiddev(struct hid_device *hdev)
+{
+	return !!hid_match_id(hdev, hid_hiddev_list);
+}
+
+
+static ssize_t
+read_report_descriptor(struct file *filp, struct kobject *kobj,
+		struct bin_attribute *attr,
+		char *buf, loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct hid_device *hdev = to_hid_device(dev);
+
+	if (off >= hdev->rsize)
+		return 0;
+
+	if (off + count > hdev->rsize)
+		count = hdev->rsize - off;
+
+	memcpy(buf, hdev->rdesc + off, count);
+
+	return count;
+}
+
+static ssize_t
+show_country(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+
+	return sprintf(buf, "%02x\n", hdev->country & 0xff);
+}
+
+static struct bin_attribute dev_bin_attr_report_desc = {
+	.attr = { .name = "report_descriptor", .mode = 0444 },
+	.read = read_report_descriptor,
+	.size = HID_MAX_DESCRIPTOR_SIZE,
+};
+
+static const struct device_attribute dev_attr_country = {
+	.attr = { .name = "country", .mode = 0444 },
+	.show = show_country,
+};
+
+int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
+{
+	static const char *types[] = { "Device", "Pointer", "Mouse", "Device",
+		"Joystick", "Gamepad", "Keyboard", "Keypad",
+		"Multi-Axis Controller"
+	};
+	const char *type, *bus;
+	char buf[64] = "";
+	unsigned int i;
+	int len;
+	int ret;
+
+	if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
+		connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
+	if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE)
+		connect_mask |= HID_CONNECT_HIDINPUT_FORCE;
+	if (hdev->bus != BUS_USB)
+		connect_mask &= ~HID_CONNECT_HIDDEV;
+	if (hid_hiddev(hdev))
+		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
+
+	if ((connect_mask & HID_CONNECT_HIDINPUT) && !hidinput_connect(hdev,
+				connect_mask & HID_CONNECT_HIDINPUT_FORCE))
+		hdev->claimed |= HID_CLAIMED_INPUT;
+
+	if ((connect_mask & HID_CONNECT_HIDDEV) && hdev->hiddev_connect &&
+			!hdev->hiddev_connect(hdev,
+				connect_mask & HID_CONNECT_HIDDEV_FORCE))
+		hdev->claimed |= HID_CLAIMED_HIDDEV;
+	if ((connect_mask & HID_CONNECT_HIDRAW) && !hidraw_connect(hdev))
+		hdev->claimed |= HID_CLAIMED_HIDRAW;
+
+	if (connect_mask & HID_CONNECT_DRIVER)
+		hdev->claimed |= HID_CLAIMED_DRIVER;
+
+	/* 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;
+	}
+
+	if ((hdev->claimed & HID_CLAIMED_INPUT) &&
+			(connect_mask & HID_CONNECT_FF) && hdev->ff_init)
+		hdev->ff_init(hdev);
+
+	len = 0;
+	if (hdev->claimed & HID_CLAIMED_INPUT)
+		len += sprintf(buf + len, "input");
+	if (hdev->claimed & HID_CLAIMED_HIDDEV)
+		len += sprintf(buf + len, "%shiddev%d", len ? "," : "",
+				((struct hiddev *)hdev->hiddev)->minor);
+	if (hdev->claimed & HID_CLAIMED_HIDRAW)
+		len += sprintf(buf + len, "%shidraw%d", len ? "," : "",
+				((struct hidraw *)hdev->hidraw)->minor);
+
+	type = "Device";
+	for (i = 0; i < hdev->maxcollection; i++) {
+		struct hid_collection *col = &hdev->collection[i];
+		if (col->type == HID_COLLECTION_APPLICATION &&
+		   (col->usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&
+		   (col->usage & 0xffff) < ARRAY_SIZE(types)) {
+			type = types[col->usage & 0xffff];
+			break;
+		}
+	}
+
+	switch (hdev->bus) {
+	case BUS_USB:
+		bus = "USB";
+		break;
+	case BUS_BLUETOOTH:
+		bus = "BLUETOOTH";
+		break;
+	case BUS_I2C:
+		bus = "I2C";
+		break;
+	default:
+		bus = "<UNKNOWN>";
+	}
+
+	ret = device_create_file(&hdev->dev, &dev_attr_country);
+	if (ret)
+		hid_warn(hdev,
+			 "can't create sysfs country code attribute err: %d\n", ret);
+
+	hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n",
+		 buf, bus, hdev->version >> 8, hdev->version & 0xff,
+		 type, hdev->name, hdev->phys);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_connect);
+
+void hid_disconnect(struct hid_device *hdev)
+{
+	device_remove_file(&hdev->dev, &dev_attr_country);
+	if (hdev->claimed & HID_CLAIMED_INPUT)
+		hidinput_disconnect(hdev);
+	if (hdev->claimed & HID_CLAIMED_HIDDEV)
+		hdev->hiddev_disconnect(hdev);
+	if (hdev->claimed & HID_CLAIMED_HIDRAW)
+		hidraw_disconnect(hdev);
+	hdev->claimed = 0;
+}
+EXPORT_SYMBOL_GPL(hid_disconnect);
+
+/**
+ * hid_hw_start - start underlying HW
+ * @hdev: hid device
+ * @connect_mask: which outputs to connect, see HID_CONNECT_*
+ *
+ * Call this in probe function *after* hid_parse. This will setup HW
+ * buffers and start the device (if not defeirred to device open).
+ * hid_hw_stop must be called if this was successful.
+ */
+int hid_hw_start(struct hid_device *hdev, unsigned int connect_mask)
+{
+	int error;
+
+	error = hdev->ll_driver->start(hdev);
+	if (error)
+		return error;
+
+	if (connect_mask) {
+		error = hid_connect(hdev, connect_mask);
+		if (error) {
+			hdev->ll_driver->stop(hdev);
+			return error;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_hw_start);
+
+/**
+ * hid_hw_stop - stop underlying HW
+ * @hdev: hid device
+ *
+ * This is usually called from remove function or from probe when something
+ * failed and hid_hw_start was called already.
+ */
+void hid_hw_stop(struct hid_device *hdev)
+{
+	hid_disconnect(hdev);
+	hdev->ll_driver->stop(hdev);
+}
+EXPORT_SYMBOL_GPL(hid_hw_stop);
+
+/**
+ * hid_hw_open - signal underlying HW to start delivering events
+ * @hdev: hid device
+ *
+ * Tell underlying HW to start delivering events from the device.
+ * This function should be called sometime after successful call
+ * to hid_hw_start().
+ */
+int hid_hw_open(struct hid_device *hdev)
+{
+	int ret;
+
+	ret = mutex_lock_killable(&hdev->ll_open_lock);
+	if (ret)
+		return ret;
+
+	if (!hdev->ll_open_count++) {
+		ret = hdev->ll_driver->open(hdev);
+		if (ret)
+			hdev->ll_open_count--;
+	}
+
+	mutex_unlock(&hdev->ll_open_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_hw_open);
+
+/**
+ * hid_hw_close - signal underlaying HW to stop delivering events
+ *
+ * @hdev: hid device
+ *
+ * This function indicates that we are not interested in the events
+ * from this device anymore. Delivery of events may or may not stop,
+ * depending on the number of users still outstanding.
+ */
+void hid_hw_close(struct hid_device *hdev)
+{
+	mutex_lock(&hdev->ll_open_lock);
+	if (!--hdev->ll_open_count)
+		hdev->ll_driver->close(hdev);
+	mutex_unlock(&hdev->ll_open_lock);
+}
+EXPORT_SYMBOL_GPL(hid_hw_close);
+
+struct hid_dynid {
+	struct list_head list;
+	struct hid_device_id id;
+};
+
+/**
+ * store_new_id - add a new HID device ID to this driver and re-probe devices
+ * @driver: target device driver
+ * @buf: buffer for scanning device ID data
+ * @count: input size
+ *
+ * Adds a new dynamic hid device ID to this driver,
+ * and causes the driver to probe for all devices again.
+ */
+static ssize_t new_id_store(struct device_driver *drv, const char *buf,
+		size_t count)
+{
+	struct hid_driver *hdrv = to_hid_driver(drv);
+	struct hid_dynid *dynid;
+	__u32 bus, vendor, product;
+	unsigned long driver_data = 0;
+	int ret;
+
+	ret = sscanf(buf, "%x %x %x %lx",
+			&bus, &vendor, &product, &driver_data);
+	if (ret < 3)
+		return -EINVAL;
+
+	dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
+	if (!dynid)
+		return -ENOMEM;
+
+	dynid->id.bus = bus;
+	dynid->id.group = HID_GROUP_ANY;
+	dynid->id.vendor = vendor;
+	dynid->id.product = product;
+	dynid->id.driver_data = driver_data;
+
+	spin_lock(&hdrv->dyn_lock);
+	list_add_tail(&dynid->list, &hdrv->dyn_list);
+	spin_unlock(&hdrv->dyn_lock);
+
+	ret = driver_attach(&hdrv->driver);
+
+	return ret ? : count;
+}
+static DRIVER_ATTR_WO(new_id);
+
+static struct attribute *hid_drv_attrs[] = {
+	&driver_attr_new_id.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(hid_drv);
+
+static void hid_free_dynids(struct hid_driver *hdrv)
+{
+	struct hid_dynid *dynid, *n;
+
+	spin_lock(&hdrv->dyn_lock);
+	list_for_each_entry_safe(dynid, n, &hdrv->dyn_list, list) {
+		list_del(&dynid->list);
+		kfree(dynid);
+	}
+	spin_unlock(&hdrv->dyn_lock);
+}
+
+const struct hid_device_id *hid_match_device(struct hid_device *hdev,
+					     struct hid_driver *hdrv)
+{
+	struct hid_dynid *dynid;
+
+	spin_lock(&hdrv->dyn_lock);
+	list_for_each_entry(dynid, &hdrv->dyn_list, list) {
+		if (hid_match_one_id(hdev, &dynid->id)) {
+			spin_unlock(&hdrv->dyn_lock);
+			return &dynid->id;
+		}
+	}
+	spin_unlock(&hdrv->dyn_lock);
+
+	return hid_match_id(hdev, hdrv->id_table);
+}
+EXPORT_SYMBOL_GPL(hid_match_device);
+
+static int hid_bus_match(struct device *dev, struct device_driver *drv)
+{
+	struct hid_driver *hdrv = to_hid_driver(drv);
+	struct hid_device *hdev = to_hid_device(dev);
+
+	return hid_match_device(hdev, hdrv) != NULL;
+}
+
+/**
+ * hid_compare_device_paths - check if both devices share the same path
+ * @hdev_a: hid device
+ * @hdev_b: hid device
+ * @separator: char to use as separator
+ *
+ * Check if two devices share the same path up to the last occurrence of
+ * the separator char. Both paths must exist (i.e., zero-length paths
+ * don't match).
+ */
+bool hid_compare_device_paths(struct hid_device *hdev_a,
+			      struct hid_device *hdev_b, char separator)
+{
+	int n1 = strrchr(hdev_a->phys, separator) - hdev_a->phys;
+	int n2 = strrchr(hdev_b->phys, separator) - hdev_b->phys;
+
+	if (n1 != n2 || n1 <= 0 || n2 <= 0)
+		return false;
+
+	return !strncmp(hdev_a->phys, hdev_b->phys, n1);
+}
+EXPORT_SYMBOL_GPL(hid_compare_device_paths);
+
+static int hid_device_probe(struct device *dev)
+{
+	struct hid_driver *hdrv = to_hid_driver(dev->driver);
+	struct hid_device *hdev = to_hid_device(dev);
+	const struct hid_device_id *id;
+	int ret = 0;
+
+	if (down_interruptible(&hdev->driver_input_lock)) {
+		ret = -EINTR;
+		goto end;
+	}
+	hdev->io_started = false;
+
+	clear_bit(ffs(HID_STAT_REPROBED), &hdev->status);
+
+	if (!hdev->driver) {
+		id = hid_match_device(hdev, hdrv);
+		if (id == NULL) {
+			ret = -ENODEV;
+			goto unlock;
+		}
+
+		if (hdrv->match) {
+			if (!hdrv->match(hdev, hid_ignore_special_drivers)) {
+				ret = -ENODEV;
+				goto unlock;
+			}
+		} else {
+			/*
+			 * hid-generic implements .match(), so if
+			 * hid_ignore_special_drivers is set, we can safely
+			 * return.
+			 */
+			if (hid_ignore_special_drivers) {
+				ret = -ENODEV;
+				goto unlock;
+			}
+		}
+
+		/* reset the quirks that has been previously set */
+		hdev->quirks = hid_lookup_quirk(hdev);
+		hdev->driver = hdrv;
+		if (hdrv->probe) {
+			ret = hdrv->probe(hdev, id);
+		} else { /* default probe */
+			ret = hid_open_report(hdev);
+			if (!ret)
+				ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+		}
+		if (ret) {
+			hid_close_report(hdev);
+			hdev->driver = NULL;
+		}
+	}
+unlock:
+	if (!hdev->io_started)
+		up(&hdev->driver_input_lock);
+end:
+	return ret;
+}
+
+static int hid_device_remove(struct device *dev)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct hid_driver *hdrv;
+	int ret = 0;
+
+	if (down_interruptible(&hdev->driver_input_lock)) {
+		ret = -EINTR;
+		goto end;
+	}
+	hdev->io_started = false;
+
+	hdrv = hdev->driver;
+	if (hdrv) {
+		if (hdrv->remove)
+			hdrv->remove(hdev);
+		else /* default remove */
+			hid_hw_stop(hdev);
+		hid_close_report(hdev);
+		hdev->driver = NULL;
+	}
+
+	if (!hdev->io_started)
+		up(&hdev->driver_input_lock);
+end:
+	return ret;
+}
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
+			     char *buf)
+{
+	struct hid_device *hdev = container_of(dev, struct hid_device, dev);
+
+	return scnprintf(buf, PAGE_SIZE, "hid:b%04Xg%04Xv%08Xp%08X\n",
+			 hdev->bus, hdev->group, hdev->vendor, hdev->product);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *hid_dev_attrs[] = {
+	&dev_attr_modalias.attr,
+	NULL,
+};
+static struct bin_attribute *hid_dev_bin_attrs[] = {
+	&dev_bin_attr_report_desc,
+	NULL
+};
+static const struct attribute_group hid_dev_group = {
+	.attrs = hid_dev_attrs,
+	.bin_attrs = hid_dev_bin_attrs,
+};
+__ATTRIBUTE_GROUPS(hid_dev);
+
+static int hid_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+
+	if (add_uevent_var(env, "HID_ID=%04X:%08X:%08X",
+			hdev->bus, hdev->vendor, hdev->product))
+		return -ENOMEM;
+
+	if (add_uevent_var(env, "HID_NAME=%s", hdev->name))
+		return -ENOMEM;
+
+	if (add_uevent_var(env, "HID_PHYS=%s", hdev->phys))
+		return -ENOMEM;
+
+	if (add_uevent_var(env, "HID_UNIQ=%s", hdev->uniq))
+		return -ENOMEM;
+
+	if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X",
+			   hdev->bus, hdev->group, hdev->vendor, hdev->product))
+		return -ENOMEM;
+
+	return 0;
+}
+
+struct bus_type hid_bus_type = {
+	.name		= "hid",
+	.dev_groups	= hid_dev_groups,
+	.drv_groups	= hid_drv_groups,
+	.match		= hid_bus_match,
+	.probe		= hid_device_probe,
+	.remove		= hid_device_remove,
+	.uevent		= hid_uevent,
+};
+EXPORT_SYMBOL(hid_bus_type);
+
+int hid_add_device(struct hid_device *hdev)
+{
+	static atomic_t id = ATOMIC_INIT(0);
+	int ret;
+
+	if (WARN_ON(hdev->status & HID_STAT_ADDED))
+		return -EBUSY;
+
+	hdev->quirks = hid_lookup_quirk(hdev);
+
+	/* we need to kill them here, otherwise they will stay allocated to
+	 * wait for coming driver */
+	if (hid_ignore(hdev))
+		return -ENODEV;
+
+	/*
+	 * Check for the mandatory transport channel.
+	 */
+	 if (!hdev->ll_driver->raw_request) {
+		hid_err(hdev, "transport driver missing .raw_request()\n");
+		return -EINVAL;
+	 }
+
+	/*
+	 * Read the device report descriptor once and use as template
+	 * for the driver-specific modifications.
+	 */
+	ret = hdev->ll_driver->parse(hdev);
+	if (ret)
+		return ret;
+	if (!hdev->dev_rdesc)
+		return -ENODEV;
+
+	/*
+	 * Scan generic devices for group information
+	 */
+	if (hid_ignore_special_drivers) {
+		hdev->group = HID_GROUP_GENERIC;
+	} else if (!hdev->group &&
+		   !(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
+		ret = hid_scan_report(hdev);
+		if (ret)
+			hid_warn(hdev, "bad device descriptor (%d)\n", ret);
+	}
+
+	/* XXX hack, any other cleaner solution after the driver core
+	 * is converted to allow more than 20 bytes as the device name? */
+	dev_set_name(&hdev->dev, "%04X:%04X:%04X.%04X", hdev->bus,
+		     hdev->vendor, hdev->product, atomic_inc_return(&id));
+
+	hid_debug_register(hdev, dev_name(&hdev->dev));
+	ret = device_add(&hdev->dev);
+	if (!ret)
+		hdev->status |= HID_STAT_ADDED;
+	else
+		hid_debug_unregister(hdev);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hid_add_device);
+
+/**
+ * hid_allocate_device - allocate new hid device descriptor
+ *
+ * Allocate and initialize hid device, so that hid_destroy_device might be
+ * used to free it.
+ *
+ * New hid_device pointer is returned on success, otherwise ERR_PTR encoded
+ * error value.
+ */
+struct hid_device *hid_allocate_device(void)
+{
+	struct hid_device *hdev;
+	int ret = -ENOMEM;
+
+	hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
+	if (hdev == NULL)
+		return ERR_PTR(ret);
+
+	device_initialize(&hdev->dev);
+	hdev->dev.release = hid_device_release;
+	hdev->dev.bus = &hid_bus_type;
+	device_enable_async_suspend(&hdev->dev);
+
+	hid_close_report(hdev);
+
+	init_waitqueue_head(&hdev->debug_wait);
+	INIT_LIST_HEAD(&hdev->debug_list);
+	spin_lock_init(&hdev->debug_list_lock);
+	sema_init(&hdev->driver_input_lock, 1);
+	mutex_init(&hdev->ll_open_lock);
+
+	return hdev;
+}
+EXPORT_SYMBOL_GPL(hid_allocate_device);
+
+static void hid_remove_device(struct hid_device *hdev)
+{
+	if (hdev->status & HID_STAT_ADDED) {
+		device_del(&hdev->dev);
+		hid_debug_unregister(hdev);
+		hdev->status &= ~HID_STAT_ADDED;
+	}
+	kfree(hdev->dev_rdesc);
+	hdev->dev_rdesc = NULL;
+	hdev->dev_rsize = 0;
+}
+
+/**
+ * hid_destroy_device - free previously allocated device
+ *
+ * @hdev: hid device
+ *
+ * If you allocate hid_device through hid_allocate_device, you should ever
+ * free by this function.
+ */
+void hid_destroy_device(struct hid_device *hdev)
+{
+	hid_remove_device(hdev);
+	put_device(&hdev->dev);
+}
+EXPORT_SYMBOL_GPL(hid_destroy_device);
+
+
+static int __hid_bus_reprobe_drivers(struct device *dev, void *data)
+{
+	struct hid_driver *hdrv = data;
+	struct hid_device *hdev = to_hid_device(dev);
+
+	if (hdev->driver == hdrv &&
+	    !hdrv->match(hdev, hid_ignore_special_drivers) &&
+	    !test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
+		return device_reprobe(dev);
+
+	return 0;
+}
+
+static int __hid_bus_driver_added(struct device_driver *drv, void *data)
+{
+	struct hid_driver *hdrv = to_hid_driver(drv);
+
+	if (hdrv->match) {
+		bus_for_each_dev(&hid_bus_type, NULL, hdrv,
+				 __hid_bus_reprobe_drivers);
+	}
+
+	return 0;
+}
+
+static int __bus_removed_driver(struct device_driver *drv, void *data)
+{
+	return bus_rescan_devices(&hid_bus_type);
+}
+
+int __hid_register_driver(struct hid_driver *hdrv, struct module *owner,
+		const char *mod_name)
+{
+	int ret;
+
+	hdrv->driver.name = hdrv->name;
+	hdrv->driver.bus = &hid_bus_type;
+	hdrv->driver.owner = owner;
+	hdrv->driver.mod_name = mod_name;
+
+	INIT_LIST_HEAD(&hdrv->dyn_list);
+	spin_lock_init(&hdrv->dyn_lock);
+
+	ret = driver_register(&hdrv->driver);
+
+	if (ret == 0)
+		bus_for_each_drv(&hid_bus_type, NULL, NULL,
+				 __hid_bus_driver_added);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__hid_register_driver);
+
+void hid_unregister_driver(struct hid_driver *hdrv)
+{
+	driver_unregister(&hdrv->driver);
+	hid_free_dynids(hdrv);
+
+	bus_for_each_drv(&hid_bus_type, NULL, hdrv, __bus_removed_driver);
+}
+EXPORT_SYMBOL_GPL(hid_unregister_driver);
+
+int hid_check_keys_pressed(struct hid_device *hid)
+{
+	struct hid_input *hidinput;
+	int i;
+
+	if (!(hid->claimed & HID_CLAIMED_INPUT))
+		return 0;
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		for (i = 0; i < BITS_TO_LONGS(KEY_MAX); i++)
+			if (hidinput->input->key[i])
+				return 1;
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
+
+static int __init hid_init(void)
+{
+	int ret;
+
+	if (hid_debug)
+		pr_warn("hid_debug is now used solely for parser and driver debugging.\n"
+			"debugfs is now used for inspecting the device (report descriptor, reports)\n");
+
+	ret = bus_register(&hid_bus_type);
+	if (ret) {
+		pr_err("can't register hid bus\n");
+		goto err;
+	}
+
+	ret = hidraw_init();
+	if (ret)
+		goto err_bus;
+
+	hid_debug_init();
+
+	return 0;
+err_bus:
+	bus_unregister(&hid_bus_type);
+err:
+	return ret;
+}
+
+static void __exit hid_exit(void)
+{
+	hid_debug_exit();
+	hidraw_exit();
+	bus_unregister(&hid_bus_type);
+	hid_quirks_exit(HID_BUS_ANY);
+}
+
+module_init(hid_init);
+module_exit(hid_exit);
+
+MODULE_AUTHOR("Andreas Gal");
+MODULE_AUTHOR("Vojtech Pavlik");
+MODULE_AUTHOR("Jiri Kosina");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c
new file mode 100644
index 0000000..ec9e060
--- /dev/null
+++ b/drivers/hid/hid-corsair.c
@@ -0,0 +1,754 @@
+/*
+ * HID driver for Corsair devices
+ *
+ * Supported devices:
+ *  - Vengeance K70 Keyboard
+ *  - K70 RAPIDFIRE Keyboard
+ *  - Vengeance K90 Keyboard
+ *  - Scimitar PRO RGB Gaming Mouse
+ *
+ * Copyright (c) 2015 Clement Vuchener
+ * Copyright (c) 2017 Oscar Campos
+ * Copyright (c) 2017 Aaron Bottegal
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/leds.h>
+
+#include "hid-ids.h"
+
+#define CORSAIR_USE_K90_MACRO	(1<<0)
+#define CORSAIR_USE_K90_BACKLIGHT	(1<<1)
+
+struct k90_led {
+	struct led_classdev cdev;
+	int brightness;
+	struct work_struct work;
+	bool removed;
+};
+
+struct k90_drvdata {
+	struct k90_led record_led;
+};
+
+struct corsair_drvdata {
+	unsigned long quirks;
+	struct k90_drvdata *k90;
+	struct k90_led *backlight;
+};
+
+#define K90_GKEY_COUNT	18
+
+static int corsair_usage_to_gkey(unsigned int usage)
+{
+	/* G1 (0xd0) to G16 (0xdf) */
+	if (usage >= 0xd0 && usage <= 0xdf)
+		return usage - 0xd0 + 1;
+	/* G17 (0xe8) to G18 (0xe9) */
+	if (usage >= 0xe8 && usage <= 0xe9)
+		return usage - 0xe8 + 17;
+	return 0;
+}
+
+static unsigned short corsair_gkey_map[K90_GKEY_COUNT] = {
+	BTN_TRIGGER_HAPPY1,
+	BTN_TRIGGER_HAPPY2,
+	BTN_TRIGGER_HAPPY3,
+	BTN_TRIGGER_HAPPY4,
+	BTN_TRIGGER_HAPPY5,
+	BTN_TRIGGER_HAPPY6,
+	BTN_TRIGGER_HAPPY7,
+	BTN_TRIGGER_HAPPY8,
+	BTN_TRIGGER_HAPPY9,
+	BTN_TRIGGER_HAPPY10,
+	BTN_TRIGGER_HAPPY11,
+	BTN_TRIGGER_HAPPY12,
+	BTN_TRIGGER_HAPPY13,
+	BTN_TRIGGER_HAPPY14,
+	BTN_TRIGGER_HAPPY15,
+	BTN_TRIGGER_HAPPY16,
+	BTN_TRIGGER_HAPPY17,
+	BTN_TRIGGER_HAPPY18,
+};
+
+module_param_array_named(gkey_codes, corsair_gkey_map, ushort, NULL, S_IRUGO);
+MODULE_PARM_DESC(gkey_codes, "Key codes for the G-keys");
+
+static unsigned short corsair_record_keycodes[2] = {
+	BTN_TRIGGER_HAPPY19,
+	BTN_TRIGGER_HAPPY20
+};
+
+module_param_array_named(recordkey_codes, corsair_record_keycodes, ushort,
+			 NULL, S_IRUGO);
+MODULE_PARM_DESC(recordkey_codes, "Key codes for the MR (start and stop record) button");
+
+static unsigned short corsair_profile_keycodes[3] = {
+	BTN_TRIGGER_HAPPY21,
+	BTN_TRIGGER_HAPPY22,
+	BTN_TRIGGER_HAPPY23
+};
+
+module_param_array_named(profilekey_codes, corsair_profile_keycodes, ushort,
+			 NULL, S_IRUGO);
+MODULE_PARM_DESC(profilekey_codes, "Key codes for the profile buttons");
+
+#define CORSAIR_USAGE_SPECIAL_MIN 0xf0
+#define CORSAIR_USAGE_SPECIAL_MAX 0xff
+
+#define CORSAIR_USAGE_MACRO_RECORD_START 0xf6
+#define CORSAIR_USAGE_MACRO_RECORD_STOP 0xf7
+
+#define CORSAIR_USAGE_PROFILE 0xf1
+#define CORSAIR_USAGE_M1 0xf1
+#define CORSAIR_USAGE_M2 0xf2
+#define CORSAIR_USAGE_M3 0xf3
+#define CORSAIR_USAGE_PROFILE_MAX 0xf3
+
+#define CORSAIR_USAGE_META_OFF 0xf4
+#define CORSAIR_USAGE_META_ON  0xf5
+
+#define CORSAIR_USAGE_LIGHT 0xfa
+#define CORSAIR_USAGE_LIGHT_OFF 0xfa
+#define CORSAIR_USAGE_LIGHT_DIM 0xfb
+#define CORSAIR_USAGE_LIGHT_MEDIUM 0xfc
+#define CORSAIR_USAGE_LIGHT_BRIGHT 0xfd
+#define CORSAIR_USAGE_LIGHT_MAX 0xfd
+
+/* USB control protocol */
+
+#define K90_REQUEST_BRIGHTNESS 49
+#define K90_REQUEST_MACRO_MODE 2
+#define K90_REQUEST_STATUS 4
+#define K90_REQUEST_GET_MODE 5
+#define K90_REQUEST_PROFILE 20
+
+#define K90_MACRO_MODE_SW 0x0030
+#define K90_MACRO_MODE_HW 0x0001
+
+#define K90_MACRO_LED_ON  0x0020
+#define K90_MACRO_LED_OFF 0x0040
+
+/*
+ * LED class devices
+ */
+
+#define K90_BACKLIGHT_LED_SUFFIX "::backlight"
+#define K90_RECORD_LED_SUFFIX "::record"
+
+static enum led_brightness k90_backlight_get(struct led_classdev *led_cdev)
+{
+	int ret;
+	struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+	struct device *dev = led->cdev.dev->parent;
+	struct usb_interface *usbif = to_usb_interface(dev->parent);
+	struct usb_device *usbdev = interface_to_usbdev(usbif);
+	int brightness;
+	char *data;
+
+	data = kmalloc(8, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+			      K90_REQUEST_STATUS,
+			      USB_DIR_IN | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, 0, 0, data, 8,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret < 5) {
+		dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
+			 ret);
+		ret = -EIO;
+		goto out;
+	}
+	brightness = data[4];
+	if (brightness < 0 || brightness > 3) {
+		dev_warn(dev,
+			 "Read invalid backlight brightness: %02hhx.\n",
+			 data[4]);
+		ret = -EIO;
+		goto out;
+	}
+	ret = brightness;
+out:
+	kfree(data);
+
+	return ret;
+}
+
+static enum led_brightness k90_record_led_get(struct led_classdev *led_cdev)
+{
+	struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+
+	return led->brightness;
+}
+
+static void k90_brightness_set(struct led_classdev *led_cdev,
+			       enum led_brightness brightness)
+{
+	struct k90_led *led = container_of(led_cdev, struct k90_led, cdev);
+
+	led->brightness = brightness;
+	schedule_work(&led->work);
+}
+
+static void k90_backlight_work(struct work_struct *work)
+{
+	int ret;
+	struct k90_led *led = container_of(work, struct k90_led, work);
+	struct device *dev;
+	struct usb_interface *usbif;
+	struct usb_device *usbdev;
+
+	if (led->removed)
+		return;
+
+	dev = led->cdev.dev->parent;
+	usbif = to_usb_interface(dev->parent);
+	usbdev = interface_to_usbdev(usbif);
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+			      K90_REQUEST_BRIGHTNESS,
+			      USB_DIR_OUT | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, led->brightness, 0,
+			      NULL, 0, USB_CTRL_SET_TIMEOUT);
+	if (ret != 0)
+		dev_warn(dev, "Failed to set backlight brightness (error: %d).\n",
+			 ret);
+}
+
+static void k90_record_led_work(struct work_struct *work)
+{
+	int ret;
+	struct k90_led *led = container_of(work, struct k90_led, work);
+	struct device *dev;
+	struct usb_interface *usbif;
+	struct usb_device *usbdev;
+	int value;
+
+	if (led->removed)
+		return;
+
+	dev = led->cdev.dev->parent;
+	usbif = to_usb_interface(dev->parent);
+	usbdev = interface_to_usbdev(usbif);
+
+	if (led->brightness > 0)
+		value = K90_MACRO_LED_ON;
+	else
+		value = K90_MACRO_LED_OFF;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+			      K90_REQUEST_MACRO_MODE,
+			      USB_DIR_OUT | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, value, 0, NULL, 0,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret != 0)
+		dev_warn(dev, "Failed to set record LED state (error: %d).\n",
+			 ret);
+}
+
+/*
+ * Keyboard attributes
+ */
+
+static ssize_t k90_show_macro_mode(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	int ret;
+	struct usb_interface *usbif = to_usb_interface(dev->parent);
+	struct usb_device *usbdev = interface_to_usbdev(usbif);
+	const char *macro_mode;
+	char *data;
+
+	data = kmalloc(2, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+			      K90_REQUEST_GET_MODE,
+			      USB_DIR_IN | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, 0, 0, data, 2,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret < 1) {
+		dev_warn(dev, "Failed to get K90 initial mode (error %d).\n",
+			 ret);
+		ret = -EIO;
+		goto out;
+	}
+
+	switch (data[0]) {
+	case K90_MACRO_MODE_HW:
+		macro_mode = "HW";
+		break;
+
+	case K90_MACRO_MODE_SW:
+		macro_mode = "SW";
+		break;
+	default:
+		dev_warn(dev, "K90 in unknown mode: %02hhx.\n",
+			 data[0]);
+		ret = -EIO;
+		goto out;
+	}
+
+	ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
+out:
+	kfree(data);
+
+	return ret;
+}
+
+static ssize_t k90_store_macro_mode(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int ret;
+	struct usb_interface *usbif = to_usb_interface(dev->parent);
+	struct usb_device *usbdev = interface_to_usbdev(usbif);
+	__u16 value;
+
+	if (strncmp(buf, "SW", 2) == 0)
+		value = K90_MACRO_MODE_SW;
+	else if (strncmp(buf, "HW", 2) == 0)
+		value = K90_MACRO_MODE_HW;
+	else
+		return -EINVAL;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+			      K90_REQUEST_MACRO_MODE,
+			      USB_DIR_OUT | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, value, 0, NULL, 0,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret != 0) {
+		dev_warn(dev, "Failed to set macro mode.\n");
+		return ret;
+	}
+
+	return count;
+}
+
+static ssize_t k90_show_current_profile(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	int ret;
+	struct usb_interface *usbif = to_usb_interface(dev->parent);
+	struct usb_device *usbdev = interface_to_usbdev(usbif);
+	int current_profile;
+	char *data;
+
+	data = kmalloc(8, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0),
+			      K90_REQUEST_STATUS,
+			      USB_DIR_IN | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, 0, 0, data, 8,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret < 8) {
+		dev_warn(dev, "Failed to get K90 initial state (error %d).\n",
+			 ret);
+		ret = -EIO;
+		goto out;
+	}
+	current_profile = data[7];
+	if (current_profile < 1 || current_profile > 3) {
+		dev_warn(dev, "Read invalid current profile: %02hhx.\n",
+			 data[7]);
+		ret = -EIO;
+		goto out;
+	}
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
+out:
+	kfree(data);
+
+	return ret;
+}
+
+static ssize_t k90_store_current_profile(struct device *dev,
+					 struct device_attribute *attr,
+					 const char *buf, size_t count)
+{
+	int ret;
+	struct usb_interface *usbif = to_usb_interface(dev->parent);
+	struct usb_device *usbdev = interface_to_usbdev(usbif);
+	int profile;
+
+	if (kstrtoint(buf, 10, &profile))
+		return -EINVAL;
+	if (profile < 1 || profile > 3)
+		return -EINVAL;
+
+	ret = usb_control_msg(usbdev, usb_sndctrlpipe(usbdev, 0),
+			      K90_REQUEST_PROFILE,
+			      USB_DIR_OUT | USB_TYPE_VENDOR |
+			      USB_RECIP_DEVICE, profile, 0, NULL, 0,
+			      USB_CTRL_SET_TIMEOUT);
+	if (ret != 0) {
+		dev_warn(dev, "Failed to change current profile (error %d).\n",
+			 ret);
+		return ret;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(macro_mode, 0644, k90_show_macro_mode, k90_store_macro_mode);
+static DEVICE_ATTR(current_profile, 0644, k90_show_current_profile,
+		   k90_store_current_profile);
+
+static struct attribute *k90_attrs[] = {
+	&dev_attr_macro_mode.attr,
+	&dev_attr_current_profile.attr,
+	NULL
+};
+
+static const struct attribute_group k90_attr_group = {
+	.attrs = k90_attrs,
+};
+
+/*
+ * Driver functions
+ */
+
+static int k90_init_backlight(struct hid_device *dev)
+{
+	int ret;
+	struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+	size_t name_sz;
+	char *name;
+
+	drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL);
+	if (!drvdata->backlight) {
+		ret = -ENOMEM;
+		goto fail_backlight_alloc;
+	}
+
+	name_sz =
+	    strlen(dev_name(&dev->dev)) + sizeof(K90_BACKLIGHT_LED_SUFFIX);
+	name = kzalloc(name_sz, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto fail_name_alloc;
+	}
+	snprintf(name, name_sz, "%s" K90_BACKLIGHT_LED_SUFFIX,
+		 dev_name(&dev->dev));
+	drvdata->backlight->removed = false;
+	drvdata->backlight->cdev.name = name;
+	drvdata->backlight->cdev.max_brightness = 3;
+	drvdata->backlight->cdev.brightness_set = k90_brightness_set;
+	drvdata->backlight->cdev.brightness_get = k90_backlight_get;
+	INIT_WORK(&drvdata->backlight->work, k90_backlight_work);
+	ret = led_classdev_register(&dev->dev, &drvdata->backlight->cdev);
+	if (ret != 0)
+		goto fail_register_cdev;
+
+	return 0;
+
+fail_register_cdev:
+	kfree(drvdata->backlight->cdev.name);
+fail_name_alloc:
+	kfree(drvdata->backlight);
+	drvdata->backlight = NULL;
+fail_backlight_alloc:
+	return ret;
+}
+
+static int k90_init_macro_functions(struct hid_device *dev)
+{
+	int ret;
+	struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+	struct k90_drvdata *k90;
+	size_t name_sz;
+	char *name;
+
+	k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL);
+	if (!k90) {
+		ret = -ENOMEM;
+		goto fail_drvdata;
+	}
+	drvdata->k90 = k90;
+
+	/* Init LED device for record LED */
+	name_sz = strlen(dev_name(&dev->dev)) + sizeof(K90_RECORD_LED_SUFFIX);
+	name = kzalloc(name_sz, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto fail_record_led_alloc;
+	}
+	snprintf(name, name_sz, "%s" K90_RECORD_LED_SUFFIX,
+		 dev_name(&dev->dev));
+	k90->record_led.removed = false;
+	k90->record_led.cdev.name = name;
+	k90->record_led.cdev.max_brightness = 1;
+	k90->record_led.cdev.brightness_set = k90_brightness_set;
+	k90->record_led.cdev.brightness_get = k90_record_led_get;
+	INIT_WORK(&k90->record_led.work, k90_record_led_work);
+	k90->record_led.brightness = 0;
+	ret = led_classdev_register(&dev->dev, &k90->record_led.cdev);
+	if (ret != 0)
+		goto fail_record_led;
+
+	/* Init attributes */
+	ret = sysfs_create_group(&dev->dev.kobj, &k90_attr_group);
+	if (ret != 0)
+		goto fail_sysfs;
+
+	return 0;
+
+fail_sysfs:
+	k90->record_led.removed = true;
+	led_classdev_unregister(&k90->record_led.cdev);
+	cancel_work_sync(&k90->record_led.work);
+fail_record_led:
+	kfree(k90->record_led.cdev.name);
+fail_record_led_alloc:
+	kfree(k90);
+fail_drvdata:
+	drvdata->k90 = NULL;
+	return ret;
+}
+
+static void k90_cleanup_backlight(struct hid_device *dev)
+{
+	struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+
+	if (drvdata->backlight) {
+		drvdata->backlight->removed = true;
+		led_classdev_unregister(&drvdata->backlight->cdev);
+		cancel_work_sync(&drvdata->backlight->work);
+		kfree(drvdata->backlight->cdev.name);
+		kfree(drvdata->backlight);
+	}
+}
+
+static void k90_cleanup_macro_functions(struct hid_device *dev)
+{
+	struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+	struct k90_drvdata *k90 = drvdata->k90;
+
+	if (k90) {
+		sysfs_remove_group(&dev->dev.kobj, &k90_attr_group);
+
+		k90->record_led.removed = true;
+		led_classdev_unregister(&k90->record_led.cdev);
+		cancel_work_sync(&k90->record_led.work);
+		kfree(k90->record_led.cdev.name);
+
+		kfree(k90);
+	}
+}
+
+static int corsair_probe(struct hid_device *dev, const struct hid_device_id *id)
+{
+	int ret;
+	unsigned long quirks = id->driver_data;
+	struct corsair_drvdata *drvdata;
+	struct usb_interface *usbif = to_usb_interface(dev->dev.parent);
+
+	drvdata = devm_kzalloc(&dev->dev, sizeof(struct corsair_drvdata),
+			       GFP_KERNEL);
+	if (drvdata == NULL)
+		return -ENOMEM;
+	drvdata->quirks = quirks;
+	hid_set_drvdata(dev, drvdata);
+
+	ret = hid_parse(dev);
+	if (ret != 0) {
+		hid_err(dev, "parse failed\n");
+		return ret;
+	}
+	ret = hid_hw_start(dev, HID_CONNECT_DEFAULT);
+	if (ret != 0) {
+		hid_err(dev, "hw start failed\n");
+		return ret;
+	}
+
+	if (usbif->cur_altsetting->desc.bInterfaceNumber == 0) {
+		if (quirks & CORSAIR_USE_K90_MACRO) {
+			ret = k90_init_macro_functions(dev);
+			if (ret != 0)
+				hid_warn(dev, "Failed to initialize K90 macro functions.\n");
+		}
+		if (quirks & CORSAIR_USE_K90_BACKLIGHT) {
+			ret = k90_init_backlight(dev);
+			if (ret != 0)
+				hid_warn(dev, "Failed to initialize K90 backlight.\n");
+		}
+	}
+
+	return 0;
+}
+
+static void corsair_remove(struct hid_device *dev)
+{
+	k90_cleanup_macro_functions(dev);
+	k90_cleanup_backlight(dev);
+
+	hid_hw_stop(dev);
+}
+
+static int corsair_event(struct hid_device *dev, struct hid_field *field,
+			 struct hid_usage *usage, __s32 value)
+{
+	struct corsair_drvdata *drvdata = hid_get_drvdata(dev);
+
+	if (!drvdata->k90)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case CORSAIR_USAGE_MACRO_RECORD_START:
+		drvdata->k90->record_led.brightness = 1;
+		break;
+	case CORSAIR_USAGE_MACRO_RECORD_STOP:
+		drvdata->k90->record_led.brightness = 0;
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int corsair_input_mapping(struct hid_device *dev,
+				 struct hid_input *input,
+				 struct hid_field *field,
+				 struct hid_usage *usage, unsigned long **bit,
+				 int *max)
+{
+	int gkey;
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD)
+		return 0;
+
+	gkey = corsair_usage_to_gkey(usage->hid & HID_USAGE);
+	if (gkey != 0) {
+		hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+				    corsair_gkey_map[gkey - 1]);
+		return 1;
+	}
+	if ((usage->hid & HID_USAGE) >= CORSAIR_USAGE_SPECIAL_MIN &&
+	    (usage->hid & HID_USAGE) <= CORSAIR_USAGE_SPECIAL_MAX) {
+		switch (usage->hid & HID_USAGE) {
+		case CORSAIR_USAGE_MACRO_RECORD_START:
+			hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+					    corsair_record_keycodes[0]);
+			return 1;
+
+		case CORSAIR_USAGE_MACRO_RECORD_STOP:
+			hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+					    corsair_record_keycodes[1]);
+			return 1;
+
+		case CORSAIR_USAGE_M1:
+			hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+					    corsair_profile_keycodes[0]);
+			return 1;
+
+		case CORSAIR_USAGE_M2:
+			hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+					    corsair_profile_keycodes[1]);
+			return 1;
+
+		case CORSAIR_USAGE_M3:
+			hid_map_usage_clear(input, usage, bit, max, EV_KEY,
+					    corsair_profile_keycodes[2]);
+			return 1;
+
+		default:
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * The report descriptor of some of the Corsair gaming mice is
+ * non parseable as they define two consecutive Logical Minimum for
+ * the Usage Page (Consumer) in rdescs bytes 75 and 77 being 77 0x16
+ * that should be obviousy 0x26 for Logical Magimum of 16 bits. This
+ * prevents poper parsing of the report descriptor due Logical
+ * Minimum being larger than Logical Maximum.
+ *
+ * This driver fixes the report descriptor for:
+ * - USB ID 1b1c:1b34, sold as GLAIVE RGB Gaming mouse
+ * - USB ID 1b1c:1b3e, sold as Scimitar RGB Pro Gaming mouse
+ */
+
+static __u8 *corsair_mouse_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) {
+		/*
+		 * Corsair GLAIVE RGB and Scimitar RGB Pro report descriptor is
+		 * broken and defines two different Logical Minimum for the
+		 * Consumer Application. The byte 77 should be a 0x26 defining
+		 * a 16 bits integer for the Logical Maximum but it is a 0x16
+		 * instead (Logical Minimum)
+		 */
+		switch (hdev->product) {
+		case USB_DEVICE_ID_CORSAIR_GLAIVE_RGB:
+		case USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB:
+			if (*rsize >= 172 && rdesc[75] == 0x15 && rdesc[77] == 0x16
+			&& rdesc[78] == 0xff && rdesc[79] == 0x0f) {
+				hid_info(hdev, "Fixing up report descriptor\n");
+				rdesc[77] = 0x26;
+			}
+			break;
+		}
+
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id corsair_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90),
+		.driver_data = CORSAIR_USE_K90_MACRO |
+			       CORSAIR_USE_K90_BACKLIGHT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR,
+            USB_DEVICE_ID_CORSAIR_GLAIVE_RGB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR,
+            USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB) },
+	/*
+	 * Vengeance K70 and K70 RAPIDFIRE share product IDs.
+	 */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR,
+            USB_DEVICE_ID_CORSAIR_K70R) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, corsair_devices);
+
+static struct hid_driver corsair_driver = {
+	.name = "corsair",
+	.id_table = corsair_devices,
+	.probe = corsair_probe,
+	.event = corsair_event,
+	.remove = corsair_remove,
+	.input_mapping = corsair_input_mapping,
+	.report_fixup = corsair_mouse_report_fixup,
+};
+
+module_hid_driver(corsair_driver);
+
+MODULE_LICENSE("GPL");
+/* Original K90 driver author */
+MODULE_AUTHOR("Clement Vuchener");
+/* Scimitar PRO RGB driver author */
+MODULE_AUTHOR("Oscar Campos");
+MODULE_DESCRIPTION("HID driver for Corsair devices");
diff --git a/drivers/hid/hid-cougar.c b/drivers/hid/hid-cougar.c
new file mode 100644
index 0000000..ad2e87d
--- /dev/null
+++ b/drivers/hid/hid-cougar.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  HID driver for Cougar 500k Gaming Keyboard
+ *
+ *  Copyright (c) 2018 Daniel M. Lambea <dmlambea@gmail.com>
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Daniel M. Lambea <dmlambea@gmail.com>");
+MODULE_DESCRIPTION("Cougar 500k Gaming Keyboard");
+MODULE_LICENSE("GPL");
+MODULE_INFO(key_mappings, "G1-G6 are mapped to F13-F18");
+
+static int cougar_g6_is_space = 1;
+module_param_named(g6_is_space, cougar_g6_is_space, int, 0600);
+MODULE_PARM_DESC(g6_is_space,
+	"If set, G6 programmable key sends SPACE instead of F18 (0=off, 1=on) (default=1)");
+
+
+#define COUGAR_VENDOR_USAGE	0xff00ff00
+
+#define COUGAR_FIELD_CODE	1
+#define COUGAR_FIELD_ACTION	2
+
+#define COUGAR_KEY_G1		0x83
+#define COUGAR_KEY_G2		0x84
+#define COUGAR_KEY_G3		0x85
+#define COUGAR_KEY_G4		0x86
+#define COUGAR_KEY_G5		0x87
+#define COUGAR_KEY_G6		0x78
+#define COUGAR_KEY_FN		0x0d
+#define COUGAR_KEY_MR		0x6f
+#define COUGAR_KEY_M1		0x80
+#define COUGAR_KEY_M2		0x81
+#define COUGAR_KEY_M3		0x82
+#define COUGAR_KEY_LEDS		0x67
+#define COUGAR_KEY_LOCK		0x6e
+
+
+/* Default key mappings. The special key COUGAR_KEY_G6 is defined first
+ * because it is more frequent to use the spacebar rather than any other
+ * special keys. Depending on the value of the parameter 'g6_is_space',
+ * the mapping will be updated in the probe function.
+ */
+static unsigned char cougar_mapping[][2] = {
+	{ COUGAR_KEY_G6,   KEY_SPACE },
+	{ COUGAR_KEY_G1,   KEY_F13 },
+	{ COUGAR_KEY_G2,   KEY_F14 },
+	{ COUGAR_KEY_G3,   KEY_F15 },
+	{ COUGAR_KEY_G4,   KEY_F16 },
+	{ COUGAR_KEY_G5,   KEY_F17 },
+	{ COUGAR_KEY_LOCK, KEY_SCREENLOCK },
+/* The following keys are handled by the hardware itself, so no special
+ * treatment is required:
+	{ COUGAR_KEY_FN, KEY_RESERVED },
+	{ COUGAR_KEY_MR, KEY_RESERVED },
+	{ COUGAR_KEY_M1, KEY_RESERVED },
+	{ COUGAR_KEY_M2, KEY_RESERVED },
+	{ COUGAR_KEY_M3, KEY_RESERVED },
+	{ COUGAR_KEY_LEDS, KEY_RESERVED },
+*/
+	{ 0, 0 },
+};
+
+struct cougar_shared {
+	struct list_head list;
+	struct kref kref;
+	bool enabled;
+	struct hid_device *dev;
+	struct input_dev *input;
+};
+
+struct cougar {
+	bool special_intf;
+	struct cougar_shared *shared;
+};
+
+static LIST_HEAD(cougar_udev_list);
+static DEFINE_MUTEX(cougar_udev_list_lock);
+
+static void cougar_fix_g6_mapping(struct hid_device *hdev)
+{
+	int i;
+
+	for (i = 0; cougar_mapping[i][0]; i++) {
+		if (cougar_mapping[i][0] == COUGAR_KEY_G6) {
+			cougar_mapping[i][1] =
+				cougar_g6_is_space ? KEY_SPACE : KEY_F18;
+			hid_info(hdev, "G6 mapped to %s\n",
+				 cougar_g6_is_space ? "space" : "F18");
+			return;
+		}
+	}
+	hid_warn(hdev, "no mapping defined for G6/spacebar");
+}
+
+/*
+ * Constant-friendly rdesc fixup for mouse interface
+ */
+static __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+				 unsigned int *rsize)
+{
+	if (rdesc[2] == 0x09 && rdesc[3] == 0x02 &&
+	    (rdesc[115] | rdesc[116] << 8) >= HID_MAX_USAGES) {
+		hid_info(hdev,
+			"usage count exceeds max: fixing up report descriptor\n");
+		rdesc[115] = ((HID_MAX_USAGES-1) & 0xff);
+		rdesc[116] = ((HID_MAX_USAGES-1) >> 8);
+	}
+	return rdesc;
+}
+
+static struct cougar_shared *cougar_get_shared_data(struct hid_device *hdev)
+{
+	struct cougar_shared *shared;
+
+	/* Try to find an already-probed interface from the same device */
+	list_for_each_entry(shared, &cougar_udev_list, list) {
+		if (hid_compare_device_paths(hdev, shared->dev, '/')) {
+			kref_get(&shared->kref);
+			return shared;
+		}
+	}
+	return NULL;
+}
+
+static void cougar_release_shared_data(struct kref *kref)
+{
+	struct cougar_shared *shared = container_of(kref,
+						    struct cougar_shared, kref);
+
+	mutex_lock(&cougar_udev_list_lock);
+	list_del(&shared->list);
+	mutex_unlock(&cougar_udev_list_lock);
+
+	kfree(shared);
+}
+
+static void cougar_remove_shared_data(void *resource)
+{
+	struct cougar *cougar = resource;
+
+	if (cougar->shared) {
+		kref_put(&cougar->shared->kref, cougar_release_shared_data);
+		cougar->shared = NULL;
+	}
+}
+
+/*
+ * Bind the device group's shared data to this cougar struct.
+ * If no shared data exists for this group, create and initialize it.
+ */
+static int cougar_bind_shared_data(struct hid_device *hdev, struct cougar *cougar)
+{
+	struct cougar_shared *shared;
+	int error = 0;
+
+	mutex_lock(&cougar_udev_list_lock);
+
+	shared = cougar_get_shared_data(hdev);
+	if (!shared) {
+		shared = kzalloc(sizeof(*shared), GFP_KERNEL);
+		if (!shared) {
+			error = -ENOMEM;
+			goto out;
+		}
+
+		kref_init(&shared->kref);
+		shared->dev = hdev;
+		list_add_tail(&shared->list, &cougar_udev_list);
+	}
+
+	cougar->shared = shared;
+
+	error = devm_add_action(&hdev->dev, cougar_remove_shared_data, cougar);
+	if (error) {
+		mutex_unlock(&cougar_udev_list_lock);
+		cougar_remove_shared_data(cougar);
+		return error;
+	}
+
+out:
+	mutex_unlock(&cougar_udev_list_lock);
+	return error;
+}
+
+static int cougar_probe(struct hid_device *hdev,
+			const struct hid_device_id *id)
+{
+	struct cougar *cougar;
+	struct hid_input *next, *hidinput = NULL;
+	unsigned int connect_mask;
+	int error;
+
+	cougar = devm_kzalloc(&hdev->dev, sizeof(*cougar), GFP_KERNEL);
+	if (!cougar)
+		return -ENOMEM;
+	hid_set_drvdata(hdev, cougar);
+
+	error = hid_parse(hdev);
+	if (error) {
+		hid_err(hdev, "parse failed\n");
+		goto fail;
+	}
+
+	if (hdev->collection->usage == COUGAR_VENDOR_USAGE) {
+		cougar->special_intf = true;
+		connect_mask = HID_CONNECT_HIDRAW;
+	} else
+		connect_mask = HID_CONNECT_DEFAULT;
+
+	error = hid_hw_start(hdev, connect_mask);
+	if (error) {
+		hid_err(hdev, "hw start failed\n");
+		goto fail;
+	}
+
+	error = cougar_bind_shared_data(hdev, cougar);
+	if (error)
+		goto fail_stop_and_cleanup;
+
+	/* The custom vendor interface will use the hid_input registered
+	 * for the keyboard interface, in order to send translated key codes
+	 * to it.
+	 */
+	if (hdev->collection->usage == HID_GD_KEYBOARD) {
+		cougar_fix_g6_mapping(hdev);
+		list_for_each_entry_safe(hidinput, next, &hdev->inputs, list) {
+			if (hidinput->registered && hidinput->input != NULL) {
+				cougar->shared->input = hidinput->input;
+				cougar->shared->enabled = true;
+				break;
+			}
+		}
+	} else if (hdev->collection->usage == COUGAR_VENDOR_USAGE) {
+		error = hid_hw_open(hdev);
+		if (error)
+			goto fail_stop_and_cleanup;
+	}
+	return 0;
+
+fail_stop_and_cleanup:
+	hid_hw_stop(hdev);
+fail:
+	hid_set_drvdata(hdev, NULL);
+	return error;
+}
+
+/*
+ * Convert events from vendor intf to input key events
+ */
+static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report,
+			    u8 *data, int size)
+{
+	struct cougar *cougar;
+	unsigned char code, action;
+	int i;
+
+	cougar = hid_get_drvdata(hdev);
+	if (!cougar->special_intf || !cougar->shared ||
+	    !cougar->shared->input || !cougar->shared->enabled)
+		return 0;
+
+	code = data[COUGAR_FIELD_CODE];
+	action = data[COUGAR_FIELD_ACTION];
+	for (i = 0; cougar_mapping[i][0]; i++) {
+		if (code == cougar_mapping[i][0]) {
+			input_event(cougar->shared->input, EV_KEY,
+				    cougar_mapping[i][1], action);
+			input_sync(cougar->shared->input);
+			return 0;
+		}
+	}
+	hid_warn(hdev, "unmapped special key code %x: ignoring\n", code);
+	return 0;
+}
+
+static void cougar_remove(struct hid_device *hdev)
+{
+	struct cougar *cougar = hid_get_drvdata(hdev);
+
+	if (cougar) {
+		/* Stop the vendor intf to process more events */
+		if (cougar->shared)
+			cougar->shared->enabled = false;
+		if (cougar->special_intf)
+			hid_hw_close(hdev);
+	}
+	hid_hw_stop(hdev);
+}
+
+static struct hid_device_id cougar_id_table[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
+			 USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) },
+	{}
+};
+MODULE_DEVICE_TABLE(hid, cougar_id_table);
+
+static struct hid_driver cougar_driver = {
+	.name			= "cougar",
+	.id_table		= cougar_id_table,
+	.report_fixup		= cougar_report_fixup,
+	.probe			= cougar_probe,
+	.remove			= cougar_remove,
+	.raw_event		= cougar_raw_event,
+};
+
+module_hid_driver(cougar_driver);
diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c
new file mode 100644
index 0000000..271f314
--- /dev/null
+++ b/drivers/hid/hid-cp2112.c
@@ -0,0 +1,1482 @@
+/*
+ * hid-cp2112.c - Silicon Labs HID USB to SMBus master bridge
+ * Copyright (c) 2013,2014 Uplogix, Inc.
+ * David Barksdale <dbarksdale@uplogix.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+/*
+ * The Silicon Labs CP2112 chip is a USB HID device which provides an
+ * SMBus controller for talking to slave devices and 8 GPIO pins. The
+ * host communicates with the CP2112 via raw HID reports.
+ *
+ * Data Sheet:
+ *   http://www.silabs.com/Support%20Documents/TechnicalDocs/CP2112.pdf
+ * Programming Interface Specification:
+ *   https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf
+ */
+
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/hid.h>
+#include <linux/hidraw.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/nls.h>
+#include <linux/usb/ch9.h>
+#include "hid-ids.h"
+
+#define CP2112_REPORT_MAX_LENGTH		64
+#define CP2112_GPIO_CONFIG_LENGTH		5
+#define CP2112_GPIO_GET_LENGTH			2
+#define CP2112_GPIO_SET_LENGTH			3
+
+enum {
+	CP2112_GPIO_CONFIG		= 0x02,
+	CP2112_GPIO_GET			= 0x03,
+	CP2112_GPIO_SET			= 0x04,
+	CP2112_GET_VERSION_INFO		= 0x05,
+	CP2112_SMBUS_CONFIG		= 0x06,
+	CP2112_DATA_READ_REQUEST	= 0x10,
+	CP2112_DATA_WRITE_READ_REQUEST	= 0x11,
+	CP2112_DATA_READ_FORCE_SEND	= 0x12,
+	CP2112_DATA_READ_RESPONSE	= 0x13,
+	CP2112_DATA_WRITE_REQUEST	= 0x14,
+	CP2112_TRANSFER_STATUS_REQUEST	= 0x15,
+	CP2112_TRANSFER_STATUS_RESPONSE	= 0x16,
+	CP2112_CANCEL_TRANSFER		= 0x17,
+	CP2112_LOCK_BYTE		= 0x20,
+	CP2112_USB_CONFIG		= 0x21,
+	CP2112_MANUFACTURER_STRING	= 0x22,
+	CP2112_PRODUCT_STRING		= 0x23,
+	CP2112_SERIAL_STRING		= 0x24,
+};
+
+enum {
+	STATUS0_IDLE		= 0x00,
+	STATUS0_BUSY		= 0x01,
+	STATUS0_COMPLETE	= 0x02,
+	STATUS0_ERROR		= 0x03,
+};
+
+enum {
+	STATUS1_TIMEOUT_NACK		= 0x00,
+	STATUS1_TIMEOUT_BUS		= 0x01,
+	STATUS1_ARBITRATION_LOST	= 0x02,
+	STATUS1_READ_INCOMPLETE		= 0x03,
+	STATUS1_WRITE_INCOMPLETE	= 0x04,
+	STATUS1_SUCCESS			= 0x05,
+};
+
+struct cp2112_smbus_config_report {
+	u8 report;		/* CP2112_SMBUS_CONFIG */
+	__be32 clock_speed;	/* Hz */
+	u8 device_address;	/* Stored in the upper 7 bits */
+	u8 auto_send_read;	/* 1 = enabled, 0 = disabled */
+	__be16 write_timeout;	/* ms, 0 = no timeout */
+	__be16 read_timeout;	/* ms, 0 = no timeout */
+	u8 scl_low_timeout;	/* 1 = enabled, 0 = disabled */
+	__be16 retry_time;	/* # of retries, 0 = no limit */
+} __packed;
+
+struct cp2112_usb_config_report {
+	u8 report;	/* CP2112_USB_CONFIG */
+	__le16 vid;	/* Vendor ID */
+	__le16 pid;	/* Product ID */
+	u8 max_power;	/* Power requested in 2mA units */
+	u8 power_mode;	/* 0x00 = bus powered
+			   0x01 = self powered & regulator off
+			   0x02 = self powered & regulator on */
+	u8 release_major;
+	u8 release_minor;
+	u8 mask;	/* What fields to program */
+} __packed;
+
+struct cp2112_read_req_report {
+	u8 report;	/* CP2112_DATA_READ_REQUEST */
+	u8 slave_address;
+	__be16 length;
+} __packed;
+
+struct cp2112_write_read_req_report {
+	u8 report;	/* CP2112_DATA_WRITE_READ_REQUEST */
+	u8 slave_address;
+	__be16 length;
+	u8 target_address_length;
+	u8 target_address[16];
+} __packed;
+
+struct cp2112_write_req_report {
+	u8 report;	/* CP2112_DATA_WRITE_REQUEST */
+	u8 slave_address;
+	u8 length;
+	u8 data[61];
+} __packed;
+
+struct cp2112_force_read_report {
+	u8 report;	/* CP2112_DATA_READ_FORCE_SEND */
+	__be16 length;
+} __packed;
+
+struct cp2112_xfer_status_report {
+	u8 report;	/* CP2112_TRANSFER_STATUS_RESPONSE */
+	u8 status0;	/* STATUS0_* */
+	u8 status1;	/* STATUS1_* */
+	__be16 retries;
+	__be16 length;
+} __packed;
+
+struct cp2112_string_report {
+	u8 dummy;		/* force .string to be aligned */
+	u8 report;		/* CP2112_*_STRING */
+	u8 length;		/* length in bytes of everyting after .report */
+	u8 type;		/* USB_DT_STRING */
+	wchar_t string[30];	/* UTF16_LITTLE_ENDIAN string */
+} __packed;
+
+/* Number of times to request transfer status before giving up waiting for a
+   transfer to complete. This may need to be changed if SMBUS clock, retries,
+   or read/write/scl_low timeout settings are changed. */
+static const int XFER_STATUS_RETRIES = 10;
+
+/* Time in ms to wait for a CP2112_DATA_READ_RESPONSE or
+   CP2112_TRANSFER_STATUS_RESPONSE. */
+static const int RESPONSE_TIMEOUT = 50;
+
+static const struct hid_device_id cp2112_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, cp2112_devices);
+
+struct cp2112_device {
+	struct i2c_adapter adap;
+	struct hid_device *hdev;
+	wait_queue_head_t wait;
+	u8 read_data[61];
+	u8 read_length;
+	u8 hwversion;
+	int xfer_status;
+	atomic_t read_avail;
+	atomic_t xfer_avail;
+	struct gpio_chip gc;
+	u8 *in_out_buffer;
+	struct mutex lock;
+
+	struct gpio_desc *desc[8];
+	bool gpio_poll;
+	struct delayed_work gpio_poll_worker;
+	unsigned long irq_mask;
+	u8 gpio_prev_state;
+};
+
+static int gpio_push_pull = 0xFF;
+module_param(gpio_push_pull, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(gpio_push_pull, "GPIO push-pull configuration bitmask");
+
+static int cp2112_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	struct cp2112_device *dev = gpiochip_get_data(chip);
+	struct hid_device *hdev = dev->hdev;
+	u8 *buf = dev->in_out_buffer;
+	int ret;
+
+	mutex_lock(&dev->lock);
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret != CP2112_GPIO_CONFIG_LENGTH) {
+		hid_err(hdev, "error requesting GPIO config: %d\n", ret);
+		if (ret >= 0)
+			ret = -EIO;
+		goto exit;
+	}
+
+	buf[1] &= ~(1 << offset);
+	buf[2] = gpio_push_pull;
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	if (ret != CP2112_GPIO_CONFIG_LENGTH) {
+		hid_err(hdev, "error setting GPIO config: %d\n", ret);
+		if (ret >= 0)
+			ret = -EIO;
+		goto exit;
+	}
+
+	ret = 0;
+
+exit:
+	mutex_unlock(&dev->lock);
+	return ret;
+}
+
+static void cp2112_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct cp2112_device *dev = gpiochip_get_data(chip);
+	struct hid_device *hdev = dev->hdev;
+	u8 *buf = dev->in_out_buffer;
+	int ret;
+
+	mutex_lock(&dev->lock);
+
+	buf[0] = CP2112_GPIO_SET;
+	buf[1] = value ? 0xff : 0;
+	buf[2] = 1 << offset;
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_SET, buf,
+				 CP2112_GPIO_SET_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	if (ret < 0)
+		hid_err(hdev, "error setting GPIO values: %d\n", ret);
+
+	mutex_unlock(&dev->lock);
+}
+
+static int cp2112_gpio_get_all(struct gpio_chip *chip)
+{
+	struct cp2112_device *dev = gpiochip_get_data(chip);
+	struct hid_device *hdev = dev->hdev;
+	u8 *buf = dev->in_out_buffer;
+	int ret;
+
+	mutex_lock(&dev->lock);
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_GET, buf,
+				 CP2112_GPIO_GET_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret != CP2112_GPIO_GET_LENGTH) {
+		hid_err(hdev, "error requesting GPIO values: %d\n", ret);
+		ret = ret < 0 ? ret : -EIO;
+		goto exit;
+	}
+
+	ret = buf[1];
+
+exit:
+	mutex_unlock(&dev->lock);
+
+	return ret;
+}
+
+static int cp2112_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	int ret;
+
+	ret = cp2112_gpio_get_all(chip);
+	if (ret < 0)
+		return ret;
+
+	return (ret >> offset) & 1;
+}
+
+static int cp2112_gpio_direction_output(struct gpio_chip *chip,
+					unsigned offset, int value)
+{
+	struct cp2112_device *dev = gpiochip_get_data(chip);
+	struct hid_device *hdev = dev->hdev;
+	u8 *buf = dev->in_out_buffer;
+	int ret;
+
+	mutex_lock(&dev->lock);
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret != CP2112_GPIO_CONFIG_LENGTH) {
+		hid_err(hdev, "error requesting GPIO config: %d\n", ret);
+		goto fail;
+	}
+
+	buf[1] |= 1 << offset;
+	buf[2] = gpio_push_pull;
+
+	ret = hid_hw_raw_request(hdev, CP2112_GPIO_CONFIG, buf,
+				 CP2112_GPIO_CONFIG_LENGTH, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	if (ret < 0) {
+		hid_err(hdev, "error setting GPIO config: %d\n", ret);
+		goto fail;
+	}
+
+	mutex_unlock(&dev->lock);
+
+	/*
+	 * Set gpio value when output direction is already set,
+	 * as specified in AN495, Rev. 0.2, cpt. 4.4
+	 */
+	cp2112_gpio_set(chip, offset, value);
+
+	return 0;
+
+fail:
+	mutex_unlock(&dev->lock);
+	return ret < 0 ? ret : -EIO;
+}
+
+static int cp2112_hid_get(struct hid_device *hdev, unsigned char report_number,
+			  u8 *data, size_t count, unsigned char report_type)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, report_number, buf, count,
+				       report_type, HID_REQ_GET_REPORT);
+	memcpy(data, buf, count);
+	kfree(buf);
+	return ret;
+}
+
+static int cp2112_hid_output(struct hid_device *hdev, u8 *data, size_t count,
+			     unsigned char report_type)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmemdup(data, count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (report_type == HID_OUTPUT_REPORT)
+		ret = hid_hw_output_report(hdev, buf, count);
+	else
+		ret = hid_hw_raw_request(hdev, buf[0], buf, count, report_type,
+				HID_REQ_SET_REPORT);
+
+	kfree(buf);
+	return ret;
+}
+
+static int cp2112_wait(struct cp2112_device *dev, atomic_t *avail)
+{
+	int ret = 0;
+
+	/* We have sent either a CP2112_TRANSFER_STATUS_REQUEST or a
+	 * CP2112_DATA_READ_FORCE_SEND and we are waiting for the response to
+	 * come in cp2112_raw_event or timeout. There will only be one of these
+	 * in flight at any one time. The timeout is extremely large and is a
+	 * last resort if the CP2112 has died. If we do timeout we don't expect
+	 * to receive the response which would cause data races, it's not like
+	 * we can do anything about it anyway.
+	 */
+	ret = wait_event_interruptible_timeout(dev->wait,
+		atomic_read(avail), msecs_to_jiffies(RESPONSE_TIMEOUT));
+	if (-ERESTARTSYS == ret)
+		return ret;
+	if (!ret)
+		return -ETIMEDOUT;
+
+	atomic_set(avail, 0);
+	return 0;
+}
+
+static int cp2112_xfer_status(struct cp2112_device *dev)
+{
+	struct hid_device *hdev = dev->hdev;
+	u8 buf[2];
+	int ret;
+
+	buf[0] = CP2112_TRANSFER_STATUS_REQUEST;
+	buf[1] = 0x01;
+	atomic_set(&dev->xfer_avail, 0);
+
+	ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT);
+	if (ret < 0) {
+		hid_warn(hdev, "Error requesting status: %d\n", ret);
+		return ret;
+	}
+
+	ret = cp2112_wait(dev, &dev->xfer_avail);
+	if (ret)
+		return ret;
+
+	return dev->xfer_status;
+}
+
+static int cp2112_read(struct cp2112_device *dev, u8 *data, size_t size)
+{
+	struct hid_device *hdev = dev->hdev;
+	struct cp2112_force_read_report report;
+	int ret;
+
+	if (size > sizeof(dev->read_data))
+		size = sizeof(dev->read_data);
+	report.report = CP2112_DATA_READ_FORCE_SEND;
+	report.length = cpu_to_be16(size);
+
+	atomic_set(&dev->read_avail, 0);
+
+	ret = cp2112_hid_output(hdev, &report.report, sizeof(report),
+				HID_OUTPUT_REPORT);
+	if (ret < 0) {
+		hid_warn(hdev, "Error requesting data: %d\n", ret);
+		return ret;
+	}
+
+	ret = cp2112_wait(dev, &dev->read_avail);
+	if (ret)
+		return ret;
+
+	hid_dbg(hdev, "read %d of %zd bytes requested\n",
+		dev->read_length, size);
+
+	if (size > dev->read_length)
+		size = dev->read_length;
+
+	memcpy(data, dev->read_data, size);
+	return dev->read_length;
+}
+
+static int cp2112_read_req(void *buf, u8 slave_address, u16 length)
+{
+	struct cp2112_read_req_report *report = buf;
+
+	if (length < 1 || length > 512)
+		return -EINVAL;
+
+	report->report = CP2112_DATA_READ_REQUEST;
+	report->slave_address = slave_address << 1;
+	report->length = cpu_to_be16(length);
+	return sizeof(*report);
+}
+
+static int cp2112_write_read_req(void *buf, u8 slave_address, u16 length,
+				 u8 command, u8 *data, u8 data_length)
+{
+	struct cp2112_write_read_req_report *report = buf;
+
+	if (length < 1 || length > 512
+	    || data_length > sizeof(report->target_address) - 1)
+		return -EINVAL;
+
+	report->report = CP2112_DATA_WRITE_READ_REQUEST;
+	report->slave_address = slave_address << 1;
+	report->length = cpu_to_be16(length);
+	report->target_address_length = data_length + 1;
+	report->target_address[0] = command;
+	memcpy(&report->target_address[1], data, data_length);
+	return data_length + 6;
+}
+
+static int cp2112_write_req(void *buf, u8 slave_address, u8 command, u8 *data,
+			    u8 data_length)
+{
+	struct cp2112_write_req_report *report = buf;
+
+	if (data_length > sizeof(report->data) - 1)
+		return -EINVAL;
+
+	report->report = CP2112_DATA_WRITE_REQUEST;
+	report->slave_address = slave_address << 1;
+	report->length = data_length + 1;
+	report->data[0] = command;
+	memcpy(&report->data[1], data, data_length);
+	return data_length + 4;
+}
+
+static int cp2112_i2c_write_req(void *buf, u8 slave_address, u8 *data,
+				u8 data_length)
+{
+	struct cp2112_write_req_report *report = buf;
+
+	if (data_length > sizeof(report->data))
+		return -EINVAL;
+
+	report->report = CP2112_DATA_WRITE_REQUEST;
+	report->slave_address = slave_address << 1;
+	report->length = data_length;
+	memcpy(report->data, data, data_length);
+	return data_length + 3;
+}
+
+static int cp2112_i2c_write_read_req(void *buf, u8 slave_address,
+				     u8 *addr, int addr_length,
+				     int read_length)
+{
+	struct cp2112_write_read_req_report *report = buf;
+
+	if (read_length < 1 || read_length > 512 ||
+	    addr_length > sizeof(report->target_address))
+		return -EINVAL;
+
+	report->report = CP2112_DATA_WRITE_READ_REQUEST;
+	report->slave_address = slave_address << 1;
+	report->length = cpu_to_be16(read_length);
+	report->target_address_length = addr_length;
+	memcpy(report->target_address, addr, addr_length);
+	return addr_length + 5;
+}
+
+static int cp2112_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			   int num)
+{
+	struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data;
+	struct hid_device *hdev = dev->hdev;
+	u8 buf[64];
+	ssize_t count;
+	ssize_t read_length = 0;
+	u8 *read_buf = NULL;
+	unsigned int retries;
+	int ret;
+
+	hid_dbg(hdev, "I2C %d messages\n", num);
+
+	if (num == 1) {
+		if (msgs->flags & I2C_M_RD) {
+			hid_dbg(hdev, "I2C read %#04x len %d\n",
+				msgs->addr, msgs->len);
+			read_length = msgs->len;
+			read_buf = msgs->buf;
+			count = cp2112_read_req(buf, msgs->addr, msgs->len);
+		} else {
+			hid_dbg(hdev, "I2C write %#04x len %d\n",
+				msgs->addr, msgs->len);
+			count = cp2112_i2c_write_req(buf, msgs->addr,
+						     msgs->buf, msgs->len);
+		}
+		if (count < 0)
+			return count;
+	} else if (dev->hwversion > 1 &&  /* no repeated start in rev 1 */
+		   num == 2 &&
+		   msgs[0].addr == msgs[1].addr &&
+		   !(msgs[0].flags & I2C_M_RD) && (msgs[1].flags & I2C_M_RD)) {
+		hid_dbg(hdev, "I2C write-read %#04x wlen %d rlen %d\n",
+			msgs[0].addr, msgs[0].len, msgs[1].len);
+		read_length = msgs[1].len;
+		read_buf = msgs[1].buf;
+		count = cp2112_i2c_write_read_req(buf, msgs[0].addr,
+				msgs[0].buf, msgs[0].len, msgs[1].len);
+		if (count < 0)
+			return count;
+	} else {
+		hid_err(hdev,
+			"Multi-message I2C transactions not supported\n");
+		return -EOPNOTSUPP;
+	}
+
+	ret = hid_hw_power(hdev, PM_HINT_FULLON);
+	if (ret < 0) {
+		hid_err(hdev, "power management error: %d\n", ret);
+		return ret;
+	}
+
+	ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT);
+	if (ret < 0) {
+		hid_warn(hdev, "Error starting transaction: %d\n", ret);
+		goto power_normal;
+	}
+
+	for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) {
+		ret = cp2112_xfer_status(dev);
+		if (-EBUSY == ret)
+			continue;
+		if (ret < 0)
+			goto power_normal;
+		break;
+	}
+
+	if (XFER_STATUS_RETRIES <= retries) {
+		hid_warn(hdev, "Transfer timed out, cancelling.\n");
+		buf[0] = CP2112_CANCEL_TRANSFER;
+		buf[1] = 0x01;
+
+		ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT);
+		if (ret < 0)
+			hid_warn(hdev, "Error cancelling transaction: %d\n",
+				 ret);
+
+		ret = -ETIMEDOUT;
+		goto power_normal;
+	}
+
+	for (count = 0; count < read_length;) {
+		ret = cp2112_read(dev, read_buf + count, read_length - count);
+		if (ret < 0)
+			goto power_normal;
+		if (ret == 0) {
+			hid_err(hdev, "read returned 0\n");
+			ret = -EIO;
+			goto power_normal;
+		}
+		count += ret;
+		if (count > read_length) {
+			/*
+			 * The hardware returned too much data.
+			 * This is mostly harmless because cp2112_read()
+			 * has a limit check so didn't overrun our
+			 * buffer.  Nevertheless, we return an error
+			 * because something is seriously wrong and
+			 * it shouldn't go unnoticed.
+			 */
+			hid_err(hdev, "long read: %d > %zd\n",
+				ret, read_length - count + ret);
+			ret = -EIO;
+			goto power_normal;
+		}
+	}
+
+	/* return the number of transferred messages */
+	ret = num;
+
+power_normal:
+	hid_hw_power(hdev, PM_HINT_NORMAL);
+	hid_dbg(hdev, "I2C transfer finished: %d\n", ret);
+	return ret;
+}
+
+static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
+		       unsigned short flags, char read_write, u8 command,
+		       int size, union i2c_smbus_data *data)
+{
+	struct cp2112_device *dev = (struct cp2112_device *)adap->algo_data;
+	struct hid_device *hdev = dev->hdev;
+	u8 buf[64];
+	__le16 word;
+	ssize_t count;
+	size_t read_length = 0;
+	unsigned int retries;
+	int ret;
+
+	hid_dbg(hdev, "%s addr 0x%x flags 0x%x cmd 0x%x size %d\n",
+		read_write == I2C_SMBUS_WRITE ? "write" : "read",
+		addr, flags, command, size);
+
+	switch (size) {
+	case I2C_SMBUS_BYTE:
+		read_length = 1;
+
+		if (I2C_SMBUS_READ == read_write)
+			count = cp2112_read_req(buf, addr, read_length);
+		else
+			count = cp2112_write_req(buf, addr, command, NULL,
+						 0);
+		break;
+	case I2C_SMBUS_BYTE_DATA:
+		read_length = 1;
+
+		if (I2C_SMBUS_READ == read_write)
+			count = cp2112_write_read_req(buf, addr, read_length,
+						      command, NULL, 0);
+		else
+			count = cp2112_write_req(buf, addr, command,
+						 &data->byte, 1);
+		break;
+	case I2C_SMBUS_WORD_DATA:
+		read_length = 2;
+		word = cpu_to_le16(data->word);
+
+		if (I2C_SMBUS_READ == read_write)
+			count = cp2112_write_read_req(buf, addr, read_length,
+						      command, NULL, 0);
+		else
+			count = cp2112_write_req(buf, addr, command,
+						 (u8 *)&word, 2);
+		break;
+	case I2C_SMBUS_PROC_CALL:
+		size = I2C_SMBUS_WORD_DATA;
+		read_write = I2C_SMBUS_READ;
+		read_length = 2;
+		word = cpu_to_le16(data->word);
+
+		count = cp2112_write_read_req(buf, addr, read_length, command,
+					      (u8 *)&word, 2);
+		break;
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (read_write == I2C_SMBUS_READ) {
+			read_length = data->block[0];
+			count = cp2112_write_read_req(buf, addr, read_length,
+						      command, NULL, 0);
+		} else {
+			count = cp2112_write_req(buf, addr, command,
+						 data->block + 1,
+						 data->block[0]);
+		}
+		break;
+	case I2C_SMBUS_BLOCK_DATA:
+		if (I2C_SMBUS_READ == read_write) {
+			count = cp2112_write_read_req(buf, addr,
+						      I2C_SMBUS_BLOCK_MAX,
+						      command, NULL, 0);
+		} else {
+			count = cp2112_write_req(buf, addr, command,
+						 data->block,
+						 data->block[0] + 1);
+		}
+		break;
+	case I2C_SMBUS_BLOCK_PROC_CALL:
+		size = I2C_SMBUS_BLOCK_DATA;
+		read_write = I2C_SMBUS_READ;
+
+		count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX,
+					      command, data->block,
+					      data->block[0] + 1);
+		break;
+	default:
+		hid_warn(hdev, "Unsupported transaction %d\n", size);
+		return -EOPNOTSUPP;
+	}
+
+	if (count < 0)
+		return count;
+
+	ret = hid_hw_power(hdev, PM_HINT_FULLON);
+	if (ret < 0) {
+		hid_err(hdev, "power management error: %d\n", ret);
+		return ret;
+	}
+
+	ret = cp2112_hid_output(hdev, buf, count, HID_OUTPUT_REPORT);
+	if (ret < 0) {
+		hid_warn(hdev, "Error starting transaction: %d\n", ret);
+		goto power_normal;
+	}
+
+	for (retries = 0; retries < XFER_STATUS_RETRIES; ++retries) {
+		ret = cp2112_xfer_status(dev);
+		if (-EBUSY == ret)
+			continue;
+		if (ret < 0)
+			goto power_normal;
+		break;
+	}
+
+	if (XFER_STATUS_RETRIES <= retries) {
+		hid_warn(hdev, "Transfer timed out, cancelling.\n");
+		buf[0] = CP2112_CANCEL_TRANSFER;
+		buf[1] = 0x01;
+
+		ret = cp2112_hid_output(hdev, buf, 2, HID_OUTPUT_REPORT);
+		if (ret < 0)
+			hid_warn(hdev, "Error cancelling transaction: %d\n",
+				 ret);
+
+		ret = -ETIMEDOUT;
+		goto power_normal;
+	}
+
+	if (I2C_SMBUS_WRITE == read_write) {
+		ret = 0;
+		goto power_normal;
+	}
+
+	if (I2C_SMBUS_BLOCK_DATA == size)
+		read_length = ret;
+
+	ret = cp2112_read(dev, buf, read_length);
+	if (ret < 0)
+		goto power_normal;
+	if (ret != read_length) {
+		hid_warn(hdev, "short read: %d < %zd\n", ret, read_length);
+		ret = -EIO;
+		goto power_normal;
+	}
+
+	switch (size) {
+	case I2C_SMBUS_BYTE:
+	case I2C_SMBUS_BYTE_DATA:
+		data->byte = buf[0];
+		break;
+	case I2C_SMBUS_WORD_DATA:
+		data->word = le16_to_cpup((__le16 *)buf);
+		break;
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		memcpy(data->block + 1, buf, read_length);
+		break;
+	case I2C_SMBUS_BLOCK_DATA:
+		if (read_length > I2C_SMBUS_BLOCK_MAX) {
+			ret = -EPROTO;
+			goto power_normal;
+		}
+
+		memcpy(data->block, buf, read_length);
+		break;
+	}
+
+	ret = 0;
+power_normal:
+	hid_hw_power(hdev, PM_HINT_NORMAL);
+	hid_dbg(hdev, "transfer finished: %d\n", ret);
+	return ret;
+}
+
+static u32 cp2112_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C |
+		I2C_FUNC_SMBUS_BYTE |
+		I2C_FUNC_SMBUS_BYTE_DATA |
+		I2C_FUNC_SMBUS_WORD_DATA |
+		I2C_FUNC_SMBUS_BLOCK_DATA |
+		I2C_FUNC_SMBUS_I2C_BLOCK |
+		I2C_FUNC_SMBUS_PROC_CALL |
+		I2C_FUNC_SMBUS_BLOCK_PROC_CALL;
+}
+
+static const struct i2c_algorithm smbus_algorithm = {
+	.master_xfer	= cp2112_i2c_xfer,
+	.smbus_xfer	= cp2112_xfer,
+	.functionality	= cp2112_functionality,
+};
+
+static int cp2112_get_usb_config(struct hid_device *hdev,
+				 struct cp2112_usb_config_report *cfg)
+{
+	int ret;
+
+	ret = cp2112_hid_get(hdev, CP2112_USB_CONFIG, (u8 *)cfg, sizeof(*cfg),
+			     HID_FEATURE_REPORT);
+	if (ret != sizeof(*cfg)) {
+		hid_err(hdev, "error reading usb config: %d\n", ret);
+		if (ret < 0)
+			return ret;
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int cp2112_set_usb_config(struct hid_device *hdev,
+				 struct cp2112_usb_config_report *cfg)
+{
+	int ret;
+
+	BUG_ON(cfg->report != CP2112_USB_CONFIG);
+
+	ret = cp2112_hid_output(hdev, (u8 *)cfg, sizeof(*cfg),
+				HID_FEATURE_REPORT);
+	if (ret != sizeof(*cfg)) {
+		hid_err(hdev, "error writing usb config: %d\n", ret);
+		if (ret < 0)
+			return ret;
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void chmod_sysfs_attrs(struct hid_device *hdev);
+
+#define CP2112_CONFIG_ATTR(name, store, format, ...) \
+static ssize_t name##_store(struct device *kdev, \
+			    struct device_attribute *attr, const char *buf, \
+			    size_t count) \
+{ \
+	struct hid_device *hdev = to_hid_device(kdev); \
+	struct cp2112_usb_config_report cfg; \
+	int ret = cp2112_get_usb_config(hdev, &cfg); \
+	if (ret) \
+		return ret; \
+	store; \
+	ret = cp2112_set_usb_config(hdev, &cfg); \
+	if (ret) \
+		return ret; \
+	chmod_sysfs_attrs(hdev); \
+	return count; \
+} \
+static ssize_t name##_show(struct device *kdev, \
+			   struct device_attribute *attr, char *buf) \
+{ \
+	struct hid_device *hdev = to_hid_device(kdev); \
+	struct cp2112_usb_config_report cfg; \
+	int ret = cp2112_get_usb_config(hdev, &cfg); \
+	if (ret) \
+		return ret; \
+	return scnprintf(buf, PAGE_SIZE, format, ##__VA_ARGS__); \
+} \
+static DEVICE_ATTR_RW(name);
+
+CP2112_CONFIG_ATTR(vendor_id, ({
+	u16 vid;
+
+	if (sscanf(buf, "%hi", &vid) != 1)
+		return -EINVAL;
+
+	cfg.vid = cpu_to_le16(vid);
+	cfg.mask = 0x01;
+}), "0x%04x\n", le16_to_cpu(cfg.vid));
+
+CP2112_CONFIG_ATTR(product_id, ({
+	u16 pid;
+
+	if (sscanf(buf, "%hi", &pid) != 1)
+		return -EINVAL;
+
+	cfg.pid = cpu_to_le16(pid);
+	cfg.mask = 0x02;
+}), "0x%04x\n", le16_to_cpu(cfg.pid));
+
+CP2112_CONFIG_ATTR(max_power, ({
+	int mA;
+
+	if (sscanf(buf, "%i", &mA) != 1)
+		return -EINVAL;
+
+	cfg.max_power = (mA + 1) / 2;
+	cfg.mask = 0x04;
+}), "%u mA\n", cfg.max_power * 2);
+
+CP2112_CONFIG_ATTR(power_mode, ({
+	if (sscanf(buf, "%hhi", &cfg.power_mode) != 1)
+		return -EINVAL;
+
+	cfg.mask = 0x08;
+}), "%u\n", cfg.power_mode);
+
+CP2112_CONFIG_ATTR(release_version, ({
+	if (sscanf(buf, "%hhi.%hhi", &cfg.release_major, &cfg.release_minor)
+	    != 2)
+		return -EINVAL;
+
+	cfg.mask = 0x10;
+}), "%u.%u\n", cfg.release_major, cfg.release_minor);
+
+#undef CP2112_CONFIG_ATTR
+
+struct cp2112_pstring_attribute {
+	struct device_attribute attr;
+	unsigned char report;
+};
+
+static ssize_t pstr_store(struct device *kdev,
+			  struct device_attribute *kattr, const char *buf,
+			  size_t count)
+{
+	struct hid_device *hdev = to_hid_device(kdev);
+	struct cp2112_pstring_attribute *attr =
+		container_of(kattr, struct cp2112_pstring_attribute, attr);
+	struct cp2112_string_report report;
+	int ret;
+
+	memset(&report, 0, sizeof(report));
+
+	ret = utf8s_to_utf16s(buf, count, UTF16_LITTLE_ENDIAN,
+			      report.string, ARRAY_SIZE(report.string));
+	report.report = attr->report;
+	report.length = ret * sizeof(report.string[0]) + 2;
+	report.type = USB_DT_STRING;
+
+	ret = cp2112_hid_output(hdev, &report.report, report.length + 1,
+				HID_FEATURE_REPORT);
+	if (ret != report.length + 1) {
+		hid_err(hdev, "error writing %s string: %d\n", kattr->attr.name,
+			ret);
+		if (ret < 0)
+			return ret;
+		return -EIO;
+	}
+
+	chmod_sysfs_attrs(hdev);
+	return count;
+}
+
+static ssize_t pstr_show(struct device *kdev,
+			 struct device_attribute *kattr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(kdev);
+	struct cp2112_pstring_attribute *attr =
+		container_of(kattr, struct cp2112_pstring_attribute, attr);
+	struct cp2112_string_report report;
+	u8 length;
+	int ret;
+
+	ret = cp2112_hid_get(hdev, attr->report, &report.report,
+			     sizeof(report) - 1, HID_FEATURE_REPORT);
+	if (ret < 3) {
+		hid_err(hdev, "error reading %s string: %d\n", kattr->attr.name,
+			ret);
+		if (ret < 0)
+			return ret;
+		return -EIO;
+	}
+
+	if (report.length < 2) {
+		hid_err(hdev, "invalid %s string length: %d\n",
+			kattr->attr.name, report.length);
+		return -EIO;
+	}
+
+	length = report.length > ret - 1 ? ret - 1 : report.length;
+	length = (length - 2) / sizeof(report.string[0]);
+	ret = utf16s_to_utf8s(report.string, length, UTF16_LITTLE_ENDIAN, buf,
+			      PAGE_SIZE - 1);
+	buf[ret++] = '\n';
+	return ret;
+}
+
+#define CP2112_PSTR_ATTR(name, _report) \
+static struct cp2112_pstring_attribute dev_attr_##name = { \
+	.attr = __ATTR(name, (S_IWUSR | S_IRUGO), pstr_show, pstr_store), \
+	.report = _report, \
+};
+
+CP2112_PSTR_ATTR(manufacturer,	CP2112_MANUFACTURER_STRING);
+CP2112_PSTR_ATTR(product,	CP2112_PRODUCT_STRING);
+CP2112_PSTR_ATTR(serial,	CP2112_SERIAL_STRING);
+
+#undef CP2112_PSTR_ATTR
+
+static const struct attribute_group cp2112_attr_group = {
+	.attrs = (struct attribute *[]){
+		&dev_attr_vendor_id.attr,
+		&dev_attr_product_id.attr,
+		&dev_attr_max_power.attr,
+		&dev_attr_power_mode.attr,
+		&dev_attr_release_version.attr,
+		&dev_attr_manufacturer.attr.attr,
+		&dev_attr_product.attr.attr,
+		&dev_attr_serial.attr.attr,
+		NULL
+	}
+};
+
+/* Chmoding our sysfs attributes is simply a way to expose which fields in the
+ * PROM have already been programmed. We do not depend on this preventing
+ * writing to these attributes since the CP2112 will simply ignore writes to
+ * already-programmed fields. This is why there is no sense in fixing this
+ * racy behaviour.
+ */
+static void chmod_sysfs_attrs(struct hid_device *hdev)
+{
+	struct attribute **attr;
+	u8 buf[2];
+	int ret;
+
+	ret = cp2112_hid_get(hdev, CP2112_LOCK_BYTE, buf, sizeof(buf),
+			     HID_FEATURE_REPORT);
+	if (ret != sizeof(buf)) {
+		hid_err(hdev, "error reading lock byte: %d\n", ret);
+		return;
+	}
+
+	for (attr = cp2112_attr_group.attrs; *attr; ++attr) {
+		umode_t mode = (buf[1] & 1) ? S_IWUSR | S_IRUGO : S_IRUGO;
+		ret = sysfs_chmod_file(&hdev->dev.kobj, *attr, mode);
+		if (ret < 0)
+			hid_err(hdev, "error chmoding sysfs file %s\n",
+				(*attr)->name);
+		buf[1] >>= 1;
+	}
+}
+
+static void cp2112_gpio_irq_ack(struct irq_data *d)
+{
+}
+
+static void cp2112_gpio_irq_mask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct cp2112_device *dev = gpiochip_get_data(gc);
+
+	__clear_bit(d->hwirq, &dev->irq_mask);
+}
+
+static void cp2112_gpio_irq_unmask(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct cp2112_device *dev = gpiochip_get_data(gc);
+
+	__set_bit(d->hwirq, &dev->irq_mask);
+}
+
+static void cp2112_gpio_poll_callback(struct work_struct *work)
+{
+	struct cp2112_device *dev = container_of(work, struct cp2112_device,
+						 gpio_poll_worker.work);
+	struct irq_data *d;
+	u8 gpio_mask;
+	u8 virqs = (u8)dev->irq_mask;
+	u32 irq_type;
+	int irq, virq, ret;
+
+	ret = cp2112_gpio_get_all(&dev->gc);
+	if (ret == -ENODEV) /* the hardware has been disconnected */
+		return;
+	if (ret < 0)
+		goto exit;
+
+	gpio_mask = ret;
+
+	while (virqs) {
+		virq = ffs(virqs) - 1;
+		virqs &= ~BIT(virq);
+
+		if (!dev->gc.to_irq)
+			break;
+
+		irq = dev->gc.to_irq(&dev->gc, virq);
+
+		d = irq_get_irq_data(irq);
+		if (!d)
+			continue;
+
+		irq_type = irqd_get_trigger_type(d);
+
+		if (gpio_mask & BIT(virq)) {
+			/* Level High */
+
+			if (irq_type & IRQ_TYPE_LEVEL_HIGH)
+				handle_nested_irq(irq);
+
+			if ((irq_type & IRQ_TYPE_EDGE_RISING) &&
+			    !(dev->gpio_prev_state & BIT(virq)))
+				handle_nested_irq(irq);
+		} else {
+			/* Level Low */
+
+			if (irq_type & IRQ_TYPE_LEVEL_LOW)
+				handle_nested_irq(irq);
+
+			if ((irq_type & IRQ_TYPE_EDGE_FALLING) &&
+			    (dev->gpio_prev_state & BIT(virq)))
+				handle_nested_irq(irq);
+		}
+	}
+
+	dev->gpio_prev_state = gpio_mask;
+
+exit:
+	if (dev->gpio_poll)
+		schedule_delayed_work(&dev->gpio_poll_worker, 10);
+}
+
+
+static unsigned int cp2112_gpio_irq_startup(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct cp2112_device *dev = gpiochip_get_data(gc);
+
+	INIT_DELAYED_WORK(&dev->gpio_poll_worker, cp2112_gpio_poll_callback);
+
+	cp2112_gpio_direction_input(gc, d->hwirq);
+
+	if (!dev->gpio_poll) {
+		dev->gpio_poll = true;
+		schedule_delayed_work(&dev->gpio_poll_worker, 0);
+	}
+
+	cp2112_gpio_irq_unmask(d);
+	return 0;
+}
+
+static void cp2112_gpio_irq_shutdown(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct cp2112_device *dev = gpiochip_get_data(gc);
+
+	cancel_delayed_work_sync(&dev->gpio_poll_worker);
+}
+
+static int cp2112_gpio_irq_type(struct irq_data *d, unsigned int type)
+{
+	return 0;
+}
+
+static struct irq_chip cp2112_gpio_irqchip = {
+	.name = "cp2112-gpio",
+	.irq_startup = cp2112_gpio_irq_startup,
+	.irq_shutdown = cp2112_gpio_irq_shutdown,
+	.irq_ack = cp2112_gpio_irq_ack,
+	.irq_mask = cp2112_gpio_irq_mask,
+	.irq_unmask = cp2112_gpio_irq_unmask,
+	.irq_set_type = cp2112_gpio_irq_type,
+};
+
+static int __maybe_unused cp2112_allocate_irq(struct cp2112_device *dev,
+					      int pin)
+{
+	int ret;
+
+	if (dev->desc[pin])
+		return -EINVAL;
+
+	dev->desc[pin] = gpiochip_request_own_desc(&dev->gc, pin,
+						   "HID/I2C:Event");
+	if (IS_ERR(dev->desc[pin])) {
+		dev_err(dev->gc.parent, "Failed to request GPIO\n");
+		return PTR_ERR(dev->desc[pin]);
+	}
+
+	ret = gpiochip_lock_as_irq(&dev->gc, pin);
+	if (ret) {
+		dev_err(dev->gc.parent, "Failed to lock GPIO as interrupt\n");
+		goto err_desc;
+	}
+
+	ret = gpiod_to_irq(dev->desc[pin]);
+	if (ret < 0) {
+		dev_err(dev->gc.parent, "Failed to translate GPIO to IRQ\n");
+		goto err_lock;
+	}
+
+	return ret;
+
+err_lock:
+	gpiochip_unlock_as_irq(&dev->gc, pin);
+err_desc:
+	gpiochip_free_own_desc(dev->desc[pin]);
+	dev->desc[pin] = NULL;
+	return ret;
+}
+
+static int cp2112_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct cp2112_device *dev;
+	u8 buf[3];
+	struct cp2112_smbus_config_report config;
+	int ret;
+
+	dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->in_out_buffer = devm_kzalloc(&hdev->dev, CP2112_REPORT_MAX_LENGTH,
+					  GFP_KERNEL);
+	if (!dev->in_out_buffer)
+		return -ENOMEM;
+
+	mutex_init(&dev->lock);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "hw open failed\n");
+		goto err_hid_stop;
+	}
+
+	ret = hid_hw_power(hdev, PM_HINT_FULLON);
+	if (ret < 0) {
+		hid_err(hdev, "power management error: %d\n", ret);
+		goto err_hid_close;
+	}
+
+	ret = cp2112_hid_get(hdev, CP2112_GET_VERSION_INFO, buf, sizeof(buf),
+			     HID_FEATURE_REPORT);
+	if (ret != sizeof(buf)) {
+		hid_err(hdev, "error requesting version\n");
+		if (ret >= 0)
+			ret = -EIO;
+		goto err_power_normal;
+	}
+
+	hid_info(hdev, "Part Number: 0x%02X Device Version: 0x%02X\n",
+		 buf[1], buf[2]);
+
+	ret = cp2112_hid_get(hdev, CP2112_SMBUS_CONFIG, (u8 *)&config,
+			     sizeof(config), HID_FEATURE_REPORT);
+	if (ret != sizeof(config)) {
+		hid_err(hdev, "error requesting SMBus config\n");
+		if (ret >= 0)
+			ret = -EIO;
+		goto err_power_normal;
+	}
+
+	config.retry_time = cpu_to_be16(1);
+
+	ret = cp2112_hid_output(hdev, (u8 *)&config, sizeof(config),
+				HID_FEATURE_REPORT);
+	if (ret != sizeof(config)) {
+		hid_err(hdev, "error setting SMBus config\n");
+		if (ret >= 0)
+			ret = -EIO;
+		goto err_power_normal;
+	}
+
+	hid_set_drvdata(hdev, (void *)dev);
+	dev->hdev		= hdev;
+	dev->adap.owner		= THIS_MODULE;
+	dev->adap.class		= I2C_CLASS_HWMON;
+	dev->adap.algo		= &smbus_algorithm;
+	dev->adap.algo_data	= dev;
+	dev->adap.dev.parent	= &hdev->dev;
+	snprintf(dev->adap.name, sizeof(dev->adap.name),
+		 "CP2112 SMBus Bridge on hidraw%d",
+		 ((struct hidraw *)hdev->hidraw)->minor);
+	dev->hwversion = buf[2];
+	init_waitqueue_head(&dev->wait);
+
+	hid_device_io_start(hdev);
+	ret = i2c_add_adapter(&dev->adap);
+	hid_device_io_stop(hdev);
+
+	if (ret) {
+		hid_err(hdev, "error registering i2c adapter\n");
+		goto err_power_normal;
+	}
+
+	hid_dbg(hdev, "adapter registered\n");
+
+	dev->gc.label			= "cp2112_gpio";
+	dev->gc.direction_input		= cp2112_gpio_direction_input;
+	dev->gc.direction_output	= cp2112_gpio_direction_output;
+	dev->gc.set			= cp2112_gpio_set;
+	dev->gc.get			= cp2112_gpio_get;
+	dev->gc.base			= -1;
+	dev->gc.ngpio			= 8;
+	dev->gc.can_sleep		= 1;
+	dev->gc.parent			= &hdev->dev;
+
+	ret = gpiochip_add_data(&dev->gc, dev);
+	if (ret < 0) {
+		hid_err(hdev, "error registering gpio chip\n");
+		goto err_free_i2c;
+	}
+
+	ret = sysfs_create_group(&hdev->dev.kobj, &cp2112_attr_group);
+	if (ret < 0) {
+		hid_err(hdev, "error creating sysfs attrs\n");
+		goto err_gpiochip_remove;
+	}
+
+	chmod_sysfs_attrs(hdev);
+	hid_hw_power(hdev, PM_HINT_NORMAL);
+
+	ret = gpiochip_irqchip_add(&dev->gc, &cp2112_gpio_irqchip, 0,
+				   handle_simple_irq, IRQ_TYPE_NONE);
+	if (ret) {
+		dev_err(dev->gc.parent, "failed to add IRQ chip\n");
+		goto err_sysfs_remove;
+	}
+
+	return ret;
+
+err_sysfs_remove:
+	sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group);
+err_gpiochip_remove:
+	gpiochip_remove(&dev->gc);
+err_free_i2c:
+	i2c_del_adapter(&dev->adap);
+err_power_normal:
+	hid_hw_power(hdev, PM_HINT_NORMAL);
+err_hid_close:
+	hid_hw_close(hdev);
+err_hid_stop:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void cp2112_remove(struct hid_device *hdev)
+{
+	struct cp2112_device *dev = hid_get_drvdata(hdev);
+	int i;
+
+	sysfs_remove_group(&hdev->dev.kobj, &cp2112_attr_group);
+	i2c_del_adapter(&dev->adap);
+
+	if (dev->gpio_poll) {
+		dev->gpio_poll = false;
+		cancel_delayed_work_sync(&dev->gpio_poll_worker);
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dev->desc); i++) {
+		gpiochip_unlock_as_irq(&dev->gc, i);
+		gpiochip_free_own_desc(dev->desc[i]);
+	}
+
+	gpiochip_remove(&dev->gc);
+	/* i2c_del_adapter has finished removing all i2c devices from our
+	 * adapter. Well behaved devices should no longer call our cp2112_xfer
+	 * and should have waited for any pending calls to finish. It has also
+	 * waited for device_unregister(&adap->dev) to complete. Therefore we
+	 * can safely free our struct cp2112_device.
+	 */
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+}
+
+static int cp2112_raw_event(struct hid_device *hdev, struct hid_report *report,
+			    u8 *data, int size)
+{
+	struct cp2112_device *dev = hid_get_drvdata(hdev);
+	struct cp2112_xfer_status_report *xfer = (void *)data;
+
+	switch (data[0]) {
+	case CP2112_TRANSFER_STATUS_RESPONSE:
+		hid_dbg(hdev, "xfer status: %02x %02x %04x %04x\n",
+			xfer->status0, xfer->status1,
+			be16_to_cpu(xfer->retries), be16_to_cpu(xfer->length));
+
+		switch (xfer->status0) {
+		case STATUS0_IDLE:
+			dev->xfer_status = -EAGAIN;
+			break;
+		case STATUS0_BUSY:
+			dev->xfer_status = -EBUSY;
+			break;
+		case STATUS0_COMPLETE:
+			dev->xfer_status = be16_to_cpu(xfer->length);
+			break;
+		case STATUS0_ERROR:
+			switch (xfer->status1) {
+			case STATUS1_TIMEOUT_NACK:
+			case STATUS1_TIMEOUT_BUS:
+				dev->xfer_status = -ETIMEDOUT;
+				break;
+			default:
+				dev->xfer_status = -EIO;
+				break;
+			}
+			break;
+		default:
+			dev->xfer_status = -EINVAL;
+			break;
+		}
+
+		atomic_set(&dev->xfer_avail, 1);
+		break;
+	case CP2112_DATA_READ_RESPONSE:
+		hid_dbg(hdev, "read response: %02x %02x\n", data[1], data[2]);
+
+		dev->read_length = data[2];
+		if (dev->read_length > sizeof(dev->read_data))
+			dev->read_length = sizeof(dev->read_data);
+
+		memcpy(dev->read_data, &data[3], dev->read_length);
+		atomic_set(&dev->read_avail, 1);
+		break;
+	default:
+		hid_err(hdev, "unknown report\n");
+
+		return 0;
+	}
+
+	wake_up_interruptible(&dev->wait);
+	return 1;
+}
+
+static struct hid_driver cp2112_driver = {
+	.name		= "cp2112",
+	.id_table	= cp2112_devices,
+	.probe		= cp2112_probe,
+	.remove		= cp2112_remove,
+	.raw_event	= cp2112_raw_event,
+};
+
+module_hid_driver(cp2112_driver);
+MODULE_DESCRIPTION("Silicon Labs HID USB to SMBus master bridge");
+MODULE_AUTHOR("David Barksdale <dbarksdale@uplogix.com>");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/hid/hid-cypress.c b/drivers/hid/hid-cypress.c
new file mode 100644
index 0000000..1689568
--- /dev/null
+++ b/drivers/hid/hid-cypress.c
@@ -0,0 +1,148 @@
+/*
+ *  HID driver for some cypress "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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/input.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define CP_RDESC_SWAPPED_MIN_MAX	0x01
+#define CP_2WHEEL_MOUSE_HACK		0x02
+#define CP_2WHEEL_MOUSE_HACK_ON		0x04
+
+/*
+ * Some USB barcode readers from cypress have usage min and usage max in
+ * the wrong order
+ */
+static __u8 *cp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	unsigned int i;
+
+	if (!(quirks & CP_RDESC_SWAPPED_MIN_MAX))
+		return rdesc;
+
+	if (*rsize < 4)
+		return rdesc;
+
+	for (i = 0; i < *rsize - 4; i++)
+		if (rdesc[i] == 0x29 && rdesc[i + 2] == 0x19) {
+			rdesc[i] = 0x19;
+			rdesc[i + 2] = 0x29;
+			swap(rdesc[i + 3], rdesc[i + 1]);
+		}
+	return rdesc;
+}
+
+static int cp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	if (!(quirks & CP_2WHEEL_MOUSE_HACK))
+		return 0;
+
+	if (usage->type == EV_REL && usage->code == REL_WHEEL)
+		set_bit(REL_HWHEEL, *bit);
+	if (usage->hid == 0x00090005)
+		return -1;
+
+	return 0;
+}
+
+static int cp_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type || !(quirks & CP_2WHEEL_MOUSE_HACK))
+		return 0;
+
+	if (usage->hid == 0x00090005) {
+		if (value)
+			quirks |=  CP_2WHEEL_MOUSE_HACK_ON;
+		else
+			quirks &= ~CP_2WHEEL_MOUSE_HACK_ON;
+		hid_set_drvdata(hdev, (void *)quirks);
+		return 1;
+	}
+
+	if (usage->code == REL_WHEEL && (quirks & CP_2WHEEL_MOUSE_HACK_ON)) {
+		struct input_dev *input = field->hidinput->input;
+
+		input_event(input, usage->type, REL_HWHEEL, value);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int cp_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	unsigned long quirks = id->driver_data;
+	int ret;
+
+	hid_set_drvdata(hdev, (void *)quirks);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static const struct hid_device_id cp_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_1),
+		.driver_data = CP_RDESC_SWAPPED_MIN_MAX },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_BARCODE_2),
+		.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 },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, cp_devices);
+
+static struct hid_driver cp_driver = {
+	.name = "cypress",
+	.id_table = cp_devices,
+	.report_fixup = cp_report_fixup,
+	.input_mapped = cp_input_mapped,
+	.event = cp_event,
+	.probe = cp_probe,
+};
+module_hid_driver(cp_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
new file mode 100644
index 0000000..b481002
--- /dev/null
+++ b/drivers/hid/hid-debug.c
@@ -0,0 +1,1259 @@
+/*
+ *  (c) 1999 Andreas Gal		<gal@cs.uni-magdeburg.de>
+ *  (c) 2000-2001 Vojtech Pavlik	<vojtech@ucw.cz>
+ *  (c) 2007-2009 Jiri Kosina
+ *
+ *  HID debugging support
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/sched/signal.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/poll.h>
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+
+static struct dentry *hid_debug_root;
+
+struct hid_usage_entry {
+	unsigned  page;
+	unsigned  usage;
+	const char     *description;
+};
+
+static const struct hid_usage_entry hid_usage_table[] = {
+  {  0,      0, "Undefined" },
+  {  1,      0, "GenericDesktop" },
+    {0, 0x01, "Pointer"},
+    {0, 0x02, "Mouse"},
+    {0, 0x04, "Joystick"},
+    {0, 0x05, "GamePad"},
+    {0, 0x06, "Keyboard"},
+    {0, 0x07, "Keypad"},
+    {0, 0x08, "MultiAxis"},
+      {0, 0x30, "X"},
+      {0, 0x31, "Y"},
+      {0, 0x32, "Z"},
+      {0, 0x33, "Rx"},
+      {0, 0x34, "Ry"},
+      {0, 0x35, "Rz"},
+      {0, 0x36, "Slider"},
+      {0, 0x37, "Dial"},
+      {0, 0x38, "Wheel"},
+      {0, 0x39, "HatSwitch"},
+    {0, 0x3a, "CountedBuffer"},
+      {0, 0x3b, "ByteCount"},
+      {0, 0x3c, "MotionWakeup"},
+      {0, 0x3d, "Start"},
+      {0, 0x3e, "Select"},
+      {0, 0x40, "Vx"},
+      {0, 0x41, "Vy"},
+      {0, 0x42, "Vz"},
+      {0, 0x43, "Vbrx"},
+      {0, 0x44, "Vbry"},
+      {0, 0x45, "Vbrz"},
+      {0, 0x46, "Vno"},
+    {0, 0x80, "SystemControl"},
+      {0, 0x81, "SystemPowerDown"},
+      {0, 0x82, "SystemSleep"},
+      {0, 0x83, "SystemWakeUp"},
+      {0, 0x84, "SystemContextMenu"},
+      {0, 0x85, "SystemMainMenu"},
+      {0, 0x86, "SystemAppMenu"},
+      {0, 0x87, "SystemMenuHelp"},
+      {0, 0x88, "SystemMenuExit"},
+      {0, 0x89, "SystemMenuSelect"},
+      {0, 0x8a, "SystemMenuRight"},
+      {0, 0x8b, "SystemMenuLeft"},
+      {0, 0x8c, "SystemMenuUp"},
+      {0, 0x8d, "SystemMenuDown"},
+      {0, 0x90, "D-PadUp"},
+      {0, 0x91, "D-PadDown"},
+      {0, 0x92, "D-PadRight"},
+      {0, 0x93, "D-PadLeft"},
+  {  2, 0, "Simulation" },
+      {0, 0xb0, "Aileron"},
+      {0, 0xb1, "AileronTrim"},
+      {0, 0xb2, "Anti-Torque"},
+      {0, 0xb3, "Autopilot"},
+      {0, 0xb4, "Chaff"},
+      {0, 0xb5, "Collective"},
+      {0, 0xb6, "DiveBrake"},
+      {0, 0xb7, "ElectronicCountermeasures"},
+      {0, 0xb8, "Elevator"},
+      {0, 0xb9, "ElevatorTrim"},
+      {0, 0xba, "Rudder"},
+      {0, 0xbb, "Throttle"},
+      {0, 0xbc, "FlightCommunications"},
+      {0, 0xbd, "FlareRelease"},
+      {0, 0xbe, "LandingGear"},
+      {0, 0xbf, "ToeBrake"},
+  {  6, 0, "GenericDeviceControls" },
+      {0, 0x20, "BatteryStrength" },
+      {0, 0x21, "WirelessChannel" },
+      {0, 0x22, "WirelessID" },
+      {0, 0x23, "DiscoverWirelessControl" },
+      {0, 0x24, "SecurityCodeCharacterEntered" },
+      {0, 0x25, "SecurityCodeCharactedErased" },
+      {0, 0x26, "SecurityCodeCleared" },
+  {  7, 0, "Keyboard" },
+  {  8, 0, "LED" },
+      {0, 0x01, "NumLock"},
+      {0, 0x02, "CapsLock"},
+      {0, 0x03, "ScrollLock"},
+      {0, 0x04, "Compose"},
+      {0, 0x05, "Kana"},
+      {0, 0x4b, "GenericIndicator"},
+  {  9, 0, "Button" },
+  { 10, 0, "Ordinal" },
+  { 12, 0, "Consumer" },
+      {0, 0x238, "HorizontalWheel"},
+  { 13, 0, "Digitizers" },
+    {0, 0x01, "Digitizer"},
+    {0, 0x02, "Pen"},
+    {0, 0x03, "LightPen"},
+    {0, 0x04, "TouchScreen"},
+    {0, 0x05, "TouchPad"},
+    {0, 0x0e, "DeviceConfiguration"},
+    {0, 0x20, "Stylus"},
+    {0, 0x21, "Puck"},
+    {0, 0x22, "Finger"},
+    {0, 0x23, "DeviceSettings"},
+    {0, 0x30, "TipPressure"},
+    {0, 0x31, "BarrelPressure"},
+    {0, 0x32, "InRange"},
+    {0, 0x33, "Touch"},
+    {0, 0x34, "UnTouch"},
+    {0, 0x35, "Tap"},
+    {0, 0x39, "TabletFunctionKey"},
+    {0, 0x3a, "ProgramChangeKey"},
+    {0, 0x3c, "Invert"},
+    {0, 0x42, "TipSwitch"},
+    {0, 0x43, "SecondaryTipSwitch"},
+    {0, 0x44, "BarrelSwitch"},
+    {0, 0x45, "Eraser"},
+    {0, 0x46, "TabletPick"},
+    {0, 0x47, "Confidence"},
+    {0, 0x48, "Width"},
+    {0, 0x49, "Height"},
+    {0, 0x51, "ContactID"},
+    {0, 0x52, "InputMode"},
+    {0, 0x53, "DeviceIndex"},
+    {0, 0x54, "ContactCount"},
+    {0, 0x55, "ContactMaximumNumber"},
+    {0, 0x59, "ButtonType"},
+    {0, 0x5A, "SecondaryBarrelSwitch"},
+    {0, 0x5B, "TransducerSerialNumber"},
+  { 15, 0, "PhysicalInterfaceDevice" },
+    {0, 0x00, "Undefined"},
+    {0, 0x01, "Physical_Interface_Device"},
+      {0, 0x20, "Normal"},
+    {0, 0x21, "Set_Effect_Report"},
+      {0, 0x22, "Effect_Block_Index"},
+      {0, 0x23, "Parameter_Block_Offset"},
+      {0, 0x24, "ROM_Flag"},
+      {0, 0x25, "Effect_Type"},
+        {0, 0x26, "ET_Constant_Force"},
+        {0, 0x27, "ET_Ramp"},
+        {0, 0x28, "ET_Custom_Force_Data"},
+        {0, 0x30, "ET_Square"},
+        {0, 0x31, "ET_Sine"},
+        {0, 0x32, "ET_Triangle"},
+        {0, 0x33, "ET_Sawtooth_Up"},
+        {0, 0x34, "ET_Sawtooth_Down"},
+        {0, 0x40, "ET_Spring"},
+        {0, 0x41, "ET_Damper"},
+        {0, 0x42, "ET_Inertia"},
+        {0, 0x43, "ET_Friction"},
+      {0, 0x50, "Duration"},
+      {0, 0x51, "Sample_Period"},
+      {0, 0x52, "Gain"},
+      {0, 0x53, "Trigger_Button"},
+      {0, 0x54, "Trigger_Repeat_Interval"},
+      {0, 0x55, "Axes_Enable"},
+        {0, 0x56, "Direction_Enable"},
+      {0, 0x57, "Direction"},
+      {0, 0x58, "Type_Specific_Block_Offset"},
+        {0, 0x59, "Block_Type"},
+        {0, 0x5A, "Set_Envelope_Report"},
+          {0, 0x5B, "Attack_Level"},
+          {0, 0x5C, "Attack_Time"},
+          {0, 0x5D, "Fade_Level"},
+          {0, 0x5E, "Fade_Time"},
+        {0, 0x5F, "Set_Condition_Report"},
+        {0, 0x60, "CP_Offset"},
+        {0, 0x61, "Positive_Coefficient"},
+        {0, 0x62, "Negative_Coefficient"},
+        {0, 0x63, "Positive_Saturation"},
+        {0, 0x64, "Negative_Saturation"},
+        {0, 0x65, "Dead_Band"},
+      {0, 0x66, "Download_Force_Sample"},
+      {0, 0x67, "Isoch_Custom_Force_Enable"},
+      {0, 0x68, "Custom_Force_Data_Report"},
+        {0, 0x69, "Custom_Force_Data"},
+        {0, 0x6A, "Custom_Force_Vendor_Defined_Data"},
+      {0, 0x6B, "Set_Custom_Force_Report"},
+        {0, 0x6C, "Custom_Force_Data_Offset"},
+        {0, 0x6D, "Sample_Count"},
+      {0, 0x6E, "Set_Periodic_Report"},
+        {0, 0x6F, "Offset"},
+        {0, 0x70, "Magnitude"},
+        {0, 0x71, "Phase"},
+        {0, 0x72, "Period"},
+      {0, 0x73, "Set_Constant_Force_Report"},
+        {0, 0x74, "Set_Ramp_Force_Report"},
+        {0, 0x75, "Ramp_Start"},
+        {0, 0x76, "Ramp_End"},
+      {0, 0x77, "Effect_Operation_Report"},
+        {0, 0x78, "Effect_Operation"},
+          {0, 0x79, "Op_Effect_Start"},
+          {0, 0x7A, "Op_Effect_Start_Solo"},
+          {0, 0x7B, "Op_Effect_Stop"},
+          {0, 0x7C, "Loop_Count"},
+      {0, 0x7D, "Device_Gain_Report"},
+        {0, 0x7E, "Device_Gain"},
+    {0, 0x7F, "PID_Pool_Report"},
+      {0, 0x80, "RAM_Pool_Size"},
+      {0, 0x81, "ROM_Pool_Size"},
+      {0, 0x82, "ROM_Effect_Block_Count"},
+      {0, 0x83, "Simultaneous_Effects_Max"},
+      {0, 0x84, "Pool_Alignment"},
+    {0, 0x85, "PID_Pool_Move_Report"},
+      {0, 0x86, "Move_Source"},
+      {0, 0x87, "Move_Destination"},
+      {0, 0x88, "Move_Length"},
+    {0, 0x89, "PID_Block_Load_Report"},
+      {0, 0x8B, "Block_Load_Status"},
+      {0, 0x8C, "Block_Load_Success"},
+      {0, 0x8D, "Block_Load_Full"},
+      {0, 0x8E, "Block_Load_Error"},
+      {0, 0x8F, "Block_Handle"},
+      {0, 0x90, "PID_Block_Free_Report"},
+      {0, 0x91, "Type_Specific_Block_Handle"},
+    {0, 0x92, "PID_State_Report"},
+      {0, 0x94, "Effect_Playing"},
+      {0, 0x95, "PID_Device_Control_Report"},
+        {0, 0x96, "PID_Device_Control"},
+        {0, 0x97, "DC_Enable_Actuators"},
+        {0, 0x98, "DC_Disable_Actuators"},
+        {0, 0x99, "DC_Stop_All_Effects"},
+        {0, 0x9A, "DC_Device_Reset"},
+        {0, 0x9B, "DC_Device_Pause"},
+        {0, 0x9C, "DC_Device_Continue"},
+      {0, 0x9F, "Device_Paused"},
+      {0, 0xA0, "Actuators_Enabled"},
+      {0, 0xA4, "Safety_Switch"},
+      {0, 0xA5, "Actuator_Override_Switch"},
+      {0, 0xA6, "Actuator_Power"},
+    {0, 0xA7, "Start_Delay"},
+    {0, 0xA8, "Parameter_Block_Size"},
+    {0, 0xA9, "Device_Managed_Pool"},
+    {0, 0xAA, "Shared_Parameter_Blocks"},
+    {0, 0xAB, "Create_New_Effect_Report"},
+    {0, 0xAC, "RAM_Pool_Available"},
+  {  0x20, 0, "Sensor" },
+    { 0x20, 0x01, "Sensor" },
+    { 0x20, 0x10, "Biometric" },
+      { 0x20, 0x11, "BiometricHumanPresence" },
+      { 0x20, 0x12, "BiometricHumanProximity" },
+      { 0x20, 0x13, "BiometricHumanTouch" },
+    { 0x20, 0x20, "Electrical" },
+      { 0x20, 0x21, "ElectricalCapacitance" },
+      { 0x20, 0x22, "ElectricalCurrent" },
+      { 0x20, 0x23, "ElectricalPower" },
+      { 0x20, 0x24, "ElectricalInductance" },
+      { 0x20, 0x25, "ElectricalResistance" },
+      { 0x20, 0x26, "ElectricalVoltage" },
+      { 0x20, 0x27, "ElectricalPoteniometer" },
+      { 0x20, 0x28, "ElectricalFrequency" },
+      { 0x20, 0x29, "ElectricalPeriod" },
+    { 0x20, 0x30, "Environmental" },
+      { 0x20, 0x31, "EnvironmentalAtmosphericPressure" },
+      { 0x20, 0x32, "EnvironmentalHumidity" },
+      { 0x20, 0x33, "EnvironmentalTemperature" },
+      { 0x20, 0x34, "EnvironmentalWindDirection" },
+      { 0x20, 0x35, "EnvironmentalWindSpeed" },
+    { 0x20, 0x40, "Light" },
+      { 0x20, 0x41, "LightAmbientLight" },
+      { 0x20, 0x42, "LightConsumerInfrared" },
+    { 0x20, 0x50, "Location" },
+      { 0x20, 0x51, "LocationBroadcast" },
+      { 0x20, 0x52, "LocationDeadReckoning" },
+      { 0x20, 0x53, "LocationGPS" },
+      { 0x20, 0x54, "LocationLookup" },
+      { 0x20, 0x55, "LocationOther" },
+      { 0x20, 0x56, "LocationStatic" },
+      { 0x20, 0x57, "LocationTriangulation" },
+    { 0x20, 0x60, "Mechanical" },
+      { 0x20, 0x61, "MechanicalBooleanSwitch" },
+      { 0x20, 0x62, "MechanicalBooleanSwitchArray" },
+      { 0x20, 0x63, "MechanicalMultivalueSwitch" },
+      { 0x20, 0x64, "MechanicalForce" },
+      { 0x20, 0x65, "MechanicalPressure" },
+      { 0x20, 0x66, "MechanicalStrain" },
+      { 0x20, 0x67, "MechanicalWeight" },
+      { 0x20, 0x68, "MechanicalHapticVibrator" },
+      { 0x20, 0x69, "MechanicalHallEffectSwitch" },
+    { 0x20, 0x70, "Motion" },
+      { 0x20, 0x71, "MotionAccelerometer1D" },
+      { 0x20, 0x72, "MotionAccelerometer2D" },
+      { 0x20, 0x73, "MotionAccelerometer3D" },
+      { 0x20, 0x74, "MotionGyrometer1D" },
+      { 0x20, 0x75, "MotionGyrometer2D" },
+      { 0x20, 0x76, "MotionGyrometer3D" },
+      { 0x20, 0x77, "MotionMotionDetector" },
+      { 0x20, 0x78, "MotionSpeedometer" },
+      { 0x20, 0x79, "MotionAccelerometer" },
+      { 0x20, 0x7A, "MotionGyrometer" },
+    { 0x20, 0x80, "Orientation" },
+      { 0x20, 0x81, "OrientationCompass1D" },
+      { 0x20, 0x82, "OrientationCompass2D" },
+      { 0x20, 0x83, "OrientationCompass3D" },
+      { 0x20, 0x84, "OrientationInclinometer1D" },
+      { 0x20, 0x85, "OrientationInclinometer2D" },
+      { 0x20, 0x86, "OrientationInclinometer3D" },
+      { 0x20, 0x87, "OrientationDistance1D" },
+      { 0x20, 0x88, "OrientationDistance2D" },
+      { 0x20, 0x89, "OrientationDistance3D" },
+      { 0x20, 0x8A, "OrientationDeviceOrientation" },
+      { 0x20, 0x8B, "OrientationCompass" },
+      { 0x20, 0x8C, "OrientationInclinometer" },
+      { 0x20, 0x8D, "OrientationDistance" },
+    { 0x20, 0x90, "Scanner" },
+      { 0x20, 0x91, "ScannerBarcode" },
+      { 0x20, 0x91, "ScannerRFID" },
+      { 0x20, 0x91, "ScannerNFC" },
+    { 0x20, 0xA0, "Time" },
+      { 0x20, 0xA1, "TimeAlarmTimer" },
+      { 0x20, 0xA2, "TimeRealTimeClock" },
+    { 0x20, 0xE0, "Other" },
+      { 0x20, 0xE1, "OtherCustom" },
+      { 0x20, 0xE2, "OtherGeneric" },
+      { 0x20, 0xE3, "OtherGenericEnumerator" },
+  { 0x84, 0, "Power Device" },
+    { 0x84, 0x02, "PresentStatus" },
+    { 0x84, 0x03, "ChangeStatus" },
+    { 0x84, 0x04, "UPS" },
+    { 0x84, 0x05, "PowerSupply" },
+    { 0x84, 0x10, "BatterySystem" },
+    { 0x84, 0x11, "BatterySystemID" },
+    { 0x84, 0x12, "Battery" },
+    { 0x84, 0x13, "BatteryID" },
+    { 0x84, 0x14, "Charger" },
+    { 0x84, 0x15, "ChargerID" },
+    { 0x84, 0x16, "PowerConverter" },
+    { 0x84, 0x17, "PowerConverterID" },
+    { 0x84, 0x18, "OutletSystem" },
+    { 0x84, 0x19, "OutletSystemID" },
+    { 0x84, 0x1a, "Input" },
+    { 0x84, 0x1b, "InputID" },
+    { 0x84, 0x1c, "Output" },
+    { 0x84, 0x1d, "OutputID" },
+    { 0x84, 0x1e, "Flow" },
+    { 0x84, 0x1f, "FlowID" },
+    { 0x84, 0x20, "Outlet" },
+    { 0x84, 0x21, "OutletID" },
+    { 0x84, 0x22, "Gang" },
+    { 0x84, 0x24, "PowerSummary" },
+    { 0x84, 0x25, "PowerSummaryID" },
+    { 0x84, 0x30, "Voltage" },
+    { 0x84, 0x31, "Current" },
+    { 0x84, 0x32, "Frequency" },
+    { 0x84, 0x33, "ApparentPower" },
+    { 0x84, 0x35, "PercentLoad" },
+    { 0x84, 0x40, "ConfigVoltage" },
+    { 0x84, 0x41, "ConfigCurrent" },
+    { 0x84, 0x43, "ConfigApparentPower" },
+    { 0x84, 0x53, "LowVoltageTransfer" },
+    { 0x84, 0x54, "HighVoltageTransfer" },
+    { 0x84, 0x56, "DelayBeforeStartup" },
+    { 0x84, 0x57, "DelayBeforeShutdown" },
+    { 0x84, 0x58, "Test" },
+    { 0x84, 0x5a, "AudibleAlarmControl" },
+    { 0x84, 0x60, "Present" },
+    { 0x84, 0x61, "Good" },
+    { 0x84, 0x62, "InternalFailure" },
+    { 0x84, 0x65, "Overload" },
+    { 0x84, 0x66, "OverCharged" },
+    { 0x84, 0x67, "OverTemperature" },
+    { 0x84, 0x68, "ShutdownRequested" },
+    { 0x84, 0x69, "ShutdownImminent" },
+    { 0x84, 0x6b, "SwitchOn/Off" },
+    { 0x84, 0x6c, "Switchable" },
+    { 0x84, 0x6d, "Used" },
+    { 0x84, 0x6e, "Boost" },
+    { 0x84, 0x73, "CommunicationLost" },
+    { 0x84, 0xfd, "iManufacturer" },
+    { 0x84, 0xfe, "iProduct" },
+    { 0x84, 0xff, "iSerialNumber" },
+  { 0x85, 0, "Battery System" },
+    { 0x85, 0x01, "SMBBatteryMode" },
+    { 0x85, 0x02, "SMBBatteryStatus" },
+    { 0x85, 0x03, "SMBAlarmWarning" },
+    { 0x85, 0x04, "SMBChargerMode" },
+    { 0x85, 0x05, "SMBChargerStatus" },
+    { 0x85, 0x06, "SMBChargerSpecInfo" },
+    { 0x85, 0x07, "SMBSelectorState" },
+    { 0x85, 0x08, "SMBSelectorPresets" },
+    { 0x85, 0x09, "SMBSelectorInfo" },
+    { 0x85, 0x29, "RemainingCapacityLimit" },
+    { 0x85, 0x2c, "CapacityMode" },
+    { 0x85, 0x42, "BelowRemainingCapacityLimit" },
+    { 0x85, 0x44, "Charging" },
+    { 0x85, 0x45, "Discharging" },
+    { 0x85, 0x4b, "NeedReplacement" },
+    { 0x85, 0x66, "RemainingCapacity" },
+    { 0x85, 0x68, "RunTimeToEmpty" },
+    { 0x85, 0x6a, "AverageTimeToFull" },
+    { 0x85, 0x83, "DesignCapacity" },
+    { 0x85, 0x85, "ManufacturerDate" },
+    { 0x85, 0x89, "iDeviceChemistry" },
+    { 0x85, 0x8b, "Rechargeable" },
+    { 0x85, 0x8f, "iOEMInformation" },
+    { 0x85, 0x8d, "CapacityGranularity1" },
+    { 0x85, 0xd0, "ACPresent" },
+  /* pages 0xff00 to 0xffff are vendor-specific */
+  { 0xffff, 0, "Vendor-specific-FF" },
+  { 0, 0, NULL }
+};
+
+/* Either output directly into simple seq_file, or (if f == NULL)
+ * allocate a separate buffer that will then be passed to the 'events'
+ * ringbuffer.
+ *
+ * This is because these functions can be called both for "one-shot"
+ * "rdesc" while resolving, or for blocking "events".
+ *
+ * This holds both for resolv_usage_page() and hid_resolv_usage().
+ */
+static char *resolv_usage_page(unsigned page, struct seq_file *f) {
+	const struct hid_usage_entry *p;
+	char *buf = NULL;
+
+	if (!f) {
+		buf = kzalloc(HID_DEBUG_BUFSIZE, GFP_ATOMIC);
+		if (!buf)
+			return ERR_PTR(-ENOMEM);
+	}
+
+	for (p = hid_usage_table; p->description; p++)
+		if (p->page == page) {
+			if (!f) {
+				snprintf(buf, HID_DEBUG_BUFSIZE, "%s",
+						p->description);
+				return buf;
+			}
+			else {
+				seq_printf(f, "%s", p->description);
+				return NULL;
+			}
+		}
+	if (!f)
+		snprintf(buf, HID_DEBUG_BUFSIZE, "%04x", page);
+	else
+		seq_printf(f, "%04x", page);
+	return buf;
+}
+
+char *hid_resolv_usage(unsigned usage, struct seq_file *f) {
+	const struct hid_usage_entry *p;
+	char *buf = NULL;
+	int len = 0;
+
+	buf = resolv_usage_page(usage >> 16, f);
+	if (IS_ERR(buf)) {
+		pr_err("error allocating HID debug buffer\n");
+		return NULL;
+	}
+
+
+	if (!f) {
+		len = strlen(buf);
+		snprintf(buf+len, max(0, HID_DEBUG_BUFSIZE - len), ".");
+		len++;
+	}
+	else {
+		seq_printf(f, ".");
+	}
+	for (p = hid_usage_table; p->description; p++)
+		if (p->page == (usage >> 16)) {
+			for(++p; p->description && p->usage != 0; p++)
+				if (p->usage == (usage & 0xffff)) {
+					if (!f)
+						snprintf(buf + len,
+							max(0,HID_DEBUG_BUFSIZE - len - 1),
+							"%s", p->description);
+					else
+						seq_printf(f,
+							"%s",
+							p->description);
+					return buf;
+				}
+			break;
+		}
+	if (!f)
+		snprintf(buf + len, max(0, HID_DEBUG_BUFSIZE - len - 1),
+				"%04x", usage & 0xffff);
+	else
+		seq_printf(f, "%04x", usage & 0xffff);
+	return buf;
+}
+EXPORT_SYMBOL_GPL(hid_resolv_usage);
+
+static void tab(int n, struct seq_file *f) {
+	seq_printf(f, "%*s", n, "");
+}
+
+void hid_dump_field(struct hid_field *field, int n, struct seq_file *f) {
+	int j;
+
+	if (field->physical) {
+		tab(n, f);
+		seq_printf(f, "Physical(");
+		hid_resolv_usage(field->physical, f); seq_printf(f, ")\n");
+	}
+	if (field->logical) {
+		tab(n, f);
+		seq_printf(f, "Logical(");
+		hid_resolv_usage(field->logical, f); seq_printf(f, ")\n");
+	}
+	if (field->application) {
+		tab(n, f);
+		seq_printf(f, "Application(");
+		hid_resolv_usage(field->application, f); seq_printf(f, ")\n");
+	}
+	tab(n, f); seq_printf(f, "Usage(%d)\n", field->maxusage);
+	for (j = 0; j < field->maxusage; j++) {
+		tab(n+2, f); hid_resolv_usage(field->usage[j].hid, f); seq_printf(f, "\n");
+	}
+	if (field->logical_minimum != field->logical_maximum) {
+		tab(n, f); seq_printf(f, "Logical Minimum(%d)\n", field->logical_minimum);
+		tab(n, f); seq_printf(f, "Logical Maximum(%d)\n", field->logical_maximum);
+	}
+	if (field->physical_minimum != field->physical_maximum) {
+		tab(n, f); seq_printf(f, "Physical Minimum(%d)\n", field->physical_minimum);
+		tab(n, f); seq_printf(f, "Physical Maximum(%d)\n", field->physical_maximum);
+	}
+	if (field->unit_exponent) {
+		tab(n, f); seq_printf(f, "Unit Exponent(%d)\n", field->unit_exponent);
+	}
+	if (field->unit) {
+		static const char *systems[5] = { "None", "SI Linear", "SI Rotation", "English Linear", "English Rotation" };
+		static const char *units[5][8] = {
+			{ "None", "None", "None", "None", "None", "None", "None", "None" },
+			{ "None", "Centimeter", "Gram", "Seconds", "Kelvin",     "Ampere", "Candela", "None" },
+			{ "None", "Radians",    "Gram", "Seconds", "Kelvin",     "Ampere", "Candela", "None" },
+			{ "None", "Inch",       "Slug", "Seconds", "Fahrenheit", "Ampere", "Candela", "None" },
+			{ "None", "Degrees",    "Slug", "Seconds", "Fahrenheit", "Ampere", "Candela", "None" }
+		};
+
+		int i;
+		int sys;
+                __u32 data = field->unit;
+
+		/* First nibble tells us which system we're in. */
+		sys = data & 0xf;
+		data >>= 4;
+
+		if(sys > 4) {
+			tab(n, f); seq_printf(f, "Unit(Invalid)\n");
+		}
+		else {
+			int earlier_unit = 0;
+
+			tab(n, f); seq_printf(f, "Unit(%s : ", systems[sys]);
+
+			for (i=1 ; i<sizeof(__u32)*2 ; i++) {
+				char nibble = data & 0xf;
+				data >>= 4;
+				if (nibble != 0) {
+					if(earlier_unit++ > 0)
+						seq_printf(f, "*");
+					seq_printf(f, "%s", units[sys][i]);
+					if(nibble != 1) {
+						/* This is a _signed_ nibble(!) */
+
+						int val = nibble & 0x7;
+						if(nibble & 0x08)
+							val = -((0x7 & ~val) +1);
+						seq_printf(f, "^%d", val);
+					}
+				}
+			}
+			seq_printf(f, ")\n");
+		}
+	}
+	tab(n, f); seq_printf(f, "Report Size(%u)\n", field->report_size);
+	tab(n, f); seq_printf(f, "Report Count(%u)\n", field->report_count);
+	tab(n, f); seq_printf(f, "Report Offset(%u)\n", field->report_offset);
+
+	tab(n, f); seq_printf(f, "Flags( ");
+	j = field->flags;
+	seq_printf(f, "%s", HID_MAIN_ITEM_CONSTANT & j ? "Constant " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_VARIABLE & j ? "Variable " : "Array ");
+	seq_printf(f, "%s", HID_MAIN_ITEM_RELATIVE & j ? "Relative " : "Absolute ");
+	seq_printf(f, "%s", HID_MAIN_ITEM_WRAP & j ? "Wrap " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_NONLINEAR & j ? "NonLinear " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_NO_PREFERRED & j ? "NoPreferredState " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_NULL_STATE & j ? "NullState " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_VOLATILE & j ? "Volatile " : "");
+	seq_printf(f, "%s", HID_MAIN_ITEM_BUFFERED_BYTE & j ? "BufferedByte " : "");
+	seq_printf(f, ")\n");
+}
+EXPORT_SYMBOL_GPL(hid_dump_field);
+
+void hid_dump_device(struct hid_device *device, struct seq_file *f)
+{
+	struct hid_report_enum *report_enum;
+	struct hid_report *report;
+	struct list_head *list;
+	unsigned i,k;
+	static const char *table[] = {"INPUT", "OUTPUT", "FEATURE"};
+
+	for (i = 0; i < HID_REPORT_TYPES; i++) {
+		report_enum = device->report_enum + i;
+		list = report_enum->report_list.next;
+		while (list != &report_enum->report_list) {
+			report = (struct hid_report *) list;
+			tab(2, f);
+			seq_printf(f, "%s", table[i]);
+			if (report->id)
+				seq_printf(f, "(%d)", report->id);
+			seq_printf(f, "[%s]", table[report->type]);
+			seq_printf(f, "\n");
+			for (k = 0; k < report->maxfield; k++) {
+				tab(4, f);
+				seq_printf(f, "Field(%d)\n", k);
+				hid_dump_field(report->field[k], 6, f);
+			}
+			list = list->next;
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(hid_dump_device);
+
+/* enqueue string to 'events' ring buffer */
+void hid_debug_event(struct hid_device *hdev, char *buf)
+{
+	unsigned i;
+	struct hid_debug_list *list;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hdev->debug_list_lock, flags);
+	list_for_each_entry(list, &hdev->debug_list, node) {
+		for (i = 0; buf[i]; i++)
+			list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] =
+				buf[i];
+		list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE;
+        }
+	spin_unlock_irqrestore(&hdev->debug_list_lock, flags);
+
+	wake_up_interruptible(&hdev->debug_wait);
+}
+EXPORT_SYMBOL_GPL(hid_debug_event);
+
+void hid_dump_report(struct hid_device *hid, int type, u8 *data,
+		int size)
+{
+	struct hid_report_enum *report_enum;
+	char *buf;
+	unsigned int i;
+
+	buf = kmalloc(HID_DEBUG_BUFSIZE, GFP_ATOMIC);
+
+	if (!buf)
+		return;
+
+	report_enum = hid->report_enum + type;
+
+	/* dump the report */
+	snprintf(buf, HID_DEBUG_BUFSIZE - 1,
+			"\nreport (size %u) (%snumbered) = ", size,
+			report_enum->numbered ? "" : "un");
+	hid_debug_event(hid, buf);
+
+	for (i = 0; i < size; i++) {
+		snprintf(buf, HID_DEBUG_BUFSIZE - 1,
+				" %02x", data[i]);
+		hid_debug_event(hid, buf);
+	}
+	hid_debug_event(hid, "\n");
+	kfree(buf);
+}
+EXPORT_SYMBOL_GPL(hid_dump_report);
+
+void hid_dump_input(struct hid_device *hdev, struct hid_usage *usage, __s32 value)
+{
+	char *buf;
+	int len;
+
+	buf = hid_resolv_usage(usage->hid, NULL);
+	if (!buf)
+		return;
+	len = strlen(buf);
+	snprintf(buf + len, HID_DEBUG_BUFSIZE - len - 1, " = %d\n", value);
+
+	hid_debug_event(hdev, buf);
+
+	kfree(buf);
+        wake_up_interruptible(&hdev->debug_wait);
+
+}
+EXPORT_SYMBOL_GPL(hid_dump_input);
+
+static const char *events[EV_MAX + 1] = {
+	[EV_SYN] = "Sync",			[EV_KEY] = "Key",
+	[EV_REL] = "Relative",			[EV_ABS] = "Absolute",
+	[EV_MSC] = "Misc",			[EV_LED] = "LED",
+	[EV_SND] = "Sound",			[EV_REP] = "Repeat",
+	[EV_FF] = "ForceFeedback",		[EV_PWR] = "Power",
+	[EV_FF_STATUS] = "ForceFeedbackStatus",
+};
+
+static const char *syncs[3] = {
+	[SYN_REPORT] = "Report",		[SYN_CONFIG] = "Config",
+	[SYN_MT_REPORT] = "MT Report",
+};
+
+static const char *keys[KEY_MAX + 1] = {
+	[KEY_RESERVED] = "Reserved",		[KEY_ESC] = "Esc",
+	[KEY_1] = "1",				[KEY_2] = "2",
+	[KEY_3] = "3",				[KEY_4] = "4",
+	[KEY_5] = "5",				[KEY_6] = "6",
+	[KEY_7] = "7",				[KEY_8] = "8",
+	[KEY_9] = "9",				[KEY_0] = "0",
+	[KEY_MINUS] = "Minus",			[KEY_EQUAL] = "Equal",
+	[KEY_BACKSPACE] = "Backspace",		[KEY_TAB] = "Tab",
+	[KEY_Q] = "Q",				[KEY_W] = "W",
+	[KEY_E] = "E",				[KEY_R] = "R",
+	[KEY_T] = "T",				[KEY_Y] = "Y",
+	[KEY_U] = "U",				[KEY_I] = "I",
+	[KEY_O] = "O",				[KEY_P] = "P",
+	[KEY_LEFTBRACE] = "LeftBrace",		[KEY_RIGHTBRACE] = "RightBrace",
+	[KEY_ENTER] = "Enter",			[KEY_LEFTCTRL] = "LeftControl",
+	[KEY_A] = "A",				[KEY_S] = "S",
+	[KEY_D] = "D",				[KEY_F] = "F",
+	[KEY_G] = "G",				[KEY_H] = "H",
+	[KEY_J] = "J",				[KEY_K] = "K",
+	[KEY_L] = "L",				[KEY_SEMICOLON] = "Semicolon",
+	[KEY_APOSTROPHE] = "Apostrophe",	[KEY_GRAVE] = "Grave",
+	[KEY_LEFTSHIFT] = "LeftShift",		[KEY_BACKSLASH] = "BackSlash",
+	[KEY_Z] = "Z",				[KEY_X] = "X",
+	[KEY_C] = "C",				[KEY_V] = "V",
+	[KEY_B] = "B",				[KEY_N] = "N",
+	[KEY_M] = "M",				[KEY_COMMA] = "Comma",
+	[KEY_DOT] = "Dot",			[KEY_SLASH] = "Slash",
+	[KEY_RIGHTSHIFT] = "RightShift",	[KEY_KPASTERISK] = "KPAsterisk",
+	[KEY_LEFTALT] = "LeftAlt",		[KEY_SPACE] = "Space",
+	[KEY_CAPSLOCK] = "CapsLock",		[KEY_F1] = "F1",
+	[KEY_F2] = "F2",			[KEY_F3] = "F3",
+	[KEY_F4] = "F4",			[KEY_F5] = "F5",
+	[KEY_F6] = "F6",			[KEY_F7] = "F7",
+	[KEY_F8] = "F8",			[KEY_F9] = "F9",
+	[KEY_F10] = "F10",			[KEY_NUMLOCK] = "NumLock",
+	[KEY_SCROLLLOCK] = "ScrollLock",	[KEY_KP7] = "KP7",
+	[KEY_KP8] = "KP8",			[KEY_KP9] = "KP9",
+	[KEY_KPMINUS] = "KPMinus",		[KEY_KP4] = "KP4",
+	[KEY_KP5] = "KP5",			[KEY_KP6] = "KP6",
+	[KEY_KPPLUS] = "KPPlus",		[KEY_KP1] = "KP1",
+	[KEY_KP2] = "KP2",			[KEY_KP3] = "KP3",
+	[KEY_KP0] = "KP0",			[KEY_KPDOT] = "KPDot",
+	[KEY_ZENKAKUHANKAKU] = "Zenkaku/Hankaku", [KEY_102ND] = "102nd",
+	[KEY_F11] = "F11",			[KEY_F12] = "F12",
+	[KEY_RO] = "RO",			[KEY_KATAKANA] = "Katakana",
+	[KEY_HIRAGANA] = "HIRAGANA",		[KEY_HENKAN] = "Henkan",
+	[KEY_KATAKANAHIRAGANA] = "Katakana/Hiragana", [KEY_MUHENKAN] = "Muhenkan",
+	[KEY_KPJPCOMMA] = "KPJpComma",		[KEY_KPENTER] = "KPEnter",
+	[KEY_RIGHTCTRL] = "RightCtrl",		[KEY_KPSLASH] = "KPSlash",
+	[KEY_SYSRQ] = "SysRq",			[KEY_RIGHTALT] = "RightAlt",
+	[KEY_LINEFEED] = "LineFeed",		[KEY_HOME] = "Home",
+	[KEY_UP] = "Up",			[KEY_PAGEUP] = "PageUp",
+	[KEY_LEFT] = "Left",			[KEY_RIGHT] = "Right",
+	[KEY_END] = "End",			[KEY_DOWN] = "Down",
+	[KEY_PAGEDOWN] = "PageDown",		[KEY_INSERT] = "Insert",
+	[KEY_DELETE] = "Delete",		[KEY_MACRO] = "Macro",
+	[KEY_MUTE] = "Mute",			[KEY_VOLUMEDOWN] = "VolumeDown",
+	[KEY_VOLUMEUP] = "VolumeUp",		[KEY_POWER] = "Power",
+	[KEY_KPEQUAL] = "KPEqual",		[KEY_KPPLUSMINUS] = "KPPlusMinus",
+	[KEY_PAUSE] = "Pause",			[KEY_KPCOMMA] = "KPComma",
+	[KEY_HANGUEL] = "Hangeul",		[KEY_HANJA] = "Hanja",
+	[KEY_YEN] = "Yen",			[KEY_LEFTMETA] = "LeftMeta",
+	[KEY_RIGHTMETA] = "RightMeta",		[KEY_COMPOSE] = "Compose",
+	[KEY_STOP] = "Stop",			[KEY_AGAIN] = "Again",
+	[KEY_PROPS] = "Props",			[KEY_UNDO] = "Undo",
+	[KEY_FRONT] = "Front",			[KEY_COPY] = "Copy",
+	[KEY_OPEN] = "Open",			[KEY_PASTE] = "Paste",
+	[KEY_FIND] = "Find",			[KEY_CUT] = "Cut",
+	[KEY_HELP] = "Help",			[KEY_MENU] = "Menu",
+	[KEY_CALC] = "Calc",			[KEY_SETUP] = "Setup",
+	[KEY_SLEEP] = "Sleep",			[KEY_WAKEUP] = "WakeUp",
+	[KEY_FILE] = "File",			[KEY_SENDFILE] = "SendFile",
+	[KEY_DELETEFILE] = "DeleteFile",	[KEY_XFER] = "X-fer",
+	[KEY_PROG1] = "Prog1",			[KEY_PROG2] = "Prog2",
+	[KEY_WWW] = "WWW",			[KEY_MSDOS] = "MSDOS",
+	[KEY_COFFEE] = "Coffee",		[KEY_ROTATE_DISPLAY] = "RotateDisplay",
+	[KEY_CYCLEWINDOWS] = "CycleWindows",	[KEY_MAIL] = "Mail",
+	[KEY_BOOKMARKS] = "Bookmarks",		[KEY_COMPUTER] = "Computer",
+	[KEY_BACK] = "Back",			[KEY_FORWARD] = "Forward",
+	[KEY_CLOSECD] = "CloseCD",		[KEY_EJECTCD] = "EjectCD",
+	[KEY_EJECTCLOSECD] = "EjectCloseCD",	[KEY_NEXTSONG] = "NextSong",
+	[KEY_PLAYPAUSE] = "PlayPause",		[KEY_PREVIOUSSONG] = "PreviousSong",
+	[KEY_STOPCD] = "StopCD",		[KEY_RECORD] = "Record",
+	[KEY_REWIND] = "Rewind",		[KEY_PHONE] = "Phone",
+	[KEY_ISO] = "ISOKey",			[KEY_CONFIG] = "Config",
+	[KEY_HOMEPAGE] = "HomePage",		[KEY_REFRESH] = "Refresh",
+	[KEY_EXIT] = "Exit",			[KEY_MOVE] = "Move",
+	[KEY_EDIT] = "Edit",			[KEY_SCROLLUP] = "ScrollUp",
+	[KEY_SCROLLDOWN] = "ScrollDown",	[KEY_KPLEFTPAREN] = "KPLeftParenthesis",
+	[KEY_KPRIGHTPAREN] = "KPRightParenthesis", [KEY_NEW] = "New",
+	[KEY_REDO] = "Redo",			[KEY_F13] = "F13",
+	[KEY_F14] = "F14",			[KEY_F15] = "F15",
+	[KEY_F16] = "F16",			[KEY_F17] = "F17",
+	[KEY_F18] = "F18",			[KEY_F19] = "F19",
+	[KEY_F20] = "F20",			[KEY_F21] = "F21",
+	[KEY_F22] = "F22",			[KEY_F23] = "F23",
+	[KEY_F24] = "F24",			[KEY_PLAYCD] = "PlayCD",
+	[KEY_PAUSECD] = "PauseCD",		[KEY_PROG3] = "Prog3",
+	[KEY_PROG4] = "Prog4",			[KEY_SUSPEND] = "Suspend",
+	[KEY_CLOSE] = "Close",			[KEY_PLAY] = "Play",
+	[KEY_FASTFORWARD] = "FastForward",	[KEY_BASSBOOST] = "BassBoost",
+	[KEY_PRINT] = "Print",			[KEY_HP] = "HP",
+	[KEY_CAMERA] = "Camera",		[KEY_SOUND] = "Sound",
+	[KEY_QUESTION] = "Question",		[KEY_EMAIL] = "Email",
+	[KEY_CHAT] = "Chat",			[KEY_SEARCH] = "Search",
+	[KEY_CONNECT] = "Connect",		[KEY_FINANCE] = "Finance",
+	[KEY_SPORT] = "Sport",			[KEY_SHOP] = "Shop",
+	[KEY_ALTERASE] = "AlternateErase",	[KEY_CANCEL] = "Cancel",
+	[KEY_BRIGHTNESSDOWN] = "BrightnessDown", [KEY_BRIGHTNESSUP] = "BrightnessUp",
+	[KEY_MEDIA] = "Media",			[KEY_UNKNOWN] = "Unknown",
+	[BTN_DPAD_UP] = "BtnDPadUp",		[BTN_DPAD_DOWN] = "BtnDPadDown",
+	[BTN_DPAD_LEFT] = "BtnDPadLeft",	[BTN_DPAD_RIGHT] = "BtnDPadRight",
+	[BTN_0] = "Btn0",			[BTN_1] = "Btn1",
+	[BTN_2] = "Btn2",			[BTN_3] = "Btn3",
+	[BTN_4] = "Btn4",			[BTN_5] = "Btn5",
+	[BTN_6] = "Btn6",			[BTN_7] = "Btn7",
+	[BTN_8] = "Btn8",			[BTN_9] = "Btn9",
+	[BTN_LEFT] = "LeftBtn",			[BTN_RIGHT] = "RightBtn",
+	[BTN_MIDDLE] = "MiddleBtn",		[BTN_SIDE] = "SideBtn",
+	[BTN_EXTRA] = "ExtraBtn",		[BTN_FORWARD] = "ForwardBtn",
+	[BTN_BACK] = "BackBtn",			[BTN_TASK] = "TaskBtn",
+	[BTN_TRIGGER] = "Trigger",		[BTN_THUMB] = "ThumbBtn",
+	[BTN_THUMB2] = "ThumbBtn2",		[BTN_TOP] = "TopBtn",
+	[BTN_TOP2] = "TopBtn2",			[BTN_PINKIE] = "PinkieBtn",
+	[BTN_BASE] = "BaseBtn",			[BTN_BASE2] = "BaseBtn2",
+	[BTN_BASE3] = "BaseBtn3",		[BTN_BASE4] = "BaseBtn4",
+	[BTN_BASE5] = "BaseBtn5",		[BTN_BASE6] = "BaseBtn6",
+	[BTN_DEAD] = "BtnDead",			[BTN_A] = "BtnA",
+	[BTN_B] = "BtnB",			[BTN_C] = "BtnC",
+	[BTN_X] = "BtnX",			[BTN_Y] = "BtnY",
+	[BTN_Z] = "BtnZ",			[BTN_TL] = "BtnTL",
+	[BTN_TR] = "BtnTR",			[BTN_TL2] = "BtnTL2",
+	[BTN_TR2] = "BtnTR2",			[BTN_SELECT] = "BtnSelect",
+	[BTN_START] = "BtnStart",		[BTN_MODE] = "BtnMode",
+	[BTN_THUMBL] = "BtnThumbL",		[BTN_THUMBR] = "BtnThumbR",
+	[BTN_TOOL_PEN] = "ToolPen",		[BTN_TOOL_RUBBER] = "ToolRubber",
+	[BTN_TOOL_BRUSH] = "ToolBrush",		[BTN_TOOL_PENCIL] = "ToolPencil",
+	[BTN_TOOL_AIRBRUSH] = "ToolAirbrush",	[BTN_TOOL_FINGER] = "ToolFinger",
+	[BTN_TOOL_MOUSE] = "ToolMouse",		[BTN_TOOL_LENS] = "ToolLens",
+	[BTN_TOUCH] = "Touch",			[BTN_STYLUS] = "Stylus",
+	[BTN_STYLUS2] = "Stylus2",		[BTN_TOOL_DOUBLETAP] = "ToolDoubleTap",
+	[BTN_TOOL_TRIPLETAP] = "ToolTripleTap",	[BTN_TOOL_QUADTAP] = "ToolQuadrupleTap",
+	[BTN_GEAR_DOWN] = "WheelBtn",
+	[BTN_GEAR_UP] = "Gear up",		[KEY_OK] = "Ok",
+	[KEY_SELECT] = "Select",		[KEY_GOTO] = "Goto",
+	[KEY_CLEAR] = "Clear",			[KEY_POWER2] = "Power2",
+	[KEY_OPTION] = "Option",		[KEY_INFO] = "Info",
+	[KEY_TIME] = "Time",			[KEY_VENDOR] = "Vendor",
+	[KEY_ARCHIVE] = "Archive",		[KEY_PROGRAM] = "Program",
+	[KEY_CHANNEL] = "Channel",		[KEY_FAVORITES] = "Favorites",
+	[KEY_EPG] = "EPG",			[KEY_PVR] = "PVR",
+	[KEY_MHP] = "MHP",			[KEY_LANGUAGE] = "Language",
+	[KEY_TITLE] = "Title",			[KEY_SUBTITLE] = "Subtitle",
+	[KEY_ANGLE] = "Angle",			[KEY_ZOOM] = "Zoom",
+	[KEY_MODE] = "Mode",			[KEY_KEYBOARD] = "Keyboard",
+	[KEY_SCREEN] = "Screen",		[KEY_PC] = "PC",
+	[KEY_TV] = "TV",			[KEY_TV2] = "TV2",
+	[KEY_VCR] = "VCR",			[KEY_VCR2] = "VCR2",
+	[KEY_SAT] = "Sat",			[KEY_SAT2] = "Sat2",
+	[KEY_CD] = "CD",			[KEY_TAPE] = "Tape",
+	[KEY_RADIO] = "Radio",			[KEY_TUNER] = "Tuner",
+	[KEY_PLAYER] = "Player",		[KEY_TEXT] = "Text",
+	[KEY_DVD] = "DVD",			[KEY_AUX] = "Aux",
+	[KEY_MP3] = "MP3",			[KEY_AUDIO] = "Audio",
+	[KEY_VIDEO] = "Video",			[KEY_DIRECTORY] = "Directory",
+	[KEY_LIST] = "List",			[KEY_MEMO] = "Memo",
+	[KEY_CALENDAR] = "Calendar",		[KEY_RED] = "Red",
+	[KEY_GREEN] = "Green",			[KEY_YELLOW] = "Yellow",
+	[KEY_BLUE] = "Blue",			[KEY_CHANNELUP] = "ChannelUp",
+	[KEY_CHANNELDOWN] = "ChannelDown",	[KEY_FIRST] = "First",
+	[KEY_LAST] = "Last",			[KEY_AB] = "AB",
+	[KEY_NEXT] = "Next",			[KEY_RESTART] = "Restart",
+	[KEY_SLOW] = "Slow",			[KEY_SHUFFLE] = "Shuffle",
+	[KEY_BREAK] = "Break",			[KEY_PREVIOUS] = "Previous",
+	[KEY_DIGITS] = "Digits",		[KEY_TEEN] = "TEEN",
+	[KEY_TWEN] = "TWEN",			[KEY_DEL_EOL] = "DeleteEOL",
+	[KEY_DEL_EOS] = "DeleteEOS",		[KEY_INS_LINE] = "InsertLine",
+	[KEY_DEL_LINE] = "DeleteLine",
+	[KEY_SEND] = "Send",			[KEY_REPLY] = "Reply",
+	[KEY_FORWARDMAIL] = "ForwardMail",	[KEY_SAVE] = "Save",
+	[KEY_DOCUMENTS] = "Documents",		[KEY_SPELLCHECK] = "SpellCheck",
+	[KEY_LOGOFF] = "Logoff",
+	[KEY_FN] = "Fn",			[KEY_FN_ESC] = "Fn+ESC",
+	[KEY_FN_1] = "Fn+1",			[KEY_FN_2] = "Fn+2",
+	[KEY_FN_B] = "Fn+B",			[KEY_FN_D] = "Fn+D",
+	[KEY_FN_E] = "Fn+E",			[KEY_FN_F] = "Fn+F",
+	[KEY_FN_S] = "Fn+S",
+	[KEY_FN_F1] = "Fn+F1",			[KEY_FN_F2] = "Fn+F2",
+	[KEY_FN_F3] = "Fn+F3",			[KEY_FN_F4] = "Fn+F4",
+	[KEY_FN_F5] = "Fn+F5",			[KEY_FN_F6] = "Fn+F6",
+	[KEY_FN_F7] = "Fn+F7",			[KEY_FN_F8] = "Fn+F8",
+	[KEY_FN_F9] = "Fn+F9",			[KEY_FN_F10] = "Fn+F10",
+	[KEY_FN_F11] = "Fn+F11",		[KEY_FN_F12] = "Fn+F12",
+	[KEY_KBDILLUMTOGGLE] = "KbdIlluminationToggle",
+	[KEY_KBDILLUMDOWN] = "KbdIlluminationDown",
+	[KEY_KBDILLUMUP] = "KbdIlluminationUp",
+	[KEY_SWITCHVIDEOMODE] = "SwitchVideoMode",
+	[KEY_BUTTONCONFIG] = "ButtonConfig",
+	[KEY_TASKMANAGER] = "TaskManager",
+	[KEY_JOURNAL] = "Journal",
+	[KEY_CONTROLPANEL] = "ControlPanel",
+	[KEY_APPSELECT] = "AppSelect",
+	[KEY_SCREENSAVER] = "ScreenSaver",
+	[KEY_VOICECOMMAND] = "VoiceCommand",
+	[KEY_BRIGHTNESS_MIN] = "BrightnessMin",
+	[KEY_BRIGHTNESS_MAX] = "BrightnessMax",
+	[KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",
+	[KEY_KBDINPUTASSIST_PREV] = "KbdInputAssistPrev",
+	[KEY_KBDINPUTASSIST_NEXT] = "KbdInputAssistNext",
+	[KEY_KBDINPUTASSIST_PREVGROUP] = "KbdInputAssistPrevGroup",
+	[KEY_KBDINPUTASSIST_NEXTGROUP] = "KbdInputAssistNextGroup",
+	[KEY_KBDINPUTASSIST_ACCEPT] = "KbdInputAssistAccept",
+	[KEY_KBDINPUTASSIST_CANCEL] = "KbdInputAssistCancel",
+};
+
+static const char *relatives[REL_MAX + 1] = {
+	[REL_X] = "X",			[REL_Y] = "Y",
+	[REL_Z] = "Z",			[REL_RX] = "Rx",
+	[REL_RY] = "Ry",		[REL_RZ] = "Rz",
+	[REL_HWHEEL] = "HWheel",	[REL_DIAL] = "Dial",
+	[REL_WHEEL] = "Wheel",		[REL_MISC] = "Misc",
+};
+
+static const char *absolutes[ABS_CNT] = {
+	[ABS_X] = "X",			[ABS_Y] = "Y",
+	[ABS_Z] = "Z",			[ABS_RX] = "Rx",
+	[ABS_RY] = "Ry",		[ABS_RZ] = "Rz",
+	[ABS_THROTTLE] = "Throttle",	[ABS_RUDDER] = "Rudder",
+	[ABS_WHEEL] = "Wheel",		[ABS_GAS] = "Gas",
+	[ABS_BRAKE] = "Brake",		[ABS_HAT0X] = "Hat0X",
+	[ABS_HAT0Y] = "Hat0Y",		[ABS_HAT1X] = "Hat1X",
+	[ABS_HAT1Y] = "Hat1Y",		[ABS_HAT2X] = "Hat2X",
+	[ABS_HAT2Y] = "Hat2Y",		[ABS_HAT3X] = "Hat3X",
+	[ABS_HAT3Y] = "Hat 3Y",		[ABS_PRESSURE] = "Pressure",
+	[ABS_DISTANCE] = "Distance",	[ABS_TILT_X] = "XTilt",
+	[ABS_TILT_Y] = "YTilt",		[ABS_TOOL_WIDTH] = "ToolWidth",
+	[ABS_VOLUME] = "Volume",	[ABS_MISC] = "Misc",
+	[ABS_MT_TOUCH_MAJOR] = "MTMajor",
+	[ABS_MT_TOUCH_MINOR] = "MTMinor",
+	[ABS_MT_WIDTH_MAJOR] = "MTMajorW",
+	[ABS_MT_WIDTH_MINOR] = "MTMinorW",
+	[ABS_MT_ORIENTATION] = "MTOrientation",
+	[ABS_MT_POSITION_X] = "MTPositionX",
+	[ABS_MT_POSITION_Y] = "MTPositionY",
+	[ABS_MT_TOOL_TYPE] = "MTToolType",
+	[ABS_MT_BLOB_ID] = "MTBlobID",
+};
+
+static const char *misc[MSC_MAX + 1] = {
+	[MSC_SERIAL] = "Serial",	[MSC_PULSELED] = "Pulseled",
+	[MSC_GESTURE] = "Gesture",	[MSC_RAW] = "RawData"
+};
+
+static const char *leds[LED_MAX + 1] = {
+	[LED_NUML] = "NumLock",		[LED_CAPSL] = "CapsLock",
+	[LED_SCROLLL] = "ScrollLock",	[LED_COMPOSE] = "Compose",
+	[LED_KANA] = "Kana",		[LED_SLEEP] = "Sleep",
+	[LED_SUSPEND] = "Suspend",	[LED_MUTE] = "Mute",
+	[LED_MISC] = "Misc",
+};
+
+static const char *repeats[REP_MAX + 1] = {
+	[REP_DELAY] = "Delay",		[REP_PERIOD] = "Period"
+};
+
+static const char *sounds[SND_MAX + 1] = {
+	[SND_CLICK] = "Click",		[SND_BELL] = "Bell",
+	[SND_TONE] = "Tone"
+};
+
+static const char **names[EV_MAX + 1] = {
+	[EV_SYN] = syncs,			[EV_KEY] = keys,
+	[EV_REL] = relatives,			[EV_ABS] = absolutes,
+	[EV_MSC] = misc,			[EV_LED] = leds,
+	[EV_SND] = sounds,			[EV_REP] = repeats,
+};
+
+static void hid_resolv_event(__u8 type, __u16 code, struct seq_file *f)
+{
+	seq_printf(f, "%s.%s", events[type] ? events[type] : "?",
+		names[type] ? (names[type][code] ? names[type][code] : "?") : "?");
+}
+
+static void hid_dump_input_mapping(struct hid_device *hid, struct seq_file *f)
+{
+	int i, j, k;
+	struct hid_report *report;
+	struct hid_usage *usage;
+
+	for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) {
+		list_for_each_entry(report, &hid->report_enum[k].report_list, list) {
+			for (i = 0; i < report->maxfield; i++) {
+				for ( j = 0; j < report->field[i]->maxusage; j++) {
+					usage = report->field[i]->usage + j;
+					hid_resolv_usage(usage->hid, f);
+					seq_printf(f, " ---> ");
+					hid_resolv_event(usage->type, usage->code, f);
+					seq_printf(f, "\n");
+				}
+			}
+		}
+	}
+
+}
+
+static int hid_debug_rdesc_show(struct seq_file *f, void *p)
+{
+	struct hid_device *hdev = f->private;
+	const __u8 *rdesc = hdev->rdesc;
+	unsigned rsize = hdev->rsize;
+	int i;
+
+	if (!rdesc) {
+		rdesc = hdev->dev_rdesc;
+		rsize = hdev->dev_rsize;
+	}
+
+	/* dump HID report descriptor */
+	for (i = 0; i < rsize; i++)
+		seq_printf(f, "%02x ", rdesc[i]);
+	seq_printf(f, "\n\n");
+
+	/* dump parsed data and input mappings */
+	hid_dump_device(hdev, f);
+	seq_printf(f, "\n");
+	hid_dump_input_mapping(hdev, f);
+
+	return 0;
+}
+
+static int hid_debug_rdesc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hid_debug_rdesc_show, inode->i_private);
+}
+
+static int hid_debug_events_open(struct inode *inode, struct file *file)
+{
+	int err = 0;
+	struct hid_debug_list *list;
+	unsigned long flags;
+
+	if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	if (!(list->hid_debug_buf = kzalloc(HID_DEBUG_BUFSIZE, GFP_KERNEL))) {
+		err = -ENOMEM;
+		kfree(list);
+		goto out;
+	}
+	list->hdev = (struct hid_device *) inode->i_private;
+	file->private_data = list;
+	mutex_init(&list->read_mutex);
+
+	spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
+	list_add_tail(&list->node, &list->hdev->debug_list);
+	spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
+
+out:
+	return err;
+}
+
+static ssize_t hid_debug_events_read(struct file *file, char __user *buffer,
+		size_t count, loff_t *ppos)
+{
+	struct hid_debug_list *list = file->private_data;
+	int ret = 0, len;
+	DECLARE_WAITQUEUE(wait, current);
+
+	mutex_lock(&list->read_mutex);
+	while (ret == 0) {
+		if (list->head == list->tail) {
+			add_wait_queue(&list->hdev->debug_wait, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			while (list->head == list->tail) {
+				if (file->f_flags & O_NONBLOCK) {
+					ret = -EAGAIN;
+					break;
+				}
+				if (signal_pending(current)) {
+					ret = -ERESTARTSYS;
+					break;
+				}
+
+				if (!list->hdev || !list->hdev->debug) {
+					ret = -EIO;
+					set_current_state(TASK_RUNNING);
+					goto out;
+				}
+
+				/* allow O_NONBLOCK from other threads */
+				mutex_unlock(&list->read_mutex);
+				schedule();
+				mutex_lock(&list->read_mutex);
+				set_current_state(TASK_INTERRUPTIBLE);
+			}
+
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&list->hdev->debug_wait, &wait);
+		}
+
+		if (ret)
+			goto out;
+
+		/* pass the ringbuffer contents to userspace */
+copy_rest:
+		if (list->tail == list->head)
+			goto out;
+		if (list->tail > list->head) {
+			len = list->tail - list->head;
+			if (len > count)
+				len = count;
+
+			if (copy_to_user(buffer + ret, &list->hid_debug_buf[list->head], len)) {
+				ret = -EFAULT;
+				goto out;
+			}
+			ret += len;
+			list->head += len;
+		} else {
+			len = HID_DEBUG_BUFSIZE - list->head;
+			if (len > count)
+				len = count;
+
+			if (copy_to_user(buffer, &list->hid_debug_buf[list->head], len)) {
+				ret = -EFAULT;
+				goto out;
+			}
+			list->head = 0;
+			ret += len;
+			count -= len;
+			if (count > 0)
+				goto copy_rest;
+		}
+
+	}
+out:
+	mutex_unlock(&list->read_mutex);
+	return ret;
+}
+
+static __poll_t hid_debug_events_poll(struct file *file, poll_table *wait)
+{
+	struct hid_debug_list *list = file->private_data;
+
+	poll_wait(file, &list->hdev->debug_wait, wait);
+	if (list->head != list->tail)
+		return EPOLLIN | EPOLLRDNORM;
+	if (!list->hdev->debug)
+		return EPOLLERR | EPOLLHUP;
+	return 0;
+}
+
+static int hid_debug_events_release(struct inode *inode, struct file *file)
+{
+	struct hid_debug_list *list = file->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
+	list_del(&list->node);
+	spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
+	kfree(list->hid_debug_buf);
+	kfree(list);
+
+	return 0;
+}
+
+static const struct file_operations hid_debug_rdesc_fops = {
+	.open           = hid_debug_rdesc_open,
+	.read           = seq_read,
+	.llseek         = seq_lseek,
+	.release        = single_release,
+};
+
+static const struct file_operations hid_debug_events_fops = {
+	.owner =        THIS_MODULE,
+	.open           = hid_debug_events_open,
+	.read           = hid_debug_events_read,
+	.poll		= hid_debug_events_poll,
+	.release        = hid_debug_events_release,
+	.llseek		= noop_llseek,
+};
+
+
+void hid_debug_register(struct hid_device *hdev, const char *name)
+{
+	hdev->debug_dir = debugfs_create_dir(name, hid_debug_root);
+	hdev->debug_rdesc = debugfs_create_file("rdesc", 0400,
+			hdev->debug_dir, hdev, &hid_debug_rdesc_fops);
+	hdev->debug_events = debugfs_create_file("events", 0400,
+			hdev->debug_dir, hdev, &hid_debug_events_fops);
+	hdev->debug = 1;
+}
+
+void hid_debug_unregister(struct hid_device *hdev)
+{
+	hdev->debug = 0;
+	wake_up_interruptible(&hdev->debug_wait);
+	debugfs_remove(hdev->debug_rdesc);
+	debugfs_remove(hdev->debug_events);
+	debugfs_remove(hdev->debug_dir);
+}
+
+void hid_debug_init(void)
+{
+	hid_debug_root = debugfs_create_dir("hid", NULL);
+}
+
+void hid_debug_exit(void)
+{
+	debugfs_remove_recursive(hid_debug_root);
+}
+
diff --git a/drivers/hid/hid-dr.c b/drivers/hid/hid-dr.c
new file mode 100644
index 0000000..818ea7d
--- /dev/null
+++ b/drivers/hid/hid-dr.c
@@ -0,0 +1,325 @@
+/*
+ * Force feedback support for DragonRise Inc. game controllers
+ *
+ * From what I have gathered, these devices are mass produced in China and are
+ * distributed under several vendors. They often share the same design as
+ * the original PlayStation DualShock controller.
+ *
+ * 0079:0006 "DragonRise Inc.   Generic   USB  Joystick  "
+ *  - tested with a Tesun USB-703 game controller.
+ *
+ * Copyright (c) 2009 Richard Walmsley <richwalm@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#ifdef CONFIG_DRAGONRISE_FF
+
+struct drff_device {
+	struct hid_report *report;
+};
+
+static int drff_play(struct input_dev *dev, void *data,
+				 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct drff_device *drff = data;
+	int strong, weak;
+
+	strong = effect->u.rumble.strong_magnitude;
+	weak = effect->u.rumble.weak_magnitude;
+
+	dbg_hid("called with 0x%04x 0x%04x", strong, weak);
+
+	if (strong || weak) {
+		strong = strong * 0xff / 0xffff;
+		weak = weak * 0xff / 0xffff;
+
+		/* While reverse engineering this device, I found that when
+		   this value is set, it causes the strong rumble to function
+		   at a near maximum speed, so we'll bypass it. */
+		if (weak == 0x0a)
+			weak = 0x0b;
+
+		drff->report->field[0]->value[0] = 0x51;
+		drff->report->field[0]->value[1] = 0x00;
+		drff->report->field[0]->value[2] = weak;
+		drff->report->field[0]->value[4] = strong;
+		hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
+
+		drff->report->field[0]->value[0] = 0xfa;
+		drff->report->field[0]->value[1] = 0xfe;
+	} else {
+		drff->report->field[0]->value[0] = 0xf3;
+		drff->report->field[0]->value[1] = 0x00;
+	}
+
+	drff->report->field[0]->value[2] = 0x00;
+	drff->report->field[0]->value[4] = 0x00;
+	dbg_hid("running with 0x%02x 0x%02x", strong, weak);
+	hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int drff_init(struct hid_device *hid)
+{
+	struct drff_device *drff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_first_entry(&hid->inputs,
+						struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	report = list_first_entry(report_list, struct hid_report, list);
+	if (report->maxfield < 1) {
+		hid_err(hid, "no fields in the report\n");
+		return -ENODEV;
+	}
+
+	if (report->field[0]->report_count < 7) {
+		hid_err(hid, "not enough values in the field\n");
+		return -ENODEV;
+	}
+
+	drff = kzalloc(sizeof(struct drff_device), GFP_KERNEL);
+	if (!drff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, drff, drff_play);
+	if (error) {
+		kfree(drff);
+		return error;
+	}
+
+	drff->report = report;
+	drff->report->field[0]->value[0] = 0xf3;
+	drff->report->field[0]->value[1] = 0x00;
+	drff->report->field[0]->value[2] = 0x00;
+	drff->report->field[0]->value[3] = 0x00;
+	drff->report->field[0]->value[4] = 0x00;
+	drff->report->field[0]->value[5] = 0x00;
+	drff->report->field[0]->value[6] = 0x00;
+	hid_hw_request(hid, drff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "Force Feedback for DragonRise Inc. "
+		 "game controllers by Richard Walmsley <richwalm@gmail.com>\n");
+
+	return 0;
+}
+#else
+static inline int drff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+/*
+ * The original descriptor of joystick with PID 0x0011, represented by DVTech PC
+ * JS19. It seems both copied from another device and a result of confusion
+ * either about the specification or about the program used to create the
+ * descriptor. In any case, it's a wonder it works on Windows.
+ *
+ *  Usage Page (Desktop),             ; Generic desktop controls (01h)
+ *  Usage (Joystick),                 ; Joystick (04h, application collection)
+ *  Collection (Application),
+ *    Collection (Logical),
+ *      Report Size (8),
+ *      Report Count (5),
+ *      Logical Minimum (0),
+ *      Logical Maximum (255),
+ *      Physical Minimum (0),
+ *      Physical Maximum (255),
+ *      Usage (X),                    ; X (30h, dynamic value)
+ *      Usage (X),                    ; X (30h, dynamic value)
+ *      Usage (X),                    ; X (30h, dynamic value)
+ *      Usage (X),                    ; X (30h, dynamic value)
+ *      Usage (Y),                    ; Y (31h, dynamic value)
+ *      Input (Variable),
+ *      Report Size (4),
+ *      Report Count (1),
+ *      Logical Maximum (7),
+ *      Physical Maximum (315),
+ *      Unit (Degrees),
+ *      Usage (00h),
+ *      Input (Variable, Null State),
+ *      Unit,
+ *      Report Size (1),
+ *      Report Count (10),
+ *      Logical Maximum (1),
+ *      Physical Maximum (1),
+ *      Usage Page (Button),          ; Button (09h)
+ *      Usage Minimum (01h),
+ *      Usage Maximum (0Ah),
+ *      Input (Variable),
+ *      Usage Page (FF00h),           ; FF00h, vendor-defined
+ *      Report Size (1),
+ *      Report Count (10),
+ *      Logical Maximum (1),
+ *      Physical Maximum (1),
+ *      Usage (01h),
+ *      Input (Variable),
+ *    End Collection,
+ *    Collection (Logical),
+ *      Report Size (8),
+ *      Report Count (4),
+ *      Physical Maximum (255),
+ *      Logical Maximum (255),
+ *      Usage (02h),
+ *      Output (Variable),
+ *    End Collection,
+ *  End Collection
+ */
+
+/* Size of the original descriptor of the PID 0x0011 joystick */
+#define PID0011_RDESC_ORIG_SIZE	101
+
+/* Fixed report descriptor for PID 0x011 joystick */
+static __u8 pid0011_rdesc_fixed[] = {
+	0x05, 0x01,         /*  Usage Page (Desktop),           */
+	0x09, 0x04,         /*  Usage (Joystick),               */
+	0xA1, 0x01,         /*  Collection (Application),       */
+	0xA1, 0x02,         /*      Collection (Logical),       */
+	0x14,               /*          Logical Minimum (0),    */
+	0x75, 0x08,         /*          Report Size (8),        */
+	0x95, 0x03,         /*          Report Count (3),       */
+	0x81, 0x01,         /*          Input (Constant),       */
+	0x26, 0xFF, 0x00,   /*          Logical Maximum (255),  */
+	0x95, 0x02,         /*          Report Count (2),       */
+	0x09, 0x30,         /*          Usage (X),              */
+	0x09, 0x31,         /*          Usage (Y),              */
+	0x81, 0x02,         /*          Input (Variable),       */
+	0x75, 0x01,         /*          Report Size (1),        */
+	0x95, 0x04,         /*          Report Count (4),       */
+	0x81, 0x01,         /*          Input (Constant),       */
+	0x25, 0x01,         /*          Logical Maximum (1),    */
+	0x95, 0x0A,         /*          Report Count (10),      */
+	0x05, 0x09,         /*          Usage Page (Button),    */
+	0x19, 0x01,         /*          Usage Minimum (01h),    */
+	0x29, 0x0A,         /*          Usage Maximum (0Ah),    */
+	0x81, 0x02,         /*          Input (Variable),       */
+	0x95, 0x0A,         /*          Report Count (10),      */
+	0x81, 0x01,         /*          Input (Constant),       */
+	0xC0,               /*      End Collection,             */
+	0xC0                /*  End Collection                  */
+};
+
+static __u8 *dr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+				unsigned int *rsize)
+{
+	switch (hdev->product) {
+	case 0x0011:
+		if (*rsize == PID0011_RDESC_ORIG_SIZE) {
+			rdesc = pid0011_rdesc_fixed;
+			*rsize = sizeof(pid0011_rdesc_fixed);
+		}
+		break;
+	}
+	return rdesc;
+}
+
+#define map_abs(c)      hid_map_usage(hi, usage, bit, max, EV_ABS, (c))
+#define map_rel(c)      hid_map_usage(hi, usage, bit, max, EV_REL, (c))
+
+static int dr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+			    struct hid_field *field, struct hid_usage *usage,
+			    unsigned long **bit, int *max)
+{
+	switch (usage->hid) {
+	/*
+	 * revert to the old hid-input behavior where axes
+	 * can be randomly assigned when hid->usage is reused.
+	 */
+	case HID_GD_X: case HID_GD_Y: case HID_GD_Z:
+	case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ:
+		if (field->flags & HID_MAIN_ITEM_RELATIVE)
+			map_rel(usage->hid & 0xf);
+		else
+			map_abs(usage->hid & 0xf);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int dr_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	dev_dbg(&hdev->dev, "DragonRise Inc. HID hardware probe...");
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	switch (hdev->product) {
+	case 0x0006:
+		ret = drff_init(hdev);
+		if (ret) {
+			dev_err(&hdev->dev, "force feedback init failed\n");
+			hid_hw_stop(hdev);
+			goto err;
+		}
+		break;
+	}
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id dr_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006),  },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011),  },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, dr_devices);
+
+static struct hid_driver dr_driver = {
+	.name = "dragonrise",
+	.id_table = dr_devices,
+	.report_fixup = dr_report_fixup,
+	.probe = dr_probe,
+	.input_mapping = dr_input_mapping,
+};
+module_hid_driver(dr_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c
new file mode 100644
index 0000000..07e26c3
--- /dev/null
+++ b/drivers/hid/hid-elan.c
@@ -0,0 +1,550 @@
+/*
+ * HID Driver for ELAN Touchpad
+ *
+ * Currently only supports touchpad found on HP Pavilion X2 10
+ *
+ * Copyright (c) 2016 Alexandrov Stanislav <neko@nya.ai>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/input/mt.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+#define ELAN_MT_I2C		0x5d
+#define ELAN_SINGLE_FINGER	0x81
+#define ELAN_MT_FIRST_FINGER	0x82
+#define ELAN_MT_SECOND_FINGER	0x83
+#define ELAN_INPUT_REPORT_SIZE	8
+#define ELAN_I2C_REPORT_SIZE	32
+#define ELAN_FINGER_DATA_LEN	5
+#define ELAN_MAX_FINGERS	5
+#define ELAN_MAX_PRESSURE	255
+#define ELAN_TP_USB_INTF	1
+
+#define ELAN_FEATURE_REPORT	0x0d
+#define ELAN_FEATURE_SIZE	5
+#define ELAN_PARAM_MAX_X	6
+#define ELAN_PARAM_MAX_Y	7
+#define ELAN_PARAM_RES		8
+
+#define ELAN_MUTE_LED_REPORT	0xBC
+#define ELAN_LED_REPORT_SIZE	8
+
+#define ELAN_HAS_LED		BIT(0)
+
+struct elan_drvdata {
+	struct input_dev *input;
+	u8 prev_report[ELAN_INPUT_REPORT_SIZE];
+	struct led_classdev mute_led;
+	u8 mute_led_state;
+	u16 max_x;
+	u16 max_y;
+	u16 res_x;
+	u16 res_y;
+};
+
+static int is_not_elan_touchpad(struct hid_device *hdev)
+{
+	if (hdev->bus == BUS_USB) {
+		struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+		return (intf->altsetting->desc.bInterfaceNumber !=
+			ELAN_TP_USB_INTF);
+	}
+
+	return 0;
+}
+
+static int elan_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+			      struct hid_field *field, struct hid_usage *usage,
+			      unsigned long **bit, int *max)
+{
+	if (is_not_elan_touchpad(hdev))
+		return 0;
+
+	if (field->report->id == ELAN_SINGLE_FINGER ||
+	    field->report->id == ELAN_MT_FIRST_FINGER ||
+	    field->report->id == ELAN_MT_SECOND_FINGER ||
+	    field->report->id == ELAN_MT_I2C)
+		return -1;
+
+	return 0;
+}
+
+static int elan_get_device_param(struct hid_device *hdev,
+				 unsigned char *dmabuf, unsigned char param)
+{
+	int ret;
+
+	dmabuf[0] = ELAN_FEATURE_REPORT;
+	dmabuf[1] = 0x05;
+	dmabuf[2] = 0x03;
+	dmabuf[3] = param;
+	dmabuf[4] = 0x01;
+
+	ret = hid_hw_raw_request(hdev, ELAN_FEATURE_REPORT, dmabuf,
+				 ELAN_FEATURE_SIZE, HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	if (ret != ELAN_FEATURE_SIZE) {
+		hid_err(hdev, "Set report error for parm %d: %d\n", param, ret);
+		return ret;
+	}
+
+	ret = hid_hw_raw_request(hdev, ELAN_FEATURE_REPORT, dmabuf,
+				 ELAN_FEATURE_SIZE, HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret != ELAN_FEATURE_SIZE) {
+		hid_err(hdev, "Get report error for parm %d: %d\n", param, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static unsigned int elan_convert_res(char val)
+{
+	/*
+	 * (value from firmware) * 10 + 790 = dpi
+	 * dpi * 10 / 254 = dots/mm
+	 */
+	return (val * 10 + 790) * 10 / 254;
+}
+
+static int elan_get_device_params(struct hid_device *hdev)
+{
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+	unsigned char *dmabuf;
+	int ret;
+
+	dmabuf = kmalloc(ELAN_FEATURE_SIZE, GFP_KERNEL);
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = elan_get_device_param(hdev, dmabuf, ELAN_PARAM_MAX_X);
+	if (ret)
+		goto err;
+
+	drvdata->max_x = (dmabuf[4] << 8) | dmabuf[3];
+
+	ret = elan_get_device_param(hdev, dmabuf, ELAN_PARAM_MAX_Y);
+	if (ret)
+		goto err;
+
+	drvdata->max_y = (dmabuf[4] << 8) | dmabuf[3];
+
+	ret = elan_get_device_param(hdev, dmabuf, ELAN_PARAM_RES);
+	if (ret)
+		goto err;
+
+	drvdata->res_x = elan_convert_res(dmabuf[3]);
+	drvdata->res_y = elan_convert_res(dmabuf[4]);
+
+err:
+	kfree(dmabuf);
+	return ret;
+}
+
+static int elan_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	int ret;
+	struct input_dev *input;
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (is_not_elan_touchpad(hdev))
+		return 0;
+
+	ret = elan_get_device_params(hdev);
+	if (ret)
+		return ret;
+
+	input = devm_input_allocate_device(&hdev->dev);
+	if (!input)
+		return -ENOMEM;
+
+	input->name = "Elan Touchpad";
+	input->phys = hdev->phys;
+	input->uniq = hdev->uniq;
+	input->id.bustype = hdev->bus;
+	input->id.vendor  = hdev->vendor;
+	input->id.product = hdev->product;
+	input->id.version = hdev->version;
+	input->dev.parent = &hdev->dev;
+
+	input_set_abs_params(input, ABS_MT_POSITION_X, 0, drvdata->max_x,
+			     0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y, 0, drvdata->max_y,
+			     0, 0);
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0, ELAN_MAX_PRESSURE,
+			     0, 0);
+
+	__set_bit(BTN_LEFT, input->keybit);
+	__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
+	ret = input_mt_init_slots(input, ELAN_MAX_FINGERS, INPUT_MT_POINTER);
+	if (ret) {
+		hid_err(hdev, "Failed to init elan MT slots: %d\n", ret);
+		return ret;
+	}
+
+	input_abs_set_res(input, ABS_X, drvdata->res_x);
+	input_abs_set_res(input, ABS_Y, drvdata->res_y);
+
+	ret = input_register_device(input);
+	if (ret) {
+		hid_err(hdev, "Failed to register elan input device: %d\n",
+			ret);
+		input_free_device(input);
+		return ret;
+	}
+
+	drvdata->input = input;
+
+	return 0;
+}
+
+static void elan_report_mt_slot(struct elan_drvdata *drvdata, u8 *data,
+				unsigned int slot_num)
+{
+	struct input_dev *input = drvdata->input;
+	int x, y, p;
+
+	bool active = !!data;
+
+	input_mt_slot(input, slot_num);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER, active);
+	if (active) {
+		x = ((data[0] & 0xF0) << 4) | data[1];
+		y = drvdata->max_y -
+		    (((data[0] & 0x07) << 8) | data[2]);
+		p = data[4];
+
+		input_report_abs(input, ABS_MT_POSITION_X, x);
+		input_report_abs(input, ABS_MT_POSITION_Y, y);
+		input_report_abs(input, ABS_MT_PRESSURE, p);
+	}
+}
+
+static void elan_usb_report_input(struct elan_drvdata *drvdata, u8 *data)
+{
+	int i;
+	struct input_dev *input = drvdata->input;
+
+	/*
+	 * There is 3 types of reports: for single touch,
+	 * for multitouch - first finger and for multitouch - second finger
+	 *
+	 * packet structure for ELAN_SINGLE_FINGER and ELAN_MT_FIRST_FINGER:
+	 *
+	 * byte 1: 1   0   0   0   0   0   0   1  // 0x81 or 0x82
+	 * byte 2: 0   0   0   0   0   0   0   0  // looks like unused
+	 * byte 3: f5  f4  f3  f2  f1  0   0   L
+	 * byte 4: x12 x11 x10 x9  0?  y11 y10 y9
+	 * byte 5: x8  x7  x6  x5  x4  x3  x2  x1
+	 * byte 6: y8  y7  y6  y5  y4  y3  y2  y1
+	 * byte 7: sy4 sy3 sy2 sy1 sx4 sx3 sx2 sx1
+	 * byte 8: p8  p7  p6  p5  p4  p3  p2  p1
+	 *
+	 * packet structure for ELAN_MT_SECOND_FINGER:
+	 *
+	 * byte 1: 1   0   0   0   0   0   1   1  // 0x83
+	 * byte 2: x12 x11 x10 x9  0   y11 y10 y9
+	 * byte 3: x8  x7  x6  x5  x4  x3  x2  x1
+	 * byte 4: y8  y7  y6  y5  y4  y3  y2  y1
+	 * byte 5: sy4 sy3 sy2 sy1 sx4 sx3 sx2 sx1
+	 * byte 6: p8  p7  p6  p5  p4  p3  p2  p1
+	 * byte 7: 0   0   0   0   0   0   0   0
+	 * byte 8: 0   0   0   0   0   0   0   0
+	 *
+	 * f5-f1: finger touch bits
+	 * L: clickpad button
+	 * sy / sx: finger width / height expressed in traces, the total number
+	 *          of traces can be queried by doing a HID_REQ_SET_REPORT
+	 *          { 0x0d, 0x05, 0x03, 0x05, 0x01 } followed by a GET, in the
+	 *          returned buf, buf[3]=no-x-traces, buf[4]=no-y-traces.
+	 * p: pressure
+	 */
+
+	if (data[0] == ELAN_SINGLE_FINGER) {
+		for (i = 0; i < ELAN_MAX_FINGERS; i++) {
+			if (data[2] & BIT(i + 3))
+				elan_report_mt_slot(drvdata, data + 3, i);
+			else
+				elan_report_mt_slot(drvdata, NULL, i);
+		}
+		input_report_key(input, BTN_LEFT, data[2] & 0x01);
+	}
+	/*
+	 * When touched with two fingers Elan touchpad will emit two HID reports
+	 * first is ELAN_MT_FIRST_FINGER and second is ELAN_MT_SECOND_FINGER
+	 * we will save ELAN_MT_FIRST_FINGER report and wait for
+	 * ELAN_MT_SECOND_FINGER to finish multitouch
+	 */
+	if (data[0] == ELAN_MT_FIRST_FINGER) {
+		memcpy(drvdata->prev_report, data,
+		       sizeof(drvdata->prev_report));
+		return;
+	}
+
+	if (data[0] == ELAN_MT_SECOND_FINGER) {
+		int first = 0;
+		u8 *prev_report = drvdata->prev_report;
+
+		if (prev_report[0] != ELAN_MT_FIRST_FINGER)
+			return;
+
+		for (i = 0; i < ELAN_MAX_FINGERS; i++) {
+			if (prev_report[2] & BIT(i + 3)) {
+				if (!first) {
+					first = 1;
+					elan_report_mt_slot(drvdata, prev_report + 3, i);
+				} else {
+					elan_report_mt_slot(drvdata, data + 1, i);
+				}
+			} else {
+				elan_report_mt_slot(drvdata, NULL, i);
+			}
+		}
+		input_report_key(input, BTN_LEFT, prev_report[2] & 0x01);
+	}
+
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static void elan_i2c_report_input(struct elan_drvdata *drvdata, u8 *data)
+{
+	struct input_dev *input = drvdata->input;
+	u8 *finger_data;
+	int i;
+
+	/*
+	 * Elan MT touchpads in i2c mode send finger data in the same format
+	 * as in USB mode, but then with all fingers in a single packet.
+	 *
+	 * packet structure for ELAN_MT_I2C:
+	 *
+	 * byte     1: 1   0   0   1   1   1   0   1   // 0x5d
+	 * byte     2: f5  f4  f3  f2  f1  0   0   L
+	 * byte     3: x12 x11 x10 x9  0?  y11 y10 y9
+	 * byte     4: x8  x7  x6  x5  x4  x3  x2  x1
+	 * byte     5: y8  y7  y6  y5  y4  y3  y2  y1
+	 * byte     6: sy4 sy3 sy2 sy1 sx4 sx3 sx2 sx1
+	 * byte     7: p8  p7  p6  p5  p4  p3  p2  p1
+	 * byte  8-12: Same as byte 3-7 for second finger down
+	 * byte 13-17: Same as byte 3-7 for third finger down
+	 * byte 18-22: Same as byte 3-7 for fourth finger down
+	 * byte 23-27: Same as byte 3-7 for fifth finger down
+	 */
+
+	finger_data = data + 2;
+	for (i = 0; i < ELAN_MAX_FINGERS; i++) {
+		if (data[1] & BIT(i + 3)) {
+			elan_report_mt_slot(drvdata, finger_data, i);
+			finger_data += ELAN_FINGER_DATA_LEN;
+		} else {
+			elan_report_mt_slot(drvdata, NULL, i);
+		}
+	}
+
+	input_report_key(input, BTN_LEFT, data[1] & 0x01);
+	input_mt_sync_frame(input);
+	input_sync(input);
+}
+
+static int elan_raw_event(struct hid_device *hdev,
+			  struct hid_report *report, u8 *data, int size)
+{
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (is_not_elan_touchpad(hdev))
+		return 0;
+
+	if (data[0] == ELAN_SINGLE_FINGER ||
+	    data[0] == ELAN_MT_FIRST_FINGER ||
+	    data[0] == ELAN_MT_SECOND_FINGER) {
+		if (size == ELAN_INPUT_REPORT_SIZE) {
+			elan_usb_report_input(drvdata, data);
+			return 1;
+		}
+	}
+
+	if (data[0] == ELAN_MT_I2C && size == ELAN_I2C_REPORT_SIZE) {
+		elan_i2c_report_input(drvdata, data);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int elan_start_multitouch(struct hid_device *hdev)
+{
+	int ret;
+
+	/*
+	 * This byte sequence will enable multitouch mode and disable
+	 * mouse emulation
+	 */
+	const unsigned char buf[] = { 0x0D, 0x00, 0x03, 0x21, 0x00 };
+	unsigned char *dmabuf = kmemdup(buf, sizeof(buf), GFP_KERNEL);
+
+	if (!dmabuf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, sizeof(buf),
+				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	kfree(dmabuf);
+
+	if (ret != sizeof(buf)) {
+		hid_err(hdev, "Failed to start multitouch: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static enum led_brightness elan_mute_led_get_brigtness(struct led_classdev *led_cdev)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	return drvdata->mute_led_state;
+}
+
+static int elan_mute_led_set_brigtness(struct led_classdev *led_cdev,
+				       enum led_brightness value)
+{
+	int ret;
+	u8 led_state;
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	unsigned char *dmabuf = kzalloc(ELAN_LED_REPORT_SIZE, GFP_KERNEL);
+
+	if (!dmabuf)
+		return -ENOMEM;
+
+	led_state = !!value;
+
+	dmabuf[0] = ELAN_MUTE_LED_REPORT;
+	dmabuf[1] = 0x02;
+	dmabuf[2] = led_state;
+
+	ret = hid_hw_raw_request(hdev, dmabuf[0], dmabuf, ELAN_LED_REPORT_SIZE,
+				 HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	kfree(dmabuf);
+
+	if (ret != ELAN_LED_REPORT_SIZE) {
+		hid_err(hdev, "Failed to set mute led brightness: %d\n", ret);
+		return ret;
+	}
+
+	drvdata->mute_led_state = led_state;
+	return 0;
+}
+
+static int elan_init_mute_led(struct hid_device *hdev)
+{
+	struct elan_drvdata *drvdata = hid_get_drvdata(hdev);
+	struct led_classdev *mute_led = &drvdata->mute_led;
+
+	mute_led->name = "elan:red:mute";
+	mute_led->brightness_get = elan_mute_led_get_brigtness;
+	mute_led->brightness_set_blocking = elan_mute_led_set_brigtness;
+	mute_led->max_brightness = LED_ON;
+	mute_led->dev = &hdev->dev;
+
+	return devm_led_classdev_register(&hdev->dev, mute_led);
+}
+
+static int elan_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct elan_drvdata *drvdata;
+
+	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+
+	if (!drvdata)
+		return -ENOMEM;
+
+	hid_set_drvdata(hdev, drvdata);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "Hid Parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "Hid hw start failed\n");
+		return ret;
+	}
+
+	if (is_not_elan_touchpad(hdev))
+		return 0;
+
+	if (!drvdata->input) {
+		hid_err(hdev, "Input device is not registred\n");
+		ret = -ENAVAIL;
+		goto err;
+	}
+
+	ret = elan_start_multitouch(hdev);
+	if (ret)
+		goto err;
+
+	if (id->driver_data & ELAN_HAS_LED) {
+		ret = elan_init_mute_led(hdev);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void elan_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id elan_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2),
+	  .driver_data = ELAN_HAS_LED },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2_10_COVER),
+	  .driver_data = ELAN_HAS_LED },
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_TOSHIBA_CLICK_L9W) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, elan_devices);
+
+static struct hid_driver elan_driver = {
+	.name = "elan",
+	.id_table = elan_devices,
+	.input_mapping = elan_input_mapping,
+	.input_configured = elan_input_configured,
+	.raw_event = elan_raw_event,
+	.probe = elan_probe,
+	.remove = elan_remove,
+};
+
+module_hid_driver(elan_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexandrov Stanislav");
+MODULE_DESCRIPTION("Driver for HID ELAN Touchpads");
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
new file mode 100644
index 0000000..ae8e941
--- /dev/null
+++ b/drivers/hid/hid-elecom.c
@@ -0,0 +1,103 @@
+/*
+ *  HID driver for ELECOM devices:
+ *  - BM084 Bluetooth Mouse
+ *  - EX-G Trackballs (M-XT3DRBK, M-XT3URBK, M-XT4DRBK)
+ *  - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
+ *  - HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
+ *
+ *  Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
+ *  Copyright (c) 2016 Yuxuan Shui <yshuiv7@gmail.com>
+ *  Copyright (c) 2017 Diego Elio Pettenò <flameeyes@flameeyes.eu>
+ *  Copyright (c) 2017 Alex Manoussakis <amanou@gnu.org>
+ *  Copyright (c) 2017 Tomasz Kramkowski <tk@the-tk.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/*
+ * Certain ELECOM mice misreport their button count meaning that they only work
+ * correctly with the ELECOM mouse assistant software which is unavailable for
+ * Linux. A four extra INPUT reports and a FEATURE report are described by the
+ * report descriptor but it does not appear that these enable software to
+ * control what the extra buttons map to. The only simple and straightforward
+ * solution seems to involve fixing up the report descriptor.
+ *
+ * Report descriptor format:
+ * Positions 13, 15, 21 and 31 store the button bit count, button usage minimum,
+ * button usage maximum and padding bit count respectively.
+ */
+#define MOUSE_BUTTONS_MAX 8
+static void mouse_button_fixup(struct hid_device *hdev,
+			       __u8 *rdesc, unsigned int rsize,
+			       int nbuttons)
+{
+	if (rsize < 32 || rdesc[12] != 0x95 ||
+	    rdesc[14] != 0x75 || rdesc[15] != 0x01 ||
+	    rdesc[20] != 0x29 || rdesc[30] != 0x75)
+		return;
+	hid_info(hdev, "Fixing up Elecom mouse button count\n");
+	nbuttons = clamp(nbuttons, 0, MOUSE_BUTTONS_MAX);
+	rdesc[13] = nbuttons;
+	rdesc[21] = nbuttons;
+	rdesc[31] = MOUSE_BUTTONS_MAX - nbuttons;
+}
+
+static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_ELECOM_BM084:
+		/* The BM084 Bluetooth mouse includes a non-existing horizontal
+		 * wheel in the HID descriptor. */
+		if (*rsize >= 48 && rdesc[46] == 0x05 && rdesc[47] == 0x0c) {
+			hid_info(hdev, "Fixing up Elecom BM084 report descriptor\n");
+			rdesc[47] = 0x00;
+		}
+		break;
+	case USB_DEVICE_ID_ELECOM_M_XT3URBK:
+	case USB_DEVICE_ID_ELECOM_M_XT3DRBK:
+	case USB_DEVICE_ID_ELECOM_M_XT4DRBK:
+		mouse_button_fixup(hdev, rdesc, *rsize, 6);
+		break;
+	case USB_DEVICE_ID_ELECOM_M_DT1URBK:
+	case USB_DEVICE_ID_ELECOM_M_DT1DRBK:
+	case USB_DEVICE_ID_ELECOM_M_HT1URBK:
+	case USB_DEVICE_ID_ELECOM_M_HT1DRBK:
+		mouse_button_fixup(hdev, rdesc, *rsize, 8);
+		break;
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id elecom_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, elecom_devices);
+
+static struct hid_driver elecom_driver = {
+	.name = "elecom",
+	.id_table = elecom_devices,
+	.report_fixup = elecom_report_fixup
+};
+module_hid_driver(elecom_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-elo.c b/drivers/hid/hid-elo.c
new file mode 100644
index 0000000..5eea6fe
--- /dev/null
+++ b/drivers/hid/hid-elo.c
@@ -0,0 +1,314 @@
+/*
+ * HID driver for ELO usb touchscreen 4000/4500
+ *
+ * Copyright (c) 2013 Jiri Slaby
+ *
+ * Data parsing taken from elousb driver by Vojtech Pavlik.
+ *
+ * This driver is licensed under the terms of GPLv2.
+ */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define ELO_PERIODIC_READ_INTERVAL	HZ
+#define ELO_SMARTSET_CMD_TIMEOUT	2000 /* msec */
+
+/* Elo SmartSet commands */
+#define ELO_FLUSH_SMARTSET_RESPONSES	0x02 /* Flush all pending smartset responses */
+#define ELO_SEND_SMARTSET_COMMAND	0x05 /* Send a smartset command */
+#define ELO_GET_SMARTSET_RESPONSE	0x06 /* Get a smartset response */
+#define ELO_DIAG			0x64 /* Diagnostics command */
+#define ELO_SMARTSET_PACKET_SIZE	8
+
+struct elo_priv {
+	struct usb_device *usbdev;
+	struct delayed_work work;
+	unsigned char buffer[ELO_SMARTSET_PACKET_SIZE];
+};
+
+static struct workqueue_struct *wq;
+static bool use_fw_quirk = true;
+module_param(use_fw_quirk, bool, S_IRUGO);
+MODULE_PARM_DESC(use_fw_quirk, "Do periodic pokes for broken M firmwares (default = true)");
+
+static int elo_input_configured(struct hid_device *hdev,
+		struct hid_input *hidinput)
+{
+	struct input_dev *input = hidinput->input;
+
+	/*
+	 * ELO devices have one Button usage in GenDesk field, which makes
+	 * hid-input map it to BTN_LEFT; that confuses userspace, which then
+	 * considers the device to be a mouse/touchpad instead of touchscreen.
+	 */
+	clear_bit(BTN_LEFT, input->keybit);
+	set_bit(BTN_TOUCH, input->keybit);
+	set_bit(ABS_PRESSURE, input->absbit);
+	input_set_abs_params(input, ABS_PRESSURE, 0, 256, 0, 0);
+
+	return 0;
+}
+
+static void elo_process_data(struct input_dev *input, const u8 *data, int size)
+{
+	int press;
+
+	input_report_abs(input, ABS_X, (data[3] << 8) | data[2]);
+	input_report_abs(input, ABS_Y, (data[5] << 8) | data[4]);
+
+	press = 0;
+	if (data[1] & 0x80)
+		press = (data[7] << 8) | data[6];
+	input_report_abs(input, ABS_PRESSURE, press);
+
+	if (data[1] & 0x03) {
+		input_report_key(input, BTN_TOUCH, 1);
+		input_sync(input);
+	}
+
+	if (data[1] & 0x04)
+		input_report_key(input, BTN_TOUCH, 0);
+
+	input_sync(input);
+}
+
+static int elo_raw_event(struct hid_device *hdev, struct hid_report *report,
+	 u8 *data, int size)
+{
+	struct hid_input *hidinput;
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || list_empty(&hdev->inputs))
+		return 0;
+
+	hidinput = list_first_entry(&hdev->inputs, struct hid_input, list);
+
+	switch (report->id) {
+	case 0:
+		if (data[0] == 'T') {	/* Mandatory ELO packet marker */
+			elo_process_data(hidinput->input, data, size);
+			return 1;
+		}
+		break;
+	default:	/* unknown report */
+		/* Unknown report type; pass upstream */
+		hid_info(hdev, "unknown report type %d\n", report->id);
+		break;
+	}
+
+	return 0;
+}
+
+static int elo_smartset_send_get(struct usb_device *dev, u8 command,
+		void *data)
+{
+	unsigned int pipe;
+	u8 dir;
+
+	if (command == ELO_SEND_SMARTSET_COMMAND) {
+		pipe = usb_sndctrlpipe(dev, 0);
+		dir = USB_DIR_OUT;
+	} else if (command == ELO_GET_SMARTSET_RESPONSE) {
+		pipe = usb_rcvctrlpipe(dev, 0);
+		dir = USB_DIR_IN;
+	} else
+		return -EINVAL;
+
+	return usb_control_msg(dev, pipe, command,
+			dir | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, 0, data, ELO_SMARTSET_PACKET_SIZE,
+			ELO_SMARTSET_CMD_TIMEOUT);
+}
+
+static int elo_flush_smartset_responses(struct usb_device *dev)
+{
+	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			ELO_FLUSH_SMARTSET_RESPONSES,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+static void elo_work(struct work_struct *work)
+{
+	struct elo_priv *priv = container_of(work, struct elo_priv, work.work);
+	struct usb_device *dev = priv->usbdev;
+	unsigned char *buffer = priv->buffer;
+	int ret;
+
+	ret = elo_flush_smartset_responses(dev);
+	if (ret < 0) {
+		dev_err(&dev->dev, "initial FLUSH_SMARTSET_RESPONSES failed, error %d\n",
+				ret);
+		goto fail;
+	}
+
+	/* send Diagnostics command */
+	*buffer = ELO_DIAG;
+	ret = elo_smartset_send_get(dev, ELO_SEND_SMARTSET_COMMAND, buffer);
+	if (ret < 0) {
+		dev_err(&dev->dev, "send Diagnostics Command failed, error %d\n",
+				ret);
+		goto fail;
+	}
+
+	/* get the result */
+	ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE, buffer);
+	if (ret < 0) {
+		dev_err(&dev->dev, "get Diagnostics Command response failed, error %d\n",
+				ret);
+		goto fail;
+	}
+
+	/* read the ack */
+	if (*buffer != 'A') {
+		ret = elo_smartset_send_get(dev, ELO_GET_SMARTSET_RESPONSE,
+				buffer);
+		if (ret < 0) {
+			dev_err(&dev->dev, "get acknowledge response failed, error %d\n",
+					ret);
+			goto fail;
+		}
+	}
+
+fail:
+	ret = elo_flush_smartset_responses(dev);
+	if (ret < 0)
+		dev_err(&dev->dev, "final FLUSH_SMARTSET_RESPONSES failed, error %d\n",
+				ret);
+	queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL);
+}
+
+/*
+ * Not all Elo devices need the periodic HID descriptor reads.
+ * Only firmware version M needs this.
+ */
+static bool elo_broken_firmware(struct usb_device *dev)
+{
+	struct usb_device *hub = dev->parent;
+	struct usb_device *child = NULL;
+	u16 fw_lvl = le16_to_cpu(dev->descriptor.bcdDevice);
+	u16 child_vid, child_pid;
+	int i;
+    
+	if (!use_fw_quirk)
+		return false;
+	if (fw_lvl != 0x10d)
+		return false;
+
+	/* iterate sibling devices of the touch controller */
+	usb_hub_for_each_child(hub, i, child) {
+		child_vid = le16_to_cpu(child->descriptor.idVendor);
+		child_pid = le16_to_cpu(child->descriptor.idProduct);
+
+		/*
+		 * If one of the devices below is present attached as a sibling of 
+		 * the touch controller then  this is a newer IBM 4820 monitor that 
+		 * does not need the IBM-requested workaround if fw level is
+		 * 0x010d - aka 'M'.
+		 * No other HW can have this combination.
+		 */
+		if (child_vid==0x04b3) {
+			switch (child_pid) {
+			case 0x4676: /* 4820 21x Video */
+			case 0x4677: /* 4820 51x Video */
+			case 0x4678: /* 4820 2Lx Video */
+			case 0x4679: /* 4820 5Lx Video */
+				return false;
+			}
+		}
+	}
+	return true;
+}
+
+static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct elo_priv *priv;
+	int ret;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	INIT_DELAYED_WORK(&priv->work, elo_work);
+	priv->usbdev = interface_to_usbdev(to_usb_interface(hdev->dev.parent));
+
+	hid_set_drvdata(hdev, priv);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	if (elo_broken_firmware(priv->usbdev)) {
+		hid_info(hdev, "broken firmware found, installing workaround\n");
+		queue_delayed_work(wq, &priv->work, ELO_PERIODIC_READ_INTERVAL);
+	}
+
+	return 0;
+err_free:
+	kfree(priv);
+	return ret;
+}
+
+static void elo_remove(struct hid_device *hdev)
+{
+	struct elo_priv *priv = hid_get_drvdata(hdev);
+
+	hid_hw_stop(hdev);
+	cancel_delayed_work_sync(&priv->work);
+	kfree(priv);
+}
+
+static const struct hid_device_id elo_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009), },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030), },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, elo_devices);
+
+static struct hid_driver elo_driver = {
+	.name = "elo",
+	.id_table = elo_devices,
+	.probe = elo_probe,
+	.remove = elo_remove,
+	.raw_event = elo_raw_event,
+	.input_configured = elo_input_configured,
+};
+
+static int __init elo_driver_init(void)
+{
+	int ret;
+
+	wq = create_singlethread_workqueue("elousb");
+	if (!wq)
+		return -ENOMEM;
+
+	ret = hid_register_driver(&elo_driver);
+	if (ret)
+		destroy_workqueue(wq);
+
+	return ret;
+}
+module_init(elo_driver_init);
+
+static void __exit elo_driver_exit(void)
+{
+	hid_unregister_driver(&elo_driver);
+	destroy_workqueue(wq);
+}
+module_exit(elo_driver_exit);
+
+MODULE_AUTHOR("Jiri Slaby <jslaby@suse.cz>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-emsff.c b/drivers/hid/hid-emsff.c
new file mode 100644
index 0000000..d82d75b
--- /dev/null
+++ b/drivers/hid/hid-emsff.c
@@ -0,0 +1,154 @@
+/*
+ *  Force feedback support for EMS Trio Linker Plus II
+ *
+ *  Copyright (c) 2010 Ignaz Forster <ignaz.forster@gmx.de>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+struct emsff_device {
+	struct hid_report *report;
+};
+
+static int emsff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct emsff_device *emsff = data;
+	int weak, strong;
+
+	weak = effect->u.rumble.weak_magnitude;
+	strong = effect->u.rumble.strong_magnitude;
+
+	dbg_hid("called with 0x%04x 0x%04x\n", strong, weak);
+
+	weak = weak * 0xff / 0xffff;
+	strong = strong * 0xff / 0xffff;
+
+	emsff->report->field[0]->value[1] = weak;
+	emsff->report->field[0]->value[2] = strong;
+
+	dbg_hid("running with 0x%02x 0x%02x\n", strong, weak);
+	hid_hw_request(hid, emsff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int emsff_init(struct hid_device *hid)
+{
+	struct emsff_device *emsff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_first_entry(&hid->inputs,
+						struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	report = list_first_entry(report_list, struct hid_report, list);
+	if (report->maxfield < 1) {
+		hid_err(hid, "no fields in the report\n");
+		return -ENODEV;
+	}
+
+	if (report->field[0]->report_count < 7) {
+		hid_err(hid, "not enough values in the field\n");
+		return -ENODEV;
+	}
+
+	emsff = kzalloc(sizeof(struct emsff_device), GFP_KERNEL);
+	if (!emsff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, emsff, emsff_play);
+	if (error) {
+		kfree(emsff);
+		return error;
+	}
+
+	emsff->report = report;
+	emsff->report->field[0]->value[0] = 0x01;
+	emsff->report->field[0]->value[1] = 0x00;
+	emsff->report->field[0]->value[2] = 0x00;
+	emsff->report->field[0]->value[3] = 0x00;
+	emsff->report->field[0]->value[4] = 0x00;
+	emsff->report->field[0]->value[5] = 0x00;
+	emsff->report->field[0]->value[6] = 0x00;
+	hid_hw_request(hid, emsff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "force feedback for EMS based devices by Ignaz Forster <ignaz.forster@gmx.de>\n");
+
+	return 0;
+}
+
+static int ems_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	ret = emsff_init(hdev);
+	if (ret) {
+		dev_err(&hdev->dev, "force feedback init failed\n");
+		hid_hw_stop(hdev);
+		goto err;
+	}
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id ems_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ems_devices);
+
+static struct hid_driver ems_driver = {
+	.name = "hkems",
+	.id_table = ems_devices,
+	.probe = ems_probe,
+};
+module_hid_driver(ems_driver);
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/hid/hid-ezkey.c b/drivers/hid/hid-ezkey.c
new file mode 100644
index 0000000..212ac6b
--- /dev/null
+++ b/drivers/hid/hid-ezkey.c
@@ -0,0 +1,81 @@
+/*
+ *  HID driver for some ezkey "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define ez_map_rel(c)	hid_map_usage(hi, usage, bit, max, EV_REL, (c))
+#define ez_map_key(c)	hid_map_usage(hi, usage, bit, max, EV_KEY, (c))
+
+static int ez_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x230: ez_map_key(BTN_MOUSE);	break;
+	case 0x231: ez_map_rel(REL_WHEEL);	break;
+	/*
+	 * this keyboard has a scrollwheel implemented in
+	 * totally broken way. We map this usage temporarily
+	 * to HWHEEL and handle it in the event quirk handler
+	 */
+	case 0x232: ez_map_rel(REL_HWHEEL);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int ez_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type)
+		return 0;
+
+	/* handle the temporary quirky mapping to HWHEEL */
+	if (usage->type == EV_REL && usage->code == REL_HWHEEL) {
+		struct input_dev *input = field->hidinput->input;
+		input_event(input, usage->type, REL_WHEEL, -value);
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id ez_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ez_devices);
+
+static struct hid_driver ez_driver = {
+	.name = "ezkey",
+	.id_table = ez_devices,
+	.input_mapping = ez_input_mapping,
+	.event = ez_event,
+};
+module_hid_driver(ez_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gaff.c b/drivers/hid/hid-gaff.c
new file mode 100644
index 0000000..2d8cead
--- /dev/null
+++ b/drivers/hid/hid-gaff.c
@@ -0,0 +1,179 @@
+/*
+ *  Force feedback support for GreenAsia (Product ID 0x12) based devices
+ *
+ *  The devices are distributed under various names and the same USB device ID
+ *  can be used in many game controllers.
+ *
+ *
+ *  0e8f:0012 "GreenAsia Inc.    USB Joystick     "
+ *   - tested with MANTA Warior MM816 and SpeedLink Strike2 SL-6635.
+ *
+ *  Copyright (c) 2008 Lukasz Lubojanski <lukasz@lubojanski.info>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+#ifdef CONFIG_GREENASIA_FF
+
+struct gaff_device {
+	struct hid_report *report;
+};
+
+static int hid_gaff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct gaff_device *gaff = data;
+	int left, right;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+
+	dbg_hid("called with 0x%04x 0x%04x", left, right);
+
+	left = left * 0xfe / 0xffff;
+	right = right * 0xfe / 0xffff;
+
+	gaff->report->field[0]->value[0] = 0x51;
+	gaff->report->field[0]->value[1] = 0x0;
+	gaff->report->field[0]->value[2] = right;
+	gaff->report->field[0]->value[3] = 0;
+	gaff->report->field[0]->value[4] = left;
+	gaff->report->field[0]->value[5] = 0;
+	dbg_hid("running with 0x%02x 0x%02x", left, right);
+	hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
+
+	gaff->report->field[0]->value[0] = 0xfa;
+	gaff->report->field[0]->value[1] = 0xfe;
+	gaff->report->field[0]->value[2] = 0x0;
+	gaff->report->field[0]->value[4] = 0x0;
+
+	hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int gaff_init(struct hid_device *hid)
+{
+	struct gaff_device *gaff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct list_head *report_ptr = report_list;
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	report_ptr = report_ptr->next;
+
+	report = list_entry(report_ptr, struct hid_report, list);
+	if (report->maxfield < 1) {
+		hid_err(hid, "no fields in the report\n");
+		return -ENODEV;
+	}
+
+	if (report->field[0]->report_count < 6) {
+		hid_err(hid, "not enough values in the field\n");
+		return -ENODEV;
+	}
+
+	gaff = kzalloc(sizeof(struct gaff_device), GFP_KERNEL);
+	if (!gaff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, gaff, hid_gaff_play);
+	if (error) {
+		kfree(gaff);
+		return error;
+	}
+
+	gaff->report = report;
+	gaff->report->field[0]->value[0] = 0x51;
+	gaff->report->field[0]->value[1] = 0x00;
+	gaff->report->field[0]->value[2] = 0x00;
+	gaff->report->field[0]->value[3] = 0x00;
+	hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
+
+	gaff->report->field[0]->value[0] = 0xfa;
+	gaff->report->field[0]->value[1] = 0xfe;
+
+	hid_hw_request(hid, gaff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "Force Feedback for GreenAsia 0x12 devices by Lukasz Lubojanski <lukasz@lubojanski.info>\n");
+
+	return 0;
+}
+#else
+static inline int gaff_init(struct hid_device *hdev)
+{
+	return 0;
+}
+#endif
+
+static int ga_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	dev_dbg(&hdev->dev, "Greenasia HID hardware probe...");
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	gaff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id ga_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012),  },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ga_devices);
+
+static struct hid_driver ga_driver = {
+	.name = "greenasia",
+	.id_table = ga_devices,
+	.probe = ga_probe,
+};
+module_hid_driver(ga_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gembird.c b/drivers/hid/hid-gembird.c
new file mode 100644
index 0000000..e55e519
--- /dev/null
+++ b/drivers/hid/hid-gembird.c
@@ -0,0 +1,116 @@
+/*
+ *  HID driver for Gembird Joypad, "PC Game Controller"
+ *
+ *  Copyright (c) 2015 Red Hat, Inc
+ *  Copyright (c) 2015 Benjamin Tissoires
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#define GEMBIRD_START_FAULTY_RDESC	8
+
+static const __u8 gembird_jpd_faulty_rdesc[] = {
+	0x75, 0x08,			/*   Report Size (8)		*/
+	0x95, 0x05,			/*   Report Count (5)		*/
+	0x15, 0x00,			/*   Logical Minimum (0)	*/
+	0x26, 0xff, 0x00,		/*   Logical Maximum (255)	*/
+	0x35, 0x00,			/*   Physical Minimum (0)	*/
+	0x46, 0xff, 0x00,		/*   Physical Maximum (255)	*/
+	0x09, 0x30,			/*   Usage (X)			*/
+	0x09, 0x31,			/*   Usage (Y)			*/
+	0x09, 0x32,			/*   Usage (Z)			*/
+	0x09, 0x32,			/*   Usage (Z)			*/
+	0x09, 0x35,			/*   Usage (Rz)			*/
+	0x81, 0x02,			/*   Input (Data,Var,Abs)	*/
+};
+
+/*
+ * we fix the report descriptor by:
+ * - marking the first Z axis as constant (so it is ignored by HID)
+ * - assign the original second Z to Rx
+ * - assign the original Rz to Ry
+ */
+static const __u8 gembird_jpd_fixed_rdesc[] = {
+	0x75, 0x08,			/*   Report Size (8)		*/
+	0x95, 0x02,			/*   Report Count (2)		*/
+	0x15, 0x00,			/*   Logical Minimum (0)	*/
+	0x26, 0xff, 0x00,		/*   Logical Maximum (255)	*/
+	0x35, 0x00,			/*   Physical Minimum (0)	*/
+	0x46, 0xff, 0x00,		/*   Physical Maximum (255)	*/
+	0x09, 0x30,			/*   Usage (X)			*/
+	0x09, 0x31,			/*   Usage (Y)			*/
+	0x81, 0x02,			/*   Input (Data,Var,Abs)	*/
+	0x95, 0x01,			/*   Report Count (1)		*/
+	0x09, 0x32,			/*   Usage (Z)			*/
+	0x81, 0x01,			/*   Input (Cnst,Arr,Abs)	*/
+	0x95, 0x02,			/*   Report Count (2)		*/
+	0x09, 0x33,			/*   Usage (Rx)			*/
+	0x09, 0x34,			/*   Usage (Ry)			*/
+	0x81, 0x02,			/*   Input (Data,Var,Abs)	*/
+};
+
+static __u8 *gembird_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	__u8 *new_rdesc;
+	/* delta_size is > 0 */
+	size_t delta_size = sizeof(gembird_jpd_fixed_rdesc) -
+			    sizeof(gembird_jpd_faulty_rdesc);
+	size_t new_size = *rsize + delta_size;
+
+	if (*rsize >= 31 && !memcmp(&rdesc[GEMBIRD_START_FAULTY_RDESC],
+				    gembird_jpd_faulty_rdesc,
+				    sizeof(gembird_jpd_faulty_rdesc))) {
+		new_rdesc = devm_kzalloc(&hdev->dev, new_size, GFP_KERNEL);
+		if (new_rdesc == NULL)
+			return rdesc;
+
+		dev_info(&hdev->dev,
+			 "fixing Gembird JPD-DualForce 2 report descriptor.\n");
+
+		/* start by copying the end of the rdesc */
+		memcpy(new_rdesc + delta_size, rdesc, *rsize);
+
+		/* add the correct beginning */
+		memcpy(new_rdesc, rdesc, GEMBIRD_START_FAULTY_RDESC);
+
+		/* replace the faulty part with the fixed one */
+		memcpy(new_rdesc + GEMBIRD_START_FAULTY_RDESC,
+		       gembird_jpd_fixed_rdesc,
+		       sizeof(gembird_jpd_fixed_rdesc));
+
+		*rsize = new_size;
+		rdesc = new_rdesc;
+	}
+
+	return rdesc;
+}
+
+static const struct hid_device_id gembird_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GEMBIRD,
+			 USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, gembird_devices);
+
+static struct hid_driver gembird_driver = {
+	.name = "gembird",
+	.id_table = gembird_devices,
+	.report_fixup = gembird_report_fixup,
+};
+module_hid_driver(gembird_driver);
+
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_DESCRIPTION("HID Gembird joypad driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
new file mode 100644
index 0000000..3b6eccb
--- /dev/null
+++ b/drivers/hid/hid-generic.c
@@ -0,0 +1,89 @@
+/*
+ *  HID support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2007-2008 Oliver Neukum
+ *  Copyright (c) 2006-2012 Jiri Kosina
+ *  Copyright (c) 2012 Henrik Rydberg
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+
+#include <linux/hid.h>
+
+static struct hid_driver hid_generic;
+
+static int __check_hid_generic(struct device_driver *drv, void *data)
+{
+	struct hid_driver *hdrv = to_hid_driver(drv);
+	struct hid_device *hdev = data;
+
+	if (hdrv == &hid_generic)
+		return 0;
+
+	return hid_match_device(hdev, hdrv) != NULL;
+}
+
+static bool hid_generic_match(struct hid_device *hdev,
+			      bool ignore_special_driver)
+{
+	if (ignore_special_driver)
+		return true;
+
+	if (hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)
+		return false;
+
+	/*
+	 * If any other driver wants the device, leave the device to this other
+	 * driver.
+	 */
+	if (bus_for_each_drv(&hid_bus_type, NULL, hdev, __check_hid_generic))
+		return false;
+
+	return true;
+}
+
+static int hid_generic_probe(struct hid_device *hdev,
+			     const struct hid_device_id *id)
+{
+	int ret;
+
+	hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+
+	return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+}
+
+static const struct hid_device_id hid_table[] = {
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, hid_table);
+
+static struct hid_driver hid_generic = {
+	.name = "hid-generic",
+	.id_table = hid_table,
+	.match = hid_generic_match,
+	.probe = hid_generic_probe,
+};
+module_hid_driver(hid_generic);
+
+MODULE_AUTHOR("Henrik Rydberg");
+MODULE_DESCRIPTION("HID generic driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gfrm.c b/drivers/hid/hid-gfrm.c
new file mode 100644
index 0000000..cf477f8
--- /dev/null
+++ b/drivers/hid/hid-gfrm.c
@@ -0,0 +1,159 @@
+/*
+ * HID driver for Google Fiber TV Box remote controls
+ *
+ * Copyright (c) 2014-2015 Google Inc.
+ *
+ * Author: Petri Gynther <pgynther@google.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define GFRM100  1  /* Google Fiber GFRM100 (Bluetooth classic) */
+#define GFRM200  2  /* Google Fiber GFRM200 (Bluetooth LE) */
+
+#define GFRM100_SEARCH_KEY_REPORT_ID   0xF7
+#define GFRM100_SEARCH_KEY_DOWN        0x0
+#define GFRM100_SEARCH_KEY_AUDIO_DATA  0x1
+#define GFRM100_SEARCH_KEY_UP          0x2
+
+static u8 search_key_dn[3] = {0x40, 0x21, 0x02};
+static u8 search_key_up[3] = {0x40, 0x00, 0x00};
+
+static int gfrm_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev);
+
+	if (hdev_type == GFRM100) {
+		if (usage->hid == (HID_UP_CONSUMER | 0x4)) {
+			/* Consumer.0004 -> KEY_INFO */
+			hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_INFO);
+			return 1;
+		}
+
+		if (usage->hid == (HID_UP_CONSUMER | 0x41)) {
+			/* Consumer.0041 -> KEY_OK */
+			hid_map_usage_clear(hi, usage, bit, max, EV_KEY, KEY_OK);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	unsigned long hdev_type = (unsigned long) hid_get_drvdata(hdev);
+	int ret = 0;
+
+	if (hdev_type != GFRM100)
+		return 0;
+
+	if (size < 2 || data[0] != GFRM100_SEARCH_KEY_REPORT_ID)
+		return 0;
+
+	/*
+	 * Convert GFRM100 Search key reports into Consumer.0221 (Key.Search)
+	 * reports. Ignore audio data.
+	 */
+	switch (data[1]) {
+	case GFRM100_SEARCH_KEY_DOWN:
+		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
+					   sizeof(search_key_dn), 1);
+		break;
+
+	case GFRM100_SEARCH_KEY_AUDIO_DATA:
+		break;
+
+	case GFRM100_SEARCH_KEY_UP:
+		ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
+					   sizeof(search_key_up), 1);
+		break;
+
+	default:
+		break;
+	}
+
+	return (ret < 0) ? ret : -1;
+}
+
+static int gfrm_input_configured(struct hid_device *hid, struct hid_input *hidinput)
+{
+	/*
+	 * Enable software autorepeat with:
+	 * - repeat delay: 400 msec
+	 * - repeat period: 100 msec
+	 */
+	input_enable_softrepeat(hidinput->input, 400, 100);
+	return 0;
+}
+
+static int gfrm_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	hid_set_drvdata(hdev, (void *) id->driver_data);
+
+	ret = hid_parse(hdev);
+	if (ret)
+		goto done;
+
+	if (id->driver_data == GFRM100) {
+		/*
+		 * GFRM100 HID Report Descriptor does not describe the Search
+		 * key reports. Thus, we need to add it manually here, so that
+		 * those reports reach gfrm_raw_event() from hid_input_report().
+		 */
+		if (!hid_register_report(hdev, HID_INPUT_REPORT,
+					 GFRM100_SEARCH_KEY_REPORT_ID, 0)) {
+			ret = -ENOMEM;
+			goto done;
+		}
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+done:
+	return ret;
+}
+
+static void gfrm_remove(struct hid_device *hdev)
+{
+	hid_hw_stop(hdev);
+	hid_set_drvdata(hdev, NULL);
+}
+
+static const struct hid_device_id gfrm_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(0x58, 0x2000),
+		.driver_data = GFRM100 },
+	{ HID_BLUETOOTH_DEVICE(0x471, 0x2210),
+		.driver_data = GFRM200 },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, gfrm_devices);
+
+static struct hid_driver gfrm_driver = {
+	.name = "gfrm",
+	.id_table = gfrm_devices,
+	.probe = gfrm_probe,
+	.remove = gfrm_remove,
+	.input_mapping = gfrm_input_mapping,
+	.raw_event = gfrm_raw_event,
+	.input_configured = gfrm_input_configured,
+};
+
+module_hid_driver(gfrm_driver);
+
+MODULE_AUTHOR("Petri Gynther <pgynther@google.com>");
+MODULE_DESCRIPTION("Google Fiber TV Box remote control driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c
new file mode 100644
index 0000000..6bf4da7
--- /dev/null
+++ b/drivers/hid/hid-google-hammer.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  HID driver for Google Hammer device.
+ *
+ *  Copyright (c) 2017 Google Inc.
+ *  Author: Wei-Ning Huang <wnhuang@google.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define MAX_BRIGHTNESS 100
+
+/* HID usage for keyboard backlight (Alphanumeric display brightness) */
+#define HID_AD_BRIGHTNESS 0x00140046
+
+struct hammer_kbd_leds {
+	struct led_classdev cdev;
+	struct hid_device *hdev;
+	u8 buf[2] ____cacheline_aligned;
+};
+
+static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev,
+		enum led_brightness br)
+{
+	struct hammer_kbd_leds *led = container_of(cdev,
+						   struct hammer_kbd_leds,
+						   cdev);
+	int ret;
+
+	led->buf[0] = 0;
+	led->buf[1] = br;
+
+	/*
+	 * Request USB HID device to be in Full On mode, so that sending
+	 * hardware output report and hardware raw request won't fail.
+	 */
+	ret = hid_hw_power(led->hdev, PM_HINT_FULLON);
+	if (ret < 0) {
+		hid_err(led->hdev, "failed: device not resumed %d\n", ret);
+		return ret;
+	}
+
+	ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf));
+	if (ret == -ENOSYS)
+		ret = hid_hw_raw_request(led->hdev, 0, led->buf,
+					 sizeof(led->buf),
+					 HID_OUTPUT_REPORT,
+					 HID_REQ_SET_REPORT);
+	if (ret < 0)
+		hid_err(led->hdev, "failed to set keyboard backlight: %d\n",
+			ret);
+
+	/* Request USB HID device back to Normal Mode. */
+	hid_hw_power(led->hdev, PM_HINT_NORMAL);
+
+	return ret;
+}
+
+static int hammer_register_leds(struct hid_device *hdev)
+{
+	struct hammer_kbd_leds *kbd_backlight;
+
+	kbd_backlight = devm_kzalloc(&hdev->dev,
+				     sizeof(*kbd_backlight),
+				     GFP_KERNEL);
+	if (!kbd_backlight)
+		return -ENOMEM;
+
+	kbd_backlight->hdev = hdev;
+	kbd_backlight->cdev.name = "hammer::kbd_backlight";
+	kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS;
+	kbd_backlight->cdev.brightness_set_blocking =
+		hammer_kbd_brightness_set_blocking;
+	kbd_backlight->cdev.flags = LED_HW_PLUGGABLE;
+
+	/* Set backlight to 0% initially. */
+	hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0);
+
+	return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
+}
+
+static int hammer_input_configured(struct hid_device *hdev,
+				   struct hid_input *hi)
+{
+	struct list_head *report_list =
+		&hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report;
+
+	if (list_empty(report_list))
+		return 0;
+
+	report = list_first_entry(report_list, struct hid_report, list);
+
+	if (report->maxfield == 1 &&
+	    report->field[0]->application == HID_GD_KEYBOARD &&
+	    report->field[0]->maxusage == 1 &&
+	    report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) {
+		int err = hammer_register_leds(hdev);
+
+		if (err)
+			hid_warn(hdev,
+				"Failed to register keyboard backlight: %d\n",
+				err);
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id hammer_devices[] = {
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, hammer_devices);
+
+static struct hid_driver hammer_driver = {
+	.name = "hammer",
+	.id_table = hammer_devices,
+	.input_configured = hammer_input_configured,
+};
+module_hid_driver(hammer_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gt683r.c b/drivers/hid/hid-gt683r.c
new file mode 100644
index 0000000..a298fbd
--- /dev/null
+++ b/drivers/hid/hid-gt683r.c
@@ -0,0 +1,319 @@
+/*
+ * MSI GT683R led driver
+ *
+ * Copyright (c) 2014 Janne Kanniainen <janne.kanniainen@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define GT683R_BUFFER_SIZE			8
+
+/*
+ * GT683R_LED_OFF: all LEDs are off
+ * GT683R_LED_AUDIO: LEDs brightness depends on sound level
+ * GT683R_LED_BREATHING: LEDs brightness varies at human breathing rate
+ * GT683R_LED_NORMAL: LEDs are fully on when enabled
+ */
+enum gt683r_led_mode {
+	GT683R_LED_OFF = 0,
+	GT683R_LED_AUDIO = 2,
+	GT683R_LED_BREATHING = 3,
+	GT683R_LED_NORMAL = 5
+};
+
+enum gt683r_panels {
+	GT683R_LED_BACK = 0,
+	GT683R_LED_SIDE = 1,
+	GT683R_LED_FRONT = 2,
+	GT683R_LED_COUNT,
+};
+
+static const char * const gt683r_panel_names[] = {
+	"back",
+	"side",
+	"front",
+};
+
+struct gt683r_led {
+	struct hid_device *hdev;
+	struct led_classdev led_devs[GT683R_LED_COUNT];
+	struct mutex lock;
+	struct work_struct work;
+	enum led_brightness brightnesses[GT683R_LED_COUNT];
+	enum gt683r_led_mode mode;
+};
+
+static const struct hid_device_id gt683r_led_id[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+	{ }
+};
+
+static void gt683r_brightness_set(struct led_classdev *led_cdev,
+				enum led_brightness brightness)
+{
+	int i;
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct gt683r_led *led = hid_get_drvdata(hdev);
+
+	for (i = 0; i < GT683R_LED_COUNT; i++) {
+		if (led_cdev == &led->led_devs[i])
+			break;
+	}
+
+	if (i < GT683R_LED_COUNT) {
+		led->brightnesses[i] = brightness;
+		schedule_work(&led->work);
+	}
+}
+
+static ssize_t mode_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	u8 sysfs_mode;
+	struct hid_device *hdev = to_hid_device(dev->parent);
+	struct gt683r_led *led = hid_get_drvdata(hdev);
+
+	if (led->mode == GT683R_LED_NORMAL)
+		sysfs_mode = 0;
+	else if (led->mode == GT683R_LED_AUDIO)
+		sysfs_mode = 1;
+	else
+		sysfs_mode = 2;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", sysfs_mode);
+}
+
+static ssize_t mode_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	u8 sysfs_mode;
+	struct hid_device *hdev = to_hid_device(dev->parent);
+	struct gt683r_led *led = hid_get_drvdata(hdev);
+
+
+	if (kstrtou8(buf, 10, &sysfs_mode) || sysfs_mode > 2)
+		return -EINVAL;
+
+	mutex_lock(&led->lock);
+
+	if (sysfs_mode == 0)
+		led->mode = GT683R_LED_NORMAL;
+	else if (sysfs_mode == 1)
+		led->mode = GT683R_LED_AUDIO;
+	else
+		led->mode = GT683R_LED_BREATHING;
+
+	mutex_unlock(&led->lock);
+	schedule_work(&led->work);
+
+	return count;
+}
+
+static int gt683r_led_snd_msg(struct gt683r_led *led, u8 *msg)
+{
+	int ret;
+
+	ret = hid_hw_raw_request(led->hdev, msg[0], msg, GT683R_BUFFER_SIZE,
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	if (ret != GT683R_BUFFER_SIZE) {
+		hid_err(led->hdev,
+			"failed to send set report request: %i\n", ret);
+		if (ret < 0)
+			return ret;
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int gt683r_leds_set(struct gt683r_led *led, u8 leds)
+{
+	int ret;
+	u8 *buffer;
+
+	buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x01;
+	buffer[1] = 0x02;
+	buffer[2] = 0x30;
+	buffer[3] = leds;
+	ret = gt683r_led_snd_msg(led, buffer);
+
+	kfree(buffer);
+	return ret;
+}
+
+static int gt683r_mode_set(struct gt683r_led *led, u8 mode)
+{
+	int ret;
+	u8 *buffer;
+
+	buffer = kzalloc(GT683R_BUFFER_SIZE, GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	buffer[0] = 0x01;
+	buffer[1] = 0x02;
+	buffer[2] = 0x20;
+	buffer[3] = mode;
+	buffer[4] = 0x01;
+	ret = gt683r_led_snd_msg(led, buffer);
+
+	kfree(buffer);
+	return ret;
+}
+
+static void gt683r_led_work(struct work_struct *work)
+{
+	int i;
+	u8 leds = 0;
+	u8 mode;
+	struct gt683r_led *led = container_of(work, struct gt683r_led, work);
+
+	mutex_lock(&led->lock);
+
+	for (i = 0; i < GT683R_LED_COUNT; i++) {
+		if (led->brightnesses[i])
+			leds |= BIT(i);
+	}
+
+	if (gt683r_leds_set(led, leds))
+		goto fail;
+
+	if (leds)
+		mode = led->mode;
+	else
+		mode = GT683R_LED_OFF;
+
+	gt683r_mode_set(led, mode);
+fail:
+	mutex_unlock(&led->lock);
+}
+
+static DEVICE_ATTR_RW(mode);
+
+static struct attribute *gt683r_led_attrs[] = {
+	&dev_attr_mode.attr,
+	NULL
+};
+
+static const struct attribute_group gt683r_led_group = {
+	.name = "gt683r",
+	.attrs = gt683r_led_attrs,
+};
+
+static const struct attribute_group *gt683r_led_groups[] = {
+	&gt683r_led_group,
+	NULL
+};
+
+static int gt683r_led_probe(struct hid_device *hdev,
+			const struct hid_device_id *id)
+{
+	int i;
+	int ret;
+	int name_sz;
+	char *name;
+	struct gt683r_led *led;
+
+	led = devm_kzalloc(&hdev->dev, sizeof(*led), GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	mutex_init(&led->lock);
+	INIT_WORK(&led->work, gt683r_led_work);
+
+	led->mode = GT683R_LED_NORMAL;
+	led->hdev = hdev;
+	hid_set_drvdata(hdev, led);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid parsing failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	for (i = 0; i < GT683R_LED_COUNT; i++) {
+		name_sz = strlen(dev_name(&hdev->dev)) +
+				strlen(gt683r_panel_names[i]) + 3;
+
+		name = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+		if (!name) {
+			ret = -ENOMEM;
+			goto fail;
+		}
+
+		snprintf(name, name_sz, "%s::%s",
+				dev_name(&hdev->dev), gt683r_panel_names[i]);
+		led->led_devs[i].name = name;
+		led->led_devs[i].max_brightness = 1;
+		led->led_devs[i].brightness_set = gt683r_brightness_set;
+		led->led_devs[i].groups = gt683r_led_groups;
+
+		ret = led_classdev_register(&hdev->dev, &led->led_devs[i]);
+		if (ret) {
+			hid_err(hdev, "could not register led device\n");
+			goto fail;
+		}
+	}
+
+	return 0;
+
+fail:
+	for (i = i - 1; i >= 0; i--)
+		led_classdev_unregister(&led->led_devs[i]);
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static void gt683r_led_remove(struct hid_device *hdev)
+{
+	int i;
+	struct gt683r_led *led = hid_get_drvdata(hdev);
+
+	for (i = 0; i < GT683R_LED_COUNT; i++)
+		led_classdev_unregister(&led->led_devs[i]);
+	flush_work(&led->work);
+	hid_hw_stop(hdev);
+}
+
+static struct hid_driver gt683r_led_driver = {
+	.probe = gt683r_led_probe,
+	.remove = gt683r_led_remove,
+	.name = "gt683r_led",
+	.id_table = gt683r_led_id,
+};
+
+module_hid_driver(gt683r_led_driver);
+
+MODULE_AUTHOR("Janne Kanniainen");
+MODULE_DESCRIPTION("MSI GT683R led driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-gyration.c b/drivers/hid/hid-gyration.c
new file mode 100644
index 0000000..288d61c
--- /dev/null
+++ b/drivers/hid/hid-gyration.c
@@ -0,0 +1,93 @@
+/*
+ *  HID driver for some gyration "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2008 Jiri Slaby
+ *  Copyright (c) 2006-2008 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define gy_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int gyration_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+		return 0;
+
+	set_bit(EV_REP, hi->input->evbit);
+	switch (usage->hid & HID_USAGE) {
+	/* Reported on Gyration MCE Remote */
+	case 0x00d: gy_map_key_clear(KEY_HOME);		break;
+	case 0x024: gy_map_key_clear(KEY_DVD);		break;
+	case 0x025: gy_map_key_clear(KEY_PVR);		break;
+	case 0x046: gy_map_key_clear(KEY_MEDIA);	break;
+	case 0x047: gy_map_key_clear(KEY_MP3);		break;
+	case 0x048: gy_map_key_clear(KEY_MEDIA);	break;
+	case 0x049: gy_map_key_clear(KEY_CAMERA);	break;
+	case 0x04a: gy_map_key_clear(KEY_VIDEO);	break;
+	case 0x05a: gy_map_key_clear(KEY_TEXT);		break;
+	case 0x05b: gy_map_key_clear(KEY_RED);		break;
+	case 0x05c: gy_map_key_clear(KEY_GREEN);	break;
+	case 0x05d: gy_map_key_clear(KEY_YELLOW);	break;
+	case 0x05e: gy_map_key_clear(KEY_BLUE);		break;
+
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int gyration_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput)
+		return 0;
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK &&
+			(usage->hid & 0xff) == 0x82) {
+		struct input_dev *input = field->hidinput->input;
+		input_event(input, usage->type, usage->code, 1);
+		input_sync(input);
+		input_event(input, usage->type, usage->code, 0);
+		input_sync(input);
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id gyration_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE) },
+	{ 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) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, gyration_devices);
+
+static struct hid_driver gyration_driver = {
+	.name = "gyration",
+	.id_table = gyration_devices,
+	.input_mapping = gyration_input_mapping,
+	.event = gyration_event,
+};
+module_hid_driver(gyration_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-holtek-kbd.c b/drivers/hid/hid-holtek-kbd.c
new file mode 100644
index 0000000..6e1a4a4
--- /dev/null
+++ b/drivers/hid/hid-holtek-kbd.c
@@ -0,0 +1,172 @@
+/*
+ * 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
+};
+module_hid_driver(holtek_kbd_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-holtek-mouse.c b/drivers/hid/hid-holtek-mouse.c
new file mode 100644
index 0000000..78b3a0c
--- /dev/null
+++ b/drivers/hid/hid-holtek-mouse.c
@@ -0,0 +1,92 @@
+/*
+ * HID driver for Holtek gaming mice
+ * Copyright (c) 2013 Christian Ohm
+ * Heavily inspired by various other HID drivers that adjust the report
+ * descriptor.
+*/
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+/*
+ * The report descriptor of some Holtek based gaming mice 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.
+ *
+ * This driver fixes the report descriptor for:
+ * - USB ID 04d9:a067, sold as Sharkoon Drakonia and Perixx MX-2000
+ * - USB ID 04d9:a04a, sold as Tracer Sniper TRM-503, NOVA Gaming Slider X200
+ *   and Zalman ZM-GM1
+ * - USB ID 04d9:a081, sold as SHARKOON DarkGlider Gaming mouse
+ * - USB ID 04d9:a072, sold as LEETGION Hellion Gaming Mouse
+ * - USB ID 04d9:a0c2, sold as ETEKCITY Scroll T-140 Gaming Mouse
+ */
+
+static __u8 *holtek_mouse_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) {
+		/* Change usage maximum and logical maximum from 0x7fff to
+		 * 0x2fff, so they don't exceed HID_MAX_USAGES */
+		switch (hdev->product) {
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067:
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072:
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2:
+			if (*rsize >= 122 && rdesc[115] == 0xff && rdesc[116] == 0x7f
+					&& rdesc[120] == 0xff && rdesc[121] == 0x7f) {
+				hid_info(hdev, "Fixing up report descriptor\n");
+				rdesc[116] = rdesc[121] = 0x2f;
+			}
+			break;
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A:
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070:
+		case USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081:
+			if (*rsize >= 113 && rdesc[106] == 0xff && rdesc[107] == 0x7f
+					&& rdesc[111] == 0xff && rdesc[112] == 0x7f) {
+				hid_info(hdev, "Fixing up report descriptor\n");
+				rdesc[107] = rdesc[112] = 0x2f;
+			}
+			break;
+		}
+
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id holtek_mouse_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067) },
+        { HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT,
+			USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, holtek_mouse_devices);
+
+static struct hid_driver holtek_mouse_driver = {
+	.name = "holtek_mouse",
+	.id_table = holtek_mouse_devices,
+	.report_fixup = holtek_mouse_report_fixup,
+};
+
+module_hid_driver(holtek_mouse_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-holtekff.c b/drivers/hid/hid-holtekff.c
new file mode 100644
index 0000000..edc0f64
--- /dev/null
+++ b/drivers/hid/hid-holtekff.c
@@ -0,0 +1,225 @@
+/*
+ *  Force feedback support for Holtek On Line Grip based gamepads
+ *
+ *  These include at least a Brazilian "Clone Joypad Super Power Fire"
+ *  which uses vendor ID 0x1241 and identifies as "HOLTEK On Line Grip".
+ *
+ *  Copyright (c) 2011 Anssi Hannula <anssi.hannula@iki.fi>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+#ifdef CONFIG_HOLTEK_FF
+
+/*
+ * These commands and parameters are currently known:
+ *
+ * byte 0: command id:
+ * 	01  set effect parameters
+ * 	02  play specified effect
+ * 	03  stop specified effect
+ * 	04  stop all effects
+ * 	06  stop all effects
+ * 	(the difference between 04 and 06 isn't known; win driver
+ * 	 sends 06,04 on application init, and 06 otherwise)
+ * 
+ * Commands 01 and 02 need to be sent as pairs, i.e. you need to send 01
+ * before each 02.
+ *
+ * The rest of the bytes are parameters. Command 01 takes all of them, and
+ * commands 02,03 take only the effect id.
+ *
+ * byte 1:
+ *	bits 0-3: effect id:
+ * 		1: very strong rumble
+ * 		2: periodic rumble, short intervals
+ * 		3: very strong rumble
+ * 		4: periodic rumble, long intervals
+ * 		5: weak periodic rumble, long intervals
+ * 		6: weak periodic rumble, short intervals
+ * 		7: periodic rumble, short intervals
+ * 		8: strong periodic rumble, short intervals
+ * 		9: very strong rumble
+ * 		a: causes an error
+ * 		b: very strong periodic rumble, very short intervals
+ * 		c-f: nothing
+ *	bit 6: right (weak) motor enabled
+ *	bit 7: left (strong) motor enabled
+ *
+ * bytes 2-3:  time in milliseconds, big-endian
+ * bytes 5-6:  unknown (win driver seems to use at least 10e0 with effect 1
+ * 		       and 0014 with effect 6)
+ * byte 7:
+ *	bits 0-3: effect magnitude
+ */
+
+#define HOLTEKFF_MSG_LENGTH     7
+
+static const u8 start_effect_1[] = { 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static const u8 stop_all4[] =	   { 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static const u8 stop_all6[] =	   { 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+struct holtekff_device {
+	struct hid_field *field;
+};
+
+static void holtekff_send(struct holtekff_device *holtekff,
+			  struct hid_device *hid,
+			  const u8 data[HOLTEKFF_MSG_LENGTH])
+{
+	int i;
+
+	for (i = 0; i < HOLTEKFF_MSG_LENGTH; i++) {
+		holtekff->field->value[i] = data[i];
+	}
+
+	dbg_hid("sending %7ph\n", data);
+
+	hid_hw_request(hid, holtekff->field->report, HID_REQ_SET_REPORT);
+}
+
+static int holtekff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct holtekff_device *holtekff = data;
+	int left, right;
+	/* effect type 1, length 65535 msec */
+	u8 buf[HOLTEKFF_MSG_LENGTH] =
+		{ 0x01, 0x01, 0xff, 0xff, 0x10, 0xe0, 0x00 };
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	dbg_hid("called with 0x%04x 0x%04x\n", left, right);
+
+	if (!left && !right) {
+		holtekff_send(holtekff, hid, stop_all6);
+		return 0;
+	}
+
+	if (left)
+		buf[1] |= 0x80;
+	if (right)
+		buf[1] |= 0x40;
+
+	/* The device takes a single magnitude, so we just sum them up. */
+	buf[6] = min(0xf, (left >> 12) + (right >> 12));
+
+	holtekff_send(holtekff, hid, buf);
+	holtekff_send(holtekff, hid, start_effect_1);
+
+	return 0;
+}
+
+static int holtekff_init(struct hid_device *hid)
+{
+	struct holtekff_device *holtekff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output report found\n");
+		return -ENODEV;
+	}
+
+	report = list_entry(report_list->next, struct hid_report, list);
+
+	if (report->maxfield < 1 || report->field[0]->report_count != 7) {
+		hid_err(hid, "unexpected output report layout\n");
+		return -ENODEV;
+	}
+
+	holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
+	if (!holtekff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	holtekff->field = report->field[0];
+
+	/* initialize the same way as win driver does */
+	holtekff_send(holtekff, hid, stop_all4);
+	holtekff_send(holtekff, hid, stop_all6);
+
+	error = input_ff_create_memless(dev, holtekff, holtekff_play);
+	if (error) {
+		kfree(holtekff);
+		return error;
+	}
+
+	hid_info(hid, "Force feedback for Holtek On Line Grip based devices by Anssi Hannula <anssi.hannula@iki.fi>\n");
+
+	return 0;
+}
+#else
+static inline int holtekff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+static int holtek_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	holtekff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id holtek_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK, USB_DEVICE_ID_HOLTEK_ON_LINE_GRIP) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, holtek_devices);
+
+static struct hid_driver holtek_driver = {
+	.name = "holtek",
+	.id_table = holtek_devices,
+	.probe = holtek_probe,
+};
+module_hid_driver(holtek_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anssi Hannula <anssi.hannula@iki.fi>");
+MODULE_DESCRIPTION("Force feedback support for Holtek On Line Grip based devices");
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
new file mode 100644
index 0000000..704049e
--- /dev/null
+++ b/drivers/hid/hid-hyperv.c
@@ -0,0 +1,618 @@
+/*
+ *  Copyright (c) 2009, Citrix Systems, Inc.
+ *  Copyright (c) 2010, Microsoft Corporation.
+ *  Copyright (c) 2011, Novell Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms and conditions of the GNU General Public License,
+ *  version 2, as published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope it will be useful, but WITHOUT
+ *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ *  more details.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/completion.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/hyperv.h>
+
+
+struct hv_input_dev_info {
+	unsigned int size;
+	unsigned short vendor;
+	unsigned short product;
+	unsigned short version;
+	unsigned short reserved[11];
+};
+
+/* The maximum size of a synthetic input message. */
+#define SYNTHHID_MAX_INPUT_REPORT_SIZE 16
+
+/*
+ * Current version
+ *
+ * History:
+ * Beta, RC < 2008/1/22        1,0
+ * RC > 2008/1/22              2,0
+ */
+#define SYNTHHID_INPUT_VERSION_MAJOR	2
+#define SYNTHHID_INPUT_VERSION_MINOR	0
+#define SYNTHHID_INPUT_VERSION		(SYNTHHID_INPUT_VERSION_MINOR | \
+					 (SYNTHHID_INPUT_VERSION_MAJOR << 16))
+
+
+#pragma pack(push, 1)
+/*
+ * Message types in the synthetic input protocol
+ */
+enum synthhid_msg_type {
+	SYNTH_HID_PROTOCOL_REQUEST,
+	SYNTH_HID_PROTOCOL_RESPONSE,
+	SYNTH_HID_INITIAL_DEVICE_INFO,
+	SYNTH_HID_INITIAL_DEVICE_INFO_ACK,
+	SYNTH_HID_INPUT_REPORT,
+	SYNTH_HID_MAX
+};
+
+/*
+ * Basic message structures.
+ */
+struct synthhid_msg_hdr {
+	enum synthhid_msg_type type;
+	u32 size;
+};
+
+struct synthhid_msg {
+	struct synthhid_msg_hdr header;
+	char data[1]; /* Enclosed message */
+};
+
+union synthhid_version {
+	struct {
+		u16 minor_version;
+		u16 major_version;
+	};
+	u32 version;
+};
+
+/*
+ * Protocol messages
+ */
+struct synthhid_protocol_request {
+	struct synthhid_msg_hdr header;
+	union synthhid_version version_requested;
+};
+
+struct synthhid_protocol_response {
+	struct synthhid_msg_hdr header;
+	union synthhid_version version_requested;
+	unsigned char approved;
+};
+
+struct synthhid_device_info {
+	struct synthhid_msg_hdr header;
+	struct hv_input_dev_info hid_dev_info;
+	struct hid_descriptor hid_descriptor;
+};
+
+struct synthhid_device_info_ack {
+	struct synthhid_msg_hdr header;
+	unsigned char reserved;
+};
+
+struct synthhid_input_report {
+	struct synthhid_msg_hdr header;
+	char buffer[1];
+};
+
+#pragma pack(pop)
+
+#define INPUTVSC_SEND_RING_BUFFER_SIZE		(10*PAGE_SIZE)
+#define INPUTVSC_RECV_RING_BUFFER_SIZE		(10*PAGE_SIZE)
+
+
+enum pipe_prot_msg_type {
+	PIPE_MESSAGE_INVALID,
+	PIPE_MESSAGE_DATA,
+	PIPE_MESSAGE_MAXIMUM
+};
+
+
+struct pipe_prt_msg {
+	enum pipe_prot_msg_type type;
+	u32 size;
+	char data[1];
+};
+
+struct  mousevsc_prt_msg {
+	enum pipe_prot_msg_type type;
+	u32 size;
+	union {
+		struct synthhid_protocol_request request;
+		struct synthhid_protocol_response response;
+		struct synthhid_device_info_ack ack;
+	};
+};
+
+/*
+ * Represents an mousevsc device
+ */
+struct mousevsc_dev {
+	struct hv_device	*device;
+	bool			init_complete;
+	bool			connected;
+	struct mousevsc_prt_msg	protocol_req;
+	struct mousevsc_prt_msg	protocol_resp;
+	/* Synchronize the request/response if needed */
+	struct completion	wait_event;
+	int			dev_info_status;
+
+	struct hid_descriptor	*hid_desc;
+	unsigned char		*report_desc;
+	u32			report_desc_size;
+	struct hv_input_dev_info hid_dev_info;
+	struct hid_device       *hid_device;
+	u8			input_buf[HID_MAX_BUFFER_SIZE];
+};
+
+
+static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device)
+{
+	struct mousevsc_dev *input_dev;
+
+	input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL);
+
+	if (!input_dev)
+		return NULL;
+
+	input_dev->device = device;
+	hv_set_drvdata(device, input_dev);
+	init_completion(&input_dev->wait_event);
+	input_dev->init_complete = false;
+
+	return input_dev;
+}
+
+static void mousevsc_free_device(struct mousevsc_dev *device)
+{
+	kfree(device->hid_desc);
+	kfree(device->report_desc);
+	hv_set_drvdata(device->device, NULL);
+	kfree(device);
+}
+
+static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
+				struct synthhid_device_info *device_info)
+{
+	int ret = 0;
+	struct hid_descriptor *desc;
+	struct mousevsc_prt_msg ack;
+
+	input_device->dev_info_status = -ENOMEM;
+
+	input_device->hid_dev_info = device_info->hid_dev_info;
+	desc = &device_info->hid_descriptor;
+	if (desc->bLength == 0)
+		goto cleanup;
+
+	input_device->hid_desc = kmemdup(desc, desc->bLength, GFP_ATOMIC);
+
+	if (!input_device->hid_desc)
+		goto cleanup;
+
+	input_device->report_desc_size = desc->desc[0].wDescriptorLength;
+	if (input_device->report_desc_size == 0) {
+		input_device->dev_info_status = -EINVAL;
+		goto cleanup;
+	}
+
+	input_device->report_desc = kzalloc(input_device->report_desc_size,
+					  GFP_ATOMIC);
+
+	if (!input_device->report_desc) {
+		input_device->dev_info_status = -ENOMEM;
+		goto cleanup;
+	}
+
+	memcpy(input_device->report_desc,
+	       ((unsigned char *)desc) + desc->bLength,
+	       desc->desc[0].wDescriptorLength);
+
+	/* Send the ack */
+	memset(&ack, 0, sizeof(struct mousevsc_prt_msg));
+
+	ack.type = PIPE_MESSAGE_DATA;
+	ack.size = sizeof(struct synthhid_device_info_ack);
+
+	ack.ack.header.type = SYNTH_HID_INITIAL_DEVICE_INFO_ACK;
+	ack.ack.header.size = 1;
+	ack.ack.reserved = 0;
+
+	ret = vmbus_sendpacket(input_device->device->channel,
+			&ack,
+			sizeof(struct pipe_prt_msg) - sizeof(unsigned char) +
+			sizeof(struct synthhid_device_info_ack),
+			(unsigned long)&ack,
+			VM_PKT_DATA_INBAND,
+			VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
+
+	if (!ret)
+		input_device->dev_info_status = 0;
+
+cleanup:
+	complete(&input_device->wait_event);
+
+	return;
+}
+
+static void mousevsc_on_receive(struct hv_device *device,
+				struct vmpacket_descriptor *packet)
+{
+	struct pipe_prt_msg *pipe_msg;
+	struct synthhid_msg *hid_msg;
+	struct mousevsc_dev *input_dev = hv_get_drvdata(device);
+	struct synthhid_input_report *input_report;
+	size_t len;
+
+	pipe_msg = (struct pipe_prt_msg *)((unsigned long)packet +
+						(packet->offset8 << 3));
+
+	if (pipe_msg->type != PIPE_MESSAGE_DATA)
+		return;
+
+	hid_msg = (struct synthhid_msg *)pipe_msg->data;
+
+	switch (hid_msg->header.type) {
+	case SYNTH_HID_PROTOCOL_RESPONSE:
+		/*
+		 * While it will be impossible for us to protect against
+		 * malicious/buggy hypervisor/host, add a check here to
+		 * ensure we don't corrupt memory.
+		 */
+		if ((pipe_msg->size + sizeof(struct pipe_prt_msg)
+			- sizeof(unsigned char))
+			> sizeof(struct mousevsc_prt_msg)) {
+			WARN_ON(1);
+			break;
+		}
+
+		memcpy(&input_dev->protocol_resp, pipe_msg,
+		       pipe_msg->size + sizeof(struct pipe_prt_msg) -
+		       sizeof(unsigned char));
+		complete(&input_dev->wait_event);
+		break;
+
+	case SYNTH_HID_INITIAL_DEVICE_INFO:
+		WARN_ON(pipe_msg->size < sizeof(struct hv_input_dev_info));
+
+		/*
+		 * Parse out the device info into device attr,
+		 * hid desc and report desc
+		 */
+		mousevsc_on_receive_device_info(input_dev,
+			(struct synthhid_device_info *)pipe_msg->data);
+		break;
+	case SYNTH_HID_INPUT_REPORT:
+		input_report =
+			(struct synthhid_input_report *)pipe_msg->data;
+		if (!input_dev->init_complete)
+			break;
+
+		len = min(input_report->header.size,
+			  (u32)sizeof(input_dev->input_buf));
+		memcpy(input_dev->input_buf, input_report->buffer, len);
+		hid_input_report(input_dev->hid_device, HID_INPUT_REPORT,
+				 input_dev->input_buf, len, 1);
+
+		pm_wakeup_hard_event(&input_dev->device->device);
+
+		break;
+	default:
+		pr_err("unsupported hid msg type - type %d len %d\n",
+		       hid_msg->header.type, hid_msg->header.size);
+		break;
+	}
+
+}
+
+static void mousevsc_on_channel_callback(void *context)
+{
+	const int packet_size = 0x100;
+	int ret;
+	struct hv_device *device = context;
+	u32 bytes_recvd;
+	u64 req_id;
+	struct vmpacket_descriptor *desc;
+	unsigned char	*buffer;
+	int	bufferlen = packet_size;
+
+	buffer = kmalloc(bufferlen, GFP_ATOMIC);
+	if (!buffer)
+		return;
+
+	do {
+		ret = vmbus_recvpacket_raw(device->channel, buffer,
+					bufferlen, &bytes_recvd, &req_id);
+
+		switch (ret) {
+		case 0:
+			if (bytes_recvd <= 0) {
+				kfree(buffer);
+				return;
+			}
+			desc = (struct vmpacket_descriptor *)buffer;
+
+			switch (desc->type) {
+			case VM_PKT_COMP:
+				break;
+
+			case VM_PKT_DATA_INBAND:
+				mousevsc_on_receive(device, desc);
+				break;
+
+			default:
+				pr_err("unhandled packet type %d, tid %llx len %d\n",
+					desc->type, req_id, bytes_recvd);
+				break;
+			}
+
+			break;
+
+		case -ENOBUFS:
+			kfree(buffer);
+			/* Handle large packet */
+			bufferlen = bytes_recvd;
+			buffer = kmalloc(bytes_recvd, GFP_ATOMIC);
+
+			if (!buffer)
+				return;
+
+			break;
+		}
+	} while (1);
+
+}
+
+static int mousevsc_connect_to_vsp(struct hv_device *device)
+{
+	int ret = 0;
+	unsigned long t;
+	struct mousevsc_dev *input_dev = hv_get_drvdata(device);
+	struct mousevsc_prt_msg *request;
+	struct mousevsc_prt_msg *response;
+
+	request = &input_dev->protocol_req;
+	memset(request, 0, sizeof(struct mousevsc_prt_msg));
+
+	request->type = PIPE_MESSAGE_DATA;
+	request->size = sizeof(struct synthhid_protocol_request);
+	request->request.header.type = SYNTH_HID_PROTOCOL_REQUEST;
+	request->request.header.size = sizeof(unsigned int);
+	request->request.version_requested.version = SYNTHHID_INPUT_VERSION;
+
+	ret = vmbus_sendpacket(device->channel, request,
+				sizeof(struct pipe_prt_msg) -
+				sizeof(unsigned char) +
+				sizeof(struct synthhid_protocol_request),
+				(unsigned long)request,
+				VM_PKT_DATA_INBAND,
+				VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
+	if (ret)
+		goto cleanup;
+
+	t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ);
+	if (!t) {
+		ret = -ETIMEDOUT;
+		goto cleanup;
+	}
+
+	response = &input_dev->protocol_resp;
+
+	if (!response->response.approved) {
+		pr_err("synthhid protocol request failed (version %d)\n",
+		       SYNTHHID_INPUT_VERSION);
+		ret = -ENODEV;
+		goto cleanup;
+	}
+
+	t = wait_for_completion_timeout(&input_dev->wait_event, 5*HZ);
+	if (!t) {
+		ret = -ETIMEDOUT;
+		goto cleanup;
+	}
+
+	/*
+	 * We should have gotten the device attr, hid desc and report
+	 * desc at this point
+	 */
+	ret = input_dev->dev_info_status;
+
+cleanup:
+	return ret;
+}
+
+static int mousevsc_hid_parse(struct hid_device *hid)
+{
+	struct hv_device *dev = hid_get_drvdata(hid);
+	struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
+
+	return hid_parse_report(hid, input_dev->report_desc,
+				input_dev->report_desc_size);
+}
+
+static int mousevsc_hid_open(struct hid_device *hid)
+{
+	return 0;
+}
+
+static int mousevsc_hid_start(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void mousevsc_hid_close(struct hid_device *hid)
+{
+}
+
+static void mousevsc_hid_stop(struct hid_device *hid)
+{
+}
+
+static int mousevsc_hid_raw_request(struct hid_device *hid,
+				    unsigned char report_num,
+				    __u8 *buf, size_t len,
+				    unsigned char rtype,
+				    int reqtype)
+{
+	return 0;
+}
+
+static struct hid_ll_driver mousevsc_ll_driver = {
+	.parse = mousevsc_hid_parse,
+	.open = mousevsc_hid_open,
+	.close = mousevsc_hid_close,
+	.start = mousevsc_hid_start,
+	.stop = mousevsc_hid_stop,
+	.raw_request = mousevsc_hid_raw_request,
+};
+
+static struct hid_driver mousevsc_hid_driver;
+
+static int mousevsc_probe(struct hv_device *device,
+			const struct hv_vmbus_device_id *dev_id)
+{
+	int ret;
+	struct mousevsc_dev *input_dev;
+	struct hid_device *hid_dev;
+
+	input_dev = mousevsc_alloc_device(device);
+
+	if (!input_dev)
+		return -ENOMEM;
+
+	ret = vmbus_open(device->channel,
+		INPUTVSC_SEND_RING_BUFFER_SIZE,
+		INPUTVSC_RECV_RING_BUFFER_SIZE,
+		NULL,
+		0,
+		mousevsc_on_channel_callback,
+		device
+		);
+
+	if (ret)
+		goto probe_err0;
+
+	ret = mousevsc_connect_to_vsp(device);
+
+	if (ret)
+		goto probe_err1;
+
+	/* workaround SA-167 */
+	if (input_dev->report_desc[14] == 0x25)
+		input_dev->report_desc[14] = 0x29;
+
+	hid_dev = hid_allocate_device();
+	if (IS_ERR(hid_dev)) {
+		ret = PTR_ERR(hid_dev);
+		goto probe_err1;
+	}
+
+	hid_dev->ll_driver = &mousevsc_ll_driver;
+	hid_dev->driver = &mousevsc_hid_driver;
+	hid_dev->bus = BUS_VIRTUAL;
+	hid_dev->vendor = input_dev->hid_dev_info.vendor;
+	hid_dev->product = input_dev->hid_dev_info.product;
+	hid_dev->version = input_dev->hid_dev_info.version;
+	input_dev->hid_device = hid_dev;
+
+	sprintf(hid_dev->name, "%s", "Microsoft Vmbus HID-compliant Mouse");
+
+	hid_set_drvdata(hid_dev, device);
+
+	ret = hid_add_device(hid_dev);
+	if (ret)
+		goto probe_err1;
+
+
+	ret = hid_parse(hid_dev);
+	if (ret) {
+		hid_err(hid_dev, "parse failed\n");
+		goto probe_err2;
+	}
+
+	ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
+
+	if (ret) {
+		hid_err(hid_dev, "hw start failed\n");
+		goto probe_err2;
+	}
+
+	device_init_wakeup(&device->device, true);
+
+	input_dev->connected = true;
+	input_dev->init_complete = true;
+
+	return ret;
+
+probe_err2:
+	hid_destroy_device(hid_dev);
+
+probe_err1:
+	vmbus_close(device->channel);
+
+probe_err0:
+	mousevsc_free_device(input_dev);
+
+	return ret;
+}
+
+
+static int mousevsc_remove(struct hv_device *dev)
+{
+	struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
+
+	device_init_wakeup(&dev->device, false);
+	vmbus_close(dev->channel);
+	hid_hw_stop(input_dev->hid_device);
+	hid_destroy_device(input_dev->hid_device);
+	mousevsc_free_device(input_dev);
+
+	return 0;
+}
+
+static const struct hv_vmbus_device_id id_table[] = {
+	/* Mouse guid */
+	{ HV_MOUSE_GUID, },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(vmbus, id_table);
+
+static struct  hv_driver mousevsc_drv = {
+	.name = KBUILD_MODNAME,
+	.id_table = id_table,
+	.probe = mousevsc_probe,
+	.remove = mousevsc_remove,
+	.driver = {
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+
+static int __init mousevsc_init(void)
+{
+	return vmbus_driver_register(&mousevsc_drv);
+}
+
+static void __exit mousevsc_exit(void)
+{
+	vmbus_driver_unregister(&mousevsc_drv);
+}
+
+MODULE_LICENSE("GPL");
+module_init(mousevsc_init);
+module_exit(mousevsc_exit);
diff --git a/drivers/hid/hid-icade.c b/drivers/hid/hid-icade.c
new file mode 100644
index 0000000..76b5a75
--- /dev/null
+++ b/drivers/hid/hid-icade.c
@@ -0,0 +1,242 @@
+/*
+ *  ION iCade input driver
+ *
+ *  Copyright (c) 2012 Bastien Nocera <hadess@hadess.net>
+ *  Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/*
+ *   ↑      A C Y L
+ *  ← →
+ *   ↓      B X Z R
+ *
+ *
+ *  UP ON,OFF  = w,e
+ *  RT ON,OFF  = d,c
+ *  DN ON,OFF  = x,z
+ *  LT ON,OFF  = a,q
+ *  A  ON,OFF  = y,t
+ *  B  ON,OFF  = h,r
+ *  C  ON,OFF  = u,f
+ *  X  ON,OFF  = j,n
+ *  Y  ON,OFF  = i,m
+ *  Z  ON,OFF  = k,p
+ *  L  ON,OFF  = o,g
+ *  R  ON,OFF  = l,v
+ */
+
+/* The translation code uses HID usage instead of input layer
+ * keys. This code generates a lookup table that makes
+ * translation quick.
+ *
+ * #include <linux/input.h>
+ * #include <stdio.h>
+ * #include <assert.h>
+ *
+ * #define unk     KEY_UNKNOWN
+ *
+ * < copy of hid_keyboard[] from hid-input.c >
+ *
+ * struct icade_key_translation {
+ *     int         from;
+ *     const char *to;
+ *     int         press;
+ * };
+ *
+ * static const struct icade_key_translation icade_keys[] = {
+ *    { KEY_W,        "KEY_UP",         1 },
+ *    { KEY_E,        "KEY_UP",         0 },
+ *    { KEY_D,        "KEY_RIGHT",      1 },
+ *    { KEY_C,        "KEY_RIGHT",      0 },
+ *    { KEY_X,        "KEY_DOWN",       1 },
+ *    { KEY_Z,        "KEY_DOWN",       0 },
+ *    { KEY_A,        "KEY_LEFT",       1 },
+ *    { KEY_Q,        "KEY_LEFT",       0 },
+ *    { KEY_Y,        "BTN_A",          1 },
+ *    { KEY_T,        "BTN_A",          0 },
+ *    { KEY_H,        "BTN_B",          1 },
+ *    { KEY_R,        "BTN_B",          0 },
+ *    { KEY_U,        "BTN_C",          1 },
+ *    { KEY_F,        "BTN_C",          0 },
+ *    { KEY_J,        "BTN_X",          1 },
+ *    { KEY_N,        "BTN_X",          0 },
+ *    { KEY_I,        "BTN_Y",          1 },
+ *    { KEY_M,        "BTN_Y",          0 },
+ *    { KEY_K,        "BTN_Z",          1 },
+ *    { KEY_P,        "BTN_Z",          0 },
+ *    { KEY_O,        "BTN_THUMBL",     1 },
+ *    { KEY_G,        "BTN_THUMBL",     0 },
+ *    { KEY_L,        "BTN_THUMBR",     1 },
+ *    { KEY_V,        "BTN_THUMBR",     0 },
+ *
+ *    { }
+ * };
+ *
+ * static int
+ * usage_for_key (int key)
+ * {
+ *     int i;
+ *     for (i = 0; i < 256; i++) {
+ *     if (hid_keyboard[i] == key)
+ *         return i;
+ *     }
+ *     assert(0);
+ * }
+ *
+ * int main (int argc, char **argv)
+ * {
+ *     const struct icade_key_translation *trans;
+ *     int max_usage = 0;
+ *
+ *     for (trans = icade_keys; trans->from; trans++) {
+ *         int usage = usage_for_key (trans->from);
+ *         max_usage = usage > max_usage ? usage : max_usage;
+ *     }
+ *
+ *     printf ("#define ICADE_MAX_USAGE %d\n\n", max_usage);
+ *     printf ("struct icade_key {\n");
+ *     printf ("\tu16 to;\n");
+ *     printf ("\tu8 press:1;\n");
+ *     printf ("};\n\n");
+ *     printf ("static const struct icade_key "
+ *             "icade_usage_table[%d] = {\n", max_usage + 1);
+ *     for (trans = icade_keys; trans->from; trans++) {
+ *         printf ("\t[%d] = { %s, %d },\n",
+ *                 usage_for_key (trans->from), trans->to, trans->press);
+ *     }
+ *     printf ("};\n");
+ *
+ *     return 0;
+ * }
+ */
+
+#define ICADE_MAX_USAGE 29
+
+struct icade_key {
+	u16 to;
+	u8 press:1;
+};
+
+static const struct icade_key icade_usage_table[30] = {
+	[26] = { KEY_UP, 1 },
+	[8] = { KEY_UP, 0 },
+	[7] = { KEY_RIGHT, 1 },
+	[6] = { KEY_RIGHT, 0 },
+	[27] = { KEY_DOWN, 1 },
+	[29] = { KEY_DOWN, 0 },
+	[4] = { KEY_LEFT, 1 },
+	[20] = { KEY_LEFT, 0 },
+	[28] = { BTN_A, 1 },
+	[23] = { BTN_A, 0 },
+	[11] = { BTN_B, 1 },
+	[21] = { BTN_B, 0 },
+	[24] = { BTN_C, 1 },
+	[9] = { BTN_C, 0 },
+	[13] = { BTN_X, 1 },
+	[17] = { BTN_X, 0 },
+	[12] = { BTN_Y, 1 },
+	[16] = { BTN_Y, 0 },
+	[14] = { BTN_Z, 1 },
+	[19] = { BTN_Z, 0 },
+	[18] = { BTN_THUMBL, 1 },
+	[10] = { BTN_THUMBL, 0 },
+	[15] = { BTN_THUMBR, 1 },
+	[25] = { BTN_THUMBR, 0 },
+};
+
+static const struct icade_key *icade_find_translation(u16 from)
+{
+	if (from > ICADE_MAX_USAGE)
+		return NULL;
+	return &icade_usage_table[from];
+}
+
+static int icade_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	const struct icade_key *trans;
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type)
+		return 0;
+
+	/* We ignore the fake key up, and act only on key down */
+	if (!value)
+		return 1;
+
+	trans = icade_find_translation(usage->hid & HID_USAGE);
+
+	if (!trans)
+		return 1;
+
+	input_event(field->hidinput->input, usage->type,
+			trans->to, trans->press);
+
+	return 1;
+}
+
+static int icade_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	const struct icade_key *trans;
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_KEYBOARD) {
+		trans = icade_find_translation(usage->hid & HID_USAGE);
+
+		if (!trans)
+			return -1;
+
+		hid_map_usage(hi, usage, bit, max, EV_KEY, trans->to);
+		set_bit(trans->to, hi->input->keybit);
+
+		return 1;
+	}
+
+	/* ignore others */
+	return -1;
+
+}
+
+static int icade_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if (usage->type == EV_KEY)
+		set_bit(usage->type, hi->input->evbit);
+
+	return -1;
+}
+
+static const struct hid_device_id icade_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
+
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, icade_devices);
+
+static struct hid_driver icade_driver = {
+	.name = "icade",
+	.id_table = icade_devices,
+	.event = icade_event,
+	.input_mapped = icade_input_mapped,
+	.input_mapping = icade_input_mapping,
+};
+module_hid_driver(icade_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_DESCRIPTION("ION iCade input driver");
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
new file mode 100644
index 0000000..46182d4
--- /dev/null
+++ b/drivers/hid/hid-ids.h
@@ -0,0 +1,1227 @@
+/*
+ *  USB HID quirks support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef HID_IDS_H_FILE
+#define HID_IDS_H_FILE
+
+#define USB_VENDOR_ID_3M		0x0596
+#define USB_DEVICE_ID_3M1968		0x0500
+#define USB_DEVICE_ID_3M2256		0x0502
+#define USB_DEVICE_ID_3M3266		0x0506
+
+#define USB_VENDOR_ID_A4TECH		0x09da
+#define USB_DEVICE_ID_A4TECH_WCP32PU	0x0006
+#define USB_DEVICE_ID_A4TECH_X5_005D	0x000a
+#define USB_DEVICE_ID_A4TECH_RP_649	0x001a
+
+#define USB_VENDOR_ID_AASHIMA		0x06d6
+#define USB_DEVICE_ID_AASHIMA_GAMEPAD	0x0025
+#define USB_DEVICE_ID_AASHIMA_PREDATOR	0x0026
+
+#define USB_VENDOR_ID_ACECAD		0x0460
+#define USB_DEVICE_ID_ACECAD_FLAIR	0x0004
+#define USB_DEVICE_ID_ACECAD_302	0x0008
+
+#define USB_VENDOR_ID_ACRUX		0x1a34
+
+#define USB_VENDOR_ID_ACTIONSTAR	0x2101
+#define USB_DEVICE_ID_ACTIONSTAR_1011	0x1011
+
+#define USB_VENDOR_ID_ADS_TECH		0x06e1
+#define USB_DEVICE_ID_ADS_TECH_RADIO_SI470X	0xa155
+
+#define USB_VENDOR_ID_AFATECH		0x15a4
+#define USB_DEVICE_ID_AFATECH_AF9016	0x9016
+
+#define USB_VENDOR_ID_AIPTEK		0x08ca
+#define USB_DEVICE_ID_AIPTEK_01		0x0001
+#define USB_DEVICE_ID_AIPTEK_10		0x0010
+#define USB_DEVICE_ID_AIPTEK_20		0x0020
+#define USB_DEVICE_ID_AIPTEK_21		0x0021
+#define USB_DEVICE_ID_AIPTEK_22		0x0022
+#define USB_DEVICE_ID_AIPTEK_23		0x0023
+#define USB_DEVICE_ID_AIPTEK_24		0x0024
+
+#define USB_VENDOR_ID_AIRCABLE		0x16CA
+#define USB_DEVICE_ID_AIRCABLE1		0x1502
+
+#define USB_VENDOR_ID_AIREN		0x1a2c
+#define USB_DEVICE_ID_AIREN_SLIMPLUS	0x0002
+
+#define USB_VENDOR_ID_AKAI		0x2011
+#define USB_DEVICE_ID_AKAI_MPKMINI2	0x0715
+
+#define USB_VENDOR_ID_AKAI_09E8		0x09E8
+#define USB_DEVICE_ID_AKAI_09E8_MIDIMIX	0x0031
+
+#define USB_VENDOR_ID_ALCOR		0x058f
+#define USB_DEVICE_ID_ALCOR_USBRS232	0x9720
+
+#define USB_VENDOR_ID_ALPS		0x0433
+#define USB_DEVICE_ID_IBM_GAMEPAD	0x1101
+
+#define USB_VENDOR_ID_ALPS_JP		0x044E
+#define HID_DEVICE_ID_ALPS_U1_DUAL	0x120B
+#define HID_DEVICE_ID_ALPS_U1_DUAL_PTP	0x121F
+#define HID_DEVICE_ID_ALPS_U1_DUAL_3BTN_PTP	0x1220
+#define HID_DEVICE_ID_ALPS_U1		0x1215
+#define HID_DEVICE_ID_ALPS_T4_BTNLESS	0x120C
+
+
+#define USB_VENDOR_ID_AMI		0x046b
+#define USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE	0xff10
+
+#define USB_VENDOR_ID_ANTON		0x1130
+#define USB_DEVICE_ID_ANTON_TOUCH_PAD	0x3101
+
+#define USB_VENDOR_ID_APPLE		0x05ac
+#define BT_VENDOR_ID_APPLE		0x004c
+#define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
+#define USB_DEVICE_ID_APPLE_MAGICMOUSE	0x030d
+#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD	0x030e
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI	0x020e
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO	0x020f
+#define USB_DEVICE_ID_APPLE_GEYSER_ANSI	0x0214
+#define USB_DEVICE_ID_APPLE_GEYSER_ISO	0x0215
+#define USB_DEVICE_ID_APPLE_GEYSER_JIS	0x0216
+#define USB_DEVICE_ID_APPLE_GEYSER3_ANSI	0x0217
+#define USB_DEVICE_ID_APPLE_GEYSER3_ISO	0x0218
+#define USB_DEVICE_ID_APPLE_GEYSER3_JIS	0x0219
+#define USB_DEVICE_ID_APPLE_GEYSER4_ANSI	0x021a
+#define USB_DEVICE_ID_APPLE_GEYSER4_ISO	0x021b
+#define USB_DEVICE_ID_APPLE_GEYSER4_JIS	0x021c
+#define USB_DEVICE_ID_APPLE_ALU_MINI_ANSI	0x021d
+#define USB_DEVICE_ID_APPLE_ALU_MINI_ISO	0x021e
+#define USB_DEVICE_ID_APPLE_ALU_MINI_JIS	0x021f
+#define USB_DEVICE_ID_APPLE_ALU_ANSI	0x0220
+#define USB_DEVICE_ID_APPLE_ALU_ISO	0x0221
+#define USB_DEVICE_ID_APPLE_ALU_JIS	0x0222
+#define USB_DEVICE_ID_APPLE_WELLSPRING_ANSI	0x0223
+#define USB_DEVICE_ID_APPLE_WELLSPRING_ISO	0x0224
+#define USB_DEVICE_ID_APPLE_WELLSPRING_JIS	0x0225
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI	0x0229
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO	0x022a
+#define USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS	0x022b
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI	0x022c
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO	0x022d
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS	0x022e
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI	0x0230
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_ISO	0x0231
+#define USB_DEVICE_ID_APPLE_WELLSPRING2_JIS	0x0232
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI	0x0236
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_ISO	0x0237
+#define USB_DEVICE_ID_APPLE_WELLSPRING3_JIS	0x0238
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI	0x023f
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_ISO	0x0240
+#define USB_DEVICE_ID_APPLE_WELLSPRING4_JIS	0x0241
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI	0x0242
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO	0x0243
+#define USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS	0x0244
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI	0x0245
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_ISO	0x0246
+#define USB_DEVICE_ID_APPLE_WELLSPRING5_JIS	0x0247
+#define USB_DEVICE_ID_APPLE_ALU_REVB_ANSI	0x024f
+#define USB_DEVICE_ID_APPLE_ALU_REVB_ISO	0x0250
+#define USB_DEVICE_ID_APPLE_ALU_REVB_JIS	0x0251
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI	0x0252
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO	0x0253
+#define USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS	0x0254
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI	0x0259
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO	0x025a
+#define USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS	0x025b
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI	0x0249
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO	0x024a
+#define USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS	0x024b
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI	0x024c
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_ISO	0x024d
+#define USB_DEVICE_ID_APPLE_WELLSPRING6_JIS	0x024e
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI	0x0262
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_ISO	0x0263
+#define USB_DEVICE_ID_APPLE_WELLSPRING7_JIS	0x0264
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI  0x0239
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO   0x023a
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS   0x023b
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI  0x0255
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO   0x0256
+#define USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS   0x0257
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI   0x0267
+#define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_ANSI   0x026c
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI	0x0290
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_ISO	0x0291
+#define USB_DEVICE_ID_APPLE_WELLSPRING8_JIS	0x0292
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI	0x0272
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO		0x0273
+#define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS		0x0274
+#define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY	0x030a
+#define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY	0x030b
+#define USB_DEVICE_ID_APPLE_IRCONTROL	0x8240
+#define USB_DEVICE_ID_APPLE_IRCONTROL2	0x1440
+#define USB_DEVICE_ID_APPLE_IRCONTROL3	0x8241
+#define USB_DEVICE_ID_APPLE_IRCONTROL4	0x8242
+#define USB_DEVICE_ID_APPLE_IRCONTROL5	0x8243
+
+#define USB_VENDOR_ID_ASUS		0x0486
+#define USB_DEVICE_ID_ASUS_T91MT	0x0185
+#define USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO	0x0186
+
+#define USB_VENDOR_ID_ASUSTEK		0x0b05
+#define USB_DEVICE_ID_ASUSTEK_LCM	0x1726
+#define USB_DEVICE_ID_ASUSTEK_LCM2	0x175b
+#define USB_DEVICE_ID_ASUSTEK_T100TA_KEYBOARD	0x17e0
+#define USB_DEVICE_ID_ASUSTEK_T100TAF_KEYBOARD	0x1807
+#define USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD	0x8502
+#define USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD	0x184a
+#define USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD	0x8585
+#define USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD	0x0101
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1 0x1854
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2 0x1837
+#define USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3 0x1822
+
+#define USB_VENDOR_ID_ATEN		0x0557
+#define USB_DEVICE_ID_ATEN_UC100KM	0x2004
+#define USB_DEVICE_ID_ATEN_CS124U	0x2202
+#define USB_DEVICE_ID_ATEN_2PORTKVM	0x2204
+#define USB_DEVICE_ID_ATEN_4PORTKVM	0x2205
+#define USB_DEVICE_ID_ATEN_4PORTKVMC	0x2208
+#define USB_DEVICE_ID_ATEN_CS682	0x2213
+#define USB_DEVICE_ID_ATEN_CS692	0x8021
+#define USB_DEVICE_ID_ATEN_CS1758	0x2220
+
+#define USB_VENDOR_ID_ATMEL		0x03eb
+#define USB_DEVICE_ID_ATMEL_MULTITOUCH	0x211c
+#define USB_DEVICE_ID_ATMEL_MXT_DIGITIZER	0x2118
+#define USB_VENDOR_ID_ATMEL_V_USB	0x16c0
+#define USB_DEVICE_ID_ATMEL_V_USB	0x05df
+
+#define USB_VENDOR_ID_AUREAL		0x0755
+#define USB_DEVICE_ID_AUREAL_W01RN	0x2626
+
+#define USB_VENDOR_ID_AVERMEDIA		0x07ca
+#define USB_DEVICE_ID_AVER_FM_MR800	0xb800
+
+#define USB_VENDOR_ID_AXENTIA		0x12cf
+#define USB_DEVICE_ID_AXENTIA_FM_RADIO	0x7111
+
+#define USB_VENDOR_ID_BAANTO		0x2453
+#define USB_DEVICE_ID_BAANTO_MT_190W2	0x0100
+
+#define USB_VENDOR_ID_BELKIN		0x050d
+#define USB_DEVICE_ID_FLIP_KVM		0x3201
+
+#define USB_VENDOR_ID_BERKSHIRE		0x0c98
+#define USB_DEVICE_ID_BERKSHIRE_PCWD	0x1140
+
+#define USB_VENDOR_ID_BETOP_2185BFM	0x11c2
+#define USB_VENDOR_ID_BETOP_2185PC	0x11c0
+#define USB_VENDOR_ID_BETOP_2185V2PC	0x8380
+#define USB_VENDOR_ID_BETOP_2185V2BFM	0x20bc
+
+#define USB_VENDOR_ID_BTC		0x046e
+#define USB_DEVICE_ID_BTC_EMPREX_REMOTE	0x5578
+#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2	0x5577
+
+#define USB_VENDOR_ID_CANDO		0x2087
+#define USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH 0x0703
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH	0x0a01
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_10_1 0x0a02
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_11_6 0x0b03
+#define USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6 0x0f01
+
+#define USB_VENDOR_ID_CH		0x068e
+#define USB_DEVICE_ID_CH_PRO_THROTTLE	0x00f1
+#define USB_DEVICE_ID_CH_PRO_PEDALS	0x00f2
+#define USB_DEVICE_ID_CH_FIGHTERSTICK	0x00f3
+#define USB_DEVICE_ID_CH_COMBATSTICK	0x00f4
+#define USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE       0x0051
+#define USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE	0x00ff
+#define USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK	0x00d3
+#define USB_DEVICE_ID_CH_AXIS_295	0x001c
+
+#define USB_VENDOR_ID_CHERRY		0x046a
+#define USB_DEVICE_ID_CHERRY_CYMOTION	0x0023
+#define USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR	0x0027
+
+#define USB_VENDOR_ID_CHIC		0x05fe
+#define USB_DEVICE_ID_CHIC_GAMEPAD	0x0014
+
+#define USB_VENDOR_ID_CHICONY		0x04f2
+#define USB_DEVICE_ID_CHICONY_TACTICAL_PAD	0x0418
+#define USB_DEVICE_ID_CHICONY_MULTI_TOUCH	0xb19d
+#define USB_DEVICE_ID_CHICONY_WIRELESS	0x0618
+#define USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE	0x1053
+#define USB_DEVICE_ID_CHICONY_WIRELESS2	0x1123
+#define USB_DEVICE_ID_ASUS_AK1D		0x1125
+#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12	0x1421
+
+#define USB_VENDOR_ID_CHUNGHWAT		0x2247
+#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH	0x0001
+
+#define USB_VENDOR_ID_CIDC		0x1677
+
+#define I2C_VENDOR_ID_CIRQUE		0x0488
+#define I2C_PRODUCT_ID_CIRQUE_121F	0x121F
+
+#define USB_VENDOR_ID_CJTOUCH		0x24b8
+#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020	0x0020
+#define USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0040	0x0040
+
+#define USB_VENDOR_ID_CMEDIA		0x0d8c
+#define USB_DEVICE_ID_CM109		0x000e
+#define USB_DEVICE_ID_CM6533		0x0022
+
+#define USB_VENDOR_ID_CODEMERCS		0x07c0
+#define USB_DEVICE_ID_CODEMERCS_IOW_FIRST	0x1500
+#define USB_DEVICE_ID_CODEMERCS_IOW_LAST	0x15ff
+
+#define USB_VENDOR_ID_CORSAIR		0x1b1c
+#define USB_DEVICE_ID_CORSAIR_K90	0x1b02
+
+#define USB_VENDOR_ID_CORSAIR           0x1b1c
+#define USB_DEVICE_ID_CORSAIR_K70R      0x1b09
+#define USB_DEVICE_ID_CORSAIR_K95RGB    0x1b11
+#define USB_DEVICE_ID_CORSAIR_M65RGB    0x1b12
+#define USB_DEVICE_ID_CORSAIR_K70RGB    0x1b13
+#define USB_DEVICE_ID_CORSAIR_STRAFE    0x1b15
+#define USB_DEVICE_ID_CORSAIR_K65RGB    0x1b17
+#define USB_DEVICE_ID_CORSAIR_GLAIVE_RGB        0x1b34
+#define USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE  0x1b38
+#define USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE  0x1b39
+#define USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB  0x1b3e
+
+#define USB_VENDOR_ID_CREATIVELABS	0x041e
+#define USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51	0x322c
+#define USB_DEVICE_ID_PRODIKEYS_PCMIDI	0x2801
+
+#define USB_VENDOR_ID_CVTOUCH		0x1ff7
+#define USB_DEVICE_ID_CVTOUCH_SCREEN	0x0013
+
+#define USB_VENDOR_ID_CYGNAL		0x10c4
+#define USB_DEVICE_ID_CYGNAL_RADIO_SI470X	0x818a
+#define USB_DEVICE_ID_FOCALTECH_FTXXXX_MULTITOUCH	0x81b9
+#define USB_DEVICE_ID_CYGNAL_CP2112	0xea90
+
+#define USB_DEVICE_ID_CYGNAL_RADIO_SI4713       0x8244
+
+#define USB_VENDOR_ID_CYPRESS		0x04b4
+#define USB_DEVICE_ID_CYPRESS_MOUSE	0x0001
+#define USB_DEVICE_ID_CYPRESS_HIDCOM	0x5500
+#define USB_DEVICE_ID_CYPRESS_ULTRAMOUSE	0x7417
+#define USB_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_DATA_MODUL	0x7374
+#define USB_VENDOR_ID_DATA_MODUL_EASYMAXTOUCH	0x1201
+
+#define USB_VENDOR_ID_DEALEXTREAME	0x10c5
+#define USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701	0x819a
+
+#define USB_VENDOR_ID_DELCOM		0x0fc5
+#define USB_DEVICE_ID_DELCOM_VISUAL_IND	0xb080
+
+#define USB_VENDOR_ID_DELL				0x413c
+#define USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE	0x301a
+
+#define USB_VENDOR_ID_DELORME		0x1163
+#define USB_DEVICE_ID_DELORME_EARTHMATE	0x0100
+#define USB_DEVICE_ID_DELORME_EM_LT20	0x0200
+
+#define USB_VENDOR_ID_DMI		0x0c0b
+#define USB_DEVICE_ID_DMI_ENC		0x5fab
+
+#define USB_VENDOR_ID_DRAGONRISE		0x0079
+#define USB_DEVICE_ID_DRAGONRISE_WIIU		0x1800
+#define USB_DEVICE_ID_DRAGONRISE_PS3		0x1801
+#define USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR	0x1803
+#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE1	0x1843
+#define USB_DEVICE_ID_DRAGONRISE_GAMECUBE2	0x1844
+
+#define USB_VENDOR_ID_DWAV		0x0eef
+#define USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER	0x0001
+#define USB_DEVICE_ID_DWAV_TOUCHCONTROLLER	0x0002
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D	0x480d
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E	0x480e
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7207	0x7207
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C	0x720c
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224	0x7224
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_722A	0x722A
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E	0x725e
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7262	0x7262
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B	0x726b
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1	0x72a1
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72AA	0x72aa
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4	0x72c4
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0	0x72d0
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72FA	0x72fa
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302	0x7302
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349	0x7349
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7	0x73f7
+#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001	0xa001
+
+#define USB_VENDOR_ID_ELAN		0x04f3
+#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W	0x0401
+#define USB_DEVICE_ID_HP_X2		0x074d
+#define USB_DEVICE_ID_HP_X2_10_COVER	0x0755
+
+#define USB_VENDOR_ID_ELECOM		0x056e
+#define USB_DEVICE_ID_ELECOM_BM084	0x0061
+#define USB_DEVICE_ID_ELECOM_M_XT3URBK	0x00fb
+#define USB_DEVICE_ID_ELECOM_M_XT3DRBK	0x00fc
+#define USB_DEVICE_ID_ELECOM_M_XT4DRBK	0x00fd
+#define USB_DEVICE_ID_ELECOM_M_DT1URBK	0x00fe
+#define USB_DEVICE_ID_ELECOM_M_DT1DRBK	0x00ff
+#define USB_DEVICE_ID_ELECOM_M_HT1URBK	0x010c
+#define USB_DEVICE_ID_ELECOM_M_HT1DRBK	0x010d
+
+#define USB_VENDOR_ID_DREAM_CHEEKY	0x1d34
+#define USB_DEVICE_ID_DREAM_CHEEKY_WN	0x0004
+#define USB_DEVICE_ID_DREAM_CHEEKY_FA	0x000a
+
+#define USB_VENDOR_ID_ELITEGROUP	0x03fc
+#define USB_DEVICE_ID_ELITEGROUP_05D8	0x05d8
+
+#define USB_VENDOR_ID_ELO		0x04E7
+#define USB_DEVICE_ID_ELO_TS2515	0x0022
+#define USB_DEVICE_ID_ELO_TS2700	0x0020
+#define USB_DEVICE_ID_ELO_ACCUTOUCH_2216	0x0050
+
+#define USB_VENDOR_ID_EMS		0x2006
+#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
+
+#define USB_VENDOR_ID_FLATFROG		0x25b5
+#define USB_DEVICE_ID_MULTITOUCH_3200	0x0002
+
+#define USB_VENDOR_ID_FUTABA            0x0547
+#define USB_DEVICE_ID_LED_DISPLAY       0x7000
+
+#define USB_VENDOR_ID_FUTURE_TECHNOLOGY	0x0403
+#define USB_DEVICE_ID_RETRODE2		0x97c1
+
+#define USB_VENDOR_ID_ESSENTIAL_REALITY	0x0d7f
+#define USB_DEVICE_ID_ESSENTIAL_REALITY_P5 0x0100
+
+#define USB_VENDOR_ID_ETT		0x0664
+#define USB_DEVICE_ID_TC5UH		0x0309
+#define USB_DEVICE_ID_TC4UM		0x0306
+
+#define USB_VENDOR_ID_ETURBOTOUCH	0x22b9
+#define USB_DEVICE_ID_ETURBOTOUCH	0x0006
+#define USB_DEVICE_ID_ETURBOTOUCH_2968	0x2968
+
+#define USB_VENDOR_ID_EZKEY		0x0518
+#define USB_DEVICE_ID_BTC_8193		0x0002
+
+#define USB_VENDOR_ID_FORMOSA          0x147a
+#define USB_DEVICE_ID_FORMOSA_IR_RECEIVER      0xe03e
+
+#define USB_VENDOR_ID_FREESCALE		0x15A2
+#define USB_DEVICE_ID_FREESCALE_MX28	0x004F
+
+#define USB_VENDOR_ID_FRUCTEL	0x25B6
+#define USB_DEVICE_ID_GAMETEL_MT_MODE	0x0002
+
+#define USB_VENDOR_ID_GAMERON		0x0810
+#define USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR	0x0001
+#define USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR	0x0002
+
+#define USB_VENDOR_ID_GEMBIRD			0x11ff
+#define USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2	0x3331
+
+#define USB_VENDOR_ID_GENERAL_TOUCH	0x0dfc
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS 0x0003
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS 0x0100
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0101 0x0101
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0102 0x0102
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0106 0x0106
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A 0x010a
+#define USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100 0xe100
+
+#define USB_VENDOR_ID_GOODTOUCH		0x1aad
+#define USB_DEVICE_ID_GOODTOUCH_000f	0x000f
+
+#define USB_VENDOR_ID_GOOGLE		0x18d1
+#define USB_DEVICE_ID_GOOGLE_HAMMER	0x5022
+#define USB_DEVICE_ID_GOOGLE_TOUCH_ROSE	0x5028
+#define USB_DEVICE_ID_GOOGLE_STAFF	0x502b
+#define USB_DEVICE_ID_GOOGLE_WAND	0x502d
+#define USB_DEVICE_ID_GOOGLE_WHISKERS	0x5030
+
+#define USB_VENDOR_ID_GOTOP		0x08f2
+#define USB_DEVICE_ID_SUPER_Q2		0x007f
+#define USB_DEVICE_ID_GOGOPEN		0x00ce
+#define USB_DEVICE_ID_PENPOWER		0x00f4
+
+#define USB_VENDOR_ID_GREENASIA		0x0e8f
+#define USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD	0x3013
+
+#define USB_VENDOR_ID_GRETAGMACBETH	0x0971
+#define USB_DEVICE_ID_GRETAGMACBETH_HUEY	0x2005
+
+#define USB_VENDOR_ID_GRIFFIN		0x077d
+#define USB_DEVICE_ID_POWERMATE		0x0410
+#define USB_DEVICE_ID_SOUNDKNOB		0x04AA
+#define USB_DEVICE_ID_RADIOSHARK	0x627a
+
+#define USB_VENDOR_ID_GTCO		0x078c
+#define USB_DEVICE_ID_GTCO_90		0x0090
+#define USB_DEVICE_ID_GTCO_100		0x0100
+#define USB_DEVICE_ID_GTCO_101		0x0101
+#define USB_DEVICE_ID_GTCO_103		0x0103
+#define USB_DEVICE_ID_GTCO_104		0x0104
+#define USB_DEVICE_ID_GTCO_105		0x0105
+#define USB_DEVICE_ID_GTCO_106		0x0106
+#define USB_DEVICE_ID_GTCO_107		0x0107
+#define USB_DEVICE_ID_GTCO_108		0x0108
+#define USB_DEVICE_ID_GTCO_200		0x0200
+#define USB_DEVICE_ID_GTCO_201		0x0201
+#define USB_DEVICE_ID_GTCO_202		0x0202
+#define USB_DEVICE_ID_GTCO_203		0x0203
+#define USB_DEVICE_ID_GTCO_204		0x0204
+#define USB_DEVICE_ID_GTCO_205		0x0205
+#define USB_DEVICE_ID_GTCO_206		0x0206
+#define USB_DEVICE_ID_GTCO_207		0x0207
+#define USB_DEVICE_ID_GTCO_300		0x0300
+#define USB_DEVICE_ID_GTCO_301		0x0301
+#define USB_DEVICE_ID_GTCO_302		0x0302
+#define USB_DEVICE_ID_GTCO_303		0x0303
+#define USB_DEVICE_ID_GTCO_304		0x0304
+#define USB_DEVICE_ID_GTCO_305		0x0305
+#define USB_DEVICE_ID_GTCO_306		0x0306
+#define USB_DEVICE_ID_GTCO_307		0x0307
+#define USB_DEVICE_ID_GTCO_308		0x0308
+#define USB_DEVICE_ID_GTCO_309		0x0309
+#define USB_DEVICE_ID_GTCO_400		0x0400
+#define USB_DEVICE_ID_GTCO_401		0x0401
+#define USB_DEVICE_ID_GTCO_402		0x0402
+#define USB_DEVICE_ID_GTCO_403		0x0403
+#define USB_DEVICE_ID_GTCO_404		0x0404
+#define USB_DEVICE_ID_GTCO_405		0x0405
+#define USB_DEVICE_ID_GTCO_500		0x0500
+#define USB_DEVICE_ID_GTCO_501		0x0501
+#define USB_DEVICE_ID_GTCO_502		0x0502
+#define USB_DEVICE_ID_GTCO_503		0x0503
+#define USB_DEVICE_ID_GTCO_504		0x0504
+#define USB_DEVICE_ID_GTCO_1000		0x1000
+#define USB_DEVICE_ID_GTCO_1001		0x1001
+#define USB_DEVICE_ID_GTCO_1002		0x1002
+#define USB_DEVICE_ID_GTCO_1003		0x1003
+#define USB_DEVICE_ID_GTCO_1004		0x1004
+#define USB_DEVICE_ID_GTCO_1005		0x1005
+#define USB_DEVICE_ID_GTCO_1006		0x1006
+#define USB_DEVICE_ID_GTCO_1007		0x1007
+
+#define USB_VENDOR_ID_GYRATION		0x0c16
+#define USB_DEVICE_ID_GYRATION_REMOTE	0x0002
+#define USB_DEVICE_ID_GYRATION_REMOTE_2 0x0003
+#define USB_DEVICE_ID_GYRATION_REMOTE_3 0x0008
+
+#define I2C_VENDOR_ID_HANTICK		0x0911
+#define I2C_PRODUCT_ID_HANTICK_5288	0x5288
+
+#define USB_VENDOR_ID_HANWANG		0x0b57
+#define USB_DEVICE_ID_HANWANG_TABLET_FIRST	0x5000
+#define USB_DEVICE_ID_HANWANG_TABLET_LAST	0x8fff
+
+#define USB_VENDOR_ID_HANVON		0x20b3
+#define USB_DEVICE_ID_HANVON_MULTITOUCH	0x0a18
+
+#define USB_VENDOR_ID_HANVON_ALT	0x22ed
+#define USB_DEVICE_ID_HANVON_ALT_MULTITOUCH	0x1010
+
+#define USB_VENDOR_ID_HAPP		0x078b
+#define USB_DEVICE_ID_UGCI_DRIVING	0x0010
+#define USB_DEVICE_ID_UGCI_FLYING	0x0020
+#define USB_DEVICE_ID_UGCI_FIGHTING	0x0030
+
+#define USB_VENDOR_ID_HP		0x03f0
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A	0x0a4a
+#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A	0x0b4a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE		0x134a
+#define USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A	0x094a
+
+#define USB_VENDOR_ID_HUION		0x256c
+#define USB_DEVICE_ID_HUION_TABLET	0x006e
+
+#define USB_VENDOR_ID_IBM					0x04b3
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_III			0x3100
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_PRO			0x3103
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL			0x3105
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL		0x3108
+#define USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO	0x3109
+
+#define USB_VENDOR_ID_IDEACOM		0x1cb6
+#define USB_DEVICE_ID_IDEACOM_IDC6650	0x6650
+#define USB_DEVICE_ID_IDEACOM_IDC6651	0x6651
+#define USB_DEVICE_ID_IDEACOM_IDC6680	0x6680
+
+#define USB_VENDOR_ID_ILITEK		0x222a
+#define USB_DEVICE_ID_ILITEK_MULTITOUCH	0x0001
+
+#define USB_VENDOR_ID_INTEL_0		0x8086
+#define USB_VENDOR_ID_INTEL_1		0x8087
+#define USB_DEVICE_ID_INTEL_HID_SENSOR_0	0x09fa
+#define USB_DEVICE_ID_INTEL_HID_SENSOR_1	0x0a04
+
+#define USB_VENDOR_ID_STM_0             0x0483
+#define USB_DEVICE_ID_STM_HID_SENSOR    0x91d1
+#define USB_DEVICE_ID_STM_HID_SENSOR_1  0x9100
+
+#define USB_VENDOR_ID_ION		0x15e4
+#define USB_DEVICE_ID_ICADE		0x0132
+
+#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_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A	0xa04a
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067	0xa067
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070	0xa070
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072	0xa072
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081	0xa081
+#define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2	0xa0c2
+#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096	0xa096
+
+#define USB_VENDOR_ID_IMATION		0x0718
+#define USB_DEVICE_ID_DISC_STAKKA	0xd000
+
+#define USB_VENDOR_ID_IRTOUCHSYSTEMS	0x6615
+#define USB_DEVICE_ID_IRTOUCH_INFRARED_USB	0x0070
+
+#define USB_VENDOR_ID_INNOMEDIA			0x1292
+#define USB_DEVICE_ID_INNEX_GENESIS_ATARI	0x4745
+
+#define USB_VENDOR_ID_ITE               0x048d
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA   0x8386
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA2  0x8350
+#define USB_DEVICE_ID_ITE_LENOVO_YOGA900	0x8396
+#define USB_DEVICE_ID_ITE8595		0x8595
+
+#define USB_VENDOR_ID_JABRA		0x0b0e
+#define USB_DEVICE_ID_JABRA_SPEAK_410	0x0412
+#define USB_DEVICE_ID_JABRA_SPEAK_510	0x0420
+#define USB_DEVICE_ID_JABRA_GN9350E	0x9350
+
+#define USB_VENDOR_ID_JESS		0x0c45
+#define USB_DEVICE_ID_JESS_YUREX	0x1010
+#define USB_DEVICE_ID_ASUS_MD_5112	0x5112
+#define USB_DEVICE_ID_REDRAGON_ASURA	0x760b
+
+#define USB_VENDOR_ID_JESS2		0x0f30
+#define USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD 0x0111
+
+#define USB_VENDOR_ID_KBGEAR		0x084e
+#define USB_DEVICE_ID_KBGEAR_JAMSTUDIO	0x1001
+
+#define USB_VENDOR_ID_KENSINGTON	0x047d
+#define USB_DEVICE_ID_KS_SLIMBLADE	0x2041
+
+#define USB_VENDOR_ID_KWORLD		0x1b80
+#define USB_DEVICE_ID_KWORLD_RADIO_FM700	0xd700
+
+#define USB_VENDOR_ID_KEYTOUCH		0x0926
+#define USB_DEVICE_ID_KEYTOUCH_IEC	0x3333
+
+#define USB_VENDOR_ID_KYE		0x0458
+#define USB_DEVICE_ID_KYE_ERGO_525V	0x0087
+#define USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE	0x0138
+#define USB_DEVICE_ID_GENIUS_MANTICORE	0x0153
+#define USB_DEVICE_ID_GENIUS_GX_IMPERATOR	0x4018
+#define USB_DEVICE_ID_KYE_GPEN_560	0x5003
+#define USB_DEVICE_ID_KYE_EASYPEN_I405X	0x5010
+#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X	0x5011
+#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2	0x501a
+#define USB_DEVICE_ID_KYE_EASYPEN_M610X	0x5013
+#define USB_DEVICE_ID_KYE_PENSKETCH_M912	0x5015
+
+#define USB_VENDOR_ID_LABTEC		0x1020
+#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD	0x0006
+
+#define USB_VENDOR_ID_LCPOWER		0x1241
+#define USB_DEVICE_ID_LCPOWER_LC1000	0xf767
+
+#define USB_VENDOR_ID_LD		0x0f11
+#define USB_DEVICE_ID_LD_CASSY		0x1000
+#define USB_DEVICE_ID_LD_CASSY2		0x1001
+#define USB_DEVICE_ID_LD_POCKETCASSY	0x1010
+#define USB_DEVICE_ID_LD_POCKETCASSY2	0x1011
+#define USB_DEVICE_ID_LD_MOBILECASSY	0x1020
+#define USB_DEVICE_ID_LD_MOBILECASSY2	0x1021
+#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE	0x1031
+#define USB_DEVICE_ID_LD_MICROCASSYCURRENT	0x1032
+#define USB_DEVICE_ID_LD_MICROCASSYTIME		0x1033
+#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE	0x1035
+#define USB_DEVICE_ID_LD_MICROCASSYPH		0x1038
+#define USB_DEVICE_ID_LD_POWERANALYSERCASSY	0x1040
+#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY	0x1042
+#define USB_DEVICE_ID_LD_MACHINETESTCASSY	0x1043
+#define USB_DEVICE_ID_LD_JWM		0x1080
+#define USB_DEVICE_ID_LD_DMMP		0x1081
+#define USB_DEVICE_ID_LD_UMIP		0x1090
+#define USB_DEVICE_ID_LD_UMIC		0x10A0
+#define USB_DEVICE_ID_LD_UMIB		0x10B0
+#define USB_DEVICE_ID_LD_XRAY		0x1100
+#define USB_DEVICE_ID_LD_XRAY2		0x1101
+#define USB_DEVICE_ID_LD_XRAYCT		0x1110
+#define USB_DEVICE_ID_LD_VIDEOCOM	0x1200
+#define USB_DEVICE_ID_LD_MOTOR		0x1210
+#define USB_DEVICE_ID_LD_COM3LAB	0x2000
+#define USB_DEVICE_ID_LD_TELEPORT	0x2010
+#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020
+#define USB_DEVICE_ID_LD_POWERCONTROL	0x2030
+#define USB_DEVICE_ID_LD_MACHINETEST	0x2040
+#define USB_DEVICE_ID_LD_MOSTANALYSER	0x2050
+#define USB_DEVICE_ID_LD_MOSTANALYSER2	0x2051
+#define USB_DEVICE_ID_LD_ABSESP		0x2060
+#define USB_DEVICE_ID_LD_AUTODATABUS	0x2070
+#define USB_DEVICE_ID_LD_MCT		0x2080
+#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_DEVICE_ID_LENOVO_CUSBKBD	0x6047
+#define USB_DEVICE_ID_LENOVO_CBTKBD	0x6048
+#define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL	0x6049
+#define USB_DEVICE_ID_LENOVO_TPPRODOCK	0x6067
+#define USB_DEVICE_ID_LENOVO_X1_COVER	0x6085
+#define USB_DEVICE_ID_LENOVO_X1_TAB	0x60a3
+
+#define USB_VENDOR_ID_LG		0x1fd2
+#define USB_DEVICE_ID_LG_MULTITOUCH	0x0064
+#define USB_DEVICE_ID_LG_MELFAS_MT	0x6007
+
+#define USB_VENDOR_ID_LOGITECH		0x046d
+#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
+#define USB_DEVICE_ID_LOGITECH_T651	0xb00c
+#define USB_DEVICE_ID_LOGITECH_C007	0xc007
+#define USB_DEVICE_ID_LOGITECH_C077	0xc077
+#define USB_DEVICE_ID_LOGITECH_RECEIVER	0xc101
+#define USB_DEVICE_ID_LOGITECH_HARMONY_FIRST  0xc110
+#define USB_DEVICE_ID_LOGITECH_HARMONY_LAST 0xc14f
+#define USB_DEVICE_ID_LOGITECH_HARMONY_PS3 0x0306
+#define USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS 0xc24d
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C01A	0xc01a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C05A	0xc05a
+#define USB_DEVICE_ID_LOGITECH_MOUSE_C06A	0xc06a
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD	0xc20a
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD	0xc211
+#define USB_DEVICE_ID_LOGITECH_EXTREME_3D	0xc215
+#define USB_DEVICE_ID_LOGITECH_DUAL_ACTION	0xc216
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2	0xc218
+#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2	0xc219
+#define USB_DEVICE_ID_LOGITECH_G29_WHEEL	0xc24f
+#define USB_DEVICE_ID_LOGITECH_G920_WHEEL	0xc262
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D	0xc283
+#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO	0xc286
+#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940	0xc287
+#define USB_DEVICE_ID_LOGITECH_WINGMAN_FFG	0xc293
+#define USB_DEVICE_ID_LOGITECH_WHEEL	0xc294
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL	0xc295
+#define USB_DEVICE_ID_LOGITECH_DFP_WHEEL	0xc298
+#define USB_DEVICE_ID_LOGITECH_G25_WHEEL	0xc299
+#define USB_DEVICE_ID_LOGITECH_DFGT_WHEEL	0xc29a
+#define USB_DEVICE_ID_LOGITECH_G27_WHEEL	0xc29b
+#define USB_DEVICE_ID_LOGITECH_WII_WHEEL	0xc29c
+#define USB_DEVICE_ID_LOGITECH_ELITE_KBD	0xc30a
+#define USB_DEVICE_ID_S510_RECEIVER	0xc50c
+#define USB_DEVICE_ID_S510_RECEIVER_2	0xc517
+#define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500	0xc512
+#define USB_DEVICE_ID_MX3000_RECEIVER	0xc513
+#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER	0xc52b
+#define USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2	0xc532
+#define USB_DEVICE_ID_SPACETRAVELLER	0xc623
+#define USB_DEVICE_ID_SPACENAVIGATOR	0xc626
+#define USB_DEVICE_ID_DINOVO_DESKTOP	0xc704
+#define USB_DEVICE_ID_DINOVO_EDGE	0xc714
+#define USB_DEVICE_ID_DINOVO_MINI	0xc71f
+#define USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2	0xca03
+#define USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL	0xca04
+
+#define USB_VENDOR_ID_LUMIO		0x202e
+#define USB_DEVICE_ID_CRYSTALTOUCH	0x0006
+#define USB_DEVICE_ID_CRYSTALTOUCH_DUAL	0x0007
+
+#define USB_VENDOR_ID_MADCATZ		0x0738
+#define USB_DEVICE_ID_MADCATZ_BEATPAD	0x4540
+#define USB_DEVICE_ID_MADCATZ_RAT5	0x1705
+#define USB_DEVICE_ID_MADCATZ_RAT9	0x1709
+
+#define USB_VENDOR_ID_MCC		0x09db
+#define USB_DEVICE_ID_MCC_PMD1024LS	0x0076
+#define USB_DEVICE_ID_MCC_PMD1208LS	0x007a
+
+#define USB_VENDOR_ID_MCS		0x16d0
+#define USB_DEVICE_ID_MCS_GAMEPADBLOCK	0x0bcc
+
+#define USB_VENDOR_ID_MGE		0x0463
+#define USB_DEVICE_ID_MGE_UPS		0xffff
+#define USB_DEVICE_ID_MGE_UPS1		0x0001
+
+#define USB_VENDOR_ID_MICROCHIP		0x04d8
+#define USB_DEVICE_ID_PICKIT1		0x0032
+#define USB_DEVICE_ID_PICKIT2		0x0033
+#define USB_DEVICE_ID_PICOLCD		0xc002
+#define USB_DEVICE_ID_PICOLCD_BOOTLOADER	0xf002
+#define USB_DEVICE_ID_PICK16F1454	0x0042
+#define USB_DEVICE_ID_PICK16F1454_V2	0xf2f7
+#define USB_DEVICE_ID_LUXAFOR		0xf372
+
+#define USB_VENDOR_ID_MICROSOFT		0x045e
+#define USB_DEVICE_ID_SIDEWINDER_GV	0x003b
+#define USB_DEVICE_ID_MS_OFFICE_KB	0x0048
+#define USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0 0x009d
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K 0x00b4
+#define USB_DEVICE_ID_MS_NE4K		0x00db
+#define USB_DEVICE_ID_MS_NE4K_JP	0x00dc
+#define USB_DEVICE_ID_MS_LK6K		0x00f9
+#define USB_DEVICE_ID_MS_PRESENTER_8K_BT	0x0701
+#define USB_DEVICE_ID_MS_PRESENTER_8K_USB	0x0713
+#define USB_DEVICE_ID_MS_NE7K		0x071d
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K	0x0730
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1 0x0732
+#define USB_DEVICE_ID_MS_DIGITAL_MEDIA_600  0x0750
+#define USB_DEVICE_ID_MS_COMFORT_MOUSE_4500	0x076c
+#define USB_DEVICE_ID_MS_COMFORT_KEYBOARD 0x00e3
+#define USB_DEVICE_ID_MS_SURFACE_PRO_2   0x0799
+#define USB_DEVICE_ID_MS_TOUCH_COVER_2   0x07a7
+#define USB_DEVICE_ID_MS_TYPE_COVER_2    0x07a9
+#define USB_DEVICE_ID_MS_POWER_COVER     0x07da
+#define USB_DEVICE_ID_MS_PIXART_MOUSE    0x00cb
+
+#define USB_VENDOR_ID_MOJO		0x8282
+#define USB_DEVICE_ID_RETRO_ADAPTER	0x3201
+
+#define USB_VENDOR_ID_MONTEREY		0x0566
+#define USB_DEVICE_ID_GENIUS_KB29E	0x3004
+
+#define USB_VENDOR_ID_MSI		0x1770
+#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
+
+#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
+#define USB_DEVICE_ID_N_S_HARMONY	0xc359
+
+#define USB_VENDOR_ID_NATSU		0x08b7
+#define USB_DEVICE_ID_NATSU_GAMEPAD	0x0001
+
+#define USB_VENDOR_ID_NCR		0x0404
+#define USB_DEVICE_ID_NCR_FIRST		0x0300
+#define USB_DEVICE_ID_NCR_LAST		0x03ff
+
+#define USB_VENDOR_ID_NEC		0x073e
+#define USB_DEVICE_ID_NEC_USB_GAME_PAD	0x0301
+
+#define USB_VENDOR_ID_NEXIO		0x1870
+#define USB_DEVICE_ID_NEXIO_MULTITOUCH_420	0x010d
+#define USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750	0x0110
+
+#define USB_VENDOR_ID_NEXTWINDOW	0x1926
+#define USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN	0x0003
+
+#define USB_VENDOR_ID_NINTENDO		0x057e
+#define USB_DEVICE_ID_NINTENDO_WIIMOTE	0x0306
+#define USB_DEVICE_ID_NINTENDO_WIIMOTE2	0x0330
+
+#define USB_VENDOR_ID_NOVATEK		0x0603
+#define USB_DEVICE_ID_NOVATEK_PCT	0x0600
+#define USB_DEVICE_ID_NOVATEK_MOUSE	0x1602
+
+#define USB_VENDOR_ID_NTI               0x0757
+#define USB_DEVICE_ID_USB_SUN           0x0a00
+
+#define USB_VENDOR_ID_NTRIG		0x1b96
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN   0x0001
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1   0x0003
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2   0x0004
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_3   0x0005
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_4   0x0006
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_5   0x0007
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_6   0x0008
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_7   0x0009
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_8   0x000A
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_9   0x000B
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_10   0x000C
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_11   0x000D
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_12   0x000E
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_13   0x000F
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_14   0x0010
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_15   0x0011
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_16   0x0012
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_17   0x0013
+#define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18   0x0014
+#define USB_DEVICE_ID_NTRIG_DUOSENSE 0x1500
+
+#define USB_VENDOR_ID_ONTRAK		0x0a07
+#define USB_DEVICE_ID_ONTRAK_ADU100	0x0064
+
+#define USB_VENDOR_ID_ORTEK		0x05a4
+#define USB_DEVICE_ID_ORTEK_PKB1700	0x1700
+#define USB_DEVICE_ID_ORTEK_WKB2000	0x2000
+#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S	0x8003
+
+#define USB_VENDOR_ID_PLANTRONICS	0x047f
+
+#define USB_VENDOR_ID_PANASONIC		0x04da
+#define USB_DEVICE_ID_PANABOARD_UBT780	0x1044
+#define USB_DEVICE_ID_PANABOARD_UBT880	0x104d
+
+#define USB_VENDOR_ID_PANJIT		0x134c
+
+#define USB_VENDOR_ID_PANTHERLORD	0x0810
+#define USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK	0x0001
+
+#define USB_VENDOR_ID_PENMOUNT		0x14e1
+#define USB_DEVICE_ID_PENMOUNT_PCI	0x3500
+#define USB_DEVICE_ID_PENMOUNT_1610	0x1610
+#define USB_DEVICE_ID_PENMOUNT_1640	0x1640
+#define USB_DEVICE_ID_PENMOUNT_6000	0x6000
+
+#define USB_VENDOR_ID_PETALYNX		0x18b1
+#define USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE	0x0037
+
+#define USB_VENDOR_ID_PETZL		0x2122
+#define USB_DEVICE_ID_PETZL_HEADLAMP	0x1234
+
+#define USB_VENDOR_ID_PHILIPS		0x0471
+#define USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE 0x0617
+
+#define USB_VENDOR_ID_PI_ENGINEERING	0x05f3
+#define USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL	0xff
+
+#define USB_VENDOR_ID_PIXART				0x093a
+#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2	0x0137
+#define USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE		0x2510
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN	0x8001
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1	0x8002
+#define USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2	0x8003
+
+#define USB_VENDOR_ID_PLAYDOTCOM	0x0b43
+#define USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII	0x0003
+
+#define USB_VENDOR_ID_POWERCOM		0x0d9f
+#define USB_DEVICE_ID_POWERCOM_UPS	0x0002
+
+#define USB_VENDOR_ID_PRODIGE		0x05af
+#define USB_DEVICE_ID_PRODIGE_CORDLESS	0x3062
+
+#define USB_VENDOR_ID_QUANTA		0x0408
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH		0x3000
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001		0x3001
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003		0x3003
+#define USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008		0x3008
+
+#define I2C_VENDOR_ID_RAYDIUM		0x2386
+#define I2C_PRODUCT_ID_RAYDIUM_4B33	0x4b33
+
+#define USB_VENDOR_ID_RAZER            0x1532
+#define USB_DEVICE_ID_RAZER_BLADE_14   0x011D
+
+#define USB_VENDOR_ID_REALTEK		0x0bda
+#define USB_DEVICE_ID_REALTEK_READER	0x0152
+
+#define USB_VENDOR_ID_RETROUSB		0xf000
+#define USB_DEVICE_ID_RETROUSB_SNES_RETROPAD	0x0003
+#define USB_DEVICE_ID_RETROUSB_SNES_RETROPORT	0x00f1
+
+#define USB_VENDOR_ID_ROCCAT		0x1e7d
+#define USB_DEVICE_ID_ROCCAT_ARVO	0x30d4
+#define USB_DEVICE_ID_ROCCAT_ISKU	0x319c
+#define USB_DEVICE_ID_ROCCAT_ISKUFX	0x3264
+#define USB_DEVICE_ID_ROCCAT_KONE	0x2ced
+#define USB_DEVICE_ID_ROCCAT_KONEPLUS	0x2d51
+#define USB_DEVICE_ID_ROCCAT_KONEPURE	0x2dbe
+#define USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL	0x2db4
+#define USB_DEVICE_ID_ROCCAT_KONEXTD	0x2e22
+#define USB_DEVICE_ID_ROCCAT_KOVAPLUS	0x2d50
+#define USB_DEVICE_ID_ROCCAT_LUA	0x2c2e
+#define USB_DEVICE_ID_ROCCAT_PYRA_WIRED	0x2c24
+#define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS	0x2cf6
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK	0x3138
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW	0x31ce
+#define USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO	0x3232
+#define USB_DEVICE_ID_ROCCAT_SAVU	0x2d5a
+
+#define USB_VENDOR_ID_SAITEK		0x06a3
+#define USB_DEVICE_ID_SAITEK_RUMBLEPAD	0xff17
+#define USB_DEVICE_ID_SAITEK_PS1000	0x0621
+#define USB_DEVICE_ID_SAITEK_RAT7_OLD	0x0ccb
+#define USB_DEVICE_ID_SAITEK_RAT7_CONTAGION	0x0ccd
+#define USB_DEVICE_ID_SAITEK_RAT7	0x0cd7
+#define USB_DEVICE_ID_SAITEK_RAT9	0x0cfa
+#define USB_DEVICE_ID_SAITEK_MMO7	0x0cd0
+
+#define USB_VENDOR_ID_SAMSUNG		0x0419
+#define USB_DEVICE_ID_SAMSUNG_IR_REMOTE	0x0001
+#define USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE	0x0600
+
+#define USB_VENDOR_ID_SEMICO			0x1a2c
+#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD	0x0023
+#define USB_DEVICE_ID_SEMICO_USB_KEYKOARD2	0x0027
+
+#define USB_VENDOR_ID_SENNHEISER	0x1395
+#define USB_DEVICE_ID_SENNHEISER_BTD500USB	0x002c
+
+#define USB_VENDOR_ID_SIGMA_MICRO	0x1c4f
+#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD	0x0002
+
+#define USB_VENDOR_ID_SIGMATEL		0x066F
+#define USB_DEVICE_ID_SIGMATEL_STMP3780	0x3780
+
+#define USB_VENDOR_ID_SIS_TOUCH		0x0457
+#define USB_DEVICE_ID_SIS9200_TOUCH	0x9200
+#define USB_DEVICE_ID_SIS817_TOUCH	0x0817
+#define USB_DEVICE_ID_SIS_TS		0x1013
+#define USB_DEVICE_ID_SIS1030_TOUCH	0x1030
+
+#define USB_VENDOR_ID_SKYCABLE			0x1223
+#define	USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER	0x3F07
+
+#define USB_VENDOR_ID_SMK		0x0609
+#define USB_DEVICE_ID_SMK_PS3_BDREMOTE	0x0306
+#define USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE       0x0368
+#define USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE       0x0369
+
+
+#define USB_VENDOR_ID_SONY			0x054c
+#define USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE	0x024b
+#define USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE	0x0374
+#define USB_DEVICE_ID_SONY_PS3_BDREMOTE		0x0306
+#define USB_DEVICE_ID_SONY_PS3_CONTROLLER	0x0268
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER	0x05c4
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_2	0x09cc
+#define USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE	0x0ba0
+#define USB_DEVICE_ID_SONY_MOTION_CONTROLLER	0x03d5
+#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER	0x042f
+#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER		0x0002
+#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER	0x1000
+
+#define USB_VENDOR_ID_SINO_LITE			0x1345
+#define USB_DEVICE_ID_SINO_LITE_CONTROLLER	0x3008
+
+#define USB_VENDOR_ID_SOLID_YEAR			0x060b
+#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD	0x500a
+
+#define USB_VENDOR_ID_SOUNDGRAPH	0x15c2
+#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST	0x0034
+#define USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST	0x0046
+
+#define USB_VENDOR_ID_STANTUM		0x1f87
+#define USB_DEVICE_ID_MTP		0x0002
+
+#define USB_VENDOR_ID_STANTUM_STM		0x0483
+#define USB_DEVICE_ID_MTP_STM		0x3261
+
+#define USB_VENDOR_ID_STANTUM_SITRONIX		0x1403
+#define USB_DEVICE_ID_MTP_SITRONIX		0x5001
+
+#define USB_VENDOR_ID_VALVE			0x28de
+#define USB_DEVICE_ID_STEAM_CONTROLLER		0x1102
+#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS	0x1142
+
+#define USB_VENDOR_ID_STEELSERIES	0x1038
+#define USB_DEVICE_ID_STEELSERIES_SRWS1	0x1410
+
+#define USB_VENDOR_ID_SUN		0x0430
+#define USB_DEVICE_ID_RARITAN_KVM_DONGLE	0xcdab
+
+#define USB_VENDOR_ID_SUNPLUS		0x04fc
+#define USB_DEVICE_ID_SUNPLUS_WDESKTOP	0x05d8
+
+#define USB_VENDOR_ID_SYMBOL		0x05e0
+#define USB_DEVICE_ID_SYMBOL_SCANNER_1	0x0800
+#define USB_DEVICE_ID_SYMBOL_SCANNER_2	0x1300
+#define USB_DEVICE_ID_SYMBOL_SCANNER_3	0x1200
+
+#define USB_VENDOR_ID_SYNAPTICS		0x06cb
+#define USB_DEVICE_ID_SYNAPTICS_TP	0x0001
+#define USB_DEVICE_ID_SYNAPTICS_INT_TP	0x0002
+#define USB_DEVICE_ID_SYNAPTICS_CPAD	0x0003
+#define USB_DEVICE_ID_SYNAPTICS_TS	0x0006
+#define USB_DEVICE_ID_SYNAPTICS_STICK	0x0007
+#define USB_DEVICE_ID_SYNAPTICS_WP	0x0008
+#define USB_DEVICE_ID_SYNAPTICS_COMP_TP	0x0009
+#define USB_DEVICE_ID_SYNAPTICS_WTP	0x0010
+#define USB_DEVICE_ID_SYNAPTICS_DPAD	0x0013
+#define USB_DEVICE_ID_SYNAPTICS_LTS1	0x0af8
+#define USB_DEVICE_ID_SYNAPTICS_LTS2	0x1d10
+#define USB_DEVICE_ID_SYNAPTICS_HD	0x0ac3
+#define USB_DEVICE_ID_SYNAPTICS_QUAD_HD	0x1ac3
+#define USB_DEVICE_ID_SYNAPTICS_TP_V103	0x5710
+
+#define USB_VENDOR_ID_TEXAS_INSTRUMENTS	0x2047
+#define USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA	0x0855
+
+#define USB_VENDOR_ID_THINGM		0x27b8
+#define USB_DEVICE_ID_BLINK1		0x01ed
+
+#define USB_VENDOR_ID_THQ		0x20d6
+#define USB_DEVICE_ID_THQ_PS3_UDRAW	0xcb17
+
+#define USB_VENDOR_ID_THRUSTMASTER	0x044f
+
+#define USB_VENDOR_ID_TIVO		0x150a
+#define USB_DEVICE_ID_TIVO_SLIDE_BT	0x1200
+#define USB_DEVICE_ID_TIVO_SLIDE	0x1201
+#define USB_DEVICE_ID_TIVO_SLIDE_PRO	0x1203
+
+#define USB_VENDOR_ID_TOPSEED		0x0766
+#define USB_DEVICE_ID_TOPSEED_CYBERLINK	0x0204
+
+#define USB_VENDOR_ID_TOPSEED2		0x1784
+#define USB_DEVICE_ID_TOPSEED2_RF_COMBO	0x0004
+#define USB_DEVICE_ID_TOPSEED2_PERIPAD_701	0x0016
+
+#define USB_VENDOR_ID_TOPMAX		0x0663
+#define USB_DEVICE_ID_TOPMAX_COBRAPAD	0x0103
+
+#define USB_VENDOR_ID_TOUCH_INTL	0x1e5e
+#define USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH	0x0313
+
+#define USB_VENDOR_ID_TOUCHPACK		0x1bfd
+#define USB_DEVICE_ID_TOUCHPACK_RTS	0x1688
+
+#define USB_VENDOR_ID_TPV		0x25aa
+#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882	0x8882
+#define USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883	0x8883
+
+#define USB_VENDOR_ID_TURBOX		0x062a
+#define USB_DEVICE_ID_TURBOX_KEYBOARD	0x0201
+#define USB_DEVICE_ID_ASUS_MD_5110	0x5110
+#define USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART	0x7100
+
+#define USB_VENDOR_ID_TWINHAN		0x6253
+#define USB_DEVICE_ID_TWINHAN_IR_REMOTE	0x0100
+
+#define USB_VENDOR_ID_UCLOGIC		0x5543
+#define USB_DEVICE_ID_UCLOGIC_TABLET_PF1209	0x0042
+#define USB_DEVICE_ID_UCLOGIC_TABLET_KNA5	0x6001
+#define USB_DEVICE_ID_UCLOGIC_TABLET_TWA60	0x0064
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U	0x0003
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U	0x0004
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U	0x0005
+#define USB_DEVICE_ID_UCLOGIC_TABLET_WP1062	0x0064
+#define USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850	0x0522
+#define USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60	0x0781
+#define USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3	0x3031
+#define USB_DEVICE_ID_UGEE_TABLET_81		0x0081
+#define USB_DEVICE_ID_UGEE_TABLET_45		0x0045
+#define USB_DEVICE_ID_YIYNOVA_TABLET		0x004d
+
+#define USB_VENDOR_ID_UGEE		0x28bd
+#define USB_DEVICE_ID_UGEE_TABLET_EX07S		0x0071
+
+#define USB_VENDOR_ID_UNITEC	0x227d
+#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0709	0x0709
+#define USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19	0x0a19
+
+#define USB_VENDOR_ID_VELLEMAN		0x10cf
+#define USB_DEVICE_ID_VELLEMAN_K8055_FIRST	0x5500
+#define USB_DEVICE_ID_VELLEMAN_K8055_LAST	0x5503
+#define USB_DEVICE_ID_VELLEMAN_K8061_FIRST	0x8061
+#define USB_DEVICE_ID_VELLEMAN_K8061_LAST	0x8068
+
+#define USB_VENDOR_ID_VTL		0x0306
+#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F	0xff3f
+
+#define USB_VENDOR_ID_WACOM		0x056a
+#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH	0x81
+#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH   0x00BD
+
+#define USB_VENDOR_ID_WALTOP				0x172f
+#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH	0x0032
+#define USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH	0x0034
+#define USB_DEVICE_ID_WALTOP_Q_PAD			0x0037
+#define USB_DEVICE_ID_WALTOP_PID_0038			0x0038
+#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH	0x0501
+#define USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH	0x0500
+#define USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET	0x0502
+
+#define	USB_VENDOR_ID_WEIDA		0x2575
+#define	USB_DEVICE_ID_WEIDA_8752	0xC300
+#define	USB_DEVICE_ID_WEIDA_8755	0xC301
+
+#define USB_VENDOR_ID_WISEGROUP		0x0925
+#define USB_DEVICE_ID_SMARTJOY_PLUS	0x0005
+#define USB_DEVICE_ID_SUPER_JOY_BOX_3	0x8888
+#define USB_DEVICE_ID_QUAD_USB_JOYPAD	0x8800
+#define USB_DEVICE_ID_DUAL_USB_JOYPAD	0x8866
+
+#define USB_VENDOR_ID_WISEGROUP_LTD	0x6666
+#define USB_VENDOR_ID_WISEGROUP_LTD2	0x6677
+#define USB_DEVICE_ID_SMARTJOY_DUAL_PLUS 0x8802
+#define USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO 0x8801
+#define USB_DEVICE_ID_SUPER_DUAL_BOX_PRO 0x8802
+#define USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO 0x8804
+
+#define USB_VENDOR_ID_WISTRON		0x0fb8
+#define USB_DEVICE_ID_WISTRON_OPTICAL_TOUCH		0x1109
+
+#define USB_VENDOR_ID_X_TENSIONS               0x1ae7
+#define USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE    0x9001
+
+#define USB_VENDOR_ID_XAT	0x2505
+#define USB_DEVICE_ID_XAT_CSR	0x0220
+
+#define USB_VENDOR_ID_XIN_MO			0x16c0
+#define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE	0x05e1
+#define USB_DEVICE_ID_THT_2P_ARCADE		0x75e1
+
+#define USB_VENDOR_ID_XIROKU		0x1477
+#define USB_DEVICE_ID_XIROKU_SPX	0x1006
+#define USB_DEVICE_ID_XIROKU_MPX	0x1007
+#define USB_DEVICE_ID_XIROKU_CSR	0x100e
+#define USB_DEVICE_ID_XIROKU_SPX1	0x1021
+#define USB_DEVICE_ID_XIROKU_CSR1	0x1022
+#define USB_DEVICE_ID_XIROKU_MPX1	0x1023
+#define USB_DEVICE_ID_XIROKU_SPX2	0x1024
+#define USB_DEVICE_ID_XIROKU_CSR2	0x1025
+#define USB_DEVICE_ID_XIROKU_MPX2	0x1026
+
+#define USB_VENDOR_ID_YEALINK		0x6993
+#define USB_DEVICE_ID_YEALINK_P1K_P4K_B2K	0xb001
+
+#define USB_VENDOR_ID_ZEROPLUS		0x0c12
+
+#define USB_VENDOR_ID_ZYDACRON	0x13EC
+#define USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL	0x0006
+
+#define USB_VENDOR_ID_ZYTRONIC		0x14c8
+#define USB_DEVICE_ID_ZYTRONIC_ZXY100	0x0005
+
+#define USB_VENDOR_ID_PRIMAX	0x0461
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22	0x4d22
+#define USB_DEVICE_ID_PRIMAX_KEYBOARD	0x4e05
+#define USB_DEVICE_ID_PRIMAX_REZEL	0x4e72
+#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F	0x4d0f
+#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22	0x4e22
+
+
+#define USB_VENDOR_ID_RISO_KAGAKU	0x1294	/* Riso Kagaku Corp. */
+#define USB_DEVICE_ID_RI_KA_WEBMAIL	0x1320	/* Webmail Notifier */
+
+#define USB_VENDOR_ID_MULTIPLE_1781	0x1781
+#define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD	0x0a9d
+
+#define USB_VENDOR_ID_DRACAL_RAPHNET	0x289b
+#define USB_DEVICE_ID_RAPHNET_2NES2SNES	0x0002
+#define USB_DEVICE_ID_RAPHNET_4NES4SNES	0x0003
+
+#define USB_VENDOR_ID_UGTIZER			0x2179
+#define USB_DEVICE_ID_UGTIZER_TABLET_GP0610	0x0053
+
+#endif
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
new file mode 100644
index 0000000..a3916e5
--- /dev/null
+++ b/drivers/hid/hid-input.c
@@ -0,0 +1,1832 @@
+/*
+ *  Copyright (c) 2000-2001 Vojtech Pavlik
+ *  Copyright (c) 2006-2010 Jiri Kosina
+ *
+ *  HID to Linux Input mapping
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+
+#include "hid-ids.h"
+
+#define unk	KEY_UNKNOWN
+
+static const unsigned char hid_keyboard[256] = {
+	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
+	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
+	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
+	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
+	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
+	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
+	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
+	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
+	115,114,unk,unk,unk,121,unk, 89, 93,124, 92, 94, 95,unk,unk,unk,
+	122,123, 90, 91, 85,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,
+	unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
+	unk,unk,unk,unk,unk,unk,179,180,unk,unk,unk,unk,unk,unk,unk,unk,
+	unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,unk,
+	unk,unk,unk,unk,unk,unk,unk,unk,111,unk,unk,unk,unk,unk,unk,unk,
+	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
+	150,158,159,128,136,177,178,176,142,152,173,140,unk,unk,unk,unk
+};
+
+static const struct {
+	__s32 x;
+	__s32 y;
+}  hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}};
+
+#define map_abs(c)	hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c))
+#define map_rel(c)	hid_map_usage(hidinput, usage, &bit, &max, EV_REL, (c))
+#define map_key(c)	hid_map_usage(hidinput, usage, &bit, &max, EV_KEY, (c))
+#define map_led(c)	hid_map_usage(hidinput, usage, &bit, &max, EV_LED, (c))
+
+#define map_abs_clear(c)	hid_map_usage_clear(hidinput, usage, &bit, \
+		&max, EV_ABS, (c))
+#define map_key_clear(c)	hid_map_usage_clear(hidinput, usage, &bit, \
+		&max, EV_KEY, (c))
+
+static bool match_scancode(struct hid_usage *usage,
+			   unsigned int cur_idx, unsigned int scancode)
+{
+	return (usage->hid & (HID_USAGE_PAGE | HID_USAGE)) == scancode;
+}
+
+static bool match_keycode(struct hid_usage *usage,
+			  unsigned int cur_idx, unsigned int keycode)
+{
+	/*
+	 * We should exclude unmapped usages when doing lookup by keycode.
+	 */
+	return (usage->type == EV_KEY && usage->code == keycode);
+}
+
+static bool match_index(struct hid_usage *usage,
+			unsigned int cur_idx, unsigned int idx)
+{
+	return cur_idx == idx;
+}
+
+typedef bool (*hid_usage_cmp_t)(struct hid_usage *usage,
+				unsigned int cur_idx, unsigned int val);
+
+static struct hid_usage *hidinput_find_key(struct hid_device *hid,
+					   hid_usage_cmp_t match,
+					   unsigned int value,
+					   unsigned int *usage_idx)
+{
+	unsigned int i, j, k, cur_idx = 0;
+	struct hid_report *report;
+	struct hid_usage *usage;
+
+	for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) {
+		list_for_each_entry(report, &hid->report_enum[k].report_list, list) {
+			for (i = 0; i < report->maxfield; i++) {
+				for (j = 0; j < report->field[i]->maxusage; j++) {
+					usage = report->field[i]->usage + j;
+					if (usage->type == EV_KEY || usage->type == 0) {
+						if (match(usage, cur_idx, value)) {
+							if (usage_idx)
+								*usage_idx = cur_idx;
+							return usage;
+						}
+						cur_idx++;
+					}
+				}
+			}
+		}
+	}
+	return NULL;
+}
+
+static struct hid_usage *hidinput_locate_usage(struct hid_device *hid,
+					const struct input_keymap_entry *ke,
+					unsigned int *index)
+{
+	struct hid_usage *usage;
+	unsigned int scancode;
+
+	if (ke->flags & INPUT_KEYMAP_BY_INDEX)
+		usage = hidinput_find_key(hid, match_index, ke->index, index);
+	else if (input_scancode_to_scalar(ke, &scancode) == 0)
+		usage = hidinput_find_key(hid, match_scancode, scancode, index);
+	else
+		usage = NULL;
+
+	return usage;
+}
+
+static int hidinput_getkeycode(struct input_dev *dev,
+			       struct input_keymap_entry *ke)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct hid_usage *usage;
+	unsigned int scancode, index;
+
+	usage = hidinput_locate_usage(hid, ke, &index);
+	if (usage) {
+		ke->keycode = usage->type == EV_KEY ?
+				usage->code : KEY_RESERVED;
+		ke->index = index;
+		scancode = usage->hid & (HID_USAGE_PAGE | HID_USAGE);
+		ke->len = sizeof(scancode);
+		memcpy(ke->scancode, &scancode, sizeof(scancode));
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int hidinput_setkeycode(struct input_dev *dev,
+			       const struct input_keymap_entry *ke,
+			       unsigned int *old_keycode)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct hid_usage *usage;
+
+	usage = hidinput_locate_usage(hid, ke, NULL);
+	if (usage) {
+		*old_keycode = usage->type == EV_KEY ?
+				usage->code : KEY_RESERVED;
+		usage->code = ke->keycode;
+
+		clear_bit(*old_keycode, dev->keybit);
+		set_bit(usage->code, dev->keybit);
+		dbg_hid("Assigned keycode %d to HID usage code %x\n",
+			usage->code, usage->hid);
+
+		/*
+		 * Set the keybit for the old keycode if the old keycode is used
+		 * by another key
+		 */
+		if (hidinput_find_key(hid, match_keycode, *old_keycode, NULL))
+			set_bit(*old_keycode, dev->keybit);
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+
+/**
+ * hidinput_calc_abs_res - calculate an absolute axis resolution
+ * @field: the HID report field to calculate resolution for
+ * @code: axis code
+ *
+ * The formula is:
+ *                         (logical_maximum - logical_minimum)
+ * resolution = ----------------------------------------------------------
+ *              (physical_maximum - physical_minimum) * 10 ^ unit_exponent
+ *
+ * as seen in the HID specification v1.11 6.2.2.7 Global Items.
+ *
+ * Only exponent 1 length units are processed. Centimeters and inches are
+ * converted to millimeters. Degrees are converted to radians.
+ */
+__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
+{
+	__s32 unit_exponent = field->unit_exponent;
+	__s32 logical_extents = field->logical_maximum -
+					field->logical_minimum;
+	__s32 physical_extents = field->physical_maximum -
+					field->physical_minimum;
+	__s32 prev;
+
+	/* Check if the extents are sane */
+	if (logical_extents <= 0 || physical_extents <= 0)
+		return 0;
+
+	/*
+	 * Verify and convert units.
+	 * See HID specification v1.11 6.2.2.7 Global Items for unit decoding
+	 */
+	switch (code) {
+	case ABS_X:
+	case ABS_Y:
+	case ABS_Z:
+	case ABS_MT_POSITION_X:
+	case ABS_MT_POSITION_Y:
+	case ABS_MT_TOOL_X:
+	case ABS_MT_TOOL_Y:
+	case ABS_MT_TOUCH_MAJOR:
+	case ABS_MT_TOUCH_MINOR:
+		if (field->unit == 0x11) {		/* If centimeters */
+			/* Convert to millimeters */
+			unit_exponent += 1;
+		} else if (field->unit == 0x13) {	/* If inches */
+			/* Convert to millimeters */
+			prev = physical_extents;
+			physical_extents *= 254;
+			if (physical_extents < prev)
+				return 0;
+			unit_exponent -= 1;
+		} else {
+			return 0;
+		}
+		break;
+
+	case ABS_RX:
+	case ABS_RY:
+	case ABS_RZ:
+	case ABS_WHEEL:
+	case ABS_TILT_X:
+	case ABS_TILT_Y:
+		if (field->unit == 0x14) {		/* If degrees */
+			/* Convert to radians */
+			prev = logical_extents;
+			logical_extents *= 573;
+			if (logical_extents < prev)
+				return 0;
+			unit_exponent += 1;
+		} else if (field->unit != 0x12) {	/* If not radians */
+			return 0;
+		}
+		break;
+
+	default:
+		return 0;
+	}
+
+	/* Apply negative unit exponent */
+	for (; unit_exponent < 0; unit_exponent++) {
+		prev = logical_extents;
+		logical_extents *= 10;
+		if (logical_extents < prev)
+			return 0;
+	}
+	/* Apply positive unit exponent */
+	for (; unit_exponent > 0; unit_exponent--) {
+		prev = physical_extents;
+		physical_extents *= 10;
+		if (physical_extents < prev)
+			return 0;
+	}
+
+	/* Calculate resolution */
+	return DIV_ROUND_CLOSEST(logical_extents, physical_extents);
+}
+EXPORT_SYMBOL_GPL(hidinput_calc_abs_res);
+
+#ifdef CONFIG_HID_BATTERY_STRENGTH
+static enum power_supply_property hidinput_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_SCOPE,
+};
+
+#define HID_BATTERY_QUIRK_PERCENT	(1 << 0) /* always reports percent */
+#define HID_BATTERY_QUIRK_FEATURE	(1 << 1) /* ask for feature report */
+#define HID_BATTERY_QUIRK_IGNORE	(1 << 2) /* completely ignore the battery */
+
+static const struct hid_device_id hid_battery_quirks[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
+	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
+	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI),
+	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+			       USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO),
+	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI),
+	  HID_BATTERY_QUIRK_PERCENT | HID_BATTERY_QUIRK_FEATURE },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM,
+		USB_DEVICE_ID_ELECOM_BM084),
+	  HID_BATTERY_QUIRK_IGNORE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL,
+		USB_DEVICE_ID_SYMBOL_SCANNER_3),
+	  HID_BATTERY_QUIRK_IGNORE },
+	{}
+};
+
+static unsigned find_battery_quirk(struct hid_device *hdev)
+{
+	unsigned quirks = 0;
+	const struct hid_device_id *match;
+
+	match = hid_match_id(hdev, hid_battery_quirks);
+	if (match != NULL)
+		quirks = match->driver_data;
+
+	return quirks;
+}
+
+static int hidinput_scale_battery_capacity(struct hid_device *dev,
+					   int value)
+{
+	if (dev->battery_min < dev->battery_max &&
+	    value >= dev->battery_min && value <= dev->battery_max)
+		value = ((value - dev->battery_min) * 100) /
+			(dev->battery_max - dev->battery_min);
+
+	return value;
+}
+
+static int hidinput_query_battery_capacity(struct hid_device *dev)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 2,
+				 dev->battery_report_type, HID_REQ_GET_REPORT);
+	if (ret != 2) {
+		kfree(buf);
+		return -ENODATA;
+	}
+
+	ret = hidinput_scale_battery_capacity(dev, buf[1]);
+	kfree(buf);
+	return ret;
+}
+
+static int hidinput_get_battery_property(struct power_supply *psy,
+					 enum power_supply_property prop,
+					 union power_supply_propval *val)
+{
+	struct hid_device *dev = power_supply_get_drvdata(psy);
+	int value;
+	int ret = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_PRESENT:
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = 1;
+		break;
+
+	case POWER_SUPPLY_PROP_CAPACITY:
+		if (dev->battery_status != HID_BATTERY_REPORTED &&
+		    !dev->battery_avoid_query) {
+			value = hidinput_query_battery_capacity(dev);
+			if (value < 0)
+				return value;
+		} else  {
+			value = dev->battery_capacity;
+		}
+
+		val->intval = value;
+		break;
+
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = dev->name;
+		break;
+
+	case POWER_SUPPLY_PROP_STATUS:
+		if (dev->battery_status != HID_BATTERY_REPORTED &&
+		    !dev->battery_avoid_query) {
+			value = hidinput_query_battery_capacity(dev);
+			if (value < 0)
+				return value;
+
+			dev->battery_capacity = value;
+			dev->battery_status = HID_BATTERY_QUERIED;
+		}
+
+		if (dev->battery_status == HID_BATTERY_UNKNOWN)
+			val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		else if (dev->battery_capacity == 100)
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+		else
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, struct hid_field *field)
+{
+	struct power_supply_desc *psy_desc;
+	struct power_supply_config psy_cfg = { .drv_data = dev, };
+	unsigned quirks;
+	s32 min, max;
+	int error;
+
+	if (dev->battery)
+		return 0;	/* already initialized? */
+
+	quirks = find_battery_quirk(dev);
+
+	hid_dbg(dev, "device %x:%x:%x %d quirks %d\n",
+		dev->bus, dev->vendor, dev->product, dev->version, quirks);
+
+	if (quirks & HID_BATTERY_QUIRK_IGNORE)
+		return 0;
+
+	psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL);
+	if (!psy_desc)
+		return -ENOMEM;
+
+	psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery",
+				   strlen(dev->uniq) ?
+					dev->uniq : dev_name(&dev->dev));
+	if (!psy_desc->name) {
+		error = -ENOMEM;
+		goto err_free_mem;
+	}
+
+	psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+	psy_desc->properties = hidinput_battery_props;
+	psy_desc->num_properties = ARRAY_SIZE(hidinput_battery_props);
+	psy_desc->use_for_apm = 0;
+	psy_desc->get_property = hidinput_get_battery_property;
+
+	min = field->logical_minimum;
+	max = field->logical_maximum;
+
+	if (quirks & HID_BATTERY_QUIRK_PERCENT) {
+		min = 0;
+		max = 100;
+	}
+
+	if (quirks & HID_BATTERY_QUIRK_FEATURE)
+		report_type = HID_FEATURE_REPORT;
+
+	dev->battery_min = min;
+	dev->battery_max = max;
+	dev->battery_report_type = report_type;
+	dev->battery_report_id = field->report->id;
+
+	/*
+	 * Stylus is normally not connected to the device and thus we
+	 * can't query the device and get meaningful battery strength.
+	 * We have to wait for the device to report it on its own.
+	 */
+	dev->battery_avoid_query = report_type == HID_INPUT_REPORT &&
+				   field->physical == HID_DG_STYLUS;
+
+	dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
+	if (IS_ERR(dev->battery)) {
+		error = PTR_ERR(dev->battery);
+		hid_warn(dev, "can't register power supply: %d\n", error);
+		goto err_free_name;
+	}
+
+	power_supply_powers(dev->battery, &dev->dev);
+	return 0;
+
+err_free_name:
+	kfree(psy_desc->name);
+err_free_mem:
+	kfree(psy_desc);
+	dev->battery = NULL;
+	return error;
+}
+
+static void hidinput_cleanup_battery(struct hid_device *dev)
+{
+	const struct power_supply_desc *psy_desc;
+
+	if (!dev->battery)
+		return;
+
+	psy_desc = dev->battery->desc;
+	power_supply_unregister(dev->battery);
+	kfree(psy_desc->name);
+	kfree(psy_desc);
+	dev->battery = NULL;
+}
+
+static void hidinput_update_battery(struct hid_device *dev, int value)
+{
+	int capacity;
+
+	if (!dev->battery)
+		return;
+
+	if (value == 0 || value < dev->battery_min || value > dev->battery_max)
+		return;
+
+	capacity = hidinput_scale_battery_capacity(dev, value);
+
+	if (dev->battery_status != HID_BATTERY_REPORTED ||
+	    capacity != dev->battery_capacity) {
+		dev->battery_capacity = capacity;
+		dev->battery_status = HID_BATTERY_REPORTED;
+		power_supply_changed(dev->battery);
+	}
+}
+#else  /* !CONFIG_HID_BATTERY_STRENGTH */
+static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
+				  struct hid_field *field)
+{
+	return 0;
+}
+
+static void hidinput_cleanup_battery(struct hid_device *dev)
+{
+}
+
+static void hidinput_update_battery(struct hid_device *dev, int value)
+{
+}
+#endif	/* CONFIG_HID_BATTERY_STRENGTH */
+
+static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field,
+				     struct hid_usage *usage)
+{
+	struct input_dev *input = hidinput->input;
+	struct hid_device *device = input_get_drvdata(input);
+	int max = 0, code;
+	unsigned long *bit = NULL;
+
+	field->hidinput = hidinput;
+
+	if (field->flags & HID_MAIN_ITEM_CONSTANT)
+		goto ignore;
+
+	/* Ignore if report count is out of bounds. */
+	if (field->report_count < 1)
+		goto ignore;
+
+	/* only LED usages are supported in output fields */
+	if (field->report_type == HID_OUTPUT_REPORT &&
+			(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
+		goto ignore;
+	}
+
+	if (device->driver->input_mapping) {
+		int ret = device->driver->input_mapping(device, hidinput, field,
+				usage, &bit, &max);
+		if (ret > 0)
+			goto mapped;
+		if (ret < 0)
+			goto ignore;
+	}
+
+	switch (usage->hid & HID_USAGE_PAGE) {
+	case HID_UP_UNDEFINED:
+		goto ignore;
+
+	case HID_UP_KEYBOARD:
+		set_bit(EV_REP, input->evbit);
+
+		if ((usage->hid & HID_USAGE) < 256) {
+			if (!hid_keyboard[usage->hid & HID_USAGE]) goto ignore;
+			map_key_clear(hid_keyboard[usage->hid & HID_USAGE]);
+		} else
+			map_key(KEY_UNKNOWN);
+
+		break;
+
+	case HID_UP_BUTTON:
+		code = ((usage->hid - 1) & HID_USAGE);
+
+		switch (field->application) {
+		case HID_GD_MOUSE:
+		case HID_GD_POINTER:  code += BTN_MOUSE; break;
+		case HID_GD_JOYSTICK:
+				if (code <= 0xf)
+					code += BTN_JOYSTICK;
+				else
+					code += BTN_TRIGGER_HAPPY - 0x10;
+				break;
+		case HID_GD_GAMEPAD:
+				if (code <= 0xf)
+					code += BTN_GAMEPAD;
+				else
+					code += BTN_TRIGGER_HAPPY - 0x10;
+				break;
+		default:
+			switch (field->physical) {
+			case HID_GD_MOUSE:
+			case HID_GD_POINTER:  code += BTN_MOUSE; break;
+			case HID_GD_JOYSTICK: code += BTN_JOYSTICK; break;
+			case HID_GD_GAMEPAD:  code += BTN_GAMEPAD; break;
+			default:              code += BTN_MISC;
+			}
+		}
+
+		map_key(code);
+		break;
+
+	case HID_UP_SIMULATION:
+		switch (usage->hid & 0xffff) {
+		case 0xba: map_abs(ABS_RUDDER);   break;
+		case 0xbb: map_abs(ABS_THROTTLE); break;
+		case 0xc4: map_abs(ABS_GAS);      break;
+		case 0xc5: map_abs(ABS_BRAKE);    break;
+		case 0xc8: map_abs(ABS_WHEEL);    break;
+		default:   goto ignore;
+		}
+		break;
+
+	case HID_UP_GENDESK:
+		if ((usage->hid & 0xf0) == 0x80) {	/* SystemControl */
+			switch (usage->hid & 0xf) {
+			case 0x1: map_key_clear(KEY_POWER);  break;
+			case 0x2: map_key_clear(KEY_SLEEP);  break;
+			case 0x3: map_key_clear(KEY_WAKEUP); break;
+			case 0x4: map_key_clear(KEY_CONTEXT_MENU); break;
+			case 0x5: map_key_clear(KEY_MENU); break;
+			case 0x6: map_key_clear(KEY_PROG1); break;
+			case 0x7: map_key_clear(KEY_HELP); break;
+			case 0x8: map_key_clear(KEY_EXIT); break;
+			case 0x9: map_key_clear(KEY_SELECT); break;
+			case 0xa: map_key_clear(KEY_RIGHT); break;
+			case 0xb: map_key_clear(KEY_LEFT); break;
+			case 0xc: map_key_clear(KEY_UP); break;
+			case 0xd: map_key_clear(KEY_DOWN); break;
+			case 0xe: map_key_clear(KEY_POWER2); break;
+			case 0xf: map_key_clear(KEY_RESTART); break;
+			default: goto unknown;
+			}
+			break;
+		}
+
+		/*
+		 * Some lazy vendors declare 255 usages for System Control,
+		 * leading to the creation of ABS_X|Y axis and too many others.
+		 * It wouldn't be a problem if joydev doesn't consider the
+		 * device as a joystick then.
+		 */
+		if (field->application == HID_GD_SYSTEM_CONTROL)
+			goto ignore;
+
+		if ((usage->hid & 0xf0) == 0x90) {	/* D-pad */
+			switch (usage->hid) {
+			case HID_GD_UP:	   usage->hat_dir = 1; break;
+			case HID_GD_DOWN:  usage->hat_dir = 5; break;
+			case HID_GD_RIGHT: usage->hat_dir = 3; break;
+			case HID_GD_LEFT:  usage->hat_dir = 7; break;
+			default: goto unknown;
+			}
+			if (field->dpad) {
+				map_abs(field->dpad);
+				goto ignore;
+			}
+			map_abs(ABS_HAT0X);
+			break;
+		}
+
+		switch (usage->hid) {
+		/* These usage IDs map directly to the usage codes. */
+		case HID_GD_X: case HID_GD_Y: case HID_GD_Z:
+		case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ:
+			if (field->flags & HID_MAIN_ITEM_RELATIVE)
+				map_rel(usage->hid & 0xf);
+			else
+				map_abs_clear(usage->hid & 0xf);
+			break;
+
+		case HID_GD_SLIDER: case HID_GD_DIAL: case HID_GD_WHEEL:
+			if (field->flags & HID_MAIN_ITEM_RELATIVE)
+				map_rel(usage->hid & 0xf);
+			else
+				map_abs(usage->hid & 0xf);
+			break;
+
+		case HID_GD_HATSWITCH:
+			usage->hat_min = field->logical_minimum;
+			usage->hat_max = field->logical_maximum;
+			map_abs(ABS_HAT0X);
+			break;
+
+		case HID_GD_START:	map_key_clear(BTN_START);	break;
+		case HID_GD_SELECT:	map_key_clear(BTN_SELECT);	break;
+
+		case HID_GD_RFKILL_BTN:
+			/* MS wireless radio ctl extension, also check CA */
+			if (field->application == HID_GD_WIRELESS_RADIO_CTLS) {
+				map_key_clear(KEY_RFKILL);
+				/* We need to simulate the btn release */
+				field->flags |= HID_MAIN_ITEM_RELATIVE;
+				break;
+			}
+
+		default: goto unknown;
+		}
+
+		break;
+
+	case HID_UP_LED:
+		switch (usage->hid & 0xffff) {		      /* HID-Value:                   */
+		case 0x01:  map_led (LED_NUML);     break;    /*   "Num Lock"                 */
+		case 0x02:  map_led (LED_CAPSL);    break;    /*   "Caps Lock"                */
+		case 0x03:  map_led (LED_SCROLLL);  break;    /*   "Scroll Lock"              */
+		case 0x04:  map_led (LED_COMPOSE);  break;    /*   "Compose"                  */
+		case 0x05:  map_led (LED_KANA);     break;    /*   "Kana"                     */
+		case 0x27:  map_led (LED_SLEEP);    break;    /*   "Stand-By"                 */
+		case 0x4c:  map_led (LED_SUSPEND);  break;    /*   "System Suspend"           */
+		case 0x09:  map_led (LED_MUTE);     break;    /*   "Mute"                     */
+		case 0x4b:  map_led (LED_MISC);     break;    /*   "Generic Indicator"        */
+		case 0x19:  map_led (LED_MAIL);     break;    /*   "Message Waiting"          */
+		case 0x4d:  map_led (LED_CHARGING); break;    /*   "External Power Connected" */
+
+		default: goto ignore;
+		}
+		break;
+
+	case HID_UP_DIGITIZER:
+		switch (usage->hid & 0xff) {
+		case 0x00: /* Undefined */
+			goto ignore;
+
+		case 0x30: /* TipPressure */
+			if (!test_bit(BTN_TOUCH, input->keybit)) {
+				device->quirks |= HID_QUIRK_NOTOUCH;
+				set_bit(EV_KEY, input->evbit);
+				set_bit(BTN_TOUCH, input->keybit);
+			}
+			map_abs_clear(ABS_PRESSURE);
+			break;
+
+		case 0x32: /* InRange */
+			switch (field->physical & 0xff) {
+			case 0x21: map_key(BTN_TOOL_MOUSE); break;
+			case 0x22: map_key(BTN_TOOL_FINGER); break;
+			default: map_key(BTN_TOOL_PEN); break;
+			}
+			break;
+
+		case 0x3b: /* Battery Strength */
+			hidinput_setup_battery(device, HID_INPUT_REPORT, field);
+			usage->type = EV_PWR;
+			goto ignore;
+
+		case 0x3c: /* Invert */
+			map_key_clear(BTN_TOOL_RUBBER);
+			break;
+
+		case 0x3d: /* X Tilt */
+			map_abs_clear(ABS_TILT_X);
+			break;
+
+		case 0x3e: /* Y Tilt */
+			map_abs_clear(ABS_TILT_Y);
+			break;
+
+		case 0x33: /* Touch */
+		case 0x42: /* TipSwitch */
+		case 0x43: /* TipSwitch2 */
+			device->quirks &= ~HID_QUIRK_NOTOUCH;
+			map_key_clear(BTN_TOUCH);
+			break;
+
+		case 0x44: /* BarrelSwitch */
+			map_key_clear(BTN_STYLUS);
+			break;
+
+		case 0x45: /* ERASER */
+			/*
+			 * This event is reported when eraser tip touches the surface.
+			 * Actual eraser (BTN_TOOL_RUBBER) is set by Invert usage when
+			 * tool gets in proximity.
+			 */
+			map_key_clear(BTN_TOUCH);
+			break;
+
+		case 0x46: /* TabletPick */
+		case 0x5a: /* SecondaryBarrelSwitch */
+			map_key_clear(BTN_STYLUS2);
+			break;
+
+		case 0x5b: /* TransducerSerialNumber */
+			usage->type = EV_MSC;
+			usage->code = MSC_SERIAL;
+			bit = input->mscbit;
+			max = MSC_MAX;
+			break;
+
+		default:  goto unknown;
+		}
+		break;
+
+	case HID_UP_TELEPHONY:
+		switch (usage->hid & HID_USAGE) {
+		case 0x2f: map_key_clear(KEY_MICMUTE);		break;
+		case 0xb0: map_key_clear(KEY_NUMERIC_0);	break;
+		case 0xb1: map_key_clear(KEY_NUMERIC_1);	break;
+		case 0xb2: map_key_clear(KEY_NUMERIC_2);	break;
+		case 0xb3: map_key_clear(KEY_NUMERIC_3);	break;
+		case 0xb4: map_key_clear(KEY_NUMERIC_4);	break;
+		case 0xb5: map_key_clear(KEY_NUMERIC_5);	break;
+		case 0xb6: map_key_clear(KEY_NUMERIC_6);	break;
+		case 0xb7: map_key_clear(KEY_NUMERIC_7);	break;
+		case 0xb8: map_key_clear(KEY_NUMERIC_8);	break;
+		case 0xb9: map_key_clear(KEY_NUMERIC_9);	break;
+		case 0xba: map_key_clear(KEY_NUMERIC_STAR);	break;
+		case 0xbb: map_key_clear(KEY_NUMERIC_POUND);	break;
+		case 0xbc: map_key_clear(KEY_NUMERIC_A);	break;
+		case 0xbd: map_key_clear(KEY_NUMERIC_B);	break;
+		case 0xbe: map_key_clear(KEY_NUMERIC_C);	break;
+		case 0xbf: map_key_clear(KEY_NUMERIC_D);	break;
+		default: goto ignore;
+		}
+		break;
+
+	case HID_UP_CONSUMER:	/* USB HUT v1.12, pages 75-84 */
+		switch (usage->hid & HID_USAGE) {
+		case 0x000: goto ignore;
+		case 0x030: map_key_clear(KEY_POWER);		break;
+		case 0x031: map_key_clear(KEY_RESTART);		break;
+		case 0x032: map_key_clear(KEY_SLEEP);		break;
+		case 0x034: map_key_clear(KEY_SLEEP);		break;
+		case 0x035: map_key_clear(KEY_KBDILLUMTOGGLE);	break;
+		case 0x036: map_key_clear(BTN_MISC);		break;
+
+		case 0x040: map_key_clear(KEY_MENU);		break; /* Menu */
+		case 0x041: map_key_clear(KEY_SELECT);		break; /* Menu Pick */
+		case 0x042: map_key_clear(KEY_UP);		break; /* Menu Up */
+		case 0x043: map_key_clear(KEY_DOWN);		break; /* Menu Down */
+		case 0x044: map_key_clear(KEY_LEFT);		break; /* Menu Left */
+		case 0x045: map_key_clear(KEY_RIGHT);		break; /* Menu Right */
+		case 0x046: map_key_clear(KEY_ESC);		break; /* Menu Escape */
+		case 0x047: map_key_clear(KEY_KPPLUS);		break; /* Menu Value Increase */
+		case 0x048: map_key_clear(KEY_KPMINUS);		break; /* Menu Value Decrease */
+
+		case 0x060: map_key_clear(KEY_INFO);		break; /* Data On Screen */
+		case 0x061: map_key_clear(KEY_SUBTITLE);	break; /* Closed Caption */
+		case 0x063: map_key_clear(KEY_VCR);		break; /* VCR/TV */
+		case 0x065: map_key_clear(KEY_CAMERA);		break; /* Snapshot */
+		case 0x069: map_key_clear(KEY_RED);		break;
+		case 0x06a: map_key_clear(KEY_GREEN);		break;
+		case 0x06b: map_key_clear(KEY_BLUE);		break;
+		case 0x06c: map_key_clear(KEY_YELLOW);		break;
+		case 0x06d: map_key_clear(KEY_ZOOM);		break;
+
+		case 0x06f: map_key_clear(KEY_BRIGHTNESSUP);		break;
+		case 0x070: map_key_clear(KEY_BRIGHTNESSDOWN);		break;
+		case 0x072: map_key_clear(KEY_BRIGHTNESS_TOGGLE);	break;
+		case 0x073: map_key_clear(KEY_BRIGHTNESS_MIN);		break;
+		case 0x074: map_key_clear(KEY_BRIGHTNESS_MAX);		break;
+		case 0x075: map_key_clear(KEY_BRIGHTNESS_AUTO);		break;
+
+		case 0x082: map_key_clear(KEY_VIDEO_NEXT);	break;
+		case 0x083: map_key_clear(KEY_LAST);		break;
+		case 0x084: map_key_clear(KEY_ENTER);		break;
+		case 0x088: map_key_clear(KEY_PC);		break;
+		case 0x089: map_key_clear(KEY_TV);		break;
+		case 0x08a: map_key_clear(KEY_WWW);		break;
+		case 0x08b: map_key_clear(KEY_DVD);		break;
+		case 0x08c: map_key_clear(KEY_PHONE);		break;
+		case 0x08d: map_key_clear(KEY_PROGRAM);		break;
+		case 0x08e: map_key_clear(KEY_VIDEOPHONE);	break;
+		case 0x08f: map_key_clear(KEY_GAMES);		break;
+		case 0x090: map_key_clear(KEY_MEMO);		break;
+		case 0x091: map_key_clear(KEY_CD);		break;
+		case 0x092: map_key_clear(KEY_VCR);		break;
+		case 0x093: map_key_clear(KEY_TUNER);		break;
+		case 0x094: map_key_clear(KEY_EXIT);		break;
+		case 0x095: map_key_clear(KEY_HELP);		break;
+		case 0x096: map_key_clear(KEY_TAPE);		break;
+		case 0x097: map_key_clear(KEY_TV2);		break;
+		case 0x098: map_key_clear(KEY_SAT);		break;
+		case 0x09a: map_key_clear(KEY_PVR);		break;
+
+		case 0x09c: map_key_clear(KEY_CHANNELUP);	break;
+		case 0x09d: map_key_clear(KEY_CHANNELDOWN);	break;
+		case 0x0a0: map_key_clear(KEY_VCR2);		break;
+
+		case 0x0b0: map_key_clear(KEY_PLAY);		break;
+		case 0x0b1: map_key_clear(KEY_PAUSE);		break;
+		case 0x0b2: map_key_clear(KEY_RECORD);		break;
+		case 0x0b3: map_key_clear(KEY_FASTFORWARD);	break;
+		case 0x0b4: map_key_clear(KEY_REWIND);		break;
+		case 0x0b5: map_key_clear(KEY_NEXTSONG);	break;
+		case 0x0b6: map_key_clear(KEY_PREVIOUSSONG);	break;
+		case 0x0b7: map_key_clear(KEY_STOPCD);		break;
+		case 0x0b8: map_key_clear(KEY_EJECTCD);		break;
+		case 0x0bc: map_key_clear(KEY_MEDIA_REPEAT);	break;
+		case 0x0b9: map_key_clear(KEY_SHUFFLE);		break;
+		case 0x0bf: map_key_clear(KEY_SLOW);		break;
+
+		case 0x0cd: map_key_clear(KEY_PLAYPAUSE);	break;
+		case 0x0cf: map_key_clear(KEY_VOICECOMMAND);	break;
+		case 0x0e0: map_abs_clear(ABS_VOLUME);		break;
+		case 0x0e2: map_key_clear(KEY_MUTE);		break;
+		case 0x0e5: map_key_clear(KEY_BASSBOOST);	break;
+		case 0x0e9: map_key_clear(KEY_VOLUMEUP);	break;
+		case 0x0ea: map_key_clear(KEY_VOLUMEDOWN);	break;
+		case 0x0f5: map_key_clear(KEY_SLOW);		break;
+
+		case 0x181: map_key_clear(KEY_BUTTONCONFIG);	break;
+		case 0x182: map_key_clear(KEY_BOOKMARKS);	break;
+		case 0x183: map_key_clear(KEY_CONFIG);		break;
+		case 0x184: map_key_clear(KEY_WORDPROCESSOR);	break;
+		case 0x185: map_key_clear(KEY_EDITOR);		break;
+		case 0x186: map_key_clear(KEY_SPREADSHEET);	break;
+		case 0x187: map_key_clear(KEY_GRAPHICSEDITOR);	break;
+		case 0x188: map_key_clear(KEY_PRESENTATION);	break;
+		case 0x189: map_key_clear(KEY_DATABASE);	break;
+		case 0x18a: map_key_clear(KEY_MAIL);		break;
+		case 0x18b: map_key_clear(KEY_NEWS);		break;
+		case 0x18c: map_key_clear(KEY_VOICEMAIL);	break;
+		case 0x18d: map_key_clear(KEY_ADDRESSBOOK);	break;
+		case 0x18e: map_key_clear(KEY_CALENDAR);	break;
+		case 0x18f: map_key_clear(KEY_TASKMANAGER);	break;
+		case 0x190: map_key_clear(KEY_JOURNAL);		break;
+		case 0x191: map_key_clear(KEY_FINANCE);		break;
+		case 0x192: map_key_clear(KEY_CALC);		break;
+		case 0x193: map_key_clear(KEY_PLAYER);		break;
+		case 0x194: map_key_clear(KEY_FILE);		break;
+		case 0x196: map_key_clear(KEY_WWW);		break;
+		case 0x199: map_key_clear(KEY_CHAT);		break;
+		case 0x19c: map_key_clear(KEY_LOGOFF);		break;
+		case 0x19e: map_key_clear(KEY_COFFEE);		break;
+		case 0x19f: map_key_clear(KEY_CONTROLPANEL);		break;
+		case 0x1a2: map_key_clear(KEY_APPSELECT);		break;
+		case 0x1a3: map_key_clear(KEY_NEXT);		break;
+		case 0x1a4: map_key_clear(KEY_PREVIOUS);	break;
+		case 0x1a6: map_key_clear(KEY_HELP);		break;
+		case 0x1a7: map_key_clear(KEY_DOCUMENTS);	break;
+		case 0x1ab: map_key_clear(KEY_SPELLCHECK);	break;
+		case 0x1ae: map_key_clear(KEY_KEYBOARD);	break;
+		case 0x1b1: map_key_clear(KEY_SCREENSAVER);		break;
+		case 0x1b4: map_key_clear(KEY_FILE);		break;
+		case 0x1b6: map_key_clear(KEY_IMAGES);		break;
+		case 0x1b7: map_key_clear(KEY_AUDIO);		break;
+		case 0x1b8: map_key_clear(KEY_VIDEO);		break;
+		case 0x1bc: map_key_clear(KEY_MESSENGER);	break;
+		case 0x1bd: map_key_clear(KEY_INFO);		break;
+		case 0x201: map_key_clear(KEY_NEW);		break;
+		case 0x202: map_key_clear(KEY_OPEN);		break;
+		case 0x203: map_key_clear(KEY_CLOSE);		break;
+		case 0x204: map_key_clear(KEY_EXIT);		break;
+		case 0x207: map_key_clear(KEY_SAVE);		break;
+		case 0x208: map_key_clear(KEY_PRINT);		break;
+		case 0x209: map_key_clear(KEY_PROPS);		break;
+		case 0x21a: map_key_clear(KEY_UNDO);		break;
+		case 0x21b: map_key_clear(KEY_COPY);		break;
+		case 0x21c: map_key_clear(KEY_CUT);		break;
+		case 0x21d: map_key_clear(KEY_PASTE);		break;
+		case 0x21f: map_key_clear(KEY_FIND);		break;
+		case 0x221: map_key_clear(KEY_SEARCH);		break;
+		case 0x222: map_key_clear(KEY_GOTO);		break;
+		case 0x223: map_key_clear(KEY_HOMEPAGE);	break;
+		case 0x224: map_key_clear(KEY_BACK);		break;
+		case 0x225: map_key_clear(KEY_FORWARD);		break;
+		case 0x226: map_key_clear(KEY_STOP);		break;
+		case 0x227: map_key_clear(KEY_REFRESH);		break;
+		case 0x22a: map_key_clear(KEY_BOOKMARKS);	break;
+		case 0x22d: map_key_clear(KEY_ZOOMIN);		break;
+		case 0x22e: map_key_clear(KEY_ZOOMOUT);		break;
+		case 0x22f: map_key_clear(KEY_ZOOMRESET);	break;
+		case 0x233: map_key_clear(KEY_SCROLLUP);	break;
+		case 0x234: map_key_clear(KEY_SCROLLDOWN);	break;
+		case 0x238: map_rel(REL_HWHEEL);		break;
+		case 0x23d: map_key_clear(KEY_EDIT);		break;
+		case 0x25f: map_key_clear(KEY_CANCEL);		break;
+		case 0x269: map_key_clear(KEY_INSERT);		break;
+		case 0x26a: map_key_clear(KEY_DELETE);		break;
+		case 0x279: map_key_clear(KEY_REDO);		break;
+
+		case 0x289: map_key_clear(KEY_REPLY);		break;
+		case 0x28b: map_key_clear(KEY_FORWARDMAIL);	break;
+		case 0x28c: map_key_clear(KEY_SEND);		break;
+
+		case 0x2c7: map_key_clear(KEY_KBDINPUTASSIST_PREV);		break;
+		case 0x2c8: map_key_clear(KEY_KBDINPUTASSIST_NEXT);		break;
+		case 0x2c9: map_key_clear(KEY_KBDINPUTASSIST_PREVGROUP);		break;
+		case 0x2ca: map_key_clear(KEY_KBDINPUTASSIST_NEXTGROUP);		break;
+		case 0x2cb: map_key_clear(KEY_KBDINPUTASSIST_ACCEPT);	break;
+		case 0x2cc: map_key_clear(KEY_KBDINPUTASSIST_CANCEL);	break;
+
+		default: map_key_clear(KEY_UNKNOWN);
+		}
+		break;
+
+	case HID_UP_GENDEVCTRLS:
+		switch (usage->hid) {
+		case HID_DC_BATTERYSTRENGTH:
+			hidinput_setup_battery(device, HID_INPUT_REPORT, field);
+			usage->type = EV_PWR;
+			goto ignore;
+		}
+		goto unknown;
+
+	case HID_UP_HPVENDOR:	/* Reported on a Dutch layout HP5308 */
+		set_bit(EV_REP, input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x021: map_key_clear(KEY_PRINT);           break;
+		case 0x070: map_key_clear(KEY_HP);		break;
+		case 0x071: map_key_clear(KEY_CAMERA);		break;
+		case 0x072: map_key_clear(KEY_SOUND);		break;
+		case 0x073: map_key_clear(KEY_QUESTION);	break;
+		case 0x080: map_key_clear(KEY_EMAIL);		break;
+		case 0x081: map_key_clear(KEY_CHAT);		break;
+		case 0x082: map_key_clear(KEY_SEARCH);		break;
+		case 0x083: map_key_clear(KEY_CONNECT);	        break;
+		case 0x084: map_key_clear(KEY_FINANCE);		break;
+		case 0x085: map_key_clear(KEY_SPORT);		break;
+		case 0x086: map_key_clear(KEY_SHOP);	        break;
+		default:    goto ignore;
+		}
+		break;
+
+	case HID_UP_HPVENDOR2:
+		set_bit(EV_REP, input->evbit);
+		switch (usage->hid & HID_USAGE) {
+		case 0x001: map_key_clear(KEY_MICMUTE);		break;
+		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;
+
+	case HID_UP_CUSTOM: /* Reported on Logitech and Apple USB keyboards */
+		set_bit(EV_REP, input->evbit);
+		goto ignore;
+
+	case HID_UP_LOGIVENDOR:
+		/* intentional fallback */
+	case HID_UP_LOGIVENDOR2:
+		/* intentional fallback */
+	case HID_UP_LOGIVENDOR3:
+		goto ignore;
+
+	case HID_UP_PID:
+		switch (usage->hid & HID_USAGE) {
+		case 0xa4: map_key_clear(BTN_DEAD);	break;
+		default: goto ignore;
+		}
+		break;
+
+	default:
+	unknown:
+		if (field->report_size == 1) {
+			if (field->report->type == HID_OUTPUT_REPORT) {
+				map_led(LED_MISC);
+				break;
+			}
+			map_key(BTN_MISC);
+			break;
+		}
+		if (field->flags & HID_MAIN_ITEM_RELATIVE) {
+			map_rel(REL_MISC);
+			break;
+		}
+		map_abs(ABS_MISC);
+		break;
+	}
+
+mapped:
+	if (device->driver->input_mapped && device->driver->input_mapped(device,
+				hidinput, field, usage, &bit, &max) < 0)
+		goto ignore;
+
+	set_bit(usage->type, input->evbit);
+
+	/*
+	 * This part is *really* controversial:
+	 * - HID aims at being generic so we should do our best to export
+	 *   all incoming events
+	 * - HID describes what events are, so there is no reason for ABS_X
+	 *   to be mapped to ABS_Y
+	 * - HID is using *_MISC+N as a default value, but nothing prevents
+	 *   *_MISC+N to overwrite a legitimate even, which confuses userspace
+	 *   (for instance ABS_MISC + 7 is ABS_MT_SLOT, which has a different
+	 *   processing)
+	 *
+	 * If devices still want to use this (at their own risk), they will
+	 * have to use the quirk HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE, but
+	 * the default should be a reliable mapping.
+	 */
+	while (usage->code <= max && test_and_set_bit(usage->code, bit)) {
+		if (device->quirks & HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE) {
+			usage->code = find_next_zero_bit(bit,
+							 max + 1,
+							 usage->code);
+		} else {
+			device->status |= HID_STAT_DUP_DETECTED;
+			goto ignore;
+		}
+	}
+
+	if (usage->code > max)
+		goto ignore;
+
+	if (usage->type == EV_ABS) {
+
+		int a = field->logical_minimum;
+		int b = field->logical_maximum;
+
+		if ((device->quirks & HID_QUIRK_BADPAD) && (usage->code == ABS_X || usage->code == ABS_Y)) {
+			a = field->logical_minimum = 0;
+			b = field->logical_maximum = 255;
+		}
+
+		if (field->application == HID_GD_GAMEPAD || field->application == HID_GD_JOYSTICK)
+			input_set_abs_params(input, usage->code, a, b, (b - a) >> 8, (b - a) >> 4);
+		else	input_set_abs_params(input, usage->code, a, b, 0, 0);
+
+		input_abs_set_res(input, usage->code,
+				  hidinput_calc_abs_res(field, usage->code));
+
+		/* use a larger default input buffer for MT devices */
+		if (usage->code == ABS_MT_POSITION_X && input->hint_events_per_packet == 0)
+			input_set_events_per_packet(input, 60);
+	}
+
+	if (usage->type == EV_ABS &&
+	    (usage->hat_min < usage->hat_max || usage->hat_dir)) {
+		int i;
+		for (i = usage->code; i < usage->code + 2 && i <= max; i++) {
+			input_set_abs_params(input, i, -1, 1, 0, 0);
+			set_bit(i, input->absbit);
+		}
+		if (usage->hat_dir && !field->dpad)
+			field->dpad = usage->code;
+	}
+
+	/* for those devices which produce Consumer volume usage as relative,
+	 * we emulate pressing volumeup/volumedown appropriate number of times
+	 * in hidinput_hid_event()
+	 */
+	if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) &&
+			(usage->code == ABS_VOLUME)) {
+		set_bit(KEY_VOLUMEUP, input->keybit);
+		set_bit(KEY_VOLUMEDOWN, input->keybit);
+	}
+
+	if (usage->type == EV_KEY) {
+		set_bit(EV_MSC, input->evbit);
+		set_bit(MSC_SCAN, input->mscbit);
+	}
+
+ignore:
+	return;
+
+}
+
+void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+	struct input_dev *input;
+	unsigned *quirks = &hid->quirks;
+
+	if (!usage->type)
+		return;
+
+	if (usage->type == EV_PWR) {
+		hidinput_update_battery(hid, value);
+		return;
+	}
+
+	if (!field->hidinput)
+		return;
+
+	input = field->hidinput->input;
+
+	if (usage->hat_min < usage->hat_max || usage->hat_dir) {
+		int hat_dir = usage->hat_dir;
+		if (!hat_dir)
+			hat_dir = (value - usage->hat_min) * 8 / (usage->hat_max - usage->hat_min + 1) + 1;
+		if (hat_dir < 0 || hat_dir > 8) hat_dir = 0;
+		input_event(input, usage->type, usage->code    , hid_hat_to_axis[hat_dir].x);
+		input_event(input, usage->type, usage->code + 1, hid_hat_to_axis[hat_dir].y);
+		return;
+	}
+
+	if (usage->hid == (HID_UP_DIGITIZER | 0x003c)) { /* Invert */
+		*quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT);
+		return;
+	}
+
+	if (usage->hid == (HID_UP_DIGITIZER | 0x0032)) { /* InRange */
+		if (value) {
+			input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1);
+			return;
+		}
+		input_event(input, usage->type, usage->code, 0);
+		input_event(input, usage->type, BTN_TOOL_RUBBER, 0);
+		return;
+	}
+
+	if (usage->hid == (HID_UP_DIGITIZER | 0x0030) && (*quirks & HID_QUIRK_NOTOUCH)) { /* Pressure */
+		int a = field->logical_minimum;
+		int b = field->logical_maximum;
+		input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3));
+	}
+
+	if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */
+		dbg_hid("Maximum Effects - %d\n",value);
+		return;
+	}
+
+	if (usage->hid == (HID_UP_PID | 0x7fUL)) {
+		dbg_hid("PID Pool Report\n");
+		return;
+	}
+
+	if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */
+		return;
+
+	if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) &&
+			(usage->code == ABS_VOLUME)) {
+		int count = abs(value);
+		int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN;
+		int i;
+
+		for (i = 0; i < count; i++) {
+			input_event(input, EV_KEY, direction, 1);
+			input_sync(input);
+			input_event(input, EV_KEY, direction, 0);
+			input_sync(input);
+		}
+		return;
+	}
+
+	/*
+	 * Ignore out-of-range values as per HID specification,
+	 * section 5.10 and 6.2.25, when NULL state bit is present.
+	 * When it's not, clamp the value to match Microsoft's input
+	 * driver as mentioned in "Required HID usages for digitizers":
+	 * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp
+	 *
+	 * The logical_minimum < logical_maximum check is done so that we
+	 * don't unintentionally discard values sent by devices which
+	 * don't specify logical min and max.
+	 */
+	if ((field->flags & HID_MAIN_ITEM_VARIABLE) &&
+	    (field->logical_minimum < field->logical_maximum)) {
+		if (field->flags & HID_MAIN_ITEM_NULL_STATE &&
+		    (value < field->logical_minimum ||
+		     value > field->logical_maximum)) {
+			dbg_hid("Ignoring out-of-range value %x\n", value);
+			return;
+		}
+		value = clamp(value,
+			      field->logical_minimum,
+			      field->logical_maximum);
+	}
+
+	/*
+	 * Ignore reports for absolute data if the data didn't change. This is
+	 * not only an optimization but also fixes 'dead' key reports. Some
+	 * RollOver implementations for localized keys (like BACKSLASH/PIPE; HID
+	 * 0x31 and 0x32) report multiple keys, even though a localized keyboard
+	 * can only have one of them physically available. The 'dead' keys
+	 * report constant 0. As all map to the same keycode, they'd confuse
+	 * the input layer. If we filter the 'dead' keys on the HID level, we
+	 * skip the keycode translation and only forward real events.
+	 */
+	if (!(field->flags & (HID_MAIN_ITEM_RELATIVE |
+	                      HID_MAIN_ITEM_BUFFERED_BYTE)) &&
+			      (field->flags & HID_MAIN_ITEM_VARIABLE) &&
+	    usage->usage_index < field->maxusage &&
+	    value == field->value[usage->usage_index])
+		return;
+
+	/* report the usage code as scancode if the key status has changed */
+	if (usage->type == EV_KEY &&
+	    (!test_bit(usage->code, input->key)) == value)
+		input_event(input, EV_MSC, MSC_SCAN, usage->hid);
+
+	input_event(input, usage->type, usage->code, value);
+
+	if ((field->flags & HID_MAIN_ITEM_RELATIVE) &&
+	    usage->type == EV_KEY && value) {
+		input_sync(input);
+		input_event(input, usage->type, usage->code, 0);
+	}
+}
+
+void hidinput_report_event(struct hid_device *hid, struct hid_report *report)
+{
+	struct hid_input *hidinput;
+
+	if (hid->quirks & HID_QUIRK_NO_INPUT_SYNC)
+		return;
+
+	list_for_each_entry(hidinput, &hid->inputs, list)
+		input_sync(hidinput->input);
+}
+EXPORT_SYMBOL_GPL(hidinput_report_event);
+
+int hidinput_find_field(struct hid_device *hid, unsigned int type, unsigned int code, struct hid_field **field)
+{
+	struct hid_report *report;
+	int i, j;
+
+	list_for_each_entry(report, &hid->report_enum[HID_OUTPUT_REPORT].report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			*field = report->field[i];
+			for (j = 0; j < (*field)->maxusage; j++)
+				if ((*field)->usage[j].type == type && (*field)->usage[j].code == code)
+					return j;
+		}
+	}
+	return -1;
+}
+EXPORT_SYMBOL_GPL(hidinput_find_field);
+
+struct hid_field *hidinput_get_led_field(struct hid_device *hid)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	int i, j;
+
+	list_for_each_entry(report,
+			    &hid->report_enum[HID_OUTPUT_REPORT].report_list,
+			    list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++)
+				if (field->usage[j].type == EV_LED)
+					return field;
+		}
+	}
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(hidinput_get_led_field);
+
+unsigned int hidinput_count_leds(struct hid_device *hid)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	int i, j;
+	unsigned int count = 0;
+
+	list_for_each_entry(report,
+			    &hid->report_enum[HID_OUTPUT_REPORT].report_list,
+			    list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++)
+				if (field->usage[j].type == EV_LED &&
+				    field->value[j])
+					count += 1;
+		}
+	}
+	return count;
+}
+EXPORT_SYMBOL_GPL(hidinput_count_leds);
+
+static void hidinput_led_worker(struct work_struct *work)
+{
+	struct hid_device *hid = container_of(work, struct hid_device,
+					      led_work);
+	struct hid_field *field;
+	struct hid_report *report;
+	int ret;
+	u32 len;
+	__u8 *buf;
+
+	field = hidinput_get_led_field(hid);
+	if (!field)
+		return;
+
+	/*
+	 * field->report is accessed unlocked regarding HID core. So there might
+	 * be another incoming SET-LED request from user-space, which changes
+	 * the LED state while we assemble our outgoing buffer. However, this
+	 * doesn't matter as hid_output_report() correctly converts it into a
+	 * boolean value no matter what information is currently set on the LED
+	 * field (even garbage). So the remote device will always get a valid
+	 * request.
+	 * And in case we send a wrong value, a next led worker is spawned
+	 * for every SET-LED request so the following worker will send the
+	 * correct value, guaranteed!
+	 */
+
+	report = field->report;
+
+	/* use custom SET_REPORT request if possible (asynchronous) */
+	if (hid->ll_driver->request)
+		return hid->ll_driver->request(hid, report, HID_REQ_SET_REPORT);
+
+	/* fall back to generic raw-output-report */
+	len = hid_report_len(report);
+	buf = hid_alloc_report_buf(report, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	hid_output_report(report, buf);
+	/* synchronous output report */
+	ret = hid_hw_output_report(hid, buf, len);
+	if (ret == -ENOSYS)
+		hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT,
+				HID_REQ_SET_REPORT);
+	kfree(buf);
+}
+
+static int hidinput_input_event(struct input_dev *dev, unsigned int type,
+				unsigned int code, int value)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct hid_field *field;
+	int offset;
+
+	if (type == EV_FF)
+		return input_ff_event(dev, type, code, value);
+
+	if (type != EV_LED)
+		return -1;
+
+	if ((offset = hidinput_find_field(hid, type, code, &field)) == -1) {
+		hid_warn(dev, "event field not found\n");
+		return -1;
+	}
+
+	hid_set_field(field, offset, value);
+
+	schedule_work(&hid->led_work);
+	return 0;
+}
+
+static int hidinput_open(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	return hid_hw_open(hid);
+}
+
+static void hidinput_close(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	hid_hw_close(hid);
+}
+
+static void report_features(struct hid_device *hid)
+{
+	struct hid_driver *drv = hid->driver;
+	struct hid_report_enum *rep_enum;
+	struct hid_report *rep;
+	struct hid_usage *usage;
+	int i, j;
+
+	rep_enum = &hid->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(rep, &rep_enum->report_list, list)
+		for (i = 0; i < rep->maxfield; i++) {
+			/* Ignore if report count is out of bounds. */
+			if (rep->field[i]->report_count < 1)
+				continue;
+
+			for (j = 0; j < rep->field[i]->maxusage; j++) {
+				usage = &rep->field[i]->usage[j];
+
+				/* Verify if Battery Strength feature is available */
+				if (usage->hid == HID_DC_BATTERYSTRENGTH)
+					hidinput_setup_battery(hid, HID_FEATURE_REPORT,
+							       rep->field[i]);
+
+				if (drv->feature_mapping)
+					drv->feature_mapping(hid, rep->field[i], usage);
+			}
+		}
+}
+
+static struct hid_input *hidinput_allocate(struct hid_device *hid,
+					   unsigned int application)
+{
+	struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL);
+	struct input_dev *input_dev = input_allocate_device();
+	const char *suffix = NULL;
+
+	if (!hidinput || !input_dev)
+		goto fail;
+
+	if ((hid->quirks & HID_QUIRK_INPUT_PER_APP) &&
+	    hid->maxapplication > 1) {
+		switch (application) {
+		case HID_GD_KEYBOARD:
+			suffix = "Keyboard";
+			break;
+		case HID_GD_KEYPAD:
+			suffix = "Keypad";
+			break;
+		case HID_GD_MOUSE:
+			suffix = "Mouse";
+			break;
+		case HID_DG_STYLUS:
+			suffix = "Pen";
+			break;
+		case HID_DG_TOUCHSCREEN:
+			suffix = "Touchscreen";
+			break;
+		case HID_DG_TOUCHPAD:
+			suffix = "Touchpad";
+			break;
+		case HID_GD_SYSTEM_CONTROL:
+			suffix = "System Control";
+			break;
+		case HID_CP_CONSUMER_CONTROL:
+			suffix = "Consumer Control";
+			break;
+		case HID_GD_WIRELESS_RADIO_CTLS:
+			suffix = "Wireless Radio Control";
+			break;
+		case HID_GD_SYSTEM_MULTIAXIS:
+			suffix = "System Multi Axis";
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (suffix) {
+		hidinput->name = kasprintf(GFP_KERNEL, "%s %s",
+					   hid->name, suffix);
+		if (!hidinput->name)
+			goto fail;
+	}
+
+	input_set_drvdata(input_dev, hid);
+	input_dev->event = hidinput_input_event;
+	input_dev->open = hidinput_open;
+	input_dev->close = hidinput_close;
+	input_dev->setkeycode = hidinput_setkeycode;
+	input_dev->getkeycode = hidinput_getkeycode;
+
+	input_dev->name = hidinput->name ? hidinput->name : hid->name;
+	input_dev->phys = hid->phys;
+	input_dev->uniq = hid->uniq;
+	input_dev->id.bustype = hid->bus;
+	input_dev->id.vendor  = hid->vendor;
+	input_dev->id.product = hid->product;
+	input_dev->id.version = hid->version;
+	input_dev->dev.parent = &hid->dev;
+
+	hidinput->input = input_dev;
+	hidinput->application = application;
+	list_add_tail(&hidinput->list, &hid->inputs);
+
+	INIT_LIST_HEAD(&hidinput->reports);
+
+	return hidinput;
+
+fail:
+	kfree(hidinput);
+	input_free_device(input_dev);
+	hid_err(hid, "Out of memory during hid input probe\n");
+	return NULL;
+}
+
+static bool hidinput_has_been_populated(struct hid_input *hidinput)
+{
+	int i;
+	unsigned long r = 0;
+
+	for (i = 0; i < BITS_TO_LONGS(EV_CNT); i++)
+		r |= hidinput->input->evbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(KEY_CNT); i++)
+		r |= hidinput->input->keybit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(REL_CNT); i++)
+		r |= hidinput->input->relbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(ABS_CNT); i++)
+		r |= hidinput->input->absbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(MSC_CNT); i++)
+		r |= hidinput->input->mscbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(LED_CNT); i++)
+		r |= hidinput->input->ledbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(SND_CNT); i++)
+		r |= hidinput->input->sndbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(FF_CNT); i++)
+		r |= hidinput->input->ffbit[i];
+
+	for (i = 0; i < BITS_TO_LONGS(SW_CNT); i++)
+		r |= hidinput->input->swbit[i];
+
+	return !!r;
+}
+
+static void hidinput_cleanup_hidinput(struct hid_device *hid,
+		struct hid_input *hidinput)
+{
+	struct hid_report *report;
+	int i, k;
+
+	list_del(&hidinput->list);
+	input_free_device(hidinput->input);
+	kfree(hidinput->name);
+
+	for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) {
+		if (k == HID_OUTPUT_REPORT &&
+			hid->quirks & HID_QUIRK_SKIP_OUTPUT_REPORTS)
+			continue;
+
+		list_for_each_entry(report, &hid->report_enum[k].report_list,
+				    list) {
+
+			for (i = 0; i < report->maxfield; i++)
+				if (report->field[i]->hidinput == hidinput)
+					report->field[i]->hidinput = NULL;
+		}
+	}
+
+	kfree(hidinput);
+}
+
+static struct hid_input *hidinput_match(struct hid_report *report)
+{
+	struct hid_device *hid = report->device;
+	struct hid_input *hidinput;
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		if (hidinput->report &&
+		    hidinput->report->id == report->id)
+			return hidinput;
+	}
+
+	return NULL;
+}
+
+static struct hid_input *hidinput_match_application(struct hid_report *report)
+{
+	struct hid_device *hid = report->device;
+	struct hid_input *hidinput;
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		if (hidinput->application == report->application)
+			return hidinput;
+	}
+
+	return NULL;
+}
+
+static inline void hidinput_configure_usages(struct hid_input *hidinput,
+					     struct hid_report *report)
+{
+	int i, j;
+
+	for (i = 0; i < report->maxfield; i++)
+		for (j = 0; j < report->field[i]->maxusage; j++)
+			hidinput_configure_usage(hidinput, report->field[i],
+						 report->field[i]->usage + j);
+}
+
+/*
+ * Register the input device; print a message.
+ * Configure the input layer interface
+ * Read all reports and initialize the absolute field values.
+ */
+
+int hidinput_connect(struct hid_device *hid, unsigned int force)
+{
+	struct hid_driver *drv = hid->driver;
+	struct hid_report *report;
+	struct hid_input *next, *hidinput = NULL;
+	unsigned int application;
+	int i, k;
+
+	INIT_LIST_HEAD(&hid->inputs);
+	INIT_WORK(&hid->led_work, hidinput_led_worker);
+
+	hid->status &= ~HID_STAT_DUP_DETECTED;
+
+	if (!force) {
+		for (i = 0; i < hid->maxcollection; i++) {
+			struct hid_collection *col = &hid->collection[i];
+			if (col->type == HID_COLLECTION_APPLICATION ||
+					col->type == HID_COLLECTION_PHYSICAL)
+				if (IS_INPUT_APPLICATION(col->usage))
+					break;
+		}
+
+		if (i == hid->maxcollection)
+			return -1;
+	}
+
+	report_features(hid);
+
+	for (k = HID_INPUT_REPORT; k <= HID_OUTPUT_REPORT; k++) {
+		if (k == HID_OUTPUT_REPORT &&
+			hid->quirks & HID_QUIRK_SKIP_OUTPUT_REPORTS)
+			continue;
+
+		list_for_each_entry(report, &hid->report_enum[k].report_list, list) {
+
+			if (!report->maxfield)
+				continue;
+
+			application = report->application;
+
+			/*
+			 * Find the previous hidinput report attached
+			 * to this report id.
+			 */
+			if (hid->quirks & HID_QUIRK_MULTI_INPUT)
+				hidinput = hidinput_match(report);
+			else if (hid->maxapplication > 1 &&
+				 (hid->quirks & HID_QUIRK_INPUT_PER_APP))
+				hidinput = hidinput_match_application(report);
+
+			if (!hidinput) {
+				hidinput = hidinput_allocate(hid, application);
+				if (!hidinput)
+					goto out_unwind;
+			}
+
+			hidinput_configure_usages(hidinput, report);
+
+			if (hid->quirks & HID_QUIRK_MULTI_INPUT)
+				hidinput->report = report;
+
+			list_add_tail(&report->hidinput_list,
+				      &hidinput->reports);
+		}
+	}
+
+	list_for_each_entry_safe(hidinput, next, &hid->inputs, list) {
+		if (drv->input_configured &&
+		    drv->input_configured(hid, hidinput))
+			goto out_unwind;
+
+		if (!hidinput_has_been_populated(hidinput)) {
+			/* no need to register an input device not populated */
+			hidinput_cleanup_hidinput(hid, hidinput);
+			continue;
+		}
+
+		if (input_register_device(hidinput->input))
+			goto out_unwind;
+		hidinput->registered = true;
+	}
+
+	if (list_empty(&hid->inputs)) {
+		hid_err(hid, "No inputs registered, leaving\n");
+		goto out_unwind;
+	}
+
+	if (hid->status & HID_STAT_DUP_DETECTED)
+		hid_dbg(hid,
+			"Some usages could not be mapped, please use HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE if this is legitimate.\n");
+
+	return 0;
+
+out_unwind:
+	/* unwind the ones we already registered */
+	hidinput_disconnect(hid);
+
+	return -1;
+}
+EXPORT_SYMBOL_GPL(hidinput_connect);
+
+void hidinput_disconnect(struct hid_device *hid)
+{
+	struct hid_input *hidinput, *next;
+
+	hidinput_cleanup_battery(hid);
+
+	list_for_each_entry_safe(hidinput, next, &hid->inputs, list) {
+		list_del(&hidinput->list);
+		if (hidinput->registered)
+			input_unregister_device(hidinput->input);
+		else
+			input_free_device(hidinput->input);
+		kfree(hidinput->name);
+		kfree(hidinput);
+	}
+
+	/* led_work is spawned by input_dev callbacks, but doesn't access the
+	 * parent input_dev at all. Once all input devices are removed, we
+	 * know that led_work will never get restarted, so we can cancel it
+	 * synchronously and are safe. */
+	cancel_work_sync(&hid->led_work);
+}
+EXPORT_SYMBOL_GPL(hidinput_disconnect);
+
diff --git a/drivers/hid/hid-ite.c b/drivers/hid/hid-ite.c
new file mode 100644
index 0000000..1882a4a
--- /dev/null
+++ b/drivers/hid/hid-ite.c
@@ -0,0 +1,56 @@
+/*
+ * HID driver for some ITE "special" devices
+ * Copyright (c) 2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static int ite_event(struct hid_device *hdev, struct hid_field *field,
+		     struct hid_usage *usage, __s32 value)
+{
+	struct input_dev *input;
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput)
+		return 0;
+
+	input = field->hidinput->input;
+
+	/*
+	 * The ITE8595 always reports 0 as value for the rfkill button. Luckily
+	 * it is the only button in its report, and it sends a report on
+	 * release only, so receiving a report means the button was pressed.
+	 */
+	if (usage->hid == HID_GD_RFKILL_BTN) {
+		input_event(input, EV_KEY, KEY_RFKILL, 1);
+		input_sync(input);
+		input_event(input, EV_KEY, KEY_RFKILL, 0);
+		input_sync(input);
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id ite_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ITE, USB_DEVICE_ID_ITE8595) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ite_devices);
+
+static struct hid_driver ite_driver = {
+	.name = "itetech",
+	.id_table = ite_devices,
+	.event = ite_event,
+};
+module_hid_driver(ite_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-jabra.c b/drivers/hid/hid-jabra.c
new file mode 100644
index 0000000..1f52daf
--- /dev/null
+++ b/drivers/hid/hid-jabra.c
@@ -0,0 +1,58 @@
+/*
+ *  Jabra USB HID Driver
+ *
+ *  Copyright (c) 2017 Niels Skou Olsen <nolsen@jabra.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define HID_UP_VENDOR_DEFINED_MIN	0xff000000
+#define HID_UP_VENDOR_DEFINED_MAX	0xffff0000
+
+static int jabra_input_mapping(struct hid_device *hdev,
+			       struct hid_input *hi,
+			       struct hid_field *field,
+			       struct hid_usage *usage,
+			       unsigned long **bit, int *max)
+{
+	int is_vendor_defined =
+		((usage->hid & HID_USAGE_PAGE) >= HID_UP_VENDOR_DEFINED_MIN &&
+		 (usage->hid & HID_USAGE_PAGE) <= HID_UP_VENDOR_DEFINED_MAX);
+
+	dbg_hid("hid=0x%08x appl=0x%08x coll_idx=0x%02x usage_idx=0x%02x: %s\n",
+		usage->hid,
+		field->application,
+		usage->collection_index,
+		usage->usage_index,
+		is_vendor_defined ? "ignored" : "defaulted");
+
+	/* Ignore vendor defined usages, default map standard usages */
+	return is_vendor_defined ? -1 : 0;
+}
+
+static const struct hid_device_id jabra_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, jabra_devices);
+
+static struct hid_driver jabra_driver = {
+	.name = "jabra",
+	.id_table = jabra_devices,
+	.input_mapping = jabra_input_mapping,
+};
+module_hid_driver(jabra_driver);
+
+MODULE_AUTHOR("Niels Skou Olsen <nolsen@jabra.com>");
+MODULE_DESCRIPTION("Jabra USB HID Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-kensington.c b/drivers/hid/hid-kensington.c
new file mode 100644
index 0000000..fe9a99d
--- /dev/null
+++ b/drivers/hid/hid-kensington.c
@@ -0,0 +1,52 @@
+/*
+ *  HID driver for Kensigton Slimblade Trackball
+ *
+ *  Copyright (c) 2009 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define ks_map_key(c)	hid_map_usage(hi, usage, bit, max, EV_KEY, (c))
+
+static int ks_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x01: ks_map_key(BTN_MIDDLE);	break;
+	case 0x02: ks_map_key(BTN_SIDE);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static const struct hid_device_id ks_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ks_devices);
+
+static struct hid_driver ks_driver = {
+	.name = "kensington",
+	.id_table = ks_devices,
+	.input_mapping = ks_input_mapping,
+};
+module_hid_driver(ks_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-keytouch.c b/drivers/hid/hid-keytouch.c
new file mode 100644
index 0000000..3074671
--- /dev/null
+++ b/drivers/hid/hid-keytouch.c
@@ -0,0 +1,55 @@
+/*
+ *  HID driver for Keytouch devices not fully compliant with HID standard
+ *
+ *  Copyright (c) 2011 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/* Replace the broken report descriptor of this device with rather
+ * a default one */
+static __u8 keytouch_fixed_rdesc[] = {
+0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15,
+0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08,
+0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91,
+0x02, 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00,
+0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0
+};
+
+static __u8 *keytouch_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	hid_info(hdev, "fixing up Keytouch IEC report descriptor\n");
+
+	rdesc = keytouch_fixed_rdesc;
+	*rsize = sizeof(keytouch_fixed_rdesc);
+
+	return rdesc;
+}
+
+static const struct hid_device_id keytouch_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, keytouch_devices);
+
+static struct hid_driver keytouch_driver = {
+	.name = "keytouch",
+	.id_table = keytouch_devices,
+	.report_fixup = keytouch_report_fixup,
+};
+module_hid_driver(keytouch_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jiri Kosina");
diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c
new file mode 100644
index 0000000..9c113f6
--- /dev/null
+++ b/drivers/hid/hid-kye.c
@@ -0,0 +1,701 @@
+/*
+ *  HID driver for Kye/Genius devices not fully compliant with HID standard
+ *
+ *  Copyright (c) 2009 Jiri Kosina
+ *  Copyright (c) 2009 Tomas Hanak
+ *  Copyright (c) 2012 Nikolai Kondrashov
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+/* Original EasyPen i405X report descriptor size */
+#define EASYPEN_I405X_RDESC_ORIG_SIZE	476
+
+/* Fixed EasyPen i405X report descriptor */
+static __u8 easypen_i405x_rdesc_fixed[] = {
+	0x06, 0x00, 0xFF, /*  Usage Page (FF00h),             */
+	0x09, 0x01,       /*  Usage (01h),                    */
+	0xA1, 0x01,       /*  Collection (Application),       */
+	0x85, 0x05,       /*    Report ID (5),                */
+	0x09, 0x01,       /*    Usage (01h),                  */
+	0x15, 0x80,       /*    Logical Minimum (-128),       */
+	0x25, 0x7F,       /*    Logical Maximum (127),        */
+	0x75, 0x08,       /*    Report Size (8),              */
+	0x95, 0x07,       /*    Report Count (7),             */
+	0xB1, 0x02,       /*    Feature (Variable),           */
+	0xC0,             /*  End Collection,                 */
+	0x05, 0x0D,       /*  Usage Page (Digitizer),         */
+	0x09, 0x02,       /*  Usage (Pen),                    */
+	0xA1, 0x01,       /*  Collection (Application),       */
+	0x85, 0x10,       /*    Report ID (16),               */
+	0x09, 0x20,       /*    Usage (Stylus),               */
+	0xA0,             /*    Collection (Physical),        */
+	0x14,             /*      Logical Minimum (0),        */
+	0x25, 0x01,       /*      Logical Maximum (1),        */
+	0x75, 0x01,       /*      Report Size (1),            */
+	0x09, 0x42,       /*      Usage (Tip Switch),         */
+	0x09, 0x44,       /*      Usage (Barrel Switch),      */
+	0x09, 0x46,       /*      Usage (Tablet Pick),        */
+	0x95, 0x03,       /*      Report Count (3),           */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x95, 0x04,       /*      Report Count (4),           */
+	0x81, 0x03,       /*      Input (Constant, Variable), */
+	0x09, 0x32,       /*      Usage (In Range),           */
+	0x95, 0x01,       /*      Report Count (1),           */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x75, 0x10,       /*      Report Size (16),           */
+	0x95, 0x01,       /*      Report Count (1),           */
+	0xA4,             /*      Push,                       */
+	0x05, 0x01,       /*      Usage Page (Desktop),       */
+	0x55, 0xFD,       /*      Unit Exponent (-3),         */
+	0x65, 0x13,       /*      Unit (Inch),                */
+	0x34,             /*      Physical Minimum (0),       */
+	0x09, 0x30,       /*      Usage (X),                  */
+	0x46, 0x7C, 0x15, /*      Physical Maximum (5500),    */
+	0x26, 0x00, 0x37, /*      Logical Maximum (14080),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x09, 0x31,       /*      Usage (Y),                  */
+	0x46, 0xA0, 0x0F, /*      Physical Maximum (4000),    */
+	0x26, 0x00, 0x28, /*      Logical Maximum (10240),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0xB4,             /*      Pop,                        */
+	0x09, 0x30,       /*      Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03, /*      Logical Maximum (1023),     */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0xC0,             /*    End Collection,               */
+	0xC0              /*  End Collection                  */
+};
+
+/* Original MousePen i608X report descriptor size */
+#define MOUSEPEN_I608X_RDESC_ORIG_SIZE	476
+
+/* Fixed MousePen i608X report descriptor */
+static __u8 mousepen_i608x_rdesc_fixed[] = {
+	0x06, 0x00, 0xFF, /*  Usage Page (FF00h),             */
+	0x09, 0x01,       /*  Usage (01h),                    */
+	0xA1, 0x01,       /*  Collection (Application),       */
+	0x85, 0x05,       /*    Report ID (5),                */
+	0x09, 0x01,       /*    Usage (01h),                  */
+	0x15, 0x80,       /*    Logical Minimum (-128),       */
+	0x25, 0x7F,       /*    Logical Maximum (127),        */
+	0x75, 0x08,       /*    Report Size (8),              */
+	0x95, 0x07,       /*    Report Count (7),             */
+	0xB1, 0x02,       /*    Feature (Variable),           */
+	0xC0,             /*  End Collection,                 */
+	0x05, 0x0D,       /*  Usage Page (Digitizer),         */
+	0x09, 0x02,       /*  Usage (Pen),                    */
+	0xA1, 0x01,       /*  Collection (Application),       */
+	0x85, 0x10,       /*    Report ID (16),               */
+	0x09, 0x20,       /*    Usage (Stylus),               */
+	0xA0,             /*    Collection (Physical),        */
+	0x14,             /*      Logical Minimum (0),        */
+	0x25, 0x01,       /*      Logical Maximum (1),        */
+	0x75, 0x01,       /*      Report Size (1),            */
+	0x09, 0x42,       /*      Usage (Tip Switch),         */
+	0x09, 0x44,       /*      Usage (Barrel Switch),      */
+	0x09, 0x46,       /*      Usage (Tablet Pick),        */
+	0x95, 0x03,       /*      Report Count (3),           */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x95, 0x04,       /*      Report Count (4),           */
+	0x81, 0x03,       /*      Input (Constant, Variable), */
+	0x09, 0x32,       /*      Usage (In Range),           */
+	0x95, 0x01,       /*      Report Count (1),           */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x75, 0x10,       /*      Report Size (16),           */
+	0x95, 0x01,       /*      Report Count (1),           */
+	0xA4,             /*      Push,                       */
+	0x05, 0x01,       /*      Usage Page (Desktop),       */
+	0x55, 0xFD,       /*      Unit Exponent (-3),         */
+	0x65, 0x13,       /*      Unit (Inch),                */
+	0x34,             /*      Physical Minimum (0),       */
+	0x09, 0x30,       /*      Usage (X),                  */
+	0x46, 0x40, 0x1F, /*      Physical Maximum (8000),    */
+	0x26, 0x00, 0x50, /*      Logical Maximum (20480),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x09, 0x31,       /*      Usage (Y),                  */
+	0x46, 0x70, 0x17, /*      Physical Maximum (6000),    */
+	0x26, 0x00, 0x3C, /*      Logical Maximum (15360),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0xB4,             /*      Pop,                        */
+	0x09, 0x30,       /*      Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03, /*      Logical Maximum (1023),     */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0xC0,             /*    End Collection,               */
+	0xC0,             /*  End Collection,                 */
+	0x05, 0x01,       /*  Usage Page (Desktop),           */
+	0x09, 0x02,       /*  Usage (Mouse),                  */
+	0xA1, 0x01,       /*  Collection (Application),       */
+	0x85, 0x11,       /*    Report ID (17),               */
+	0x09, 0x01,       /*    Usage (Pointer),              */
+	0xA0,             /*    Collection (Physical),        */
+	0x14,             /*      Logical Minimum (0),        */
+	0xA4,             /*      Push,                       */
+	0x05, 0x09,       /*      Usage Page (Button),        */
+	0x75, 0x01,       /*      Report Size (1),            */
+	0x19, 0x01,       /*      Usage Minimum (01h),        */
+	0x29, 0x03,       /*      Usage Maximum (03h),        */
+	0x25, 0x01,       /*      Logical Maximum (1),        */
+	0x95, 0x03,       /*      Report Count (3),           */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x95, 0x05,       /*      Report Count (5),           */
+	0x81, 0x01,       /*      Input (Constant),           */
+	0xB4,             /*      Pop,                        */
+	0x95, 0x01,       /*      Report Count (1),           */
+	0xA4,             /*      Push,                       */
+	0x55, 0xFD,       /*      Unit Exponent (-3),         */
+	0x65, 0x13,       /*      Unit (Inch),                */
+	0x34,             /*      Physical Minimum (0),       */
+	0x75, 0x10,       /*      Report Size (16),           */
+	0x09, 0x30,       /*      Usage (X),                  */
+	0x46, 0x40, 0x1F, /*      Physical Maximum (8000),    */
+	0x26, 0x00, 0x50, /*      Logical Maximum (20480),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0x09, 0x31,       /*      Usage (Y),                  */
+	0x46, 0x70, 0x17, /*      Physical Maximum (6000),    */
+	0x26, 0x00, 0x3C, /*      Logical Maximum (15360),    */
+	0x81, 0x02,       /*      Input (Variable),           */
+	0xB4,             /*      Pop,                        */
+	0x75, 0x08,       /*      Report Size (8),            */
+	0x09, 0x38,       /*      Usage (Wheel),              */
+	0x15, 0xFF,       /*      Logical Minimum (-1),       */
+	0x25, 0x01,       /*      Logical Maximum (1),        */
+	0x81, 0x06,       /*      Input (Variable, Relative), */
+	0x81, 0x01,       /*      Input (Constant),           */
+	0xC0,             /*    End Collection,               */
+	0xC0              /*  End Collection                  */
+};
+
+/* Original MousePen i608X v2 report descriptor size */
+#define MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE	482
+
+/* Fixed MousePen i608X v2 report descriptor */
+static __u8 mousepen_i608x_v2_rdesc_fixed[] = {
+	0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */
+	0x09, 0x01,                   /*  Usage (01h),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x05,                   /*    Report ID (5),                */
+	0x09, 0x01,                   /*    Usage (01h),                  */
+	0x15, 0x80,                   /*    Logical Minimum (-128),       */
+	0x25, 0x7F,                   /*    Logical Maximum (127),        */
+	0x75, 0x08,                   /*    Report Size (8),              */
+	0x95, 0x07,                   /*    Report Count (7),             */
+	0xB1, 0x02,                   /*    Feature (Variable),           */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+	0x09, 0x02,                   /*  Usage (Pen),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x10,                   /*    Report ID (16),               */
+	0x09, 0x20,                   /*    Usage (Stylus),               */
+	0xA0,                         /*    Collection (Physical),        */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x75, 0x01,                   /*      Report Size (1),            */
+	0x09, 0x42,                   /*      Usage (Tip Switch),         */
+	0x09, 0x44,                   /*      Usage (Barrel Switch),      */
+	0x09, 0x46,                   /*      Usage (Tablet Pick),        */
+	0x95, 0x03,                   /*      Report Count (3),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x95, 0x04,                   /*      Report Count (4),           */
+	0x81, 0x03,                   /*      Input (Constant, Variable), */
+	0x09, 0x32,                   /*      Usage (In Range),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x75, 0x10,                   /*      Report Size (16),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0xA4,                         /*      Push,                       */
+	0x05, 0x01,                   /*      Usage Page (Desktop),       */
+	0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+	0x65, 0x13,                   /*      Unit (Inch),                */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x09, 0x30,                   /*      Usage (X),                  */
+	0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */
+	0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x31,                   /*      Usage (Y),                  */
+	0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */
+	0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xB4,                         /*      Pop,                        */
+	0x09, 0x30,                   /*      Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xC0,                         /*    End Collection,               */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x01,                   /*  Usage Page (Desktop),           */
+	0x09, 0x02,                   /*  Usage (Mouse),                  */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x11,                   /*    Report ID (17),               */
+	0x09, 0x01,                   /*    Usage (Pointer),              */
+	0xA0,                         /*    Collection (Physical),        */
+	0x14,                         /*      Logical Minimum (0),        */
+	0xA4,                         /*      Push,                       */
+	0x05, 0x09,                   /*      Usage Page (Button),        */
+	0x75, 0x01,                   /*      Report Size (1),            */
+	0x19, 0x01,                   /*      Usage Minimum (01h),        */
+	0x29, 0x03,                   /*      Usage Maximum (03h),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x95, 0x03,                   /*      Report Count (3),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x95, 0x05,                   /*      Report Count (5),           */
+	0x81, 0x01,                   /*      Input (Constant),           */
+	0xB4,                         /*      Pop,                        */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0xA4,                         /*      Push,                       */
+	0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+	0x65, 0x13,                   /*      Unit (Inch),                */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x75, 0x10,                   /*      Report Size (16),           */
+	0x09, 0x30,                   /*      Usage (X),                  */
+	0x46, 0x40, 0x1F,             /*      Physical Maximum (8000),    */
+	0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x31,                   /*      Usage (Y),                  */
+	0x46, 0x70, 0x17,             /*      Physical Maximum (6000),    */
+	0x26, 0x00, 0x78,             /*      Logical Maximum (30720),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xB4,                         /*      Pop,                        */
+	0x75, 0x08,                   /*      Report Size (8),            */
+	0x09, 0x38,                   /*      Usage (Wheel),              */
+	0x15, 0xFF,                   /*      Logical Minimum (-1),       */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x81, 0x06,                   /*      Input (Variable, Relative), */
+	0x81, 0x01,                   /*      Input (Constant),           */
+	0xC0,                         /*    End Collection,               */
+	0xC0                          /*  End Collection                  */
+};
+
+/* Original EasyPen M610X report descriptor size */
+#define EASYPEN_M610X_RDESC_ORIG_SIZE	476
+
+/* Fixed EasyPen M610X report descriptor */
+static __u8 easypen_m610x_rdesc_fixed[] = {
+	0x06, 0x00, 0xFF,             /*  Usage Page (FF00h),             */
+	0x09, 0x01,                   /*  Usage (01h),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x05,                   /*    Report ID (5),                */
+	0x09, 0x01,                   /*    Usage (01h),                  */
+	0x15, 0x80,                   /*    Logical Minimum (-128),       */
+	0x25, 0x7F,                   /*    Logical Maximum (127),        */
+	0x75, 0x08,                   /*    Report Size (8),              */
+	0x95, 0x07,                   /*    Report Count (7),             */
+	0xB1, 0x02,                   /*    Feature (Variable),           */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+	0x09, 0x02,                   /*  Usage (Pen),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x10,                   /*    Report ID (16),               */
+	0x09, 0x20,                   /*    Usage (Stylus),               */
+	0xA0,                         /*    Collection (Physical),        */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x75, 0x01,                   /*      Report Size (1),            */
+	0x09, 0x42,                   /*      Usage (Tip Switch),         */
+	0x09, 0x44,                   /*      Usage (Barrel Switch),      */
+	0x09, 0x46,                   /*      Usage (Tablet Pick),        */
+	0x95, 0x03,                   /*      Report Count (3),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x95, 0x04,                   /*      Report Count (4),           */
+	0x81, 0x03,                   /*      Input (Constant, Variable), */
+	0x09, 0x32,                   /*      Usage (In Range),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x75, 0x10,                   /*      Report Size (16),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0xA4,                         /*      Push,                       */
+	0x05, 0x01,                   /*      Usage Page (Desktop),       */
+	0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+	0x65, 0x13,                   /*      Unit (Inch),                */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x09, 0x30,                   /*      Usage (X),                  */
+	0x46, 0x10, 0x27,             /*      Physical Maximum (10000),   */
+	0x27, 0x00, 0xA0, 0x00, 0x00, /*      Logical Maximum (40960),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x31,                   /*      Usage (Y),                  */
+	0x46, 0x6A, 0x18,             /*      Physical Maximum (6250),    */
+	0x26, 0x00, 0x64,             /*      Logical Maximum (25600),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xB4,                         /*      Pop,                        */
+	0x09, 0x30,                   /*      Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,             /*      Logical Maximum (1023),     */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xC0,                         /*    End Collection,               */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0C,                   /*  Usage Page (Consumer),          */
+	0x09, 0x01,                   /*  Usage (Consumer Control),       */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x12,                   /*    Report ID (18),               */
+	0x14,                         /*    Logical Minimum (0),          */
+	0x25, 0x01,                   /*    Logical Maximum (1),          */
+	0x75, 0x01,                   /*    Report Size (1),              */
+	0x95, 0x04,                   /*    Report Count (4),             */
+	0x0A, 0x1A, 0x02,             /*    Usage (AC Undo),              */
+	0x0A, 0x79, 0x02,             /*    Usage (AC Redo Or Repeat),    */
+	0x0A, 0x2D, 0x02,             /*    Usage (AC Zoom In),           */
+	0x0A, 0x2E, 0x02,             /*    Usage (AC Zoom Out),          */
+	0x81, 0x02,                   /*    Input (Variable),             */
+	0x95, 0x01,                   /*    Report Count (1),             */
+	0x75, 0x14,                   /*    Report Size (20),             */
+	0x81, 0x03,                   /*    Input (Constant, Variable),   */
+	0x75, 0x20,                   /*    Report Size (32),             */
+	0x81, 0x03,                   /*    Input (Constant, Variable),   */
+	0xC0                          /*  End Collection                  */
+};
+
+
+/* Original PenSketch M912 report descriptor size */
+#define PENSKETCH_M912_RDESC_ORIG_SIZE	482
+
+/* Fixed PenSketch M912 report descriptor */
+static __u8 pensketch_m912_rdesc_fixed[] = {
+	0x05, 0x01,                   /*  Usage Page (Desktop),           */
+	0x08,                         /*  Usage (00h),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x05,                   /*    Report ID (5),                */
+	0x06, 0x00, 0xFF,             /*    Usage Page (FF00h),           */
+	0x09, 0x01,                   /*    Usage (01h),                  */
+	0x15, 0x81,                   /*    Logical Minimum (-127),       */
+	0x25, 0x7F,                   /*    Logical Maximum (127),        */
+	0x75, 0x08,                   /*    Report Size (8),              */
+	0x95, 0x07,                   /*    Report Count (7),             */
+	0xB1, 0x02,                   /*    Feature (Variable),           */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+	0x09, 0x02,                   /*  Usage (Pen),                    */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x10,                   /*    Report ID (16),               */
+	0x09, 0x20,                   /*    Usage (Stylus),               */
+	0xA0,                         /*    Collection (Physical),        */
+	0x09, 0x42,                   /*      Usage (Tip Switch),         */
+	0x09, 0x44,                   /*      Usage (Barrel Switch),      */
+	0x09, 0x46,                   /*      Usage (Tablet Pick),        */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x75, 0x01,                   /*      Report Size (1),            */
+	0x95, 0x03,                   /*      Report Count (3),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x95, 0x04,                   /*      Report Count (4),           */
+	0x81, 0x03,                   /*      Input (Constant, Variable), */
+	0x09, 0x32,                   /*      Usage (In Range),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x75, 0x10,                   /*      Report Size (16),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0xA4,                         /*      Push,                       */
+	0x05, 0x01,                   /*      Usage Page (Desktop),       */
+	0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+	0x65, 0x13,                   /*      Unit (Inch),                */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x09, 0x30,                   /*      Usage (X),                  */
+	0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
+	0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x31,                   /*      Usage (Y),                  */
+	0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
+	0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xB4,                         /*      Pop,                        */
+	0x09, 0x30,                   /*      Usage (Tip Pressure),       */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x26, 0xFF, 0x07,             /*      Logical Maximum (2047),     */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xC0,                         /*    End Collection,               */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0D,                   /*  Usage Page (Digitizer),         */
+	0x09, 0x21,                   /*  Usage (Puck),                   */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x11,                   /*    Report ID (17),               */
+	0x09, 0x21,                   /*    Usage (Puck),                 */
+	0xA0,                         /*    Collection (Physical),        */
+	0x05, 0x09,                   /*      Usage Page (Button),        */
+	0x75, 0x01,                   /*      Report Size (1),            */
+	0x19, 0x01,                   /*      Usage Minimum (01h),        */
+	0x29, 0x03,                   /*      Usage Maximum (03h),        */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x95, 0x03,                   /*      Report Count (3),           */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x95, 0x04,                   /*      Report Count (4),           */
+	0x81, 0x01,                   /*      Input (Constant),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x0B, 0x32, 0x00, 0x0D, 0x00, /*      Usage (Digitizer In Range), */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0xA4,                         /*      Push,                       */
+	0x05, 0x01,                   /*      Usage Page (Desktop),       */
+	0x75, 0x10,                   /*      Report Size (16),           */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x55, 0xFD,                   /*      Unit Exponent (-3),         */
+	0x65, 0x13,                   /*      Unit (Inch),                */
+	0x14,                         /*      Logical Minimum (0),        */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x09, 0x30,                   /*      Usage (X),                  */
+	0x27, 0x00, 0xF0, 0x00, 0x00, /*      Logical Maximum (61440),    */
+	0x46, 0xE0, 0x2E,             /*      Physical Maximum (12000),   */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x31,                   /*      Usage (Y),                  */
+	0x27, 0x00, 0xB4, 0x00, 0x00, /*      Logical Maximum (46080),    */
+	0x46, 0x28, 0x23,             /*      Physical Maximum (9000),    */
+	0x81, 0x02,                   /*      Input (Variable),           */
+	0x09, 0x38,                   /*      Usage (Wheel),              */
+	0x75, 0x08,                   /*      Report Size (8),            */
+	0x95, 0x01,                   /*      Report Count (1),           */
+	0x15, 0xFF,                   /*      Logical Minimum (-1),       */
+	0x25, 0x01,                   /*      Logical Maximum (1),        */
+	0x34,                         /*      Physical Minimum (0),       */
+	0x44,                         /*      Physical Maximum (0),       */
+	0x81, 0x06,                   /*      Input (Variable, Relative), */
+	0xB4,                         /*      Pop,                        */
+	0xC0,                         /*    End Collection,               */
+	0xC0,                         /*  End Collection,                 */
+	0x05, 0x0C,                   /*  Usage Page (Consumer),          */
+	0x09, 0x01,                   /*  Usage (Consumer Control),       */
+	0xA1, 0x01,                   /*  Collection (Application),       */
+	0x85, 0x12,                   /*    Report ID (18),               */
+	0x14,                         /*    Logical Minimum (0),          */
+	0x25, 0x01,                   /*    Logical Maximum (1),          */
+	0x75, 0x01,                   /*    Report Size (1),              */
+	0x95, 0x08,                   /*    Report Count (8),             */
+	0x05, 0x0C,                   /*    Usage Page (Consumer),        */
+	0x0A, 0x6A, 0x02,             /*    Usage (AC Delete),            */
+	0x0A, 0x1A, 0x02,             /*    Usage (AC Undo),              */
+	0x0A, 0x01, 0x02,             /*    Usage (AC New),               */
+	0x0A, 0x2F, 0x02,             /*    Usage (AC Zoom),              */
+	0x0A, 0x25, 0x02,             /*    Usage (AC Forward),           */
+	0x0A, 0x24, 0x02,             /*    Usage (AC Back),              */
+	0x0A, 0x2D, 0x02,             /*    Usage (AC Zoom In),           */
+	0x0A, 0x2E, 0x02,             /*    Usage (AC Zoom Out),          */
+	0x81, 0x02,                   /*    Input (Variable),             */
+	0x95, 0x30,                   /*    Report Count (48),            */
+	0x81, 0x03,                   /*    Input (Constant, Variable),   */
+	0xC0                          /*  End Collection                  */
+};
+
+static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize, int offset, const char *device_name) {
+	/*
+	 * the fixup that need to be done:
+	 *   - change Usage Maximum in the Consumer Control
+	 *     (report ID 3) to a reasonable value
+	 */
+	if (*rsize >= offset + 31 &&
+	    /* Usage Page (Consumer Devices) */
+	    rdesc[offset] == 0x05 && rdesc[offset + 1] == 0x0c &&
+	    /* Usage (Consumer Control) */
+	    rdesc[offset + 2] == 0x09 && rdesc[offset + 3] == 0x01 &&
+	    /*   Usage Maximum > 12287 */
+	    rdesc[offset + 10] == 0x2a && rdesc[offset + 12] > 0x2f) {
+		hid_info(hdev, "fixing up %s report descriptor\n", device_name);
+		rdesc[offset + 12] = 0x2f;
+	}
+	return rdesc;
+}
+
+static __u8 *kye_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_KYE_ERGO_525V:
+		/* the fixups that need to be done:
+		 *   - change led usage page to button for extra buttons
+		 *   - report size 8 count 1 must be size 1 count 8 for button
+		 *     bitfield
+		 *   - change the button usage range to 4-7 for the extra
+		 *     buttons
+		 */
+		if (*rsize >= 75 &&
+			rdesc[61] == 0x05 && rdesc[62] == 0x08 &&
+			rdesc[63] == 0x19 && rdesc[64] == 0x08 &&
+			rdesc[65] == 0x29 && rdesc[66] == 0x0f &&
+			rdesc[71] == 0x75 && rdesc[72] == 0x08 &&
+			rdesc[73] == 0x95 && rdesc[74] == 0x01) {
+			hid_info(hdev,
+				 "fixing up Kye/Genius Ergo Mouse "
+				 "report descriptor\n");
+			rdesc[62] = 0x09;
+			rdesc[64] = 0x04;
+			rdesc[66] = 0x07;
+			rdesc[72] = 0x01;
+			rdesc[74] = 0x08;
+		}
+		break;
+	case USB_DEVICE_ID_KYE_EASYPEN_I405X:
+		if (*rsize == EASYPEN_I405X_RDESC_ORIG_SIZE) {
+			rdesc = easypen_i405x_rdesc_fixed;
+			*rsize = sizeof(easypen_i405x_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
+		if (*rsize == MOUSEPEN_I608X_RDESC_ORIG_SIZE) {
+			rdesc = mousepen_i608x_rdesc_fixed;
+			*rsize = sizeof(mousepen_i608x_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
+		if (*rsize == MOUSEPEN_I608X_V2_RDESC_ORIG_SIZE) {
+			rdesc = mousepen_i608x_v2_rdesc_fixed;
+			*rsize = sizeof(mousepen_i608x_v2_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+		if (*rsize == EASYPEN_M610X_RDESC_ORIG_SIZE) {
+			rdesc = easypen_m610x_rdesc_fixed;
+			*rsize = sizeof(easypen_m610x_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+		if (*rsize == PENSKETCH_M912_RDESC_ORIG_SIZE) {
+			rdesc = pensketch_m912_rdesc_fixed;
+			*rsize = sizeof(pensketch_m912_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE:
+		rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
+					"Genius Gila Gaming Mouse");
+		break;
+	case USB_DEVICE_ID_GENIUS_GX_IMPERATOR:
+		rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 83,
+					"Genius Gx Imperator Keyboard");
+		break;
+	case USB_DEVICE_ID_GENIUS_MANTICORE:
+		rdesc = kye_consumer_control_fixup(hdev, rdesc, rsize, 104,
+					"Genius Manticore Keyboard");
+		break;
+	}
+	return rdesc;
+}
+
+/**
+ * Enable fully-functional tablet mode by setting a special feature report.
+ *
+ * @hdev:	HID device
+ *
+ * The specific report ID and data were discovered by sniffing the
+ * Windows driver traffic.
+ */
+static int kye_tablet_enable(struct hid_device *hdev)
+{
+	struct list_head *list;
+	struct list_head *head;
+	struct hid_report *report;
+	__s32 *value;
+
+	list = &hdev->report_enum[HID_FEATURE_REPORT].report_list;
+	list_for_each(head, list) {
+		report = list_entry(head, struct hid_report, list);
+		if (report->id == 5)
+			break;
+	}
+
+	if (head == list) {
+		hid_err(hdev, "tablet-enabling feature report not found\n");
+		return -ENODEV;
+	}
+
+	if (report->maxfield < 1 || report->field[0]->report_count < 7) {
+		hid_err(hdev, "invalid tablet-enabling feature report\n");
+		return -ENODEV;
+	}
+
+	value = report->field[0]->value;
+
+	value[0] = 0x12;
+	value[1] = 0x10;
+	value[2] = 0x11;
+	value[3] = 0x12;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int kye_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	switch (id->product) {
+	case USB_DEVICE_ID_KYE_EASYPEN_I405X:
+	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X:
+	case USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2:
+	case USB_DEVICE_ID_KYE_EASYPEN_M610X:
+	case USB_DEVICE_ID_KYE_PENSKETCH_M912:
+		ret = kye_tablet_enable(hdev);
+		if (ret) {
+			hid_err(hdev, "tablet enabling failed\n");
+			goto enabling_err;
+		}
+		break;
+	case USB_DEVICE_ID_GENIUS_MANTICORE:
+		/*
+		 * The manticore keyboard needs to have all the interfaces
+		 * opened at least once to be fully functional.
+		 */
+		if (hid_hw_open(hdev))
+			hid_hw_close(hdev);
+		break;
+	}
+
+	return 0;
+enabling_err:
+	hid_hw_stop(hdev);
+err:
+	return ret;
+}
+
+static const struct hid_device_id kye_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_KYE_EASYPEN_I405X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_KYE_EASYPEN_M610X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_GENIUS_MANTICORE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE,
+				USB_DEVICE_ID_KYE_PENSKETCH_M912) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, kye_devices);
+
+static struct hid_driver kye_driver = {
+	.name = "kye",
+	.id_table = kye_devices,
+	.probe = kye_probe,
+	.report_fixup = kye_report_fixup,
+};
+module_hid_driver(kye_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lcpower.c b/drivers/hid/hid-lcpower.c
new file mode 100644
index 0000000..6424cfd
--- /dev/null
+++ b/drivers/hid/hid-lcpower.c
@@ -0,0 +1,59 @@
+/*
+ *  HID driver for LC Power Model RC1000MCE
+ *
+ *  Copyright (c) 2011 Chris Schlund 
+ *  based on hid-topseed module
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#define ts_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+        case 0x046: ts_map_key_clear(KEY_YELLOW);         break;
+        case 0x047: ts_map_key_clear(KEY_GREEN);          break;
+        case 0x049: ts_map_key_clear(KEY_BLUE);           break;
+        case 0x04a: ts_map_key_clear(KEY_RED);		  break;
+        case 0x00d: ts_map_key_clear(KEY_HOME);           break;
+        case 0x025: ts_map_key_clear(KEY_TV);             break;
+        case 0x048: ts_map_key_clear(KEY_VCR);            break;
+        case 0x024: ts_map_key_clear(KEY_MENU);           break;
+        default:
+        return 0;
+	}
+
+	return 1;
+}
+
+static const struct hid_device_id ts_devices[] = {
+	{ HID_USB_DEVICE( USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ts_devices);
+
+static struct hid_driver ts_driver = {
+	.name = "LC RC1000MCE",
+	.id_table = ts_devices,
+	.input_mapping = ts_input_mapping,
+};
+module_hid_driver(ts_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-led.c b/drivers/hid/hid-led.c
new file mode 100644
index 0000000..d3e1ab1
--- /dev/null
+++ b/drivers/hid/hid-led.c
@@ -0,0 +1,538 @@
+/*
+ * Simple USB RGB LED driver
+ *
+ * Copyright 2016 Heiner Kallweit <hkallweit1@gmail.com>
+ * Based on drivers/hid/hid-thingm.c and
+ * drivers/usb/misc/usbled.c
+ *
+ * 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, version 2.
+ */
+
+#include <linux/hid.h>
+#include <linux/hidraw.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include "hid-ids.h"
+
+enum hidled_report_type {
+	RAW_REQUEST,
+	OUTPUT_REPORT
+};
+
+enum hidled_type {
+	RISO_KAGAKU,
+	DREAM_CHEEKY,
+	THINGM,
+	DELCOM,
+	LUXAFOR,
+};
+
+static unsigned const char riso_kagaku_tbl[] = {
+/* R+2G+4B -> riso kagaku color index */
+	[0] = 0, /* black   */
+	[1] = 2, /* red     */
+	[2] = 1, /* green   */
+	[3] = 5, /* yellow  */
+	[4] = 3, /* blue    */
+	[5] = 6, /* magenta */
+	[6] = 4, /* cyan    */
+	[7] = 7  /* white   */
+};
+
+#define RISO_KAGAKU_IX(r, g, b) riso_kagaku_tbl[((r)?1:0)+((g)?2:0)+((b)?4:0)]
+
+union delcom_packet {
+	__u8 data[8];
+	struct {
+		__u8 major_cmd;
+		__u8 minor_cmd;
+		__u8 data_lsb;
+		__u8 data_msb;
+	} tx;
+	struct {
+		__u8 cmd;
+	} rx;
+	struct {
+		__le16 family_code;
+		__le16 security_code;
+		__u8 fw_version;
+	} fw;
+};
+
+#define DELCOM_GREEN_LED	0
+#define DELCOM_RED_LED		1
+#define DELCOM_BLUE_LED		2
+
+struct hidled_device;
+struct hidled_rgb;
+
+struct hidled_config {
+	enum hidled_type	type;
+	const char		*name;
+	const char		*short_name;
+	enum led_brightness	max_brightness;
+	int			num_leds;
+	size_t			report_size;
+	enum hidled_report_type	report_type;
+	int (*init)(struct hidled_device *ldev);
+	int (*write)(struct led_classdev *cdev, enum led_brightness br);
+};
+
+struct hidled_led {
+	struct led_classdev	cdev;
+	struct hidled_rgb	*rgb;
+	char			name[32];
+};
+
+struct hidled_rgb {
+	struct hidled_device	*ldev;
+	struct hidled_led	red;
+	struct hidled_led	green;
+	struct hidled_led	blue;
+	u8			num;
+};
+
+struct hidled_device {
+	const struct hidled_config *config;
+	struct hid_device       *hdev;
+	struct hidled_rgb	*rgb;
+	u8			*buf;
+	struct mutex		lock;
+};
+
+#define MAX_REPORT_SIZE		16
+
+#define to_hidled_led(arg) container_of(arg, struct hidled_led, cdev)
+
+static bool riso_kagaku_switch_green_blue;
+module_param(riso_kagaku_switch_green_blue, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(riso_kagaku_switch_green_blue,
+	"switch green and blue RGB component for Riso Kagaku devices");
+
+static int hidled_send(struct hidled_device *ldev, __u8 *buf)
+{
+	int ret;
+
+	mutex_lock(&ldev->lock);
+
+	/*
+	 * buffer provided to hid_hw_raw_request must not be on the stack
+	 * and must not be part of a data structure
+	 */
+	memcpy(ldev->buf, buf, ldev->config->report_size);
+
+	if (ldev->config->report_type == RAW_REQUEST)
+		ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
+					 ldev->config->report_size,
+					 HID_FEATURE_REPORT,
+					 HID_REQ_SET_REPORT);
+	else if (ldev->config->report_type == OUTPUT_REPORT)
+		ret = hid_hw_output_report(ldev->hdev, ldev->buf,
+					   ldev->config->report_size);
+	else
+		ret = -EINVAL;
+
+	mutex_unlock(&ldev->lock);
+
+	if (ret < 0)
+		return ret;
+
+	return ret == ldev->config->report_size ? 0 : -EMSGSIZE;
+}
+
+/* reading data is supported for report type RAW_REQUEST only */
+static int hidled_recv(struct hidled_device *ldev, __u8 *buf)
+{
+	int ret;
+
+	if (ldev->config->report_type != RAW_REQUEST)
+		return -EINVAL;
+
+	mutex_lock(&ldev->lock);
+
+	memcpy(ldev->buf, buf, ldev->config->report_size);
+
+	ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
+				 ldev->config->report_size,
+				 HID_FEATURE_REPORT,
+				 HID_REQ_SET_REPORT);
+	if (ret < 0)
+		goto err;
+
+	ret = hid_hw_raw_request(ldev->hdev, buf[0], ldev->buf,
+				 ldev->config->report_size,
+				 HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+
+	memcpy(buf, ldev->buf, ldev->config->report_size);
+err:
+	mutex_unlock(&ldev->lock);
+
+	return ret < 0 ? ret : 0;
+}
+
+static u8 riso_kagaku_index(struct hidled_rgb *rgb)
+{
+	enum led_brightness r, g, b;
+
+	r = rgb->red.cdev.brightness;
+	g = rgb->green.cdev.brightness;
+	b = rgb->blue.cdev.brightness;
+
+	if (riso_kagaku_switch_green_blue)
+		return RISO_KAGAKU_IX(r, b, g);
+	else
+		return RISO_KAGAKU_IX(r, g, b);
+}
+
+static int riso_kagaku_write(struct led_classdev *cdev, enum led_brightness br)
+{
+	struct hidled_led *led = to_hidled_led(cdev);
+	struct hidled_rgb *rgb = led->rgb;
+	__u8 buf[MAX_REPORT_SIZE] = {};
+
+	buf[1] = riso_kagaku_index(rgb);
+
+	return hidled_send(rgb->ldev, buf);
+}
+
+static int dream_cheeky_write(struct led_classdev *cdev, enum led_brightness br)
+{
+	struct hidled_led *led = to_hidled_led(cdev);
+	struct hidled_rgb *rgb = led->rgb;
+	__u8 buf[MAX_REPORT_SIZE] = {};
+
+	buf[1] = rgb->red.cdev.brightness;
+	buf[2] = rgb->green.cdev.brightness;
+	buf[3] = rgb->blue.cdev.brightness;
+	buf[7] = 0x1a;
+	buf[8] = 0x05;
+
+	return hidled_send(rgb->ldev, buf);
+}
+
+static int dream_cheeky_init(struct hidled_device *ldev)
+{
+	__u8 buf[MAX_REPORT_SIZE] = {};
+
+	/* Dream Cheeky magic */
+	buf[1] = 0x1f;
+	buf[2] = 0x02;
+	buf[4] = 0x5f;
+	buf[7] = 0x1a;
+	buf[8] = 0x03;
+
+	return hidled_send(ldev, buf);
+}
+
+static int _thingm_write(struct led_classdev *cdev, enum led_brightness br,
+			 u8 offset)
+{
+	struct hidled_led *led = to_hidled_led(cdev);
+	__u8 buf[MAX_REPORT_SIZE] = { 1, 'c' };
+
+	buf[2] = led->rgb->red.cdev.brightness;
+	buf[3] = led->rgb->green.cdev.brightness;
+	buf[4] = led->rgb->blue.cdev.brightness;
+	buf[7] = led->rgb->num + offset;
+
+	return hidled_send(led->rgb->ldev, buf);
+}
+
+static int thingm_write_v1(struct led_classdev *cdev, enum led_brightness br)
+{
+	return _thingm_write(cdev, br, 0);
+}
+
+static int thingm_write(struct led_classdev *cdev, enum led_brightness br)
+{
+	return _thingm_write(cdev, br, 1);
+}
+
+static const struct hidled_config hidled_config_thingm_v1 = {
+	.name = "ThingM blink(1) v1",
+	.short_name = "thingm",
+	.max_brightness = 255,
+	.num_leds = 1,
+	.report_size = 9,
+	.report_type = RAW_REQUEST,
+	.write = thingm_write_v1,
+};
+
+static int thingm_init(struct hidled_device *ldev)
+{
+	__u8 buf[MAX_REPORT_SIZE] = { 1, 'v' };
+	int ret;
+
+	ret = hidled_recv(ldev, buf);
+	if (ret)
+		return ret;
+
+	/* Check for firmware major version 1 */
+	if (buf[3] == '1')
+		ldev->config = &hidled_config_thingm_v1;
+
+	return 0;
+}
+
+static inline int delcom_get_lednum(const struct hidled_led *led)
+{
+	if (led == &led->rgb->red)
+		return DELCOM_RED_LED;
+	else if (led == &led->rgb->green)
+		return DELCOM_GREEN_LED;
+	else
+		return DELCOM_BLUE_LED;
+}
+
+static int delcom_enable_led(struct hidled_led *led)
+{
+	union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 12 };
+
+	dp.tx.data_lsb = 1 << delcom_get_lednum(led);
+	dp.tx.data_msb = 0;
+
+	return hidled_send(led->rgb->ldev, dp.data);
+}
+
+static int delcom_set_pwm(struct hidled_led *led)
+{
+	union delcom_packet dp = { .tx.major_cmd = 101, .tx.minor_cmd = 34 };
+
+	dp.tx.data_lsb = delcom_get_lednum(led);
+	dp.tx.data_msb = led->cdev.brightness;
+
+	return hidled_send(led->rgb->ldev, dp.data);
+}
+
+static int delcom_write(struct led_classdev *cdev, enum led_brightness br)
+{
+	struct hidled_led *led = to_hidled_led(cdev);
+	int ret;
+
+	/*
+	 * enable LED
+	 * We can't do this in the init function already because the device
+	 * is internally reset later.
+	 */
+	ret = delcom_enable_led(led);
+	if (ret)
+		return ret;
+
+	return delcom_set_pwm(led);
+}
+
+static int delcom_init(struct hidled_device *ldev)
+{
+	union delcom_packet dp = { .rx.cmd = 104 };
+	int ret;
+
+	ret = hidled_recv(ldev, dp.data);
+	if (ret)
+		return ret;
+	/*
+	 * Several Delcom devices share the same USB VID/PID
+	 * Check for family id 2 for Visual Signal Indicator
+	 */
+	return le16_to_cpu(dp.fw.family_code) == 2 ? 0 : -ENODEV;
+}
+
+static int luxafor_write(struct led_classdev *cdev, enum led_brightness br)
+{
+	struct hidled_led *led = to_hidled_led(cdev);
+	__u8 buf[MAX_REPORT_SIZE] = { [1] = 1 };
+
+	buf[2] = led->rgb->num + 1;
+	buf[3] = led->rgb->red.cdev.brightness;
+	buf[4] = led->rgb->green.cdev.brightness;
+	buf[5] = led->rgb->blue.cdev.brightness;
+
+	return hidled_send(led->rgb->ldev, buf);
+}
+
+static const struct hidled_config hidled_configs[] = {
+	{
+		.type = RISO_KAGAKU,
+		.name = "Riso Kagaku Webmail Notifier",
+		.short_name = "riso_kagaku",
+		.max_brightness = 1,
+		.num_leds = 1,
+		.report_size = 6,
+		.report_type = OUTPUT_REPORT,
+		.write = riso_kagaku_write,
+	},
+	{
+		.type = DREAM_CHEEKY,
+		.name = "Dream Cheeky Webmail Notifier",
+		.short_name = "dream_cheeky",
+		.max_brightness = 31,
+		.num_leds = 1,
+		.report_size = 9,
+		.report_type = RAW_REQUEST,
+		.init = dream_cheeky_init,
+		.write = dream_cheeky_write,
+	},
+	{
+		.type = THINGM,
+		.name = "ThingM blink(1)",
+		.short_name = "thingm",
+		.max_brightness = 255,
+		.num_leds = 2,
+		.report_size = 9,
+		.report_type = RAW_REQUEST,
+		.init = thingm_init,
+		.write = thingm_write,
+	},
+	{
+		.type = DELCOM,
+		.name = "Delcom Visual Signal Indicator G2",
+		.short_name = "delcom",
+		.max_brightness = 100,
+		.num_leds = 1,
+		.report_size = 8,
+		.report_type = RAW_REQUEST,
+		.init = delcom_init,
+		.write = delcom_write,
+	},
+	{
+		.type = LUXAFOR,
+		.name = "Greynut Luxafor",
+		.short_name = "luxafor",
+		.max_brightness = 255,
+		.num_leds = 6,
+		.report_size = 9,
+		.report_type = OUTPUT_REPORT,
+		.write = luxafor_write,
+	},
+};
+
+static int hidled_init_led(struct hidled_led *led, const char *color_name,
+			   struct hidled_rgb *rgb, unsigned int minor)
+{
+	const struct hidled_config *config = rgb->ldev->config;
+
+	if (config->num_leds > 1)
+		snprintf(led->name, sizeof(led->name), "%s%u:%s:led%u",
+			 config->short_name, minor, color_name, rgb->num);
+	else
+		snprintf(led->name, sizeof(led->name), "%s%u:%s",
+			 config->short_name, minor, color_name);
+	led->cdev.name = led->name;
+	led->cdev.max_brightness = config->max_brightness;
+	led->cdev.brightness_set_blocking = config->write;
+	led->cdev.flags = LED_HW_PLUGGABLE;
+	led->rgb = rgb;
+
+	return devm_led_classdev_register(&rgb->ldev->hdev->dev, &led->cdev);
+}
+
+static int hidled_init_rgb(struct hidled_rgb *rgb, unsigned int minor)
+{
+	int ret;
+
+	/* Register the red diode */
+	ret = hidled_init_led(&rgb->red, "red", rgb, minor);
+	if (ret)
+		return ret;
+
+	/* Register the green diode */
+	ret = hidled_init_led(&rgb->green, "green", rgb, minor);
+	if (ret)
+		return ret;
+
+	/* Register the blue diode */
+	return hidled_init_led(&rgb->blue, "blue", rgb, minor);
+}
+
+static int hidled_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct hidled_device *ldev;
+	unsigned int minor;
+	int ret, i;
+
+	ldev = devm_kzalloc(&hdev->dev, sizeof(*ldev), GFP_KERNEL);
+	if (!ldev)
+		return -ENOMEM;
+
+	ldev->buf = devm_kmalloc(&hdev->dev, MAX_REPORT_SIZE, GFP_KERNEL);
+	if (!ldev->buf)
+		return -ENOMEM;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+
+	ldev->hdev = hdev;
+	mutex_init(&ldev->lock);
+
+	for (i = 0; !ldev->config && i < ARRAY_SIZE(hidled_configs); i++)
+		if (hidled_configs[i].type == id->driver_data)
+			ldev->config = &hidled_configs[i];
+
+	if (!ldev->config)
+		return -EINVAL;
+
+	if (ldev->config->init) {
+		ret = ldev->config->init(ldev);
+		if (ret)
+			return ret;
+	}
+
+	ldev->rgb = devm_kcalloc(&hdev->dev, ldev->config->num_leds,
+				 sizeof(struct hidled_rgb), GFP_KERNEL);
+	if (!ldev->rgb)
+		return -ENOMEM;
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret)
+		return ret;
+
+	minor = ((struct hidraw *) hdev->hidraw)->minor;
+
+	for (i = 0; i < ldev->config->num_leds; i++) {
+		ldev->rgb[i].ldev = ldev;
+		ldev->rgb[i].num = i;
+		ret = hidled_init_rgb(&ldev->rgb[i], minor);
+		if (ret) {
+			hid_hw_stop(hdev);
+			return ret;
+		}
+	}
+
+	hid_info(hdev, "%s initialized\n", ldev->config->name);
+
+	return 0;
+}
+
+static const struct hid_device_id hidled_table[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU,
+	  USB_DEVICE_ID_RI_KA_WEBMAIL), .driver_data = RISO_KAGAKU },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
+	  USB_DEVICE_ID_DREAM_CHEEKY_WN), .driver_data = DREAM_CHEEKY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY,
+	  USB_DEVICE_ID_DREAM_CHEEKY_FA), .driver_data = DREAM_CHEEKY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM,
+	  USB_DEVICE_ID_BLINK1), .driver_data = THINGM },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELCOM,
+	  USB_DEVICE_ID_DELCOM_VISUAL_IND), .driver_data = DELCOM },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP,
+	  USB_DEVICE_ID_LUXAFOR), .driver_data = LUXAFOR },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, hidled_table);
+
+static struct hid_driver hidled_driver = {
+	.name = "hid-led",
+	.probe = hidled_probe,
+	.id_table = hidled_table,
+};
+
+module_hid_driver(hidled_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Heiner Kallweit <hkallweit1@gmail.com>");
+MODULE_DESCRIPTION("Simple USB RGB LED driver");
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
new file mode 100644
index 0000000..643b6eb
--- /dev/null
+++ b/drivers/hid/hid-lenovo.c
@@ -0,0 +1,940 @@
+/*
+ *  HID driver for Lenovo:
+ *  - ThinkPad USB Keyboard with TrackPoint (tpkbd)
+ *  - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
+ *  - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
+ *
+ *  Copyright (c) 2012 Bernhard Seibold
+ *  Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
+ *
+ * Linux IBM/Lenovo Scrollpoint mouse driver:
+ * - IBM Scrollpoint III
+ * - IBM Scrollpoint Pro
+ * - IBM Scrollpoint Optical
+ * - IBM Scrollpoint Optical 800dpi
+ * - IBM Scrollpoint Optical 800dpi Pro
+ * - Lenovo Scrollpoint Optical
+ *
+ *  Copyright (c) 2012 Peter De Wachter <pdewacht@gmail.com>
+ *  Copyright (c) 2018 Peter Ganzhorn <peter.ganzhorn@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+
+#include "hid-ids.h"
+
+struct lenovo_drvdata_tpkbd {
+	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;
+};
+
+struct lenovo_drvdata_cptkbd {
+	u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
+	bool fn_lock;
+	int sensitivity;
+};
+
+#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
+	0x05, 0x88,		/* Usage Page (Vendor Usage Page 0x88)	*/
+	0x09, 0x01,		/* Usage (Vendor Usage 0x01)		*/
+	0xa1, 0x01,		/* Collection (Application)		*/
+	0x85, 0x04,		/*  Report ID (4)			*/
+	0x19, 0x00,		/*  Usage Minimum (0)			*/
+	0x2a, 0xff, 0xff,	/*  Usage Maximum (65535)		*/
+};
+
+static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_TPPRODOCK:
+		/* the fixups that need to be done:
+		 *   - get a reasonable usage max for the vendor collection
+		 *     0x8801 from the report ID 4
+		 */
+		if (*rsize >= 153 &&
+		    memcmp(&rdesc[140], lenovo_pro_dock_need_fixup_collection,
+			  sizeof(lenovo_pro_dock_need_fixup_collection)) == 0) {
+			rdesc[151] = 0x01;
+			rdesc[152] = 0x00;
+		}
+		break;
+	}
+	return rdesc;
+}
+
+static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
+		/* This sub-device contains trackpoint, mark it */
+		hid_set_drvdata(hdev, (void *)1);
+		map_key_clear(KEY_MICMUTE);
+		return 1;
+	}
+	return 0;
+}
+
+static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	/* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
+	    (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
+		switch (usage->hid & HID_USAGE) {
+		case 0x00f1: /* Fn-F4: Mic mute */
+			map_key_clear(KEY_MICMUTE);
+			return 1;
+		case 0x00f2: /* Fn-F5: Brightness down */
+			map_key_clear(KEY_BRIGHTNESSDOWN);
+			return 1;
+		case 0x00f3: /* Fn-F6: Brightness up */
+			map_key_clear(KEY_BRIGHTNESSUP);
+			return 1;
+		case 0x00f4: /* Fn-F7: External display (projector) */
+			map_key_clear(KEY_SWITCHVIDEOMODE);
+			return 1;
+		case 0x00f5: /* Fn-F8: Wireless */
+			map_key_clear(KEY_WLAN);
+			return 1;
+		case 0x00f6: /* Fn-F9: Control panel */
+			map_key_clear(KEY_CONFIG);
+			return 1;
+		case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
+			map_key_clear(KEY_SCALE);
+			return 1;
+		case 0x00f9: /* Fn-F12: Open My computer (6 boxes) USB-only */
+			/* NB: This mapping is invented in raw_event below */
+			map_key_clear(KEY_FILE);
+			return 1;
+		case 0x00fa: /* Fn-Esc: Fn-lock toggle */
+			map_key_clear(KEY_FN_ESC);
+			return 1;
+		case 0x00fb: /* Middle mouse button (in native mode) */
+			map_key_clear(BTN_MIDDLE);
+			return 1;
+		}
+	}
+
+	/* Compatibility middle/wheel mappings should be ignored */
+	if (usage->hid == HID_GD_WHEEL)
+		return -1;
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON &&
+			(usage->hid & HID_USAGE) == 0x003)
+		return -1;
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
+			(usage->hid & HID_USAGE) == 0x238)
+		return -1;
+
+	/* Map wheel emulation reports: 0xffa1 = USB, 0xff10 = BT */
+	if ((usage->hid & HID_USAGE_PAGE) == 0xff100000 ||
+	    (usage->hid & HID_USAGE_PAGE) == 0xffa10000) {
+		field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE;
+		field->logical_minimum = -127;
+		field->logical_maximum = 127;
+
+		switch (usage->hid & HID_USAGE) {
+		case 0x0000:
+			hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
+			return 1;
+		case 0x0001:
+			hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
+			return 1;
+		default:
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int lenovo_input_mapping_scrollpoint(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	if (usage->hid == HID_GD_Z) {
+		hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
+		return 1;
+	}
+	return 0;
+}
+
+static int lenovo_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_TPKBD:
+		return lenovo_input_mapping_tpkbd(hdev, hi, field,
+							usage, bit, max);
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		return lenovo_input_mapping_cptkbd(hdev, hi, field,
+							usage, bit, max);
+	case USB_DEVICE_ID_IBM_SCROLLPOINT_III:
+	case USB_DEVICE_ID_IBM_SCROLLPOINT_PRO:
+	case USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL:
+	case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL:
+	case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO:
+	case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL:
+		return lenovo_input_mapping_scrollpoint(hdev, hi, field,
+							usage, bit, max);
+	default:
+		return 0;
+	}
+}
+
+#undef map_key_clear
+
+/* Send a config command to the keyboard */
+static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
+			unsigned char byte2, unsigned char byte3)
+{
+	int ret;
+	unsigned char *buf;
+
+	buf = kzalloc(3, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = 0x18;
+	buf[1] = byte2;
+	buf[2] = byte3;
+
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+		ret = hid_hw_raw_request(hdev, 0x13, buf, 3,
+					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		break;
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		ret = hid_hw_output_report(hdev, buf, 3);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	kfree(buf);
+
+	return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
+}
+
+static void lenovo_features_set_cptkbd(struct hid_device *hdev)
+{
+	int ret;
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+	ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
+	if (ret)
+		hid_err(hdev, "Fn-lock setting failed: %d\n", ret);
+
+	ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
+	if (ret)
+		hid_err(hdev, "Sensitivity setting failed: %d\n", ret);
+}
+
+static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock);
+}
+
+static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	cptkbd_data->fn_lock = !!value;
+	lenovo_features_set_cptkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		cptkbd_data->sensitivity);
+}
+
+static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	cptkbd_data->sensitivity = value;
+	lenovo_features_set_cptkbd(hdev);
+
+	return count;
+}
+
+
+static struct device_attribute dev_attr_fn_lock_cptkbd =
+	__ATTR(fn_lock, S_IWUSR | S_IRUGO,
+			attr_fn_lock_show_cptkbd,
+			attr_fn_lock_store_cptkbd);
+
+static struct device_attribute dev_attr_sensitivity_cptkbd =
+	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
+			attr_sensitivity_show_cptkbd,
+			attr_sensitivity_store_cptkbd);
+
+
+static struct attribute *lenovo_attributes_cptkbd[] = {
+	&dev_attr_fn_lock_cptkbd.attr,
+	&dev_attr_sensitivity_cptkbd.attr,
+	NULL
+};
+
+static const struct attribute_group lenovo_attr_group_cptkbd = {
+	.attrs = lenovo_attributes_cptkbd,
+};
+
+static int lenovo_raw_event(struct hid_device *hdev,
+			struct hid_report *report, u8 *data, int size)
+{
+	/*
+	 * Compact USB keyboard's Fn-F12 report holds down many other keys, and
+	 * its own key is outside the usage page range. Remove extra
+	 * keypresses and remap to inside usage page.
+	 */
+	if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+			&& size == 3
+			&& data[0] == 0x15
+			&& data[1] == 0x94
+			&& data[2] == 0x01)) {
+		data[1] = 0x00;
+		data[2] = 0x01;
+	}
+
+	return 0;
+}
+
+static int lenovo_event_cptkbd(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
+
+	/* "wheel" scroll events */
+	if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
+			usage->code == REL_HWHEEL)) {
+		/* Scroll events disable middle-click event */
+		cptkbd_data->middlebutton_state = 2;
+		return 0;
+	}
+
+	/* Middle click events */
+	if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
+		if (value == 1) {
+			cptkbd_data->middlebutton_state = 1;
+		} else if (value == 0) {
+			if (cptkbd_data->middlebutton_state == 1) {
+				/* No scrolling inbetween, send middle-click */
+				input_event(field->hidinput->input,
+					EV_KEY, BTN_MIDDLE, 1);
+				input_sync(field->hidinput->input);
+				input_event(field->hidinput->input,
+					EV_KEY, BTN_MIDDLE, 0);
+				input_sync(field->hidinput->input);
+			}
+			cptkbd_data->middlebutton_state = 0;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		return lenovo_event_cptkbd(hdev, field, usage, value);
+	default:
+		return 0;
+	}
+}
+
+static int lenovo_features_set_tpkbd(struct hid_device *hdev)
+{
+	struct hid_report *report;
+	struct lenovo_drvdata_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;
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+	return 0;
+}
+
+static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
+}
+
+static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->press_to_select = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_dragging_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
+}
+
+static ssize_t attr_dragging_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->dragging = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
+}
+
+static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->release_to_select = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_select_right_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
+}
+
+static ssize_t attr_select_right_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->select_right = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->sensitivity);
+}
+
+static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->sensitivity = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->press_speed);
+}
+
+static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int value;
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->press_speed = value;
+	lenovo_features_set_tpkbd(hdev);
+
+	return count;
+}
+
+static struct device_attribute dev_attr_press_to_select_tpkbd =
+	__ATTR(press_to_select, S_IWUSR | S_IRUGO,
+			attr_press_to_select_show_tpkbd,
+			attr_press_to_select_store_tpkbd);
+
+static struct device_attribute dev_attr_dragging_tpkbd =
+	__ATTR(dragging, S_IWUSR | S_IRUGO,
+			attr_dragging_show_tpkbd,
+			attr_dragging_store_tpkbd);
+
+static struct device_attribute dev_attr_release_to_select_tpkbd =
+	__ATTR(release_to_select, S_IWUSR | S_IRUGO,
+			attr_release_to_select_show_tpkbd,
+			attr_release_to_select_store_tpkbd);
+
+static struct device_attribute dev_attr_select_right_tpkbd =
+	__ATTR(select_right, S_IWUSR | S_IRUGO,
+			attr_select_right_show_tpkbd,
+			attr_select_right_store_tpkbd);
+
+static struct device_attribute dev_attr_sensitivity_tpkbd =
+	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
+			attr_sensitivity_show_tpkbd,
+			attr_sensitivity_store_tpkbd);
+
+static struct device_attribute dev_attr_press_speed_tpkbd =
+	__ATTR(press_speed, S_IWUSR | S_IRUGO,
+			attr_press_speed_show_tpkbd,
+			attr_press_speed_store_tpkbd);
+
+static struct attribute *lenovo_attributes_tpkbd[] = {
+	&dev_attr_press_to_select_tpkbd.attr,
+	&dev_attr_dragging_tpkbd.attr,
+	&dev_attr_release_to_select_tpkbd.attr,
+	&dev_attr_select_right_tpkbd.attr,
+	&dev_attr_sensitivity_tpkbd.attr,
+	&dev_attr_press_speed_tpkbd.attr,
+	NULL
+};
+
+static const struct attribute_group lenovo_attr_group_tpkbd = {
+	.attrs = lenovo_attributes_tpkbd,
+};
+
+static enum led_brightness lenovo_led_brightness_get_tpkbd(
+			struct led_classdev *led_cdev)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	int led_nr = 0;
+
+	if (led_cdev == &data_pointer->led_micmute)
+		led_nr = 1;
+
+	return data_pointer->led_state & (1 << led_nr)
+				? LED_FULL
+				: LED_OFF;
+}
+
+static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+	struct hid_report *report;
+	int led_nr = 0;
+
+	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;
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+}
+
+static int lenovo_probe_tpkbd(struct hid_device *hdev)
+{
+	struct device *dev = &hdev->dev;
+	struct lenovo_drvdata_tpkbd *data_pointer;
+	size_t name_sz = strlen(dev_name(dev)) + 16;
+	char *name_mute, *name_micmute;
+	int i;
+	int ret;
+
+	/*
+	 * Only register extra settings against subdevice where input_mapping
+	 * set drvdata to 1, i.e. the trackpoint.
+	 */
+	if (!hid_get_drvdata(hdev))
+		return 0;
+
+	hid_set_drvdata(hdev, NULL);
+
+	/* Validate required reports. */
+	for (i = 0; i < 4; i++) {
+		if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
+			return -ENODEV;
+	}
+	if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
+		return -ENODEV;
+
+	ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
+	if (ret)
+		hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
+
+	data_pointer = devm_kzalloc(&hdev->dev,
+				    sizeof(struct lenovo_drvdata_tpkbd),
+				    GFP_KERNEL);
+	if (data_pointer == NULL) {
+		hid_err(hdev, "Could not allocate memory for driver data\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	// set same default values as windows driver
+	data_pointer->sensitivity = 0xa0;
+	data_pointer->press_speed = 0x38;
+
+	name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+	name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+	if (name_mute == NULL || name_micmute == 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));
+	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 = lenovo_led_brightness_get_tpkbd;
+	data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
+	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 =
+		lenovo_led_brightness_get_tpkbd;
+	data_pointer->led_micmute.brightness_set =
+		lenovo_led_brightness_set_tpkbd;
+	data_pointer->led_micmute.dev = dev;
+	led_classdev_register(dev, &data_pointer->led_micmute);
+
+	lenovo_features_set_tpkbd(hdev);
+
+	return 0;
+err:
+	sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
+	return ret;
+}
+
+static int lenovo_probe_cptkbd(struct hid_device *hdev)
+{
+	int ret;
+	struct lenovo_drvdata_cptkbd *cptkbd_data;
+
+	/* All the custom action happens on the USBMOUSE device for USB */
+	if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
+			&& hdev->type != HID_TYPE_USBMOUSE) {
+		hid_dbg(hdev, "Ignoring keyboard half of device\n");
+		return 0;
+	}
+
+	cptkbd_data = devm_kzalloc(&hdev->dev,
+					sizeof(*cptkbd_data),
+					GFP_KERNEL);
+	if (cptkbd_data == NULL) {
+		hid_err(hdev, "can't alloc keyboard descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, cptkbd_data);
+
+	/*
+	 * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
+	 * regular keys
+	 */
+	ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
+	if (ret)
+		hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret);
+
+	/* Switch middle button to native mode */
+	ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01);
+	if (ret)
+		hid_warn(hdev, "Failed to switch middle button: %d\n", ret);
+
+	/* Set keyboard settings to known state */
+	cptkbd_data->middlebutton_state = 0;
+	cptkbd_data->fn_lock = true;
+	cptkbd_data->sensitivity = 0x05;
+	lenovo_features_set_cptkbd(hdev);
+
+	ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
+	if (ret)
+		hid_warn(hdev, "Could not create sysfs group: %d\n", ret);
+
+	return 0;
+}
+
+static int lenovo_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid_parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hid_hw_start failed\n");
+		goto err;
+	}
+
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_TPKBD:
+		ret = lenovo_probe_tpkbd(hdev);
+		break;
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		ret = lenovo_probe_cptkbd(hdev);
+		break;
+	default:
+		ret = 0;
+		break;
+	}
+	if (ret)
+		goto err_hid;
+
+	return 0;
+err_hid:
+	hid_hw_stop(hdev);
+err:
+	return ret;
+}
+
+static void lenovo_remove_tpkbd(struct hid_device *hdev)
+{
+	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
+
+	/*
+	 * Only the trackpoint half of the keyboard has drvdata and stuff that
+	 * needs unregistering.
+	 */
+	if (data_pointer == NULL)
+		return;
+
+	sysfs_remove_group(&hdev->dev.kobj,
+			&lenovo_attr_group_tpkbd);
+
+	led_classdev_unregister(&data_pointer->led_micmute);
+	led_classdev_unregister(&data_pointer->led_mute);
+
+	hid_set_drvdata(hdev, NULL);
+}
+
+static void lenovo_remove_cptkbd(struct hid_device *hdev)
+{
+	sysfs_remove_group(&hdev->dev.kobj,
+			&lenovo_attr_group_cptkbd);
+}
+
+static void lenovo_remove(struct hid_device *hdev)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_LENOVO_TPKBD:
+		lenovo_remove_tpkbd(hdev);
+		break;
+	case USB_DEVICE_ID_LENOVO_CUSBKBD:
+	case USB_DEVICE_ID_LENOVO_CBTKBD:
+		lenovo_remove_cptkbd(hdev);
+		break;
+	}
+
+	hid_hw_stop(hdev);
+}
+
+static int lenovo_input_configured(struct hid_device *hdev,
+		struct hid_input *hi)
+{
+	switch (hdev->product) {
+		case USB_DEVICE_ID_LENOVO_TPKBD:
+		case USB_DEVICE_ID_LENOVO_CUSBKBD:
+		case USB_DEVICE_ID_LENOVO_CBTKBD:
+			if (test_bit(EV_REL, hi->input->evbit)) {
+				/* set only for trackpoint device */
+				__set_bit(INPUT_PROP_POINTER, hi->input->propbit);
+				__set_bit(INPUT_PROP_POINTING_STICK,
+						hi->input->propbit);
+			}
+			break;
+	}
+
+	return 0;
+}
+
+
+static const struct hid_device_id lenovo_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_III) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, lenovo_devices);
+
+static struct hid_driver lenovo_driver = {
+	.name = "lenovo",
+	.id_table = lenovo_devices,
+	.input_configured = lenovo_input_configured,
+	.input_mapping = lenovo_input_mapping,
+	.probe = lenovo_probe,
+	.remove = lenovo_remove,
+	.raw_event = lenovo_raw_event,
+	.event = lenovo_event,
+	.report_fixup = lenovo_report_fixup,
+};
+module_hid_driver(lenovo_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg.c b/drivers/hid/hid-lg.c
new file mode 100644
index 0000000..596227d
--- /dev/null
+++ b/drivers/hid/hid-lg.c
@@ -0,0 +1,908 @@
+/*
+ *  HID driver for some logitech "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ *  Copyright (c) 2010 Hendrik Iben
+ */
+
+/*
+ * 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/random.h>
+#include <linux/sched.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+
+#include "usbhid/usbhid.h"
+#include "hid-ids.h"
+#include "hid-lg.h"
+#include "hid-lg4ff.h"
+
+#define LG_RDESC		0x001
+#define LG_BAD_RELATIVE_KEYS	0x002
+#define LG_DUPLICATE_USAGES	0x004
+#define LG_EXPANDED_KEYMAP	0x010
+#define LG_IGNORE_DOUBLED_WHEEL	0x020
+#define LG_WIRELESS		0x040
+#define LG_INVERT_HWHEEL	0x080
+#define LG_NOGET		0x100
+#define LG_FF			0x200
+#define LG_FF2			0x400
+#define LG_RDESC_REL_ABS	0x800
+#define LG_FF3			0x1000
+#define LG_FF4			0x2000
+
+/* Size of the original descriptors of the Driving Force (and Pro) wheels */
+#define DF_RDESC_ORIG_SIZE	130
+#define DFP_RDESC_ORIG_SIZE	97
+#define FV_RDESC_ORIG_SIZE	130
+#define MOMO_RDESC_ORIG_SIZE	87
+#define MOMO2_RDESC_ORIG_SIZE	87
+#define FFG_RDESC_ORIG_SIZE	85
+
+/* Fixed report descriptors for Logitech Driving Force (and Pro)
+ * wheel controllers
+ *
+ * The original descriptors hide the separate throttle and brake axes in
+ * a custom vendor usage page, providing only a combined value as
+ * GenericDesktop.Y.
+ * These descriptors remove the combined Y axis and instead report
+ * separate throttle (Y) and brake (RZ).
+ */
+static __u8 df_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),                   */
+0x09, 0x04,         /*  Usage (Joystick),                       */
+0xA1, 0x01,         /*  Collection (Application),               */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x0A,         /*          Report Size (10),               */
+0x14,               /*          Logical Minimum (0),            */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),         */
+0x34,               /*          Physical Minimum (0),           */
+0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),        */
+0x09, 0x30,         /*          Usage (X),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x95, 0x0C,         /*          Report Count (12),              */
+0x75, 0x01,         /*          Report Size (1),                */
+0x25, 0x01,         /*          Logical Maximum (1),            */
+0x45, 0x01,         /*          Physical Maximum (1),           */
+0x05, 0x09,         /*          Usage (Buttons),                */
+0x19, 0x01,         /*          Usage Minimum (1),              */
+0x29, 0x0c,         /*          Usage Maximum (12),             */
+0x81, 0x02,         /*          Input (Variable),               */
+0x95, 0x02,         /*          Report Count (2),               */
+0x06, 0x00, 0xFF,   /*          Usage Page (Vendor: 65280),     */
+0x09, 0x01,         /*          Usage (?: 1),                   */
+0x81, 0x02,         /*          Input (Variable),               */
+0x05, 0x01,         /*          Usage Page (Desktop),           */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x81, 0x02,         /*          Input (Variable),               */
+0x25, 0x07,         /*          Logical Maximum (7),            */
+0x46, 0x3B, 0x01,   /*          Physical Maximum (315),         */
+0x75, 0x04,         /*          Report Size (4),                */
+0x65, 0x14,         /*          Unit (Degrees),                 */
+0x09, 0x39,         /*          Usage (Hat Switch),             */
+0x81, 0x42,         /*          Input (Variable, Null State),   */
+0x75, 0x01,         /*          Report Size (1),                */
+0x95, 0x04,         /*          Report Count (4),               */
+0x65, 0x00,         /*          Unit (none),                    */
+0x06, 0x00, 0xFF,   /*          Usage Page (Vendor: 65280),     */
+0x09, 0x01,         /*          Usage (?: 1),                   */
+0x25, 0x01,         /*          Logical Maximum (1),            */
+0x45, 0x01,         /*          Physical Maximum (1),           */
+0x81, 0x02,         /*          Input (Variable),               */
+0x05, 0x01,         /*          Usage Page (Desktop),           */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x09, 0x31,         /*          Usage (Y),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x09, 0x35,         /*          Usage (Rz),                     */
+0x81, 0x02,         /*          Input (Variable),               */
+0xC0,               /*      End Collection,                     */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x95, 0x07,         /*          Report Count (7),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x09, 0x03,         /*          Usage (?: 3),                   */
+0x91, 0x02,         /*          Output (Variable),              */
+0xC0,               /*      End Collection,                     */
+0xC0                /*  End Collection                          */
+};
+
+static __u8 dfp_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),                   */
+0x09, 0x04,         /*  Usage (Joystick),                       */
+0xA1, 0x01,         /*  Collection (Application),               */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x0E,         /*          Report Size (14),               */
+0x14,               /*          Logical Minimum (0),            */
+0x26, 0xFF, 0x3F,   /*          Logical Maximum (16383),        */
+0x34,               /*          Physical Minimum (0),           */
+0x46, 0xFF, 0x3F,   /*          Physical Maximum (16383),       */
+0x09, 0x30,         /*          Usage (X),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x95, 0x0E,         /*          Report Count (14),              */
+0x75, 0x01,         /*          Report Size (1),                */
+0x25, 0x01,         /*          Logical Maximum (1),            */
+0x45, 0x01,         /*          Physical Maximum (1),           */
+0x05, 0x09,         /*          Usage Page (Button),            */
+0x19, 0x01,         /*          Usage Minimum (01h),            */
+0x29, 0x0E,         /*          Usage Maximum (0Eh),            */
+0x81, 0x02,         /*          Input (Variable),               */
+0x05, 0x01,         /*          Usage Page (Desktop),           */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x04,         /*          Report Size (4),                */
+0x25, 0x07,         /*          Logical Maximum (7),            */
+0x46, 0x3B, 0x01,   /*          Physical Maximum (315),         */
+0x65, 0x14,         /*          Unit (Degrees),                 */
+0x09, 0x39,         /*          Usage (Hat Switch),             */
+0x81, 0x42,         /*          Input (Variable, Nullstate),    */
+0x65, 0x00,         /*          Unit,                           */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x75, 0x08,         /*          Report Size (8),                */
+0x81, 0x01,         /*          Input (Constant),               */
+0x09, 0x31,         /*          Usage (Y),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x09, 0x35,         /*          Usage (Rz),                     */
+0x81, 0x02,         /*          Input (Variable),               */
+0x81, 0x01,         /*          Input (Constant),               */
+0xC0,               /*      End Collection,                     */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x09, 0x02,         /*          Usage (02h),                    */
+0x95, 0x07,         /*          Report Count (7),               */
+0x91, 0x02,         /*          Output (Variable),              */
+0xC0,               /*      End Collection,                     */
+0xC0                /*  End Collection                          */
+};
+
+static __u8 fv_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),                   */
+0x09, 0x04,         /*  Usage (Joystick),                       */
+0xA1, 0x01,         /*  Collection (Application),               */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x0A,         /*          Report Size (10),               */
+0x15, 0x00,         /*          Logical Minimum (0),            */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),         */
+0x35, 0x00,         /*          Physical Minimum (0),           */
+0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),        */
+0x09, 0x30,         /*          Usage (X),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x95, 0x0C,         /*          Report Count (12),              */
+0x75, 0x01,         /*          Report Size (1),                */
+0x25, 0x01,         /*          Logical Maximum (1),            */
+0x45, 0x01,         /*          Physical Maximum (1),           */
+0x05, 0x09,         /*          Usage Page (Button),            */
+0x19, 0x01,         /*          Usage Minimum (01h),            */
+0x29, 0x0C,         /*          Usage Maximum (0Ch),            */
+0x81, 0x02,         /*          Input (Variable),               */
+0x95, 0x02,         /*          Report Count (2),               */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),             */
+0x09, 0x01,         /*          Usage (01h),                    */
+0x81, 0x02,         /*          Input (Variable),               */
+0x09, 0x02,         /*          Usage (02h),                    */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x81, 0x02,         /*          Input (Variable),               */
+0x05, 0x01,         /*          Usage Page (Desktop),           */
+0x25, 0x07,         /*          Logical Maximum (7),            */
+0x46, 0x3B, 0x01,   /*          Physical Maximum (315),         */
+0x75, 0x04,         /*          Report Size (4),                */
+0x65, 0x14,         /*          Unit (Degrees),                 */
+0x09, 0x39,         /*          Usage (Hat Switch),             */
+0x81, 0x42,         /*          Input (Variable, Null State),   */
+0x75, 0x01,         /*          Report Size (1),                */
+0x95, 0x04,         /*          Report Count (4),               */
+0x65, 0x00,         /*          Unit,                           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),             */
+0x09, 0x01,         /*          Usage (01h),                    */
+0x25, 0x01,         /*          Logical Maximum (1),            */
+0x45, 0x01,         /*          Physical Maximum (1),           */
+0x81, 0x02,         /*          Input (Variable),               */
+0x05, 0x01,         /*          Usage Page (Desktop),           */
+0x95, 0x01,         /*          Report Count (1),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x09, 0x31,         /*          Usage (Y),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0x09, 0x32,         /*          Usage (Z),                      */
+0x81, 0x02,         /*          Input (Variable),               */
+0xC0,               /*      End Collection,                     */
+0xA1, 0x02,         /*      Collection (Logical),               */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),          */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),         */
+0x95, 0x07,         /*          Report Count (7),               */
+0x75, 0x08,         /*          Report Size (8),                */
+0x09, 0x03,         /*          Usage (03h),                    */
+0x91, 0x02,         /*          Output (Variable),              */
+0xC0,               /*      End Collection,                     */
+0xC0                /*  End Collection                          */
+};
+
+static __u8 momo_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),               */
+0x09, 0x04,         /*  Usage (Joystick),                   */
+0xA1, 0x01,         /*  Collection (Application),           */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x75, 0x0A,         /*          Report Size (10),           */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+0x35, 0x00,         /*          Physical Minimum (0),       */
+0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),    */
+0x09, 0x30,         /*          Usage (X),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x95, 0x08,         /*          Report Count (8),           */
+0x75, 0x01,         /*          Report Size (1),            */
+0x25, 0x01,         /*          Logical Maximum (1),        */
+0x45, 0x01,         /*          Physical Maximum (1),       */
+0x05, 0x09,         /*          Usage Page (Button),        */
+0x19, 0x01,         /*          Usage Minimum (01h),        */
+0x29, 0x08,         /*          Usage Maximum (08h),        */
+0x81, 0x02,         /*          Input (Variable),           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x75, 0x0E,         /*          Report Size (14),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),     */
+0x09, 0x00,         /*          Usage (00h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0x05, 0x01,         /*          Usage Page (Desktop),       */
+0x75, 0x08,         /*          Report Size (8),            */
+0x09, 0x31,         /*          Usage (Y),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x32,         /*          Usage (Z),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x01,         /*          Usage (01h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0xC0,               /*      End Collection,                 */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x09, 0x02,         /*          Usage (02h),                */
+0x95, 0x07,         /*          Report Count (7),           */
+0x91, 0x02,         /*          Output (Variable),          */
+0xC0,               /*      End Collection,                 */
+0xC0                /*  End Collection                      */
+};
+
+static __u8 momo2_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),               */
+0x09, 0x04,         /*  Usage (Joystick),                   */
+0xA1, 0x01,         /*  Collection (Application),           */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x75, 0x0A,         /*          Report Size (10),           */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+0x35, 0x00,         /*          Physical Minimum (0),       */
+0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),    */
+0x09, 0x30,         /*          Usage (X),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x95, 0x0A,         /*          Report Count (10),          */
+0x75, 0x01,         /*          Report Size (1),            */
+0x25, 0x01,         /*          Logical Maximum (1),        */
+0x45, 0x01,         /*          Physical Maximum (1),       */
+0x05, 0x09,         /*          Usage Page (Button),        */
+0x19, 0x01,         /*          Usage Minimum (01h),        */
+0x29, 0x0A,         /*          Usage Maximum (0Ah),        */
+0x81, 0x02,         /*          Input (Variable),           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x00,         /*          Usage (00h),                */
+0x95, 0x04,         /*          Report Count (4),           */
+0x81, 0x02,         /*          Input (Variable),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x75, 0x08,         /*          Report Size (8),            */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),     */
+0x09, 0x01,         /*          Usage (01h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0x05, 0x01,         /*          Usage Page (Desktop),       */
+0x09, 0x31,         /*          Usage (Y),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x32,         /*          Usage (Z),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x00,         /*          Usage (00h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0xC0,               /*      End Collection,                 */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x09, 0x02,         /*          Usage (02h),                */
+0x95, 0x07,         /*          Report Count (7),           */
+0x91, 0x02,         /*          Output (Variable),          */
+0xC0,               /*      End Collection,                 */
+0xC0                /*  End Collection                      */
+};
+
+static __u8 ffg_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop),               */
+0x09, 0x04,         /*  Usage (Joystik),                    */
+0xA1, 0x01,         /*  Collection (Application),           */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x75, 0x0A,         /*          Report Size (10),           */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+0x35, 0x00,         /*          Physical Minimum (0),       */
+0x46, 0xFF, 0x03,   /*          Physical Maximum (1023),    */
+0x09, 0x30,         /*          Usage (X),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x95, 0x06,         /*          Report Count (6),           */
+0x75, 0x01,         /*          Report Size (1),            */
+0x25, 0x01,         /*          Logical Maximum (1),        */
+0x45, 0x01,         /*          Physical Maximum (1),       */
+0x05, 0x09,         /*          Usage Page (Button),        */
+0x19, 0x01,         /*          Usage Minimum (01h),        */
+0x29, 0x06,         /*          Usage Maximum (06h),        */
+0x81, 0x02,         /*          Input (Variable),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x75, 0x08,         /*          Report Size (8),            */
+0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+0x46, 0xFF, 0x00,   /*          Physical Maximum (255),     */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x01,         /*          Usage (01h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0x05, 0x01,         /*          Usage Page (Desktop),       */
+0x81, 0x01,         /*          Input (Constant),           */
+0x09, 0x31,         /*          Usage (Y),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x32,         /*          Usage (Z),                  */
+0x81, 0x02,         /*          Input (Variable),           */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x01,         /*          Usage (01h),                */
+0x81, 0x02,         /*          Input (Variable),           */
+0xC0,               /*      End Collection,                 */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x09, 0x02,         /*          Usage (02h),                */
+0x95, 0x07,         /*          Report Count (7),           */
+0x91, 0x02,         /*          Output (Variable),          */
+0xC0,               /*      End Collection,                 */
+0xC0                /*  End Collection                      */
+};
+
+/*
+ * Certain Logitech keyboards send in report #3 keys which are far
+ * above the logical maximum described in descriptor. This extends
+ * the original value of 0x28c of logical maximum to 0x104d
+ */
+static __u8 *lg_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+
+	if ((drv_data->quirks & LG_RDESC) && *rsize >= 91 && rdesc[83] == 0x26 &&
+			rdesc[84] == 0x8c && rdesc[85] == 0x02) {
+		hid_info(hdev,
+			 "fixing up Logitech keyboard report descriptor\n");
+		rdesc[84] = rdesc[89] = 0x4d;
+		rdesc[85] = rdesc[90] = 0x10;
+	}
+	if ((drv_data->quirks & LG_RDESC_REL_ABS) && *rsize >= 51 &&
+			rdesc[32] == 0x81 && rdesc[33] == 0x06 &&
+			rdesc[49] == 0x81 && rdesc[50] == 0x06) {
+		hid_info(hdev,
+			 "fixing up rel/abs in Logitech report descriptor\n");
+		rdesc[33] = rdesc[50] = 0x02;
+	}
+
+	switch (hdev->product) {
+
+	case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
+		if (*rsize == FFG_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Wingman Formula Force GP report descriptor\n");
+			rdesc = ffg_rdesc_fixed;
+			*rsize = sizeof(ffg_rdesc_fixed);
+		}
+		break;
+
+	/* Several wheels report as this id when operating in emulation mode. */
+	case USB_DEVICE_ID_LOGITECH_WHEEL:
+		if (*rsize == DF_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Driving Force report descriptor\n");
+			rdesc = df_rdesc_fixed;
+			*rsize = sizeof(df_rdesc_fixed);
+		}
+		break;
+
+	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
+		if (*rsize == MOMO_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Momo Force (Red) report descriptor\n");
+			rdesc = momo_rdesc_fixed;
+			*rsize = sizeof(momo_rdesc_fixed);
+		}
+		break;
+
+	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
+		if (*rsize == MOMO2_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Momo Racing Force (Black) report descriptor\n");
+			rdesc = momo2_rdesc_fixed;
+			*rsize = sizeof(momo2_rdesc_fixed);
+		}
+		break;
+
+	case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL:
+		if (*rsize == FV_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Formula Vibration report descriptor\n");
+			rdesc = fv_rdesc_fixed;
+			*rsize = sizeof(fv_rdesc_fixed);
+		}
+		break;
+
+	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+		if (*rsize == DFP_RDESC_ORIG_SIZE) {
+			hid_info(hdev,
+				"fixing up Logitech Driving Force Pro report descriptor\n");
+			rdesc = dfp_rdesc_fixed;
+			*rsize = sizeof(dfp_rdesc_fixed);
+		}
+		break;
+
+	case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
+		if (*rsize >= 101 && rdesc[41] == 0x95 && rdesc[42] == 0x0B &&
+				rdesc[47] == 0x05 && rdesc[48] == 0x09) {
+			hid_info(hdev, "fixing up Logitech Speed Force Wireless report descriptor\n");
+			rdesc[41] = 0x05;
+			rdesc[42] = 0x09;
+			rdesc[47] = 0x95;
+			rdesc[48] = 0x0B;
+		}
+		break;
+	}
+
+	return rdesc;
+}
+
+#define lg_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+		EV_KEY, (c))
+
+static int lg_ultrax_remote_mapping(struct hid_input *hi,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+		return 0;
+
+	set_bit(EV_REP, hi->input->evbit);
+	switch (usage->hid & HID_USAGE) {
+	/* Reported on Logitech Ultra X Media Remote */
+	case 0x004: lg_map_key_clear(KEY_AGAIN);	break;
+	case 0x00d: lg_map_key_clear(KEY_HOME);		break;
+	case 0x024: lg_map_key_clear(KEY_SHUFFLE);	break;
+	case 0x025: lg_map_key_clear(KEY_TV);		break;
+	case 0x026: lg_map_key_clear(KEY_MENU);		break;
+	case 0x031: lg_map_key_clear(KEY_AUDIO);	break;
+	case 0x032: lg_map_key_clear(KEY_TEXT);		break;
+	case 0x033: lg_map_key_clear(KEY_LAST);		break;
+	case 0x047: lg_map_key_clear(KEY_MP3);		break;
+	case 0x048: lg_map_key_clear(KEY_DVD);		break;
+	case 0x049: lg_map_key_clear(KEY_MEDIA);	break;
+	case 0x04a: lg_map_key_clear(KEY_VIDEO);	break;
+	case 0x04b: lg_map_key_clear(KEY_ANGLE);	break;
+	case 0x04c: lg_map_key_clear(KEY_LANGUAGE);	break;
+	case 0x04d: lg_map_key_clear(KEY_SUBTITLE);	break;
+	case 0x051: lg_map_key_clear(KEY_RED);		break;
+	case 0x052: lg_map_key_clear(KEY_CLOSE);	break;
+
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int lg_dinovo_mapping(struct hid_input *hi, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+
+	case 0x00d: lg_map_key_clear(KEY_MEDIA);	break;
+	default:
+		return 0;
+
+	}
+	return 1;
+}
+
+static int lg_wireless_mapping(struct hid_input *hi, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x1001: lg_map_key_clear(KEY_MESSENGER);		break;
+	case 0x1003: lg_map_key_clear(KEY_SOUND);		break;
+	case 0x1004: lg_map_key_clear(KEY_VIDEO);		break;
+	case 0x1005: lg_map_key_clear(KEY_AUDIO);		break;
+	case 0x100a: lg_map_key_clear(KEY_DOCUMENTS);		break;
+	/* The following two entries are Playlist 1 and 2 on the MX3200 */
+	case 0x100f: lg_map_key_clear(KEY_FN_1);		break;
+	case 0x1010: lg_map_key_clear(KEY_FN_2);		break;
+	case 0x1011: lg_map_key_clear(KEY_PREVIOUSSONG);	break;
+	case 0x1012: lg_map_key_clear(KEY_NEXTSONG);		break;
+	case 0x1013: lg_map_key_clear(KEY_CAMERA);		break;
+	case 0x1014: lg_map_key_clear(KEY_MESSENGER);		break;
+	case 0x1015: lg_map_key_clear(KEY_RECORD);		break;
+	case 0x1016: lg_map_key_clear(KEY_PLAYER);		break;
+	case 0x1017: lg_map_key_clear(KEY_EJECTCD);		break;
+	case 0x1018: lg_map_key_clear(KEY_MEDIA);		break;
+	case 0x1019: lg_map_key_clear(KEY_PROG1);		break;
+	case 0x101a: lg_map_key_clear(KEY_PROG2);		break;
+	case 0x101b: lg_map_key_clear(KEY_PROG3);		break;
+	case 0x101c: lg_map_key_clear(KEY_CYCLEWINDOWS);	break;
+	case 0x101f: lg_map_key_clear(KEY_ZOOMIN);		break;
+	case 0x1020: lg_map_key_clear(KEY_ZOOMOUT);		break;
+	case 0x1021: lg_map_key_clear(KEY_ZOOMRESET);		break;
+	case 0x1023: lg_map_key_clear(KEY_CLOSE);		break;
+	case 0x1027: lg_map_key_clear(KEY_MENU);		break;
+	/* this one is marked as 'Rotate' */
+	case 0x1028: lg_map_key_clear(KEY_ANGLE);		break;
+	case 0x1029: lg_map_key_clear(KEY_SHUFFLE);		break;
+	case 0x102a: lg_map_key_clear(KEY_BACK);		break;
+	case 0x102b: lg_map_key_clear(KEY_CYCLEWINDOWS);	break;
+	case 0x102d: lg_map_key_clear(KEY_WWW);			break;
+	/* The following two are 'Start/answer call' and 'End/reject call'
+	   on the MX3200 */
+	case 0x1031: lg_map_key_clear(KEY_OK);			break;
+	case 0x1032: lg_map_key_clear(KEY_CANCEL);		break;
+	case 0x1041: lg_map_key_clear(KEY_BATTERY);		break;
+	case 0x1042: lg_map_key_clear(KEY_WORDPROCESSOR);	break;
+	case 0x1043: lg_map_key_clear(KEY_SPREADSHEET);		break;
+	case 0x1044: lg_map_key_clear(KEY_PRESENTATION);	break;
+	case 0x1045: lg_map_key_clear(KEY_UNDO);		break;
+	case 0x1046: lg_map_key_clear(KEY_REDO);		break;
+	case 0x1047: lg_map_key_clear(KEY_PRINT);		break;
+	case 0x1048: lg_map_key_clear(KEY_SAVE);		break;
+	case 0x1049: lg_map_key_clear(KEY_PROG1);		break;
+	case 0x104a: lg_map_key_clear(KEY_PROG2);		break;
+	case 0x104b: lg_map_key_clear(KEY_PROG3);		break;
+	case 0x104c: lg_map_key_clear(KEY_PROG4);		break;
+
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int lg_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	/* extended mapping for certain Logitech hardware (Logitech cordless
+	   desktop LX500) */
+	static const u8 e_keymap[] = {
+		  0,216,  0,213,175,156,  0,  0,  0,  0,
+		144,  0,  0,  0,  0,  0,  0,  0,  0,212,
+		174,167,152,161,112,  0,  0,  0,154,  0,
+		  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+		  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+		  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+		  0,  0,  0,  0,  0,183,184,185,186,187,
+		188,189,190,191,192,193,194,  0,  0,  0
+	};
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+	unsigned int hid = usage->hid;
+
+	if (hdev->product == USB_DEVICE_ID_LOGITECH_RECEIVER &&
+			lg_ultrax_remote_mapping(hi, usage, bit, max))
+		return 1;
+
+	if (hdev->product == USB_DEVICE_ID_DINOVO_MINI &&
+			lg_dinovo_mapping(hi, usage, bit, max))
+		return 1;
+
+	if ((drv_data->quirks & LG_WIRELESS) && lg_wireless_mapping(hi, usage, bit, max))
+		return 1;
+
+	if ((hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+		return 0;
+
+	hid &= HID_USAGE;
+
+	/* Special handling for Logitech Cordless Desktop */
+	if (field->application == HID_GD_MOUSE) {
+		if ((drv_data->quirks & LG_IGNORE_DOUBLED_WHEEL) &&
+				(hid == 7 || hid == 8))
+			return -1;
+	} else {
+		if ((drv_data->quirks & LG_EXPANDED_KEYMAP) &&
+				hid < ARRAY_SIZE(e_keymap) &&
+				e_keymap[hid] != 0) {
+			hid_map_usage(hi, usage, bit, max, EV_KEY,
+					e_keymap[hid]);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int lg_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+
+	if ((drv_data->quirks & LG_BAD_RELATIVE_KEYS) && usage->type == EV_KEY &&
+			(field->flags & HID_MAIN_ITEM_RELATIVE))
+		field->flags &= ~HID_MAIN_ITEM_RELATIVE;
+
+	if ((drv_data->quirks & LG_DUPLICATE_USAGES) && (usage->type == EV_KEY ||
+			 usage->type == EV_REL || usage->type == EV_ABS))
+		clear_bit(usage->code, *bit);
+
+	/* Ensure that Logitech wheels are not given a default fuzz/flat value */
+	if (usage->type == EV_ABS && (usage->code == ABS_X ||
+			usage->code == ABS_Y || usage->code == ABS_Z ||
+			usage->code == ABS_RZ)) {
+		switch (hdev->product) {
+		case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
+		case USB_DEVICE_ID_LOGITECH_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
+		case USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL:
+			field->application = HID_GD_MULTIAXIS;
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int lg_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+
+	if ((drv_data->quirks & LG_INVERT_HWHEEL) && usage->code == REL_HWHEEL) {
+		input_event(field->hidinput->input, usage->type, usage->code,
+				-value);
+		return 1;
+	}
+	if (drv_data->quirks & LG_FF4) {
+		return lg4ff_adjust_input_event(hdev, field, usage, value, drv_data);
+	}
+
+	return 0;
+}
+
+static int lg_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *rd, int size)
+{
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+
+	if (drv_data->quirks & LG_FF4)
+		return lg4ff_raw_event(hdev, report, rd, size, drv_data);
+
+	return 0;
+}
+
+static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+	__u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
+	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+	struct lg_drv_data *drv_data;
+	int ret;
+
+	/* G29 only work with the 1st interface */
+	if ((hdev->product == USB_DEVICE_ID_LOGITECH_G29_WHEEL) &&
+	    (iface_num != 0)) {
+		dbg_hid("%s: ignoring ifnum %d\n", __func__, iface_num);
+		return -ENODEV;
+	}
+
+	drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL);
+	if (!drv_data) {
+		hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
+		return -ENOMEM;
+	}
+	drv_data->quirks = id->driver_data;
+
+	hid_set_drvdata(hdev, (void *)drv_data);
+
+	if (drv_data->quirks & LG_NOGET)
+		hdev->quirks |= HID_QUIRK_NOGET;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	if (drv_data->quirks & (LG_FF | LG_FF2 | LG_FF3 | LG_FF4))
+		connect_mask &= ~HID_CONNECT_FF;
+
+	ret = hid_hw_start(hdev, connect_mask);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	/* Setup wireless link with Logitech Wii wheel */
+	if (hdev->product == USB_DEVICE_ID_LOGITECH_WII_WHEEL) {
+		static const unsigned char cbuf[] = {
+			0x00, 0xAF,  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+		};
+		u8 *buf = kmemdup(cbuf, sizeof(cbuf), GFP_KERNEL);
+
+		if (!buf) {
+			ret = -ENOMEM;
+			goto err_free;
+		}
+
+		ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(cbuf),
+					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		if (ret >= 0) {
+			/* insert a little delay of 10 jiffies ~ 40ms */
+			wait_queue_head_t wait;
+			init_waitqueue_head (&wait);
+			wait_event_interruptible_timeout(wait, 0,
+							 msecs_to_jiffies(40));
+
+			/* Select random Address */
+			buf[1] = 0xB2;
+			get_random_bytes(&buf[2], 2);
+
+			ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(cbuf),
+					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		}
+		kfree(buf);
+	}
+
+	if (drv_data->quirks & LG_FF)
+		ret = lgff_init(hdev);
+	else if (drv_data->quirks & LG_FF2)
+		ret = lg2ff_init(hdev);
+	else if (drv_data->quirks & LG_FF3)
+		ret = lg3ff_init(hdev);
+	else if (drv_data->quirks & LG_FF4)
+		ret = lg4ff_init(hdev);
+
+	if (ret)
+		goto err_free;
+
+	return 0;
+err_free:
+	kfree(drv_data);
+	return ret;
+}
+
+static void lg_remove(struct hid_device *hdev)
+{
+	struct lg_drv_data *drv_data = hid_get_drvdata(hdev);
+	if (drv_data->quirks & LG_FF4)
+		lg4ff_deinit(hdev);
+	else
+		hid_hw_stop(hdev);
+	kfree(drv_data);
+}
+
+static const struct hid_device_id lg_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
+		.driver_data = LG_RDESC | LG_WIRELESS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER),
+		.driver_data = LG_RDESC | LG_WIRELESS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2),
+		.driver_data = LG_RDESC | LG_WIRELESS },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER),
+		.driver_data = LG_BAD_RELATIVE_KEYS },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP),
+		.driver_data = LG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE),
+		.driver_data = LG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI),
+		.driver_data = LG_DUPLICATE_USAGES },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD),
+		.driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500),
+		.driver_data = LG_IGNORE_DOUBLED_WHEEL | LG_EXPANDED_KEYMAP },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D),
+		.driver_data = LG_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DUAL_ACTION),
+		.driver_data = LG_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL),
+		.driver_data = LG_NOGET | LG_FF4 },
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD),
+		.driver_data = LG_FF2 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD),
+		.driver_data = LG_FF },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2),
+		.driver_data = LG_FF },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D),
+		.driver_data = LG_FF },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO),
+		.driver_data = LG_FF },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL),
+		.driver_data = LG_NOGET | LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL),
+		.driver_data = LG_FF2 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL),
+		.driver_data = LG_NOGET | LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL),
+		.driver_data = LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG),
+		.driver_data = LG_NOGET | LG_FF4 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2),
+		.driver_data = LG_NOGET | LG_FF2 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940),
+		.driver_data = LG_FF3 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR),
+		.driver_data = LG_RDESC_REL_ABS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER),
+		.driver_data = LG_RDESC_REL_ABS },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, lg_devices);
+
+static struct hid_driver lg_driver = {
+	.name = "logitech",
+	.id_table = lg_devices,
+	.report_fixup = lg_report_fixup,
+	.input_mapping = lg_input_mapping,
+	.input_mapped = lg_input_mapped,
+	.event = lg_event,
+	.raw_event = lg_raw_event,
+	.probe = lg_probe,
+	.remove = lg_remove,
+};
+module_hid_driver(lg_driver);
+
+#ifdef CONFIG_LOGIWHEELS_FF
+int lg4ff_no_autoswitch = 0;
+module_param_named(lg4ff_no_autoswitch, lg4ff_no_autoswitch, int, S_IRUGO);
+MODULE_PARM_DESC(lg4ff_no_autoswitch, "Do not switch multimode wheels to their native mode automatically");
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-lg.h b/drivers/hid/hid-lg.h
new file mode 100644
index 0000000..3d8902b
--- /dev/null
+++ b/drivers/hid/hid-lg.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __HID_LG_H
+#define __HID_LG_H
+
+struct lg_drv_data {
+	unsigned long quirks;
+	void *device_props;	/* Device specific properties */
+};
+
+#ifdef CONFIG_LOGITECH_FF
+int lgff_init(struct hid_device *hdev);
+#else
+static inline int lgff_init(struct hid_device *hdev) { return -1; }
+#endif
+
+#ifdef CONFIG_LOGIRUMBLEPAD2_FF
+int lg2ff_init(struct hid_device *hdev);
+#else
+static inline int lg2ff_init(struct hid_device *hdev) { return -1; }
+#endif
+
+#ifdef CONFIG_LOGIG940_FF
+int lg3ff_init(struct hid_device *hdev);
+#else
+static inline int lg3ff_init(struct hid_device *hdev) { return -1; }
+#endif
+
+#endif
diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c
new file mode 100644
index 0000000..0e3fb1a
--- /dev/null
+++ b/drivers/hid/hid-lg2ff.c
@@ -0,0 +1,101 @@
+/*
+ *  Force feedback support for Logitech RumblePad and Rumblepad 2
+ *
+ *  Copyright (c) 2008 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+
+#include "hid-lg.h"
+
+struct lg2ff_device {
+	struct hid_report *report;
+};
+
+static int play_effect(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct lg2ff_device *lg2ff = data;
+	int weak, strong;
+
+	strong = effect->u.rumble.strong_magnitude;
+	weak = effect->u.rumble.weak_magnitude;
+
+	if (weak || strong) {
+		weak = weak * 0xff / 0xffff;
+		strong = strong * 0xff / 0xffff;
+
+		lg2ff->report->field[0]->value[0] = 0x51;
+		lg2ff->report->field[0]->value[2] = weak;
+		lg2ff->report->field[0]->value[4] = strong;
+	} else {
+		lg2ff->report->field[0]->value[0] = 0xf3;
+		lg2ff->report->field[0]->value[2] = 0x00;
+		lg2ff->report->field[0]->value[4] = 0x00;
+	}
+
+	hid_hw_request(hid, lg2ff->report, HID_REQ_SET_REPORT);
+	return 0;
+}
+
+int lg2ff_init(struct hid_device *hid)
+{
+	struct lg2ff_device *lg2ff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	int error;
+
+	/* Check that the report looks ok */
+	report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7);
+	if (!report)
+		return -ENODEV;
+
+	lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL);
+	if (!lg2ff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, lg2ff, play_effect);
+	if (error) {
+		kfree(lg2ff);
+		return error;
+	}
+
+	lg2ff->report = report;
+	report->field[0]->value[0] = 0xf3;
+	report->field[0]->value[1] = 0x00;
+	report->field[0]->value[2] = 0x00;
+	report->field[0]->value[3] = 0x00;
+	report->field[0]->value[4] = 0x00;
+	report->field[0]->value[5] = 0x00;
+	report->field[0]->value[6] = 0x00;
+
+	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "Force feedback for Logitech variant 2 rumble devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+}
diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c
new file mode 100644
index 0000000..8c2da18
--- /dev/null
+++ b/drivers/hid/hid-lg3ff.c
@@ -0,0 +1,156 @@
+/*
+ *  Force feedback support for Logitech Flight System G940
+ *
+ *  Copyright (c) 2009 Gary Stein <LordCnidarian@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/input.h>
+#include <linux/hid.h>
+
+#include "hid-lg.h"
+
+/*
+ * G940 Theory of Operation (from experimentation)
+ *
+ * There are 63 fields (only 3 of them currently used)
+ * 0 - seems to be command field
+ * 1 - 30 deal with the x axis
+ * 31 -60 deal with the y axis
+ *
+ * Field 1 is x axis constant force
+ * Field 31 is y axis constant force
+ *
+ * other interesting fields 1,2,3,4 on x axis
+ * (same for 31,32,33,34 on y axis)
+ *
+ * 0 0 127 127 makes the joystick autocenter hard
+ *
+ * 127 0 127 127 makes the joystick loose on the right,
+ * but stops all movemnt left
+ *
+ * -127 0 -127 -127 makes the joystick loose on the left,
+ * but stops all movement right
+ *
+ * 0 0 -127 -127 makes the joystick rattle very hard
+ *
+ * I'm sure these are effects that I don't know enough about them
+ */
+
+struct lg3ff_device {
+	struct hid_report *report;
+};
+
+static int hid_lg3ff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	int x, y;
+
+/*
+ * Available values in the field should always be 63, but we only use up to
+ * 35. Instead, clear the entire area, however big it is.
+ */
+	memset(report->field[0]->value, 0,
+	       sizeof(__s32) * report->field[0]->report_count);
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+/*
+ * Already clamped in ff_memless
+ * 0 is center (different then other logitech)
+ */
+		x = effect->u.ramp.start_level;
+		y = effect->u.ramp.end_level;
+
+		/* send command byte */
+		report->field[0]->value[0] = 0x51;
+
+/*
+ * Sign backwards from other Force3d pro
+ * which get recast here in two's complement 8 bits
+ */
+		report->field[0]->value[1] = (unsigned char)(-x);
+		report->field[0]->value[31] = (unsigned char)(-y);
+
+		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+		break;
+	}
+	return 0;
+}
+static void hid_lg3ff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+
+/*
+ * Auto Centering probed from device
+ * NOTE: deadman's switch on G940 must be covered
+ * for effects to work
+ */
+	report->field[0]->value[0] = 0x51;
+	report->field[0]->value[1] = 0x00;
+	report->field[0]->value[2] = 0x00;
+	report->field[0]->value[3] = 0x7F;
+	report->field[0]->value[4] = 0x7F;
+	report->field[0]->value[31] = 0x00;
+	report->field[0]->value[32] = 0x00;
+	report->field[0]->value[33] = 0x7F;
+	report->field[0]->value[34] = 0x7F;
+
+	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+}
+
+
+static const signed short ff3_joystick_ac[] = {
+	FF_CONSTANT,
+	FF_AUTOCENTER,
+	-1
+};
+
+int lg3ff_init(struct hid_device *hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	const signed short *ff_bits = ff3_joystick_ac;
+	int error;
+	int i;
+
+	/* Check that the report looks ok */
+	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35))
+		return -ENODEV;
+
+	/* Assume single fixed device G940 */
+	for (i = 0; ff_bits[i] >= 0; i++)
+		set_bit(ff_bits[i], dev->ffbit);
+
+	error = input_ff_create_memless(dev, NULL, hid_lg3ff_play);
+	if (error)
+		return error;
+
+	if (test_bit(FF_AUTOCENTER, dev->ffbit))
+		dev->ff->set_autocenter = hid_lg3ff_set_autocenter;
+
+	hid_info(hid, "Force feedback for Logitech Flight System G940 by Gary Stein <LordCnidarian@gmail.com>\n");
+	return 0;
+}
+
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
new file mode 100644
index 0000000..512d67e
--- /dev/null
+++ b/drivers/hid/hid-lg4ff.c
@@ -0,0 +1,1493 @@
+/*
+ *  Force feedback support for Logitech Gaming Wheels
+ *
+ *  Including G27, G25, DFP, DFGT, FFEX, Momo, Momo2 &
+ *  Speed Force Wireless (WiiWheel)
+ *
+ *  Copyright (c) 2010 Simon Wood <simon@mungewell.org>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "usbhid/usbhid.h"
+#include "hid-lg.h"
+#include "hid-lg4ff.h"
+#include "hid-ids.h"
+
+#define LG4FF_MMODE_IS_MULTIMODE 0
+#define LG4FF_MMODE_SWITCHED 1
+#define LG4FF_MMODE_NOT_MULTIMODE 2
+
+#define LG4FF_MODE_NATIVE_IDX 0
+#define LG4FF_MODE_DFEX_IDX 1
+#define LG4FF_MODE_DFP_IDX 2
+#define LG4FF_MODE_G25_IDX 3
+#define LG4FF_MODE_DFGT_IDX 4
+#define LG4FF_MODE_G27_IDX 5
+#define LG4FF_MODE_G29_IDX 6
+#define LG4FF_MODE_MAX_IDX 7
+
+#define LG4FF_MODE_NATIVE BIT(LG4FF_MODE_NATIVE_IDX)
+#define LG4FF_MODE_DFEX BIT(LG4FF_MODE_DFEX_IDX)
+#define LG4FF_MODE_DFP BIT(LG4FF_MODE_DFP_IDX)
+#define LG4FF_MODE_G25 BIT(LG4FF_MODE_G25_IDX)
+#define LG4FF_MODE_DFGT BIT(LG4FF_MODE_DFGT_IDX)
+#define LG4FF_MODE_G27 BIT(LG4FF_MODE_G27_IDX)
+#define LG4FF_MODE_G29 BIT(LG4FF_MODE_G29_IDX)
+
+#define LG4FF_DFEX_TAG "DF-EX"
+#define LG4FF_DFEX_NAME "Driving Force / Formula EX"
+#define LG4FF_DFP_TAG "DFP"
+#define LG4FF_DFP_NAME "Driving Force Pro"
+#define LG4FF_G25_TAG "G25"
+#define LG4FF_G25_NAME "G25 Racing Wheel"
+#define LG4FF_G27_TAG "G27"
+#define LG4FF_G27_NAME "G27 Racing Wheel"
+#define LG4FF_G29_TAG "G29"
+#define LG4FF_G29_NAME "G29 Racing Wheel"
+#define LG4FF_DFGT_TAG "DFGT"
+#define LG4FF_DFGT_NAME "Driving Force GT"
+
+#define LG4FF_FFEX_REV_MAJ 0x21
+#define LG4FF_FFEX_REV_MIN 0x00
+
+static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range);
+static void lg4ff_set_range_g25(struct hid_device *hid, u16 range);
+
+struct lg4ff_wheel_data {
+	const u32 product_id;
+	u16 combine;
+	u16 range;
+	const u16 min_range;
+	const u16 max_range;
+#ifdef CONFIG_LEDS_CLASS
+	u8  led_state;
+	struct led_classdev *led[5];
+#endif
+	const u32 alternate_modes;
+	const char * const real_tag;
+	const char * const real_name;
+	const u16 real_product_id;
+
+	void (*set_range)(struct hid_device *hid, u16 range);
+};
+
+struct lg4ff_device_entry {
+	spinlock_t report_lock; /* Protect output HID report */
+	struct hid_report *report;
+	struct lg4ff_wheel_data wdata;
+};
+
+static const signed short lg4ff_wheel_effects[] = {
+	FF_CONSTANT,
+	FF_AUTOCENTER,
+	-1
+};
+
+struct lg4ff_wheel {
+	const u32 product_id;
+	const signed short *ff_effects;
+	const u16 min_range;
+	const u16 max_range;
+	void (*set_range)(struct hid_device *hid, u16 range);
+};
+
+struct lg4ff_compat_mode_switch {
+	const u8 cmd_count;	/* Number of commands to send */
+	const u8 cmd[];
+};
+
+struct lg4ff_wheel_ident_info {
+	const u32 modes;
+	const u16 mask;
+	const u16 result;
+	const u16 real_product_id;
+};
+
+struct lg4ff_multimode_wheel {
+	const u16 product_id;
+	const u32 alternate_modes;
+	const char *real_tag;
+	const char *real_name;
+};
+
+struct lg4ff_alternate_mode {
+	const u16 product_id;
+	const char *tag;
+	const char *name;
+};
+
+static const struct lg4ff_wheel lg4ff_devices[] = {
+	{USB_DEVICE_ID_LOGITECH_WINGMAN_FFG, lg4ff_wheel_effects, 40, 180, NULL},
+	{USB_DEVICE_ID_LOGITECH_WHEEL,       lg4ff_wheel_effects, 40, 270, NULL},
+	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL,  lg4ff_wheel_effects, 40, 270, NULL},
+	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_dfp},
+	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
+	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,  lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
+	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
+	{USB_DEVICE_ID_LOGITECH_G29_WHEEL,   lg4ff_wheel_effects, 40, 900, lg4ff_set_range_g25},
+	{USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2, lg4ff_wheel_effects, 40, 270, NULL},
+	{USB_DEVICE_ID_LOGITECH_WII_WHEEL,   lg4ff_wheel_effects, 40, 270, NULL}
+};
+
+static const struct lg4ff_multimode_wheel lg4ff_multimode_wheels[] = {
+	{USB_DEVICE_ID_LOGITECH_DFP_WHEEL,
+	 LG4FF_MODE_NATIVE | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	 LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+	{USB_DEVICE_ID_LOGITECH_G25_WHEEL,
+	 LG4FF_MODE_NATIVE | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	 LG4FF_G25_TAG, LG4FF_G25_NAME},
+	{USB_DEVICE_ID_LOGITECH_DFGT_WHEEL,
+	 LG4FF_MODE_NATIVE | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	 LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+	{USB_DEVICE_ID_LOGITECH_G27_WHEEL,
+	 LG4FF_MODE_NATIVE | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	 LG4FF_G27_TAG, LG4FF_G27_NAME},
+	{USB_DEVICE_ID_LOGITECH_G29_WHEEL,
+	 LG4FF_MODE_NATIVE | LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	 LG4FF_G29_TAG, LG4FF_G29_NAME},
+};
+
+static const struct lg4ff_alternate_mode lg4ff_alternate_modes[] = {
+	[LG4FF_MODE_NATIVE_IDX] = {0, "native", ""},
+	[LG4FF_MODE_DFEX_IDX] = {USB_DEVICE_ID_LOGITECH_WHEEL, LG4FF_DFEX_TAG, LG4FF_DFEX_NAME},
+	[LG4FF_MODE_DFP_IDX] = {USB_DEVICE_ID_LOGITECH_DFP_WHEEL, LG4FF_DFP_TAG, LG4FF_DFP_NAME},
+	[LG4FF_MODE_G25_IDX] = {USB_DEVICE_ID_LOGITECH_G25_WHEEL, LG4FF_G25_TAG, LG4FF_G25_NAME},
+	[LG4FF_MODE_DFGT_IDX] = {USB_DEVICE_ID_LOGITECH_DFGT_WHEEL, LG4FF_DFGT_TAG, LG4FF_DFGT_NAME},
+	[LG4FF_MODE_G27_IDX] = {USB_DEVICE_ID_LOGITECH_G27_WHEEL, LG4FF_G27_TAG, LG4FF_G27_NAME},
+	[LG4FF_MODE_G29_IDX] = {USB_DEVICE_ID_LOGITECH_G29_WHEEL, LG4FF_G29_TAG, LG4FF_G29_NAME},
+};
+
+/* Multimode wheel identificators */
+static const struct lg4ff_wheel_ident_info lg4ff_dfp_ident_info = {
+	LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xf000,
+	0x1000,
+	USB_DEVICE_ID_LOGITECH_DFP_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g25_ident_info = {
+	LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xff00,
+	0x1200,
+	USB_DEVICE_ID_LOGITECH_G25_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g27_ident_info = {
+	LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xfff0,
+	0x1230,
+	USB_DEVICE_ID_LOGITECH_G27_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_dfgt_ident_info = {
+	LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xff00,
+	0x1300,
+	USB_DEVICE_ID_LOGITECH_DFGT_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info = {
+	LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xfff8,
+	0x1350,
+	USB_DEVICE_ID_LOGITECH_G29_WHEEL
+};
+
+static const struct lg4ff_wheel_ident_info lg4ff_g29_ident_info2 = {
+	LG4FF_MODE_G29 | LG4FF_MODE_G27 | LG4FF_MODE_G25 | LG4FF_MODE_DFGT | LG4FF_MODE_DFP | LG4FF_MODE_DFEX,
+	0xff00,
+	0x8900,
+	USB_DEVICE_ID_LOGITECH_G29_WHEEL
+};
+
+/* Multimode wheel identification checklists */
+static const struct lg4ff_wheel_ident_info *lg4ff_main_checklist[] = {
+	&lg4ff_g29_ident_info,
+	&lg4ff_g29_ident_info2,
+	&lg4ff_dfgt_ident_info,
+	&lg4ff_g27_ident_info,
+	&lg4ff_g25_ident_info,
+	&lg4ff_dfp_ident_info
+};
+
+/* Compatibility mode switching commands */
+/* EXT_CMD9 - Understood by G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfex = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DF-EX with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfp = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x01, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DFP with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g25 = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x02, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to G25 with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_dfgt = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x03, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to DFGT with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g27 = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x04, 0x01, 0x00, 0x00, 0x00}	/* Switch mode to G27 with detach */
+};
+
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext09_g29 = {
+	2,
+	{0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,	/* Revert mode upon USB reset */
+	 0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00}	/* Switch mode to G29 with detach */
+};
+
+/* EXT_CMD1 - Understood by DFP, G25, G27 and DFGT */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext01_dfp = {
+	1,
+	{0xf8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+/* EXT_CMD16 - Understood by G25 and G27 */
+static const struct lg4ff_compat_mode_switch lg4ff_mode_switch_ext16_g25 = {
+	1,
+	{0xf8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+/* Recalculates X axis value accordingly to currently selected range */
+static s32 lg4ff_adjust_dfp_x_axis(s32 value, u16 range)
+{
+	u16 max_range;
+	s32 new_value;
+
+	if (range == 900)
+		return value;
+	else if (range == 200)
+		return value;
+	else if (range < 200)
+		max_range = 200;
+	else
+		max_range = 900;
+
+	new_value = 8192 + mult_frac(value - 8192, max_range, range);
+	if (new_value < 0)
+		return 0;
+	else if (new_value > 16383)
+		return 16383;
+	else
+		return new_value;
+}
+
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+			     struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data)
+{
+	struct lg4ff_device_entry *entry = drv_data->device_props;
+	s32 new_value = 0;
+
+	if (!entry) {
+		hid_err(hid, "Device properties not found");
+		return 0;
+	}
+
+	switch (entry->wdata.product_id) {
+	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+		switch (usage->code) {
+		case ABS_X:
+			new_value = lg4ff_adjust_dfp_x_axis(value, entry->wdata.range);
+			input_event(field->hidinput->input, usage->type, usage->code, new_value);
+			return 1;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *rd, int size, struct lg_drv_data *drv_data)
+{
+	int offset;
+	struct lg4ff_device_entry *entry = drv_data->device_props;
+
+	if (!entry)
+		return 0;
+
+	/* adjust HID report present combined pedals data */
+	if (entry->wdata.combine) {
+		switch (entry->wdata.product_id) {
+		case USB_DEVICE_ID_LOGITECH_WHEEL:
+			rd[5] = rd[3];
+			rd[6] = 0x7F;
+			return 1;
+		case USB_DEVICE_ID_LOGITECH_WINGMAN_FFG:
+		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
+			rd[4] = rd[3];
+			rd[5] = 0x7F;
+			return 1;
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			rd[5] = rd[4];
+			rd[6] = 0x7F;
+			return 1;
+		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+			offset = 5;
+			break;
+		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+		case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+			offset = 6;
+			break;
+		case USB_DEVICE_ID_LOGITECH_WII_WHEEL:
+			offset = 3;
+			break;
+		default:
+			return 0;
+		}
+
+		/* Compute a combined axis when wheel does not supply it */
+		rd[offset] = (0xFF + rd[offset] - rd[offset+1]) >> 1;
+		rd[offset+1] = 0x7F;
+		return 1;
+	}
+
+	return 0;
+}
+
+static void lg4ff_init_wheel_data(struct lg4ff_wheel_data * const wdata, const struct lg4ff_wheel *wheel,
+				  const struct lg4ff_multimode_wheel *mmode_wheel,
+				  const u16 real_product_id)
+{
+	u32 alternate_modes = 0;
+	const char *real_tag = NULL;
+	const char *real_name = NULL;
+
+	if (mmode_wheel) {
+		alternate_modes = mmode_wheel->alternate_modes;
+		real_tag = mmode_wheel->real_tag;
+		real_name = mmode_wheel->real_name;
+	}
+
+	{
+		struct lg4ff_wheel_data t_wdata =  { .product_id = wheel->product_id,
+						     .real_product_id = real_product_id,
+						     .combine = 0,
+						     .min_range = wheel->min_range,
+						     .max_range = wheel->max_range,
+						     .set_range = wheel->set_range,
+						     .alternate_modes = alternate_modes,
+						     .real_tag = real_tag,
+						     .real_name = real_name };
+
+		memcpy(wdata, &t_wdata, sizeof(t_wdata));
+	}
+}
+
+static int lg4ff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+	s32 *value;
+	int x;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return -EINVAL;
+	}
+	value = entry->report->field[0]->value;
+
+#define CLAMP(x) do { if (x < 0) x = 0; else if (x > 0xff) x = 0xff; } while (0)
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		x = effect->u.ramp.start_level + 0x80;	/* 0x80 is no force */
+		CLAMP(x);
+
+		spin_lock_irqsave(&entry->report_lock, flags);
+		if (x == 0x80) {
+			/* De-activate force in slot-1*/
+			value[0] = 0x13;
+			value[1] = 0x00;
+			value[2] = 0x00;
+			value[3] = 0x00;
+			value[4] = 0x00;
+			value[5] = 0x00;
+			value[6] = 0x00;
+
+			hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+			spin_unlock_irqrestore(&entry->report_lock, flags);
+			return 0;
+		}
+
+		value[0] = 0x11;	/* Slot 1 */
+		value[1] = 0x08;
+		value[2] = x;
+		value[3] = 0x80;
+		value[4] = 0x00;
+		value[5] = 0x00;
+		value[6] = 0x00;
+
+		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+		spin_unlock_irqrestore(&entry->report_lock, flags);
+		break;
+	}
+	return 0;
+}
+
+/* Sends default autocentering command compatible with
+ * all wheels except Formula Force EX */
+static void lg4ff_set_autocenter_default(struct input_dev *dev, u16 magnitude)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	s32 *value;
+	u32 expand_a, expand_b;
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return;
+	}
+	value = entry->report->field[0]->value;
+
+	/* De-activate Auto-Center */
+	spin_lock_irqsave(&entry->report_lock, flags);
+	if (magnitude == 0) {
+		value[0] = 0xf5;
+		value[1] = 0x00;
+		value[2] = 0x00;
+		value[3] = 0x00;
+		value[4] = 0x00;
+		value[5] = 0x00;
+		value[6] = 0x00;
+
+		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+		spin_unlock_irqrestore(&entry->report_lock, flags);
+		return;
+	}
+
+	if (magnitude <= 0xaaaa) {
+		expand_a = 0x0c * magnitude;
+		expand_b = 0x80 * magnitude;
+	} else {
+		expand_a = (0x0c * 0xaaaa) + 0x06 * (magnitude - 0xaaaa);
+		expand_b = (0x80 * 0xaaaa) + 0xff * (magnitude - 0xaaaa);
+	}
+
+	/* Adjust for non-MOMO wheels */
+	switch (entry->wdata.product_id) {
+	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL:
+	case USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2:
+		break;
+	default:
+		expand_a = expand_a >> 1;
+		break;
+	}
+
+	value[0] = 0xfe;
+	value[1] = 0x0d;
+	value[2] = expand_a / 0xaaaa;
+	value[3] = expand_a / 0xaaaa;
+	value[4] = expand_b / 0xaaaa;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+
+	/* Activate Auto-Center */
+	value[0] = 0x14;
+	value[1] = 0x00;
+	value[2] = 0x00;
+	value[3] = 0x00;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+}
+
+/* Sends autocentering command compatible with Formula Force EX */
+static void lg4ff_set_autocenter_ffex(struct input_dev *dev, u16 magnitude)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+	s32 *value;
+	magnitude = magnitude * 90 / 65535;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return;
+	}
+	value = entry->report->field[0]->value;
+
+	spin_lock_irqsave(&entry->report_lock, flags);
+	value[0] = 0xfe;
+	value[1] = 0x03;
+	value[2] = magnitude >> 14;
+	value[3] = magnitude >> 14;
+	value[4] = magnitude;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+}
+
+/* Sends command to set range compatible with G25/G27/Driving Force GT */
+static void lg4ff_set_range_g25(struct hid_device *hid, u16 range)
+{
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+	s32 *value;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return;
+	}
+	value = entry->report->field[0]->value;
+	dbg_hid("G25/G27/DFGT: setting range to %u\n", range);
+
+	spin_lock_irqsave(&entry->report_lock, flags);
+	value[0] = 0xf8;
+	value[1] = 0x81;
+	value[2] = range & 0x00ff;
+	value[3] = (range & 0xff00) >> 8;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+}
+
+/* Sends commands to set range compatible with Driving Force Pro wheel */
+static void lg4ff_set_range_dfp(struct hid_device *hid, u16 range)
+{
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+	int start_left, start_right, full_range;
+	s32 *value;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return;
+	}
+	value = entry->report->field[0]->value;
+	dbg_hid("Driving Force Pro: setting range to %u\n", range);
+
+	/* Prepare "coarse" limit command */
+	spin_lock_irqsave(&entry->report_lock, flags);
+	value[0] = 0xf8;
+	value[1] = 0x00;	/* Set later */
+	value[2] = 0x00;
+	value[3] = 0x00;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	if (range > 200) {
+		value[1] = 0x03;
+		full_range = 900;
+	} else {
+		value[1] = 0x02;
+		full_range = 200;
+	}
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+
+	/* Prepare "fine" limit command */
+	value[0] = 0x81;
+	value[1] = 0x0b;
+	value[2] = 0x00;
+	value[3] = 0x00;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+
+	if (range == 200 || range == 900) {	/* Do not apply any fine limit */
+		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+		spin_unlock_irqrestore(&entry->report_lock, flags);
+		return;
+	}
+
+	/* Construct fine limit command */
+	start_left = (((full_range - range + 1) * 2047) / full_range);
+	start_right = 0xfff - start_left;
+
+	value[2] = start_left >> 4;
+	value[3] = start_right >> 4;
+	value[4] = 0xff;
+	value[5] = (start_right & 0xe) << 4 | (start_left & 0xe);
+	value[6] = 0xff;
+
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+}
+
+static const struct lg4ff_compat_mode_switch *lg4ff_get_mode_switch_command(const u16 real_product_id, const u16 target_product_id)
+{
+	switch (real_product_id) {
+	case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+		switch (target_product_id) {
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			return &lg4ff_mode_switch_ext01_dfp;
+		/* DFP can only be switched to its native mode */
+		default:
+			return NULL;
+		}
+		break;
+	case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+		switch (target_product_id) {
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			return &lg4ff_mode_switch_ext01_dfp;
+		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+			return &lg4ff_mode_switch_ext16_g25;
+		/* G25 can only be switched to DFP mode or its native mode */
+		default:
+			return NULL;
+		}
+		break;
+	case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+		switch (target_product_id) {
+		case USB_DEVICE_ID_LOGITECH_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfex;
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfp;
+		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+			return &lg4ff_mode_switch_ext09_g25;
+		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+			return &lg4ff_mode_switch_ext09_g27;
+		/* G27 can only be switched to DF-EX, DFP, G25 or its native mode */
+		default:
+			return NULL;
+		}
+		break;
+	case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+		switch (target_product_id) {
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfp;
+		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfgt;
+		case USB_DEVICE_ID_LOGITECH_G25_WHEEL:
+			return &lg4ff_mode_switch_ext09_g25;
+		case USB_DEVICE_ID_LOGITECH_G27_WHEEL:
+			return &lg4ff_mode_switch_ext09_g27;
+		case USB_DEVICE_ID_LOGITECH_G29_WHEEL:
+			return &lg4ff_mode_switch_ext09_g29;
+		/* G29 can only be switched to DF-EX, DFP, DFGT, G25, G27 or its native mode */
+		default:
+			return NULL;
+		}
+		break;
+	case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+		switch (target_product_id) {
+		case USB_DEVICE_ID_LOGITECH_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfex;
+		case USB_DEVICE_ID_LOGITECH_DFP_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfp;
+		case USB_DEVICE_ID_LOGITECH_DFGT_WHEEL:
+			return &lg4ff_mode_switch_ext09_dfgt;
+		/* DFGT can only be switched to DF-EX, DFP or its native mode */
+		default:
+			return NULL;
+		}
+		break;
+	/* No other wheels have multiple modes */
+	default:
+		return NULL;
+	}
+}
+
+static int lg4ff_switch_compatibility_mode(struct hid_device *hid, const struct lg4ff_compat_mode_switch *s)
+{
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	unsigned long flags;
+	s32 *value;
+	u8 i;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return -EINVAL;
+	}
+	value = entry->report->field[0]->value;
+
+	spin_lock_irqsave(&entry->report_lock, flags);
+	for (i = 0; i < s->cmd_count; i++) {
+		u8 j;
+
+		for (j = 0; j < 7; j++)
+			value[j] = s->cmd[j + (7*i)];
+
+		hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	}
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+	hid_hw_wait(hid);
+	return 0;
+}
+
+static ssize_t lg4ff_alternate_modes_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	ssize_t count = 0;
+	int i;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return 0;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return 0;
+	}
+
+	if (!entry->wdata.real_name) {
+		hid_err(hid, "NULL pointer to string\n");
+		return 0;
+	}
+
+	for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+		if (entry->wdata.alternate_modes & BIT(i)) {
+			/* Print tag and full name */
+			count += scnprintf(buf + count, PAGE_SIZE - count, "%s: %s",
+					   lg4ff_alternate_modes[i].tag,
+					   !lg4ff_alternate_modes[i].product_id ? entry->wdata.real_name : lg4ff_alternate_modes[i].name);
+			if (count >= PAGE_SIZE - 1)
+				return count;
+
+			/* Mark the currently active mode with an asterisk */
+			if (lg4ff_alternate_modes[i].product_id == entry->wdata.product_id ||
+			    (lg4ff_alternate_modes[i].product_id == 0 && entry->wdata.product_id == entry->wdata.real_product_id))
+				count += scnprintf(buf + count, PAGE_SIZE - count, " *\n");
+			else
+				count += scnprintf(buf + count, PAGE_SIZE - count, "\n");
+
+			if (count >= PAGE_SIZE - 1)
+				return count;
+		}
+	}
+
+	return count;
+}
+
+static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	const struct lg4ff_compat_mode_switch *s;
+	u16 target_product_id = 0;
+	int i, ret;
+	char *lbuf;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return -EINVAL;
+	}
+
+	/* Allow \n at the end of the input parameter */
+	lbuf = kasprintf(GFP_KERNEL, "%s", buf);
+	if (!lbuf)
+		return -ENOMEM;
+
+	i = strlen(lbuf);
+	if (lbuf[i-1] == '\n') {
+		if (i == 1) {
+			kfree(lbuf);
+			return -EINVAL;
+		}
+		lbuf[i-1] = '\0';
+	}
+
+	for (i = 0; i < LG4FF_MODE_MAX_IDX; i++) {
+		const u16 mode_product_id = lg4ff_alternate_modes[i].product_id;
+		const char *tag = lg4ff_alternate_modes[i].tag;
+
+		if (entry->wdata.alternate_modes & BIT(i)) {
+			if (!strcmp(tag, lbuf)) {
+				if (!mode_product_id)
+					target_product_id = entry->wdata.real_product_id;
+				else
+					target_product_id = mode_product_id;
+				break;
+			}
+		}
+	}
+
+	if (i == LG4FF_MODE_MAX_IDX) {
+		hid_info(hid, "Requested mode \"%s\" is not supported by the device\n", lbuf);
+		kfree(lbuf);
+		return -EINVAL;
+	}
+	kfree(lbuf); /* Not needed anymore */
+
+	if (target_product_id == entry->wdata.product_id) /* Nothing to do */
+		return count;
+
+	/* Automatic switching has to be disabled for the switch to DF-EX mode to work correctly */
+	if (target_product_id == USB_DEVICE_ID_LOGITECH_WHEEL && !lg4ff_no_autoswitch) {
+		hid_info(hid, "\"%s\" cannot be switched to \"DF-EX\" mode. Load the \"hid_logitech\" module with \"lg4ff_no_autoswitch=1\" parameter set and try again\n",
+			 entry->wdata.real_name);
+		return -EINVAL;
+	}
+
+	/* Take care of hardware limitations */
+	if ((entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_DFP_WHEEL || entry->wdata.real_product_id == USB_DEVICE_ID_LOGITECH_G25_WHEEL) &&
+	    entry->wdata.product_id > target_product_id) {
+		hid_info(hid, "\"%s\" cannot be switched back into \"%s\" mode\n", entry->wdata.real_name, lg4ff_alternate_modes[i].name);
+		return -EINVAL;
+	}
+
+	s = lg4ff_get_mode_switch_command(entry->wdata.real_product_id, target_product_id);
+	if (!s) {
+		hid_err(hid, "Invalid target product ID %X\n", target_product_id);
+		return -EINVAL;
+	}
+
+	ret = lg4ff_switch_compatibility_mode(hid, s);
+	return (ret == 0 ? count : ret);
+}
+static DEVICE_ATTR(alternate_modes, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_alternate_modes_show, lg4ff_alternate_modes_store);
+
+static ssize_t lg4ff_combine_show(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	size_t count;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return 0;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return 0;
+	}
+
+	count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.combine);
+	return count;
+}
+
+static ssize_t lg4ff_combine_store(struct device *dev, struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	u16 combine = simple_strtoul(buf, NULL, 10);
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return -EINVAL;
+	}
+
+	if (combine > 1)
+		combine = 1;
+
+	entry->wdata.combine = combine;
+	return count;
+}
+static DEVICE_ATTR(combine_pedals, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_combine_show, lg4ff_combine_store);
+
+/* Export the currently set range of the wheel */
+static ssize_t lg4ff_range_show(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	size_t count;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return 0;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return 0;
+	}
+
+	count = scnprintf(buf, PAGE_SIZE, "%u\n", entry->wdata.range);
+	return count;
+}
+
+/* Set range to user specified value, call appropriate function
+ * according to the type of the wheel */
+static ssize_t lg4ff_range_store(struct device *dev, struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	u16 range = simple_strtoul(buf, NULL, 10);
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return -EINVAL;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return -EINVAL;
+	}
+
+	if (range == 0)
+		range = entry->wdata.max_range;
+
+	/* Check if the wheel supports range setting
+	 * and that the range is within limits for the wheel */
+	if (entry->wdata.set_range && range >= entry->wdata.min_range && range <= entry->wdata.max_range) {
+		entry->wdata.set_range(hid, range);
+		entry->wdata.range = range;
+	}
+
+	return count;
+}
+static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, lg4ff_range_show, lg4ff_range_store);
+
+static ssize_t lg4ff_real_id_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	size_t count;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return 0;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return 0;
+	}
+
+	if (!entry->wdata.real_tag || !entry->wdata.real_name) {
+		hid_err(hid, "NULL pointer to string\n");
+		return 0;
+	}
+
+	count = scnprintf(buf, PAGE_SIZE, "%s: %s\n", entry->wdata.real_tag, entry->wdata.real_name);
+	return count;
+}
+
+static ssize_t lg4ff_real_id_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	/* Real ID is a read-only value */
+	return -EPERM;
+}
+static DEVICE_ATTR(real_id, S_IRUGO, lg4ff_real_id_show, lg4ff_real_id_store);
+
+#ifdef CONFIG_LEDS_CLASS
+static void lg4ff_set_leds(struct hid_device *hid, u8 leds)
+{
+	struct lg_drv_data *drv_data;
+	struct lg4ff_device_entry *entry;
+	unsigned long flags;
+	s32 *value;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Private driver data not found!\n");
+		return;
+	}
+
+	entry = drv_data->device_props;
+	if (!entry) {
+		hid_err(hid, "Device properties not found!\n");
+		return;
+	}
+	value = entry->report->field[0]->value;
+
+	spin_lock_irqsave(&entry->report_lock, flags);
+	value[0] = 0xf8;
+	value[1] = 0x12;
+	value[2] = leds;
+	value[3] = 0x00;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+	hid_hw_request(hid, entry->report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&entry->report_lock, flags);
+}
+
+static void lg4ff_led_set_brightness(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
+	struct lg4ff_device_entry *entry;
+	int i, state = 0;
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return;
+	}
+
+	entry = drv_data->device_props;
+
+	if (!entry) {
+		hid_err(hid, "Device properties not found.");
+		return;
+	}
+
+	for (i = 0; i < 5; i++) {
+		if (led_cdev != entry->wdata.led[i])
+			continue;
+		state = (entry->wdata.led_state >> i) & 1;
+		if (value == LED_OFF && state) {
+			entry->wdata.led_state &= ~(1 << i);
+			lg4ff_set_leds(hid, entry->wdata.led_state);
+		} else if (value != LED_OFF && !state) {
+			entry->wdata.led_state |= 1 << i;
+			lg4ff_set_leds(hid, entry->wdata.led_state);
+		}
+		break;
+	}
+}
+
+static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cdev)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct lg_drv_data *drv_data = hid_get_drvdata(hid);
+	struct lg4ff_device_entry *entry;
+	int i, value = 0;
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return LED_OFF;
+	}
+
+	entry = drv_data->device_props;
+
+	if (!entry) {
+		hid_err(hid, "Device properties not found.");
+		return LED_OFF;
+	}
+
+	for (i = 0; i < 5; i++)
+		if (led_cdev == entry->wdata.led[i]) {
+			value = (entry->wdata.led_state >> i) & 1;
+			break;
+		}
+
+	return value ? LED_FULL : LED_OFF;
+}
+#endif
+
+static u16 lg4ff_identify_multimode_wheel(struct hid_device *hid, const u16 reported_product_id, const u16 bcdDevice)
+{
+	u32 current_mode;
+	int i;
+
+	/* identify current mode from USB PID */
+	for (i = 1; i < ARRAY_SIZE(lg4ff_alternate_modes); i++) {
+		dbg_hid("Testing whether PID is %X\n", lg4ff_alternate_modes[i].product_id);
+		if (reported_product_id == lg4ff_alternate_modes[i].product_id)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(lg4ff_alternate_modes))
+		return 0;
+
+	current_mode = BIT(i);
+
+	for (i = 0; i < ARRAY_SIZE(lg4ff_main_checklist); i++) {
+		const u16 mask = lg4ff_main_checklist[i]->mask;
+		const u16 result = lg4ff_main_checklist[i]->result;
+		const u16 real_product_id = lg4ff_main_checklist[i]->real_product_id;
+
+		if ((current_mode & lg4ff_main_checklist[i]->modes) && \
+				(bcdDevice & mask) == result) {
+			dbg_hid("Found wheel with real PID %X whose reported PID is %X\n", real_product_id, reported_product_id);
+			return real_product_id;
+		}
+	}
+
+	/* No match found. This is either Driving Force or an unknown
+	 * wheel model, do not touch it */
+	dbg_hid("Wheel with bcdDevice %X was not recognized as multimode wheel, leaving in its current mode\n", bcdDevice);
+	return 0;
+}
+
+static int lg4ff_handle_multimode_wheel(struct hid_device *hid, u16 *real_product_id, const u16 bcdDevice)
+{
+	const u16 reported_product_id = hid->product;
+	int ret;
+
+	*real_product_id = lg4ff_identify_multimode_wheel(hid, reported_product_id, bcdDevice);
+	/* Probed wheel is not a multimode wheel */
+	if (!*real_product_id) {
+		*real_product_id = reported_product_id;
+		dbg_hid("Wheel is not a multimode wheel\n");
+		return LG4FF_MMODE_NOT_MULTIMODE;
+	}
+
+	/* Switch from "Driving Force" mode to native mode automatically.
+	 * Otherwise keep the wheel in its current mode */
+	if (reported_product_id == USB_DEVICE_ID_LOGITECH_WHEEL &&
+	    reported_product_id != *real_product_id &&
+	    !lg4ff_no_autoswitch) {
+		const struct lg4ff_compat_mode_switch *s = lg4ff_get_mode_switch_command(*real_product_id, *real_product_id);
+
+		if (!s) {
+			hid_err(hid, "Invalid product id %X\n", *real_product_id);
+			return LG4FF_MMODE_NOT_MULTIMODE;
+		}
+
+		ret = lg4ff_switch_compatibility_mode(hid, s);
+		if (ret) {
+			/* Wheel could not have been switched to native mode,
+			 * leave it in "Driving Force" mode and continue */
+			hid_err(hid, "Unable to switch wheel mode, errno %d\n", ret);
+			return LG4FF_MMODE_IS_MULTIMODE;
+		}
+		return LG4FF_MMODE_SWITCHED;
+	}
+
+	return LG4FF_MMODE_IS_MULTIMODE;
+}
+
+
+int lg4ff_init(struct hid_device *hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+	const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
+	const struct lg4ff_multimode_wheel *mmode_wheel = NULL;
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+	int error, i, j;
+	int mmode_ret, mmode_idx = -1;
+	u16 real_product_id;
+
+	/* Check that the report looks ok */
+	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
+		return -1;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Cannot add device, private driver data not allocated\n");
+		return -1;
+	}
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+	spin_lock_init(&entry->report_lock);
+	entry->report = report;
+	drv_data->device_props = entry;
+
+	/* Check if a multimode wheel has been connected and
+	 * handle it appropriately */
+	mmode_ret = lg4ff_handle_multimode_wheel(hid, &real_product_id, bcdDevice);
+
+	/* Wheel has been told to switch to native mode. There is no point in going on
+	 * with the initialization as the wheel will do a USB reset when it switches mode
+	 */
+	if (mmode_ret == LG4FF_MMODE_SWITCHED)
+		return 0;
+	else if (mmode_ret < 0) {
+		hid_err(hid, "Unable to switch device mode during initialization, errno %d\n", mmode_ret);
+		error = mmode_ret;
+		goto err_init;
+	}
+
+	/* Check what wheel has been connected */
+	for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) {
+		if (hid->product == lg4ff_devices[i].product_id) {
+			dbg_hid("Found compatible device, product ID %04X\n", lg4ff_devices[i].product_id);
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(lg4ff_devices)) {
+		hid_err(hid, "This device is flagged to be handled by the lg4ff module but this module does not know how to handle it. "
+			     "Please report this as a bug to LKML, Simon Wood <simon@mungewell.org> or "
+			     "Michal Maly <madcatxster@devoid-pointer.net>\n");
+		error = -1;
+		goto err_init;
+	}
+
+	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+		for (mmode_idx = 0; mmode_idx < ARRAY_SIZE(lg4ff_multimode_wheels); mmode_idx++) {
+			if (real_product_id == lg4ff_multimode_wheels[mmode_idx].product_id)
+				break;
+		}
+
+		if (mmode_idx == ARRAY_SIZE(lg4ff_multimode_wheels)) {
+			hid_err(hid, "Device product ID %X is not listed as a multimode wheel", real_product_id);
+			error = -1;
+			goto err_init;
+		}
+	}
+
+	/* Set supported force feedback capabilities */
+	for (j = 0; lg4ff_devices[i].ff_effects[j] >= 0; j++)
+		set_bit(lg4ff_devices[i].ff_effects[j], dev->ffbit);
+
+	error = input_ff_create_memless(dev, NULL, lg4ff_play);
+
+	if (error)
+		goto err_init;
+
+	/* Initialize device properties */
+	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+		BUG_ON(mmode_idx == -1);
+		mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
+	}
+	lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
+
+	/* Check if autocentering is available and
+	 * set the centering force to zero by default */
+	if (test_bit(FF_AUTOCENTER, dev->ffbit)) {
+		/* Formula Force EX expects different autocentering command */
+		if ((bcdDevice >> 8) == LG4FF_FFEX_REV_MAJ &&
+		    (bcdDevice & 0xff) == LG4FF_FFEX_REV_MIN)
+			dev->ff->set_autocenter = lg4ff_set_autocenter_ffex;
+		else
+			dev->ff->set_autocenter = lg4ff_set_autocenter_default;
+
+		dev->ff->set_autocenter(dev, 0);
+	}
+
+	/* Create sysfs interface */
+	error = device_create_file(&hid->dev, &dev_attr_combine_pedals);
+	if (error)
+		hid_warn(hid, "Unable to create sysfs interface for \"combine\", errno %d\n", error);
+	error = device_create_file(&hid->dev, &dev_attr_range);
+	if (error)
+		hid_warn(hid, "Unable to create sysfs interface for \"range\", errno %d\n", error);
+	if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
+		error = device_create_file(&hid->dev, &dev_attr_real_id);
+		if (error)
+			hid_warn(hid, "Unable to create sysfs interface for \"real_id\", errno %d\n", error);
+		error = device_create_file(&hid->dev, &dev_attr_alternate_modes);
+		if (error)
+			hid_warn(hid, "Unable to create sysfs interface for \"alternate_modes\", errno %d\n", error);
+	}
+	dbg_hid("sysfs interface created\n");
+
+	/* Set the maximum range to start with */
+	entry->wdata.range = entry->wdata.max_range;
+	if (entry->wdata.set_range)
+		entry->wdata.set_range(hid, entry->wdata.range);
+
+#ifdef CONFIG_LEDS_CLASS
+	/* register led subsystem - G27/G29 only */
+	entry->wdata.led_state = 0;
+	for (j = 0; j < 5; j++)
+		entry->wdata.led[j] = NULL;
+
+	if (lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G27_WHEEL ||
+			lg4ff_devices[i].product_id == USB_DEVICE_ID_LOGITECH_G29_WHEEL) {
+		struct led_classdev *led;
+		size_t name_sz;
+		char *name;
+
+		lg4ff_set_leds(hid, 0);
+
+		name_sz = strlen(dev_name(&hid->dev)) + 8;
+
+		for (j = 0; j < 5; j++) {
+			led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+			if (!led) {
+				hid_err(hid, "can't allocate memory for LED %d\n", j);
+				goto err_leds;
+			}
+
+			name = (void *)(&led[1]);
+			snprintf(name, name_sz, "%s::RPM%d", dev_name(&hid->dev), j+1);
+			led->name = name;
+			led->brightness = 0;
+			led->max_brightness = 1;
+			led->brightness_get = lg4ff_led_get_brightness;
+			led->brightness_set = lg4ff_led_set_brightness;
+
+			entry->wdata.led[j] = led;
+			error = led_classdev_register(&hid->dev, led);
+
+			if (error) {
+				hid_err(hid, "failed to register LED %d. Aborting.\n", j);
+err_leds:
+				/* Deregister LEDs (if any) */
+				for (j = 0; j < 5; j++) {
+					led = entry->wdata.led[j];
+					entry->wdata.led[j] = NULL;
+					if (!led)
+						continue;
+					led_classdev_unregister(led);
+					kfree(led);
+				}
+				goto out;	/* Let the driver continue without LEDs */
+			}
+		}
+	}
+out:
+#endif
+	hid_info(hid, "Force feedback support for Logitech Gaming Wheels\n");
+	return 0;
+
+err_init:
+	drv_data->device_props = NULL;
+	kfree(entry);
+	return error;
+}
+
+int lg4ff_deinit(struct hid_device *hid)
+{
+	struct lg4ff_device_entry *entry;
+	struct lg_drv_data *drv_data;
+
+	drv_data = hid_get_drvdata(hid);
+	if (!drv_data) {
+		hid_err(hid, "Error while deinitializing device, no private driver data.\n");
+		return -1;
+	}
+	entry = drv_data->device_props;
+	if (!entry)
+		goto out; /* Nothing more to do */
+
+	/* Multimode devices will have at least the "MODE_NATIVE" bit set */
+	if (entry->wdata.alternate_modes) {
+		device_remove_file(&hid->dev, &dev_attr_real_id);
+		device_remove_file(&hid->dev, &dev_attr_alternate_modes);
+	}
+
+	device_remove_file(&hid->dev, &dev_attr_combine_pedals);
+	device_remove_file(&hid->dev, &dev_attr_range);
+#ifdef CONFIG_LEDS_CLASS
+	{
+		int j;
+		struct led_classdev *led;
+
+		/* Deregister LEDs (if any) */
+		for (j = 0; j < 5; j++) {
+
+			led = entry->wdata.led[j];
+			entry->wdata.led[j] = NULL;
+			if (!led)
+				continue;
+			led_classdev_unregister(led);
+			kfree(led);
+		}
+	}
+#endif
+	hid_hw_stop(hid);
+	drv_data->device_props = NULL;
+
+	kfree(entry);
+out:
+	dbg_hid("Device successfully unregistered\n");
+	return 0;
+}
diff --git a/drivers/hid/hid-lg4ff.h b/drivers/hid/hid-lg4ff.h
new file mode 100644
index 0000000..e5c55d5
--- /dev/null
+++ b/drivers/hid/hid-lg4ff.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __HID_LG4FF_H
+#define __HID_LG4FF_H
+
+#ifdef CONFIG_LOGIWHEELS_FF
+extern int lg4ff_no_autoswitch; /* From hid-lg.c */
+
+int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+			     struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data);
+int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *rd, int size, struct lg_drv_data *drv_data);
+int lg4ff_init(struct hid_device *hdev);
+int lg4ff_deinit(struct hid_device *hdev);
+#else
+static inline int lg4ff_adjust_input_event(struct hid_device *hid, struct hid_field *field,
+					   struct hid_usage *usage, s32 value, struct lg_drv_data *drv_data) { return 0; }
+static inline int lg4ff_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *rd, int size, struct lg_drv_data *drv_data) { return 0; }
+static inline int lg4ff_init(struct hid_device *hdev) { return -1; }
+static inline int lg4ff_deinit(struct hid_device *hdev) { return -1; }
+#endif
+
+#endif
diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c
new file mode 100644
index 0000000..e1394af
--- /dev/null
+++ b/drivers/hid/hid-lgff.c
@@ -0,0 +1,161 @@
+/*
+ * Force feedback support for hid-compliant for some of the devices from
+ * Logitech, namely:
+ * - WingMan Cordless RumblePad
+ * - WingMan Force 3D
+ *
+ *  Copyright (c) 2002-2004 Johann Deneux
+ *  Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so by
+ * e-mail - mail your message to <johann.deneux@it.uu.se>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include <linux/hid.h>
+
+#include "hid-lg.h"
+
+struct dev_type {
+	u16 idVendor;
+	u16 idProduct;
+	const signed short *ff;
+};
+
+static const signed short ff_rumble[] = {
+	FF_RUMBLE,
+	-1
+};
+
+static const signed short ff_joystick[] = {
+	FF_CONSTANT,
+	-1
+};
+
+static const signed short ff_joystick_ac[] = {
+	FF_CONSTANT,
+	FF_AUTOCENTER,
+	-1
+};
+
+static const struct dev_type devices[] = {
+	{ 0x046d, 0xc211, ff_rumble },
+	{ 0x046d, 0xc219, ff_rumble },
+	{ 0x046d, 0xc283, ff_joystick },
+	{ 0x046d, 0xc286, ff_joystick_ac },
+	{ 0x046d, 0xc287, ff_joystick_ac },
+	{ 0x046d, 0xc293, ff_joystick },
+	{ 0x046d, 0xc295, ff_joystick },
+};
+
+static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	int x, y;
+	unsigned int left, right;
+
+#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		x = effect->u.ramp.start_level + 0x7f;	/* 0x7f is center */
+		y = effect->u.ramp.end_level + 0x7f;
+		CLAMP(x);
+		CLAMP(y);
+		report->field[0]->value[0] = 0x51;
+		report->field[0]->value[1] = 0x08;
+		report->field[0]->value[2] = x;
+		report->field[0]->value[3] = y;
+		dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
+		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+		break;
+
+	case FF_RUMBLE:
+		right = effect->u.rumble.strong_magnitude;
+		left = effect->u.rumble.weak_magnitude;
+		right = right * 0xff / 0xffff;
+		left = left * 0xff / 0xffff;
+		CLAMP(left);
+		CLAMP(right);
+		report->field[0]->value[0] = 0x42;
+		report->field[0]->value[1] = 0x00;
+		report->field[0]->value[2] = left;
+		report->field[0]->value[3] = right;
+		dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
+		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+		break;
+	}
+	return 0;
+}
+
+static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	__s32 *value = report->field[0]->value;
+	magnitude = (magnitude >> 12) & 0xf;
+	*value++ = 0xfe;
+	*value++ = 0x0d;
+	*value++ = magnitude;   /* clockwise strength */
+	*value++ = magnitude;   /* counter-clockwise strength */
+	*value++ = 0x80;
+	*value++ = 0x00;
+	*value = 0x00;
+	hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+}
+
+int lgff_init(struct hid_device* hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	const signed short *ff_bits = ff_joystick;
+	int error;
+	int i;
+
+	/* Check that the report looks ok */
+	if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7))
+		return -ENODEV;
+
+	for (i = 0; i < ARRAY_SIZE(devices); i++) {
+		if (dev->id.vendor == devices[i].idVendor &&
+		    dev->id.product == devices[i].idProduct) {
+			ff_bits = devices[i].ff;
+			break;
+		}
+	}
+
+	for (i = 0; ff_bits[i] >= 0; i++)
+		set_bit(ff_bits[i], dev->ffbit);
+
+	error = input_ff_create_memless(dev, NULL, hid_lgff_play);
+	if (error)
+		return error;
+
+	if ( test_bit(FF_AUTOCENTER, dev->ffbit) )
+		dev->ff->set_autocenter = hid_lgff_set_autocenter;
+
+	pr_info("Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@it.uu.se>\n");
+
+	return 0;
+}
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
new file mode 100644
index 0000000..826fa1e
--- /dev/null
+++ b/drivers/hid/hid-logitech-dj.c
@@ -0,0 +1,1177 @@
+/*
+ *  HID driver for Logitech Unifying receivers
+ *
+ *  Copyright (c) 2011 Logitech
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/kfifo.h>
+#include <asm/unaligned.h>
+#include "hid-ids.h"
+
+#define DJ_MAX_PAIRED_DEVICES			6
+#define DJ_MAX_NUMBER_NOTIFICATIONS		8
+#define DJ_RECEIVER_INDEX			0
+#define DJ_DEVICE_INDEX_MIN			1
+#define DJ_DEVICE_INDEX_MAX			6
+
+#define DJREPORT_SHORT_LENGTH			15
+#define DJREPORT_LONG_LENGTH			32
+
+#define REPORT_ID_DJ_SHORT			0x20
+#define REPORT_ID_DJ_LONG			0x21
+
+#define REPORT_ID_HIDPP_SHORT			0x10
+#define REPORT_ID_HIDPP_LONG			0x11
+
+#define HIDPP_REPORT_SHORT_LENGTH		7
+#define HIDPP_REPORT_LONG_LENGTH		20
+
+#define HIDPP_RECEIVER_INDEX			0xff
+
+#define REPORT_TYPE_RFREPORT_FIRST		0x01
+#define REPORT_TYPE_RFREPORT_LAST		0x1F
+
+/* Command Switch to DJ mode */
+#define REPORT_TYPE_CMD_SWITCH			0x80
+#define CMD_SWITCH_PARAM_DEVBITFIELD		0x00
+#define CMD_SWITCH_PARAM_TIMEOUT_SECONDS	0x01
+#define TIMEOUT_NO_KEEPALIVE			0x00
+
+/* Command to Get the list of Paired devices */
+#define REPORT_TYPE_CMD_GET_PAIRED_DEVICES	0x81
+
+/* Device Paired Notification */
+#define REPORT_TYPE_NOTIF_DEVICE_PAIRED		0x41
+#define SPFUNCTION_MORE_NOTIF_EXPECTED		0x01
+#define SPFUNCTION_DEVICE_LIST_EMPTY		0x02
+#define DEVICE_PAIRED_PARAM_SPFUNCTION		0x00
+#define DEVICE_PAIRED_PARAM_EQUAD_ID_LSB	0x01
+#define DEVICE_PAIRED_PARAM_EQUAD_ID_MSB	0x02
+#define DEVICE_PAIRED_RF_REPORT_TYPE		0x03
+
+/* Device Un-Paired Notification */
+#define REPORT_TYPE_NOTIF_DEVICE_UNPAIRED	0x40
+
+
+/* Connection Status Notification */
+#define REPORT_TYPE_NOTIF_CONNECTION_STATUS	0x42
+#define CONNECTION_STATUS_PARAM_STATUS		0x00
+#define STATUS_LINKLOSS				0x01
+
+/* Error Notification */
+#define REPORT_TYPE_NOTIF_ERROR			0x7F
+#define NOTIF_ERROR_PARAM_ETYPE			0x00
+#define ETYPE_KEEPALIVE_TIMEOUT			0x01
+
+/* supported DJ HID && RF report types */
+#define REPORT_TYPE_KEYBOARD			0x01
+#define REPORT_TYPE_MOUSE			0x02
+#define REPORT_TYPE_CONSUMER_CONTROL		0x03
+#define REPORT_TYPE_SYSTEM_CONTROL		0x04
+#define REPORT_TYPE_MEDIA_CENTER		0x08
+#define REPORT_TYPE_LEDS			0x0E
+
+/* RF Report types bitfield */
+#define STD_KEYBOARD				0x00000002
+#define STD_MOUSE				0x00000004
+#define MULTIMEDIA				0x00000008
+#define POWER_KEYS				0x00000010
+#define MEDIA_CENTER				0x00000100
+#define KBD_LEDS				0x00004000
+
+struct dj_report {
+	u8 report_id;
+	u8 device_index;
+	u8 report_type;
+	u8 report_params[DJREPORT_SHORT_LENGTH - 3];
+};
+
+struct dj_receiver_dev {
+	struct hid_device *hdev;
+	struct dj_device *paired_dj_devices[DJ_MAX_PAIRED_DEVICES +
+					    DJ_DEVICE_INDEX_MIN];
+	struct work_struct work;
+	struct kfifo notif_fifo;
+	spinlock_t lock;
+	bool querying_devices;
+};
+
+struct dj_device {
+	struct hid_device *hdev;
+	struct dj_receiver_dev *dj_receiver_dev;
+	u32 reports_supported;
+	u8 device_index;
+};
+
+/* Keyboard descriptor (1) */
+static const char kbd_descriptor[] = {
+	0x05, 0x01,		/* USAGE_PAGE (generic Desktop)     */
+	0x09, 0x06,		/* USAGE (Keyboard)         */
+	0xA1, 0x01,		/* COLLECTION (Application)     */
+	0x85, 0x01,		/* REPORT_ID (1)            */
+	0x95, 0x08,		/*   REPORT_COUNT (8)           */
+	0x75, 0x01,		/*   REPORT_SIZE (1)            */
+	0x15, 0x00,		/*   LOGICAL_MINIMUM (0)        */
+	0x25, 0x01,		/*   LOGICAL_MAXIMUM (1)        */
+	0x05, 0x07,		/*   USAGE_PAGE (Keyboard)      */
+	0x19, 0xE0,		/*   USAGE_MINIMUM (Left Control)   */
+	0x29, 0xE7,		/*   USAGE_MAXIMUM (Right GUI)      */
+	0x81, 0x02,		/*   INPUT (Data,Var,Abs)       */
+	0x95, 0x06,		/*   REPORT_COUNT (6)           */
+	0x75, 0x08,		/*   REPORT_SIZE (8)            */
+	0x15, 0x00,		/*   LOGICAL_MINIMUM (0)        */
+	0x26, 0xFF, 0x00,	/*   LOGICAL_MAXIMUM (255)      */
+	0x05, 0x07,		/*   USAGE_PAGE (Keyboard)      */
+	0x19, 0x00,		/*   USAGE_MINIMUM (no event)       */
+	0x2A, 0xFF, 0x00,	/*   USAGE_MAXIMUM (reserved)       */
+	0x81, 0x00,		/*   INPUT (Data,Ary,Abs)       */
+	0x85, 0x0e,		/* REPORT_ID (14)               */
+	0x05, 0x08,		/*   USAGE PAGE (LED page)      */
+	0x95, 0x05,		/*   REPORT COUNT (5)           */
+	0x75, 0x01,		/*   REPORT SIZE (1)            */
+	0x15, 0x00,		/*   LOGICAL_MINIMUM (0)        */
+	0x25, 0x01,		/*   LOGICAL_MAXIMUM (1)        */
+	0x19, 0x01,		/*   USAGE MINIMUM (1)          */
+	0x29, 0x05,		/*   USAGE MAXIMUM (5)          */
+	0x91, 0x02,		/*   OUTPUT (Data, Variable, Absolute)  */
+	0x95, 0x01,		/*   REPORT COUNT (1)           */
+	0x75, 0x03,		/*   REPORT SIZE (3)            */
+	0x91, 0x01,		/*   OUTPUT (Constant)          */
+	0xC0
+};
+
+/* Mouse descriptor (2)     */
+static const char mse_descriptor[] = {
+	0x05, 0x01,		/*  USAGE_PAGE (Generic Desktop)        */
+	0x09, 0x02,		/*  USAGE (Mouse)                       */
+	0xA1, 0x01,		/*  COLLECTION (Application)            */
+	0x85, 0x02,		/*    REPORT_ID = 2                     */
+	0x09, 0x01,		/*    USAGE (pointer)                   */
+	0xA1, 0x00,		/*    COLLECTION (physical)             */
+	0x05, 0x09,		/*      USAGE_PAGE (buttons)            */
+	0x19, 0x01,		/*      USAGE_MIN (1)                   */
+	0x29, 0x10,		/*      USAGE_MAX (16)                  */
+	0x15, 0x00,		/*      LOGICAL_MIN (0)                 */
+	0x25, 0x01,		/*      LOGICAL_MAX (1)                 */
+	0x95, 0x10,		/*      REPORT_COUNT (16)               */
+	0x75, 0x01,		/*      REPORT_SIZE (1)                 */
+	0x81, 0x02,		/*      INPUT (data var abs)            */
+	0x05, 0x01,		/*      USAGE_PAGE (generic desktop)    */
+	0x16, 0x01, 0xF8,	/*      LOGICAL_MIN (-2047)             */
+	0x26, 0xFF, 0x07,	/*      LOGICAL_MAX (2047)              */
+	0x75, 0x0C,		/*      REPORT_SIZE (12)                */
+	0x95, 0x02,		/*      REPORT_COUNT (2)                */
+	0x09, 0x30,		/*      USAGE (X)                       */
+	0x09, 0x31,		/*      USAGE (Y)                       */
+	0x81, 0x06,		/*      INPUT                           */
+	0x15, 0x81,		/*      LOGICAL_MIN (-127)              */
+	0x25, 0x7F,		/*      LOGICAL_MAX (127)               */
+	0x75, 0x08,		/*      REPORT_SIZE (8)                 */
+	0x95, 0x01,		/*      REPORT_COUNT (1)                */
+	0x09, 0x38,		/*      USAGE (wheel)                   */
+	0x81, 0x06,		/*      INPUT                           */
+	0x05, 0x0C,		/*      USAGE_PAGE(consumer)            */
+	0x0A, 0x38, 0x02,	/*      USAGE(AC Pan)                   */
+	0x95, 0x01,		/*      REPORT_COUNT (1)                */
+	0x81, 0x06,		/*      INPUT                           */
+	0xC0,			/*    END_COLLECTION                    */
+	0xC0,			/*  END_COLLECTION                      */
+};
+
+/* Consumer Control descriptor (3) */
+static const char consumer_descriptor[] = {
+	0x05, 0x0C,		/* USAGE_PAGE (Consumer Devices)       */
+	0x09, 0x01,		/* USAGE (Consumer Control)            */
+	0xA1, 0x01,		/* COLLECTION (Application)            */
+	0x85, 0x03,		/* REPORT_ID = 3                       */
+	0x75, 0x10,		/* REPORT_SIZE (16)                    */
+	0x95, 0x02,		/* REPORT_COUNT (2)                    */
+	0x15, 0x01,		/* LOGICAL_MIN (1)                     */
+	0x26, 0x8C, 0x02,	/* LOGICAL_MAX (652)                   */
+	0x19, 0x01,		/* USAGE_MIN (1)                       */
+	0x2A, 0x8C, 0x02,	/* USAGE_MAX (652)                     */
+	0x81, 0x00,		/* INPUT (Data Ary Abs)                */
+	0xC0,			/* END_COLLECTION                      */
+};				/*                                     */
+
+/* System control descriptor (4) */
+static const char syscontrol_descriptor[] = {
+	0x05, 0x01,		/*   USAGE_PAGE (Generic Desktop)      */
+	0x09, 0x80,		/*   USAGE (System Control)            */
+	0xA1, 0x01,		/*   COLLECTION (Application)          */
+	0x85, 0x04,		/*   REPORT_ID = 4                     */
+	0x75, 0x02,		/*   REPORT_SIZE (2)                   */
+	0x95, 0x01,		/*   REPORT_COUNT (1)                  */
+	0x15, 0x01,		/*   LOGICAL_MIN (1)                   */
+	0x25, 0x03,		/*   LOGICAL_MAX (3)                   */
+	0x09, 0x82,		/*   USAGE (System Sleep)              */
+	0x09, 0x81,		/*   USAGE (System Power Down)         */
+	0x09, 0x83,		/*   USAGE (System Wake Up)            */
+	0x81, 0x60,		/*   INPUT (Data Ary Abs NPrf Null)    */
+	0x75, 0x06,		/*   REPORT_SIZE (6)                   */
+	0x81, 0x03,		/*   INPUT (Cnst Var Abs)              */
+	0xC0,			/*   END_COLLECTION                    */
+};
+
+/* Media descriptor (8) */
+static const char media_descriptor[] = {
+	0x06, 0xbc, 0xff,	/* Usage Page 0xffbc                   */
+	0x09, 0x88,		/* Usage 0x0088                        */
+	0xa1, 0x01,		/* BeginCollection                     */
+	0x85, 0x08,		/*   Report ID 8                       */
+	0x19, 0x01,		/*   Usage Min 0x0001                  */
+	0x29, 0xff,		/*   Usage Max 0x00ff                  */
+	0x15, 0x01,		/*   Logical Min 1                     */
+	0x26, 0xff, 0x00,	/*   Logical Max 255                   */
+	0x75, 0x08,		/*   Report Size 8                     */
+	0x95, 0x01,		/*   Report Count 1                    */
+	0x81, 0x00,		/*   Input                             */
+	0xc0,			/* EndCollection                       */
+};				/*                                     */
+
+/* HIDPP descriptor */
+static const char hidpp_descriptor[] = {
+	0x06, 0x00, 0xff,	/* Usage Page (Vendor Defined Page 1)  */
+	0x09, 0x01,		/* Usage (Vendor Usage 1)              */
+	0xa1, 0x01,		/* Collection (Application)            */
+	0x85, 0x10,		/*   Report ID (16)                    */
+	0x75, 0x08,		/*   Report Size (8)                   */
+	0x95, 0x06,		/*   Report Count (6)                  */
+	0x15, 0x00,		/*   Logical Minimum (0)               */
+	0x26, 0xff, 0x00,	/*   Logical Maximum (255)             */
+	0x09, 0x01,		/*   Usage (Vendor Usage 1)            */
+	0x81, 0x00,		/*   Input (Data,Arr,Abs)              */
+	0x09, 0x01,		/*   Usage (Vendor Usage 1)            */
+	0x91, 0x00,		/*   Output (Data,Arr,Abs)             */
+	0xc0,			/* End Collection                      */
+	0x06, 0x00, 0xff,	/* Usage Page (Vendor Defined Page 1)  */
+	0x09, 0x02,		/* Usage (Vendor Usage 2)              */
+	0xa1, 0x01,		/* Collection (Application)            */
+	0x85, 0x11,		/*   Report ID (17)                    */
+	0x75, 0x08,		/*   Report Size (8)                   */
+	0x95, 0x13,		/*   Report Count (19)                 */
+	0x15, 0x00,		/*   Logical Minimum (0)               */
+	0x26, 0xff, 0x00,	/*   Logical Maximum (255)             */
+	0x09, 0x02,		/*   Usage (Vendor Usage 2)            */
+	0x81, 0x00,		/*   Input (Data,Arr,Abs)              */
+	0x09, 0x02,		/*   Usage (Vendor Usage 2)            */
+	0x91, 0x00,		/*   Output (Data,Arr,Abs)             */
+	0xc0,			/* End Collection                      */
+	0x06, 0x00, 0xff,	/* Usage Page (Vendor Defined Page 1)  */
+	0x09, 0x04,		/* Usage (Vendor Usage 0x04)           */
+	0xa1, 0x01,		/* Collection (Application)            */
+	0x85, 0x20,		/*   Report ID (32)                    */
+	0x75, 0x08,		/*   Report Size (8)                   */
+	0x95, 0x0e,		/*   Report Count (14)                 */
+	0x15, 0x00,		/*   Logical Minimum (0)               */
+	0x26, 0xff, 0x00,	/*   Logical Maximum (255)             */
+	0x09, 0x41,		/*   Usage (Vendor Usage 0x41)         */
+	0x81, 0x00,		/*   Input (Data,Arr,Abs)              */
+	0x09, 0x41,		/*   Usage (Vendor Usage 0x41)         */
+	0x91, 0x00,		/*   Output (Data,Arr,Abs)             */
+	0x85, 0x21,		/*   Report ID (33)                    */
+	0x95, 0x1f,		/*   Report Count (31)                 */
+	0x15, 0x00,		/*   Logical Minimum (0)               */
+	0x26, 0xff, 0x00,	/*   Logical Maximum (255)             */
+	0x09, 0x42,		/*   Usage (Vendor Usage 0x42)         */
+	0x81, 0x00,		/*   Input (Data,Arr,Abs)              */
+	0x09, 0x42,		/*   Usage (Vendor Usage 0x42)         */
+	0x91, 0x00,		/*   Output (Data,Arr,Abs)             */
+	0xc0,			/* End Collection                      */
+};
+
+/* Maximum size of all defined hid reports in bytes (including report id) */
+#define MAX_REPORT_SIZE 8
+
+/* Make sure all descriptors are present here */
+#define MAX_RDESC_SIZE				\
+	(sizeof(kbd_descriptor) +		\
+	 sizeof(mse_descriptor) +		\
+	 sizeof(consumer_descriptor) +		\
+	 sizeof(syscontrol_descriptor) +	\
+	 sizeof(media_descriptor) +	\
+	 sizeof(hidpp_descriptor))
+
+/* Number of possible hid report types that can be created by this driver.
+ *
+ * Right now, RF report types have the same report types (or report id's)
+ * than the hid report created from those RF reports. In the future
+ * this doesnt have to be true.
+ *
+ * For instance, RF report type 0x01 which has a size of 8 bytes, corresponds
+ * to hid report id 0x01, this is standard keyboard. Same thing applies to mice
+ * reports and consumer control, etc. If a new RF report is created, it doesn't
+ * has to have the same report id as its corresponding hid report, so an
+ * translation may have to take place for future report types.
+ */
+#define NUMBER_OF_HID_REPORTS 32
+static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
+	[1] = 8,		/* Standard keyboard */
+	[2] = 8,		/* Standard mouse */
+	[3] = 5,		/* Consumer control */
+	[4] = 2,		/* System control */
+	[8] = 2,		/* Media Center */
+};
+
+
+#define LOGITECH_DJ_INTERFACE_NUMBER 0x02
+
+static struct hid_ll_driver logi_dj_ll_driver;
+
+static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);
+
+static void logi_dj_recv_destroy_djhid_device(struct dj_receiver_dev *djrcv_dev,
+						struct dj_report *dj_report)
+{
+	/* Called in delayed work context */
+	struct dj_device *dj_dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&djrcv_dev->lock, flags);
+	dj_dev = djrcv_dev->paired_dj_devices[dj_report->device_index];
+	djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL;
+	spin_unlock_irqrestore(&djrcv_dev->lock, flags);
+
+	if (dj_dev != NULL) {
+		hid_destroy_device(dj_dev->hdev);
+		kfree(dj_dev);
+	} else {
+		dev_err(&djrcv_dev->hdev->dev, "%s: can't destroy a NULL device\n",
+			__func__);
+	}
+}
+
+static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,
+					  struct dj_report *dj_report)
+{
+	/* Called in delayed work context */
+	struct hid_device *djrcv_hdev = djrcv_dev->hdev;
+	struct usb_interface *intf = to_usb_interface(djrcv_hdev->dev.parent);
+	struct usb_device *usbdev = interface_to_usbdev(intf);
+	struct hid_device *dj_hiddev;
+	struct dj_device *dj_dev;
+
+	/* Device index goes from 1 to 6, we need 3 bytes to store the
+	 * semicolon, the index, and a null terminator
+	 */
+	unsigned char tmpstr[3];
+
+	if (dj_report->report_params[DEVICE_PAIRED_PARAM_SPFUNCTION] &
+	    SPFUNCTION_DEVICE_LIST_EMPTY) {
+		dbg_hid("%s: device list is empty\n", __func__);
+		djrcv_dev->querying_devices = false;
+		return;
+	}
+
+	if (djrcv_dev->paired_dj_devices[dj_report->device_index]) {
+		/* The device is already known. No need to reallocate it. */
+		dbg_hid("%s: device is already known\n", __func__);
+		return;
+	}
+
+	dj_hiddev = hid_allocate_device();
+	if (IS_ERR(dj_hiddev)) {
+		dev_err(&djrcv_hdev->dev, "%s: hid_allocate_device failed\n",
+			__func__);
+		return;
+	}
+
+	dj_hiddev->ll_driver = &logi_dj_ll_driver;
+
+	dj_hiddev->dev.parent = &djrcv_hdev->dev;
+	dj_hiddev->bus = BUS_USB;
+	dj_hiddev->vendor = le16_to_cpu(usbdev->descriptor.idVendor);
+	dj_hiddev->product =
+		(dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_MSB]
+									<< 8) |
+		dj_report->report_params[DEVICE_PAIRED_PARAM_EQUAD_ID_LSB];
+	snprintf(dj_hiddev->name, sizeof(dj_hiddev->name),
+		"Logitech Unifying Device. Wireless PID:%04x",
+		dj_hiddev->product);
+
+	dj_hiddev->group = HID_GROUP_LOGITECH_DJ_DEVICE;
+
+	usb_make_path(usbdev, dj_hiddev->phys, sizeof(dj_hiddev->phys));
+	snprintf(tmpstr, sizeof(tmpstr), ":%d", dj_report->device_index);
+	strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys));
+
+	dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL);
+
+	if (!dj_dev) {
+		dev_err(&djrcv_hdev->dev, "%s: failed allocating dj_device\n",
+			__func__);
+		goto dj_device_allocate_fail;
+	}
+
+	dj_dev->reports_supported = get_unaligned_le32(
+		dj_report->report_params + DEVICE_PAIRED_RF_REPORT_TYPE);
+	dj_dev->hdev = dj_hiddev;
+	dj_dev->dj_receiver_dev = djrcv_dev;
+	dj_dev->device_index = dj_report->device_index;
+	dj_hiddev->driver_data = dj_dev;
+
+	djrcv_dev->paired_dj_devices[dj_report->device_index] = dj_dev;
+
+	if (hid_add_device(dj_hiddev)) {
+		dev_err(&djrcv_hdev->dev, "%s: failed adding dj_device\n",
+			__func__);
+		goto hid_add_device_fail;
+	}
+
+	return;
+
+hid_add_device_fail:
+	djrcv_dev->paired_dj_devices[dj_report->device_index] = NULL;
+	kfree(dj_dev);
+dj_device_allocate_fail:
+	hid_destroy_device(dj_hiddev);
+}
+
+static void delayedwork_callback(struct work_struct *work)
+{
+	struct dj_receiver_dev *djrcv_dev =
+		container_of(work, struct dj_receiver_dev, work);
+
+	struct dj_report dj_report;
+	unsigned long flags;
+	int count;
+	int retval;
+
+	dbg_hid("%s\n", __func__);
+
+	spin_lock_irqsave(&djrcv_dev->lock, flags);
+
+	count = kfifo_out(&djrcv_dev->notif_fifo, &dj_report,
+				sizeof(struct dj_report));
+
+	if (count != sizeof(struct dj_report)) {
+		dev_err(&djrcv_dev->hdev->dev, "%s: workitem triggered without "
+			"notifications available\n", __func__);
+		spin_unlock_irqrestore(&djrcv_dev->lock, flags);
+		return;
+	}
+
+	if (!kfifo_is_empty(&djrcv_dev->notif_fifo)) {
+		if (schedule_work(&djrcv_dev->work) == 0) {
+			dbg_hid("%s: did not schedule the work item, was "
+				"already queued\n", __func__);
+		}
+	}
+
+	spin_unlock_irqrestore(&djrcv_dev->lock, flags);
+
+	switch (dj_report.report_type) {
+	case REPORT_TYPE_NOTIF_DEVICE_PAIRED:
+		logi_dj_recv_add_djhid_device(djrcv_dev, &dj_report);
+		break;
+	case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED:
+		logi_dj_recv_destroy_djhid_device(djrcv_dev, &dj_report);
+		break;
+	default:
+	/* A normal report (i. e. not belonging to a pair/unpair notification)
+	 * arriving here, means that the report arrived but we did not have a
+	 * paired dj_device associated to the report's device_index, this
+	 * means that the original "device paired" notification corresponding
+	 * to this dj_device never arrived to this driver. The reason is that
+	 * hid-core discards all packets coming from a device while probe() is
+	 * executing. */
+	if (!djrcv_dev->paired_dj_devices[dj_report.device_index]) {
+		/* ok, we don't know the device, just re-ask the
+		 * receiver for the list of connected devices. */
+		retval = logi_dj_recv_query_paired_devices(djrcv_dev);
+		if (!retval) {
+			/* everything went fine, so just leave */
+			break;
+		}
+		dev_err(&djrcv_dev->hdev->dev,
+			"%s:logi_dj_recv_query_paired_devices "
+			"error:%d\n", __func__, retval);
+		}
+		dbg_hid("%s: unexpected report type\n", __func__);
+	}
+}
+
+static void logi_dj_recv_queue_notification(struct dj_receiver_dev *djrcv_dev,
+					   struct dj_report *dj_report)
+{
+	/* We are called from atomic context (tasklet && djrcv->lock held) */
+
+	kfifo_in(&djrcv_dev->notif_fifo, dj_report, sizeof(struct dj_report));
+
+	if (schedule_work(&djrcv_dev->work) == 0) {
+		dbg_hid("%s: did not schedule the work item, was already "
+			"queued\n", __func__);
+	}
+}
+
+static void logi_dj_recv_forward_null_report(struct dj_receiver_dev *djrcv_dev,
+					     struct dj_report *dj_report)
+{
+	/* We are called from atomic context (tasklet && djrcv->lock held) */
+	unsigned int i;
+	u8 reportbuffer[MAX_REPORT_SIZE];
+	struct dj_device *djdev;
+
+	djdev = djrcv_dev->paired_dj_devices[dj_report->device_index];
+
+	memset(reportbuffer, 0, sizeof(reportbuffer));
+
+	for (i = 0; i < NUMBER_OF_HID_REPORTS; i++) {
+		if (djdev->reports_supported & (1 << i)) {
+			reportbuffer[0] = i;
+			if (hid_input_report(djdev->hdev,
+					     HID_INPUT_REPORT,
+					     reportbuffer,
+					     hid_reportid_size_map[i], 1)) {
+				dbg_hid("hid_input_report error sending null "
+					"report\n");
+			}
+		}
+	}
+}
+
+static void logi_dj_recv_forward_report(struct dj_receiver_dev *djrcv_dev,
+					struct dj_report *dj_report)
+{
+	/* We are called from atomic context (tasklet && djrcv->lock held) */
+	struct dj_device *dj_device;
+
+	dj_device = djrcv_dev->paired_dj_devices[dj_report->device_index];
+
+	if ((dj_report->report_type > ARRAY_SIZE(hid_reportid_size_map) - 1) ||
+	    (hid_reportid_size_map[dj_report->report_type] == 0)) {
+		dbg_hid("invalid report type:%x\n", dj_report->report_type);
+		return;
+	}
+
+	if (hid_input_report(dj_device->hdev,
+			HID_INPUT_REPORT, &dj_report->report_type,
+			hid_reportid_size_map[dj_report->report_type], 1)) {
+		dbg_hid("hid_input_report error\n");
+	}
+}
+
+static void logi_dj_recv_forward_hidpp(struct dj_device *dj_dev, u8 *data,
+				       int size)
+{
+	/* We are called from atomic context (tasklet && djrcv->lock held) */
+	if (hid_input_report(dj_dev->hdev, HID_INPUT_REPORT, data, size, 1))
+		dbg_hid("hid_input_report error\n");
+}
+
+static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev,
+				    struct dj_report *dj_report)
+{
+	struct hid_device *hdev = djrcv_dev->hdev;
+	struct hid_report *report;
+	struct hid_report_enum *output_report_enum;
+	u8 *data = (u8 *)(&dj_report->device_index);
+	unsigned int i;
+
+	output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
+	report = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT];
+
+	if (!report) {
+		dev_err(&hdev->dev, "%s: unable to find dj report\n", __func__);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < DJREPORT_SHORT_LENGTH - 1; i++)
+		report->field[0]->value[i] = data[i];
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
+{
+	struct dj_report *dj_report;
+	int retval;
+
+	/* no need to protect djrcv_dev->querying_devices */
+	if (djrcv_dev->querying_devices)
+		return 0;
+
+	dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
+	if (!dj_report)
+		return -ENOMEM;
+	dj_report->report_id = REPORT_ID_DJ_SHORT;
+	dj_report->device_index = 0xFF;
+	dj_report->report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES;
+	retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
+	kfree(dj_report);
+	return retval;
+}
+
+
+static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
+					  unsigned timeout)
+{
+	struct hid_device *hdev = djrcv_dev->hdev;
+	struct dj_report *dj_report;
+	u8 *buf;
+	int retval;
+
+	dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
+	if (!dj_report)
+		return -ENOMEM;
+	dj_report->report_id = REPORT_ID_DJ_SHORT;
+	dj_report->device_index = 0xFF;
+	dj_report->report_type = REPORT_TYPE_CMD_SWITCH;
+	dj_report->report_params[CMD_SWITCH_PARAM_DEVBITFIELD] = 0x3F;
+	dj_report->report_params[CMD_SWITCH_PARAM_TIMEOUT_SECONDS] = (u8)timeout;
+	retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
+
+	/*
+	 * Ugly sleep to work around a USB 3.0 bug when the receiver is still
+	 * processing the "switch-to-dj" command while we send an other command.
+	 * 50 msec should gives enough time to the receiver to be ready.
+	 */
+	msleep(50);
+
+	/*
+	 * Magical bits to set up hidpp notifications when the dj devices
+	 * are connected/disconnected.
+	 *
+	 * We can reuse dj_report because HIDPP_REPORT_SHORT_LENGTH is smaller
+	 * than DJREPORT_SHORT_LENGTH.
+	 */
+	buf = (u8 *)dj_report;
+
+	memset(buf, 0, HIDPP_REPORT_SHORT_LENGTH);
+
+	buf[0] = REPORT_ID_HIDPP_SHORT;
+	buf[1] = 0xFF;
+	buf[2] = 0x80;
+	buf[3] = 0x00;
+	buf[4] = 0x00;
+	buf[5] = 0x09;
+	buf[6] = 0x00;
+
+	hid_hw_raw_request(hdev, REPORT_ID_HIDPP_SHORT, buf,
+			HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT,
+			HID_REQ_SET_REPORT);
+
+	kfree(dj_report);
+	return retval;
+}
+
+
+static int logi_dj_ll_open(struct hid_device *hid)
+{
+	dbg_hid("%s:%s\n", __func__, hid->phys);
+	return 0;
+
+}
+
+static void logi_dj_ll_close(struct hid_device *hid)
+{
+	dbg_hid("%s:%s\n", __func__, hid->phys);
+}
+
+/*
+ * Register 0xB5 is "pairing information". It is solely intended for the
+ * receiver, so do not overwrite the device index.
+ */
+static u8 unifying_pairing_query[]  = {0x10, 0xff, 0x83, 0xb5};
+static u8 unifying_pairing_answer[] = {0x11, 0xff, 0x83, 0xb5};
+
+static int logi_dj_ll_raw_request(struct hid_device *hid,
+				  unsigned char reportnum, __u8 *buf,
+				  size_t count, unsigned char report_type,
+				  int reqtype)
+{
+	struct dj_device *djdev = hid->driver_data;
+	struct dj_receiver_dev *djrcv_dev = djdev->dj_receiver_dev;
+	u8 *out_buf;
+	int ret;
+
+	if ((buf[0] == REPORT_ID_HIDPP_SHORT) ||
+	    (buf[0] == REPORT_ID_HIDPP_LONG)) {
+		if (count < 2)
+			return -EINVAL;
+
+		/* special case where we should not overwrite
+		 * the device_index */
+		if (count == 7 && !memcmp(buf, unifying_pairing_query,
+					  sizeof(unifying_pairing_query)))
+			buf[4] = (buf[4] & 0xf0) | (djdev->device_index - 1);
+		else
+			buf[1] = djdev->device_index;
+		return hid_hw_raw_request(djrcv_dev->hdev, reportnum, buf,
+				count, report_type, reqtype);
+	}
+
+	if (buf[0] != REPORT_TYPE_LEDS)
+		return -EINVAL;
+
+	out_buf = kzalloc(DJREPORT_SHORT_LENGTH, GFP_ATOMIC);
+	if (!out_buf)
+		return -ENOMEM;
+
+	if (count > DJREPORT_SHORT_LENGTH - 2)
+		count = DJREPORT_SHORT_LENGTH - 2;
+
+	out_buf[0] = REPORT_ID_DJ_SHORT;
+	out_buf[1] = djdev->device_index;
+	memcpy(out_buf + 2, buf, count);
+
+	ret = hid_hw_raw_request(djrcv_dev->hdev, out_buf[0], out_buf,
+		DJREPORT_SHORT_LENGTH, report_type, reqtype);
+
+	kfree(out_buf);
+	return ret;
+}
+
+static void rdcat(char *rdesc, unsigned int *rsize, const char *data, unsigned int size)
+{
+	memcpy(rdesc + *rsize, data, size);
+	*rsize += size;
+}
+
+static int logi_dj_ll_parse(struct hid_device *hid)
+{
+	struct dj_device *djdev = hid->driver_data;
+	unsigned int rsize = 0;
+	char *rdesc;
+	int retval;
+
+	dbg_hid("%s\n", __func__);
+
+	djdev->hdev->version = 0x0111;
+	djdev->hdev->country = 0x00;
+
+	rdesc = kmalloc(MAX_RDESC_SIZE, GFP_KERNEL);
+	if (!rdesc)
+		return -ENOMEM;
+
+	if (djdev->reports_supported & STD_KEYBOARD) {
+		dbg_hid("%s: sending a kbd descriptor, reports_supported: %x\n",
+			__func__, djdev->reports_supported);
+		rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
+	}
+
+	if (djdev->reports_supported & STD_MOUSE) {
+		dbg_hid("%s: sending a mouse descriptor, reports_supported: "
+			"%x\n", __func__, djdev->reports_supported);
+		rdcat(rdesc, &rsize, mse_descriptor, sizeof(mse_descriptor));
+	}
+
+	if (djdev->reports_supported & MULTIMEDIA) {
+		dbg_hid("%s: sending a multimedia report descriptor: %x\n",
+			__func__, djdev->reports_supported);
+		rdcat(rdesc, &rsize, consumer_descriptor, sizeof(consumer_descriptor));
+	}
+
+	if (djdev->reports_supported & POWER_KEYS) {
+		dbg_hid("%s: sending a power keys report descriptor: %x\n",
+			__func__, djdev->reports_supported);
+		rdcat(rdesc, &rsize, syscontrol_descriptor, sizeof(syscontrol_descriptor));
+	}
+
+	if (djdev->reports_supported & MEDIA_CENTER) {
+		dbg_hid("%s: sending a media center report descriptor: %x\n",
+			__func__, djdev->reports_supported);
+		rdcat(rdesc, &rsize, media_descriptor, sizeof(media_descriptor));
+	}
+
+	if (djdev->reports_supported & KBD_LEDS) {
+		dbg_hid("%s: need to send kbd leds report descriptor: %x\n",
+			__func__, djdev->reports_supported);
+	}
+
+	rdcat(rdesc, &rsize, hidpp_descriptor, sizeof(hidpp_descriptor));
+
+	retval = hid_parse_report(hid, rdesc, rsize);
+	kfree(rdesc);
+
+	return retval;
+}
+
+static int logi_dj_ll_start(struct hid_device *hid)
+{
+	dbg_hid("%s\n", __func__);
+	return 0;
+}
+
+static void logi_dj_ll_stop(struct hid_device *hid)
+{
+	dbg_hid("%s\n", __func__);
+}
+
+
+static struct hid_ll_driver logi_dj_ll_driver = {
+	.parse = logi_dj_ll_parse,
+	.start = logi_dj_ll_start,
+	.stop = logi_dj_ll_stop,
+	.open = logi_dj_ll_open,
+	.close = logi_dj_ll_close,
+	.raw_request = logi_dj_ll_raw_request,
+};
+
+static int logi_dj_dj_event(struct hid_device *hdev,
+			     struct hid_report *report, u8 *data,
+			     int size)
+{
+	struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
+	struct dj_report *dj_report = (struct dj_report *) data;
+	unsigned long flags;
+
+	/*
+	 * Here we receive all data coming from iface 2, there are 3 cases:
+	 *
+	 * 1) Data is intended for this driver i. e. data contains arrival,
+	 * departure, etc notifications, in which case we queue them for delayed
+	 * processing by the work queue. We return 1 to hid-core as no further
+	 * processing is required from it.
+	 *
+	 * 2) Data informs a connection change, if the change means rf link
+	 * loss, then we must send a null report to the upper layer to discard
+	 * potentially pressed keys that may be repeated forever by the input
+	 * layer. Return 1 to hid-core as no further processing is required.
+	 *
+	 * 3) Data is an actual input event from a paired DJ device in which
+	 * case we forward it to the correct hid device (via hid_input_report()
+	 * ) and return 1 so hid-core does not anything else with it.
+	 */
+
+	if ((dj_report->device_index < DJ_DEVICE_INDEX_MIN) ||
+	    (dj_report->device_index > DJ_DEVICE_INDEX_MAX)) {
+		/*
+		 * Device index is wrong, bail out.
+		 * This driver can ignore safely the receiver notifications,
+		 * so ignore those reports too.
+		 */
+		if (dj_report->device_index != DJ_RECEIVER_INDEX)
+			dev_err(&hdev->dev, "%s: invalid device index:%d\n",
+				__func__, dj_report->device_index);
+		return false;
+	}
+
+	spin_lock_irqsave(&djrcv_dev->lock, flags);
+
+	if (!djrcv_dev->paired_dj_devices[dj_report->device_index]) {
+		/* received an event for an unknown device, bail out */
+		logi_dj_recv_queue_notification(djrcv_dev, dj_report);
+		goto out;
+	}
+
+	switch (dj_report->report_type) {
+	case REPORT_TYPE_NOTIF_DEVICE_PAIRED:
+		/* pairing notifications are handled above the switch */
+		break;
+	case REPORT_TYPE_NOTIF_DEVICE_UNPAIRED:
+		logi_dj_recv_queue_notification(djrcv_dev, dj_report);
+		break;
+	case REPORT_TYPE_NOTIF_CONNECTION_STATUS:
+		if (dj_report->report_params[CONNECTION_STATUS_PARAM_STATUS] ==
+		    STATUS_LINKLOSS) {
+			logi_dj_recv_forward_null_report(djrcv_dev, dj_report);
+		}
+		break;
+	default:
+		logi_dj_recv_forward_report(djrcv_dev, dj_report);
+	}
+
+out:
+	spin_unlock_irqrestore(&djrcv_dev->lock, flags);
+
+	return true;
+}
+
+static int logi_dj_hidpp_event(struct hid_device *hdev,
+			     struct hid_report *report, u8 *data,
+			     int size)
+{
+	struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
+	struct dj_report *dj_report = (struct dj_report *) data;
+	unsigned long flags;
+	u8 device_index = dj_report->device_index;
+
+	if (device_index == HIDPP_RECEIVER_INDEX) {
+		/* special case were the device wants to know its unifying
+		 * name */
+		if (size == HIDPP_REPORT_LONG_LENGTH &&
+		    !memcmp(data, unifying_pairing_answer,
+			    sizeof(unifying_pairing_answer)))
+			device_index = (data[4] & 0x0F) + 1;
+		else
+			return false;
+	}
+
+	/*
+	 * Data is from the HID++ collection, in this case, we forward the
+	 * data to the corresponding child dj device and return 0 to hid-core
+	 * so he data also goes to the hidraw device of the receiver. This
+	 * allows a user space application to implement the full HID++ routing
+	 * via the receiver.
+	 */
+
+	if ((device_index < DJ_DEVICE_INDEX_MIN) ||
+	    (device_index > DJ_DEVICE_INDEX_MAX)) {
+		/*
+		 * Device index is wrong, bail out.
+		 * This driver can ignore safely the receiver notifications,
+		 * so ignore those reports too.
+		 */
+		dev_err(&hdev->dev, "%s: invalid device index:%d\n",
+				__func__, dj_report->device_index);
+		return false;
+	}
+
+	spin_lock_irqsave(&djrcv_dev->lock, flags);
+
+	if (!djrcv_dev->paired_dj_devices[device_index])
+		/* received an event for an unknown device, bail out */
+		goto out;
+
+	logi_dj_recv_forward_hidpp(djrcv_dev->paired_dj_devices[device_index],
+				   data, size);
+
+out:
+	spin_unlock_irqrestore(&djrcv_dev->lock, flags);
+
+	return false;
+}
+
+static int logi_dj_raw_event(struct hid_device *hdev,
+			     struct hid_report *report, u8 *data,
+			     int size)
+{
+	dbg_hid("%s, size:%d\n", __func__, size);
+
+	switch (data[0]) {
+	case REPORT_ID_DJ_SHORT:
+		if (size != DJREPORT_SHORT_LENGTH) {
+			dev_err(&hdev->dev, "DJ report of bad size (%d)", size);
+			return false;
+		}
+		return logi_dj_dj_event(hdev, report, data, size);
+	case REPORT_ID_HIDPP_SHORT:
+		if (size != HIDPP_REPORT_SHORT_LENGTH) {
+			dev_err(&hdev->dev,
+				"Short HID++ report of bad size (%d)", size);
+			return false;
+		}
+		return logi_dj_hidpp_event(hdev, report, data, size);
+	case REPORT_ID_HIDPP_LONG:
+		if (size != HIDPP_REPORT_LONG_LENGTH) {
+			dev_err(&hdev->dev,
+				"Long HID++ report of bad size (%d)", size);
+			return false;
+		}
+		return logi_dj_hidpp_event(hdev, report, data, size);
+	}
+
+	return false;
+}
+
+static int logi_dj_probe(struct hid_device *hdev,
+			 const struct hid_device_id *id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct dj_receiver_dev *djrcv_dev;
+	int retval;
+
+	dbg_hid("%s called for ifnum %d\n", __func__,
+		intf->cur_altsetting->desc.bInterfaceNumber);
+
+	/* Ignore interfaces 0 and 1, they will not carry any data, dont create
+	 * any hid_device for them */
+	if (intf->cur_altsetting->desc.bInterfaceNumber !=
+	    LOGITECH_DJ_INTERFACE_NUMBER) {
+		dbg_hid("%s: ignoring ifnum %d\n", __func__,
+			intf->cur_altsetting->desc.bInterfaceNumber);
+		return -ENODEV;
+	}
+
+	/* Treat interface 2 */
+
+	djrcv_dev = kzalloc(sizeof(struct dj_receiver_dev), GFP_KERNEL);
+	if (!djrcv_dev) {
+		dev_err(&hdev->dev,
+			"%s:failed allocating dj_receiver_dev\n", __func__);
+		return -ENOMEM;
+	}
+	djrcv_dev->hdev = hdev;
+	INIT_WORK(&djrcv_dev->work, delayedwork_callback);
+	spin_lock_init(&djrcv_dev->lock);
+	if (kfifo_alloc(&djrcv_dev->notif_fifo,
+			DJ_MAX_NUMBER_NOTIFICATIONS * sizeof(struct dj_report),
+			GFP_KERNEL)) {
+		dev_err(&hdev->dev,
+			"%s:failed allocating notif_fifo\n", __func__);
+		kfree(djrcv_dev);
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, djrcv_dev);
+
+	/* Call  to usbhid to fetch the HID descriptors of interface 2 and
+	 * subsequently call to the hid/hid-core to parse the fetched
+	 * descriptors, this will in turn create the hidraw and hiddev nodes
+	 * for interface 2 of the receiver */
+	retval = hid_parse(hdev);
+	if (retval) {
+		dev_err(&hdev->dev,
+			"%s:parse of interface 2 failed\n", __func__);
+		goto hid_parse_fail;
+	}
+
+	if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, REPORT_ID_DJ_SHORT,
+				 0, DJREPORT_SHORT_LENGTH - 1)) {
+		retval = -ENODEV;
+		goto hid_parse_fail;
+	}
+
+	/* Starts the usb device and connects to upper interfaces hiddev and
+	 * hidraw */
+	retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (retval) {
+		dev_err(&hdev->dev,
+			"%s:hid_hw_start returned error\n", __func__);
+		goto hid_hw_start_fail;
+	}
+
+	retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
+	if (retval < 0) {
+		dev_err(&hdev->dev,
+			"%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n",
+			__func__, retval);
+		goto switch_to_dj_mode_fail;
+	}
+
+	/* This is enabling the polling urb on the IN endpoint */
+	retval = hid_hw_open(hdev);
+	if (retval < 0) {
+		dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
+			__func__, retval);
+		goto llopen_failed;
+	}
+
+	/* Allow incoming packets to arrive: */
+	hid_device_io_start(hdev);
+
+	retval = logi_dj_recv_query_paired_devices(djrcv_dev);
+	if (retval < 0) {
+		dev_err(&hdev->dev, "%s:logi_dj_recv_query_paired_devices "
+			"error:%d\n", __func__, retval);
+		goto logi_dj_recv_query_paired_devices_failed;
+	}
+
+	return retval;
+
+logi_dj_recv_query_paired_devices_failed:
+	hid_hw_close(hdev);
+
+llopen_failed:
+switch_to_dj_mode_fail:
+	hid_hw_stop(hdev);
+
+hid_hw_start_fail:
+hid_parse_fail:
+	kfifo_free(&djrcv_dev->notif_fifo);
+	kfree(djrcv_dev);
+	hid_set_drvdata(hdev, NULL);
+	return retval;
+
+}
+
+#ifdef CONFIG_PM
+static int logi_dj_reset_resume(struct hid_device *hdev)
+{
+	int retval;
+	struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
+
+	retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
+	if (retval < 0) {
+		dev_err(&hdev->dev,
+			"%s:logi_dj_recv_switch_to_dj_mode returned error:%d\n",
+			__func__, retval);
+	}
+
+	return 0;
+}
+#endif
+
+static void logi_dj_remove(struct hid_device *hdev)
+{
+	struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
+	struct dj_device *dj_dev;
+	int i;
+
+	dbg_hid("%s\n", __func__);
+
+	cancel_work_sync(&djrcv_dev->work);
+
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+
+	/* I suppose that at this point the only context that can access
+	 * the djrecv_data is this thread as the work item is guaranteed to
+	 * have finished and no more raw_event callbacks should arrive after
+	 * the remove callback was triggered so no locks are put around the
+	 * code below */
+	for (i = 0; i < (DJ_MAX_PAIRED_DEVICES + DJ_DEVICE_INDEX_MIN); i++) {
+		dj_dev = djrcv_dev->paired_dj_devices[i];
+		if (dj_dev != NULL) {
+			hid_destroy_device(dj_dev->hdev);
+			kfree(dj_dev);
+			djrcv_dev->paired_dj_devices[i] = NULL;
+		}
+	}
+
+	kfifo_free(&djrcv_dev->notif_fifo);
+	kfree(djrcv_dev);
+	hid_set_drvdata(hdev, NULL);
+}
+
+static const struct hid_device_id logi_dj_receivers[] = {
+	{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+		USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER)},
+	{HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
+		USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, logi_dj_receivers);
+
+static struct hid_driver logi_djreceiver_driver = {
+	.name = "logitech-djreceiver",
+	.id_table = logi_dj_receivers,
+	.probe = logi_dj_probe,
+	.remove = logi_dj_remove,
+	.raw_event = logi_dj_raw_event,
+#ifdef CONFIG_PM
+	.reset_resume = logi_dj_reset_resume,
+#endif
+};
+
+module_hid_driver(logi_djreceiver_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Logitech");
+MODULE_AUTHOR("Nestor Lopez Casado");
+MODULE_AUTHOR("nlopezcasad@logitech.com");
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
new file mode 100644
index 0000000..19cc980
--- /dev/null
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -0,0 +1,3137 @@
+/*
+ *  HIDPP protocol for Logitech Unifying receivers
+ *
+ *  Copyright (c) 2011 Logitech (c)
+ *  Copyright (c) 2012-2013 Google (c)
+ *  Copyright (c) 2013-2014 Red Hat Inc.
+ */
+
+/*
+ * 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; version 2 of the License.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/kfifo.h>
+#include <linux/input/mt.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <linux/fixp-arith.h>
+#include <asm/unaligned.h>
+#include "usbhid/usbhid.h"
+#include "hid-ids.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
+
+static bool disable_raw_mode;
+module_param(disable_raw_mode, bool, 0644);
+MODULE_PARM_DESC(disable_raw_mode,
+	"Disable Raw mode reporting for touchpads and keep firmware gestures.");
+
+static bool disable_tap_to_click;
+module_param(disable_tap_to_click, bool, 0644);
+MODULE_PARM_DESC(disable_tap_to_click,
+	"Disable Tap-To-Click mode reporting for touchpads (only on the K400 currently).");
+
+#define REPORT_ID_HIDPP_SHORT			0x10
+#define REPORT_ID_HIDPP_LONG			0x11
+#define REPORT_ID_HIDPP_VERY_LONG		0x12
+
+#define HIDPP_REPORT_SHORT_LENGTH		7
+#define HIDPP_REPORT_LONG_LENGTH		20
+#define HIDPP_REPORT_VERY_LONG_LENGTH		64
+
+#define HIDPP_QUIRK_CLASS_WTP			BIT(0)
+#define HIDPP_QUIRK_CLASS_M560			BIT(1)
+#define HIDPP_QUIRK_CLASS_K400			BIT(2)
+#define HIDPP_QUIRK_CLASS_G920			BIT(3)
+#define HIDPP_QUIRK_CLASS_K750			BIT(4)
+
+/* bits 2..20 are reserved for classes */
+/* #define HIDPP_QUIRK_CONNECT_EVENTS		BIT(21) disabled */
+#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS	BIT(22)
+#define HIDPP_QUIRK_NO_HIDINPUT			BIT(23)
+#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS	BIT(24)
+#define HIDPP_QUIRK_UNIFYING			BIT(25)
+
+#define HIDPP_QUIRK_DELAYED_INIT		HIDPP_QUIRK_NO_HIDINPUT
+
+#define HIDPP_CAPABILITY_HIDPP10_BATTERY	BIT(0)
+#define HIDPP_CAPABILITY_HIDPP20_BATTERY	BIT(1)
+#define HIDPP_CAPABILITY_BATTERY_MILEAGE	BIT(2)
+#define HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS	BIT(3)
+
+/*
+ * There are two hidpp protocols in use, the first version hidpp10 is known
+ * as register access protocol or RAP, the second version hidpp20 is known as
+ * feature access protocol or FAP
+ *
+ * Most older devices (including the Unifying usb receiver) use the RAP protocol
+ * where as most newer devices use the FAP protocol. Both protocols are
+ * compatible with the underlying transport, which could be usb, Unifiying, or
+ * bluetooth. The message lengths are defined by the hid vendor specific report
+ * descriptor for the HIDPP_SHORT report type (total message lenth 7 bytes) and
+ * the HIDPP_LONG report type (total message length 20 bytes)
+ *
+ * The RAP protocol uses both report types, whereas the FAP only uses HIDPP_LONG
+ * messages. The Unifying receiver itself responds to RAP messages (device index
+ * is 0xFF for the receiver), and all messages (short or long) with a device
+ * index between 1 and 6 are passed untouched to the corresponding paired
+ * Unifying device.
+ *
+ * The paired device can be RAP or FAP, it will receive the message untouched
+ * from the Unifiying receiver.
+ */
+
+struct fap {
+	u8 feature_index;
+	u8 funcindex_clientid;
+	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
+};
+
+struct rap {
+	u8 sub_id;
+	u8 reg_address;
+	u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U];
+};
+
+struct hidpp_report {
+	u8 report_id;
+	u8 device_index;
+	union {
+		struct fap fap;
+		struct rap rap;
+		u8 rawbytes[sizeof(struct fap)];
+	};
+} __packed;
+
+struct hidpp_battery {
+	u8 feature_index;
+	u8 solar_feature_index;
+	struct power_supply_desc desc;
+	struct power_supply *ps;
+	char name[64];
+	int status;
+	int capacity;
+	int level;
+	bool online;
+};
+
+struct hidpp_device {
+	struct hid_device *hid_dev;
+	struct mutex send_mutex;
+	void *send_receive_buf;
+	char *name;		/* will never be NULL and should not be freed */
+	wait_queue_head_t wait;
+	bool answer_available;
+	u8 protocol_major;
+	u8 protocol_minor;
+
+	void *private_data;
+
+	struct work_struct work;
+	struct kfifo delayed_work_fifo;
+	atomic_t connected;
+	struct input_dev *delayed_input;
+
+	unsigned long quirks;
+	unsigned long capabilities;
+
+	struct hidpp_battery battery;
+};
+
+/* HID++ 1.0 error codes */
+#define HIDPP_ERROR				0x8f
+#define HIDPP_ERROR_SUCCESS			0x00
+#define HIDPP_ERROR_INVALID_SUBID		0x01
+#define HIDPP_ERROR_INVALID_ADRESS		0x02
+#define HIDPP_ERROR_INVALID_VALUE		0x03
+#define HIDPP_ERROR_CONNECT_FAIL		0x04
+#define HIDPP_ERROR_TOO_MANY_DEVICES		0x05
+#define HIDPP_ERROR_ALREADY_EXISTS		0x06
+#define HIDPP_ERROR_BUSY			0x07
+#define HIDPP_ERROR_UNKNOWN_DEVICE		0x08
+#define HIDPP_ERROR_RESOURCE_ERROR		0x09
+#define HIDPP_ERROR_REQUEST_UNAVAILABLE		0x0a
+#define HIDPP_ERROR_INVALID_PARAM_VALUE		0x0b
+#define HIDPP_ERROR_WRONG_PIN_CODE		0x0c
+/* HID++ 2.0 error codes */
+#define HIDPP20_ERROR				0xff
+
+static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
+
+static int __hidpp_send_report(struct hid_device *hdev,
+				struct hidpp_report *hidpp_report)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	int fields_count, ret;
+
+	hidpp = hid_get_drvdata(hdev);
+
+	switch (hidpp_report->report_id) {
+	case REPORT_ID_HIDPP_SHORT:
+		fields_count = HIDPP_REPORT_SHORT_LENGTH;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		fields_count = HIDPP_REPORT_LONG_LENGTH;
+		break;
+	case REPORT_ID_HIDPP_VERY_LONG:
+		fields_count = HIDPP_REPORT_VERY_LONG_LENGTH;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	/*
+	 * set the device_index as the receiver, it will be overwritten by
+	 * hid_hw_request if needed
+	 */
+	hidpp_report->device_index = 0xff;
+
+	if (hidpp->quirks & HIDPP_QUIRK_FORCE_OUTPUT_REPORTS) {
+		ret = hid_hw_output_report(hdev, (u8 *)hidpp_report, fields_count);
+	} else {
+		ret = hid_hw_raw_request(hdev, hidpp_report->report_id,
+			(u8 *)hidpp_report, fields_count, HID_OUTPUT_REPORT,
+			HID_REQ_SET_REPORT);
+	}
+
+	return ret == fields_count ? 0 : -1;
+}
+
+/**
+ * hidpp_send_message_sync() returns 0 in case of success, and something else
+ * in case of a failure.
+ * - If ' something else' is positive, that means that an error has been raised
+ *   by the protocol itself.
+ * - If ' something else' is negative, that means that we had a classic error
+ *   (-ENOMEM, -EPIPE, etc...)
+ */
+static int hidpp_send_message_sync(struct hidpp_device *hidpp,
+	struct hidpp_report *message,
+	struct hidpp_report *response)
+{
+	int ret;
+
+	mutex_lock(&hidpp->send_mutex);
+
+	hidpp->send_receive_buf = response;
+	hidpp->answer_available = false;
+
+	/*
+	 * So that we can later validate the answer when it arrives
+	 * in hidpp_raw_event
+	 */
+	*response = *message;
+
+	ret = __hidpp_send_report(hidpp->hid_dev, message);
+
+	if (ret) {
+		dbg_hid("__hidpp_send_report returned err: %d\n", ret);
+		memset(response, 0, sizeof(struct hidpp_report));
+		goto exit;
+	}
+
+	if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
+				5*HZ)) {
+		dbg_hid("%s:timeout waiting for response\n", __func__);
+		memset(response, 0, sizeof(struct hidpp_report));
+		ret = -ETIMEDOUT;
+	}
+
+	if (response->report_id == REPORT_ID_HIDPP_SHORT &&
+	    response->rap.sub_id == HIDPP_ERROR) {
+		ret = response->rap.params[1];
+		dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
+		goto exit;
+	}
+
+	if ((response->report_id == REPORT_ID_HIDPP_LONG ||
+			response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
+			response->fap.feature_index == HIDPP20_ERROR) {
+		ret = response->fap.params[1];
+		dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
+		goto exit;
+	}
+
+exit:
+	mutex_unlock(&hidpp->send_mutex);
+	return ret;
+
+}
+
+static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
+	u8 feat_index, u8 funcindex_clientid, u8 *params, int param_count,
+	struct hidpp_report *response)
+{
+	struct hidpp_report *message;
+	int ret;
+
+	if (param_count > sizeof(message->fap.params))
+		return -EINVAL;
+
+	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
+	if (!message)
+		return -ENOMEM;
+
+	if (param_count > (HIDPP_REPORT_LONG_LENGTH - 4))
+		message->report_id = REPORT_ID_HIDPP_VERY_LONG;
+	else
+		message->report_id = REPORT_ID_HIDPP_LONG;
+	message->fap.feature_index = feat_index;
+	message->fap.funcindex_clientid = funcindex_clientid;
+	memcpy(&message->fap.params, params, param_count);
+
+	ret = hidpp_send_message_sync(hidpp, message, response);
+	kfree(message);
+	return ret;
+}
+
+static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
+	u8 report_id, u8 sub_id, u8 reg_address, u8 *params, int param_count,
+	struct hidpp_report *response)
+{
+	struct hidpp_report *message;
+	int ret, max_count;
+
+	switch (report_id) {
+	case REPORT_ID_HIDPP_SHORT:
+		max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		max_count = HIDPP_REPORT_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_VERY_LONG:
+		max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (param_count > max_count)
+		return -EINVAL;
+
+	message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
+	if (!message)
+		return -ENOMEM;
+	message->report_id = report_id;
+	message->rap.sub_id = sub_id;
+	message->rap.reg_address = reg_address;
+	memcpy(&message->rap.params, params, param_count);
+
+	ret = hidpp_send_message_sync(hidpp_dev, message, response);
+	kfree(message);
+	return ret;
+}
+
+static void delayed_work_cb(struct work_struct *work)
+{
+	struct hidpp_device *hidpp = container_of(work, struct hidpp_device,
+							work);
+	hidpp_connect_event(hidpp);
+}
+
+static inline bool hidpp_match_answer(struct hidpp_report *question,
+		struct hidpp_report *answer)
+{
+	return (answer->fap.feature_index == question->fap.feature_index) &&
+	   (answer->fap.funcindex_clientid == question->fap.funcindex_clientid);
+}
+
+static inline bool hidpp_match_error(struct hidpp_report *question,
+		struct hidpp_report *answer)
+{
+	return ((answer->rap.sub_id == HIDPP_ERROR) ||
+	    (answer->fap.feature_index == HIDPP20_ERROR)) &&
+	    (answer->fap.funcindex_clientid == question->fap.feature_index) &&
+	    (answer->fap.params[0] == question->fap.funcindex_clientid);
+}
+
+static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
+{
+	return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
+		(report->rap.sub_id == 0x41);
+}
+
+/**
+ * hidpp_prefix_name() prefixes the current given name with "Logitech ".
+ */
+static void hidpp_prefix_name(char **name, int name_length)
+{
+#define PREFIX_LENGTH 9 /* "Logitech " */
+
+	int new_length;
+	char *new_name;
+
+	if (name_length > PREFIX_LENGTH &&
+	    strncmp(*name, "Logitech ", PREFIX_LENGTH) == 0)
+		/* The prefix has is already in the name */
+		return;
+
+	new_length = PREFIX_LENGTH + name_length;
+	new_name = kzalloc(new_length, GFP_KERNEL);
+	if (!new_name)
+		return;
+
+	snprintf(new_name, new_length, "Logitech %s", *name);
+
+	kfree(*name);
+
+	*name = new_name;
+}
+
+/* -------------------------------------------------------------------------- */
+/* HIDP++ 1.0 commands                                                        */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_SET_REGISTER				0x80
+#define HIDPP_GET_REGISTER				0x81
+#define HIDPP_SET_LONG_REGISTER				0x82
+#define HIDPP_GET_LONG_REGISTER				0x83
+
+#define HIDPP_REG_GENERAL				0x00
+
+static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 params[3] = { 0 };
+
+	ret = hidpp_send_rap_command_sync(hidpp_dev,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_REGISTER,
+					HIDPP_REG_GENERAL,
+					NULL, 0, &response);
+	if (ret)
+		return ret;
+
+	memcpy(params, response.rap.params, 3);
+
+	/* Set the battery bit */
+	params[0] |= BIT(4);
+
+	return hidpp_send_rap_command_sync(hidpp_dev,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_SET_REGISTER,
+					HIDPP_REG_GENERAL,
+					params, 3, &response);
+}
+
+#define HIDPP_REG_BATTERY_STATUS			0x07
+
+static int hidpp10_battery_status_map_level(u8 param)
+{
+	int level;
+
+	switch (param) {
+	case 1 ... 2:
+		level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+		break;
+	case 3 ... 4:
+		level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+		break;
+	case 5 ... 6:
+		level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+		break;
+	case 7:
+		level = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+		break;
+	default:
+		level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+	}
+
+	return level;
+}
+
+static int hidpp10_battery_status_map_status(u8 param)
+{
+	int status;
+
+	switch (param) {
+	case 0x00:
+		/* discharging (in use) */
+		status = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	case 0x21: /* (standard) charging */
+	case 0x24: /* fast charging */
+	case 0x25: /* slow charging */
+		status = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+	case 0x26: /* topping charge */
+	case 0x22: /* charge complete */
+		status = POWER_SUPPLY_STATUS_FULL;
+		break;
+	case 0x20: /* unknown */
+		status = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+	/*
+	 * 0x01...0x1F = reserved (not charging)
+	 * 0x23 = charging error
+	 * 0x27..0xff = reserved
+	 */
+	default:
+		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	}
+
+	return status;
+}
+
+static int hidpp10_query_battery_status(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	int ret, status;
+
+	ret = hidpp_send_rap_command_sync(hidpp,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_REGISTER,
+					HIDPP_REG_BATTERY_STATUS,
+					NULL, 0, &response);
+	if (ret)
+		return ret;
+
+	hidpp->battery.level =
+		hidpp10_battery_status_map_level(response.rap.params[0]);
+	status = hidpp10_battery_status_map_status(response.rap.params[1]);
+	hidpp->battery.status = status;
+	/* the capacity is only available when discharging or full */
+	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
+				status == POWER_SUPPLY_STATUS_FULL;
+
+	return 0;
+}
+
+#define HIDPP_REG_BATTERY_MILEAGE			0x0D
+
+static int hidpp10_battery_mileage_map_status(u8 param)
+{
+	int status;
+
+	switch (param >> 6) {
+	case 0x00:
+		/* discharging (in use) */
+		status = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	case 0x01: /* charging */
+		status = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+	case 0x02: /* charge complete */
+		status = POWER_SUPPLY_STATUS_FULL;
+		break;
+	/*
+	 * 0x03 = charging error
+	 */
+	default:
+		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	}
+
+	return status;
+}
+
+static int hidpp10_query_battery_mileage(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	int ret, status;
+
+	ret = hidpp_send_rap_command_sync(hidpp,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_REGISTER,
+					HIDPP_REG_BATTERY_MILEAGE,
+					NULL, 0, &response);
+	if (ret)
+		return ret;
+
+	hidpp->battery.capacity = response.rap.params[0];
+	status = hidpp10_battery_mileage_map_status(response.rap.params[2]);
+	hidpp->battery.status = status;
+	/* the capacity is only available when discharging or full */
+	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
+				status == POWER_SUPPLY_STATUS_FULL;
+
+	return 0;
+}
+
+static int hidpp10_battery_event(struct hidpp_device *hidpp, u8 *data, int size)
+{
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	int status, capacity, level;
+	bool changed;
+
+	if (report->report_id != REPORT_ID_HIDPP_SHORT)
+		return 0;
+
+	switch (report->rap.sub_id) {
+	case HIDPP_REG_BATTERY_STATUS:
+		capacity = hidpp->battery.capacity;
+		level = hidpp10_battery_status_map_level(report->rawbytes[1]);
+		status = hidpp10_battery_status_map_status(report->rawbytes[2]);
+		break;
+	case HIDPP_REG_BATTERY_MILEAGE:
+		capacity = report->rap.params[0];
+		level = hidpp->battery.level;
+		status = hidpp10_battery_mileage_map_status(report->rawbytes[3]);
+		break;
+	default:
+		return 0;
+	}
+
+	changed = capacity != hidpp->battery.capacity ||
+		  level != hidpp->battery.level ||
+		  status != hidpp->battery.status;
+
+	/* the capacity is only available when discharging or full */
+	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
+				status == POWER_SUPPLY_STATUS_FULL;
+
+	if (changed) {
+		hidpp->battery.level = level;
+		hidpp->battery.status = status;
+		if (hidpp->battery.ps)
+			power_supply_changed(hidpp->battery.ps);
+	}
+
+	return 0;
+}
+
+#define HIDPP_REG_PAIRING_INFORMATION			0xB5
+#define HIDPP_EXTENDED_PAIRING				0x30
+#define HIDPP_DEVICE_NAME				0x40
+
+static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 params[1] = { HIDPP_DEVICE_NAME };
+	char *name;
+	int len;
+
+	ret = hidpp_send_rap_command_sync(hidpp_dev,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_LONG_REGISTER,
+					HIDPP_REG_PAIRING_INFORMATION,
+					params, 1, &response);
+	if (ret)
+		return NULL;
+
+	len = response.rap.params[1];
+
+	if (2 + len > sizeof(response.rap.params))
+		return NULL;
+
+	name = kzalloc(len + 1, GFP_KERNEL);
+	if (!name)
+		return NULL;
+
+	memcpy(name, &response.rap.params[2], len);
+
+	/* include the terminating '\0' */
+	hidpp_prefix_name(&name, len + 1);
+
+	return name;
+}
+
+static int hidpp_unifying_get_serial(struct hidpp_device *hidpp, u32 *serial)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 params[1] = { HIDPP_EXTENDED_PAIRING };
+
+	ret = hidpp_send_rap_command_sync(hidpp,
+					REPORT_ID_HIDPP_SHORT,
+					HIDPP_GET_LONG_REGISTER,
+					HIDPP_REG_PAIRING_INFORMATION,
+					params, 1, &response);
+	if (ret)
+		return ret;
+
+	/*
+	 * We don't care about LE or BE, we will output it as a string
+	 * with %4phD, so we need to keep the order.
+	 */
+	*serial = *((u32 *)&response.rap.params[1]);
+	return 0;
+}
+
+static int hidpp_unifying_init(struct hidpp_device *hidpp)
+{
+	struct hid_device *hdev = hidpp->hid_dev;
+	const char *name;
+	u32 serial;
+	int ret;
+
+	ret = hidpp_unifying_get_serial(hidpp, &serial);
+	if (ret)
+		return ret;
+
+	snprintf(hdev->uniq, sizeof(hdev->uniq), "%04x-%4phD",
+		 hdev->product, &serial);
+	dbg_hid("HID++ Unifying: Got serial: %s\n", hdev->uniq);
+
+	name = hidpp_unifying_get_name(hidpp);
+	if (!name)
+		return -EIO;
+
+	snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+	dbg_hid("HID++ Unifying: Got name: %s\n", name);
+
+	kfree(name);
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x0000: Root                                                               */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_ROOT					0x0000
+#define HIDPP_PAGE_ROOT_IDX				0x00
+
+#define CMD_ROOT_GET_FEATURE				0x01
+#define CMD_ROOT_GET_PROTOCOL_VERSION			0x11
+
+static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
+	u8 *feature_index, u8 *feature_type)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 params[2] = { feature >> 8, feature & 0x00FF };
+
+	ret = hidpp_send_fap_command_sync(hidpp,
+			HIDPP_PAGE_ROOT_IDX,
+			CMD_ROOT_GET_FEATURE,
+			params, 2, &response);
+	if (ret)
+		return ret;
+
+	if (response.fap.params[0] == 0)
+		return -ENOENT;
+
+	*feature_index = response.fap.params[0];
+	*feature_type = response.fap.params[1];
+
+	return ret;
+}
+
+static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp,
+			HIDPP_PAGE_ROOT_IDX,
+			CMD_ROOT_GET_PROTOCOL_VERSION,
+			NULL, 0, &response);
+
+	if (ret == HIDPP_ERROR_INVALID_SUBID) {
+		hidpp->protocol_major = 1;
+		hidpp->protocol_minor = 0;
+		return 0;
+	}
+
+	/* the device might not be connected */
+	if (ret == HIDPP_ERROR_RESOURCE_ERROR)
+		return -EIO;
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	hidpp->protocol_major = response.fap.params[0];
+	hidpp->protocol_minor = response.fap.params[1];
+
+	return ret;
+}
+
+static bool hidpp_is_connected(struct hidpp_device *hidpp)
+{
+	int ret;
+
+	ret = hidpp_root_get_protocol_version(hidpp);
+	if (!ret)
+		hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n",
+			hidpp->protocol_major, hidpp->protocol_minor);
+	return ret == 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x0005: GetDeviceNameType                                                  */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_GET_DEVICE_NAME_TYPE			0x0005
+
+#define CMD_GET_DEVICE_NAME_TYPE_GET_COUNT		0x01
+#define CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME	0x11
+#define CMD_GET_DEVICE_NAME_TYPE_GET_TYPE		0x21
+
+static int hidpp_devicenametype_get_count(struct hidpp_device *hidpp,
+	u8 feature_index, u8 *nameLength)
+{
+	struct hidpp_report response;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_GET_DEVICE_NAME_TYPE_GET_COUNT, NULL, 0, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	*nameLength = response.fap.params[0];
+
+	return ret;
+}
+
+static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
+	u8 feature_index, u8 char_index, char *device_name, int len_buf)
+{
+	struct hidpp_report response;
+	int ret, i;
+	int count;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_GET_DEVICE_NAME_TYPE_GET_DEVICE_NAME, &char_index, 1,
+		&response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	switch (response.report_id) {
+	case REPORT_ID_HIDPP_VERY_LONG:
+		count = HIDPP_REPORT_VERY_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		count = HIDPP_REPORT_LONG_LENGTH - 4;
+		break;
+	case REPORT_ID_HIDPP_SHORT:
+		count = HIDPP_REPORT_SHORT_LENGTH - 4;
+		break;
+	default:
+		return -EPROTO;
+	}
+
+	if (len_buf < count)
+		count = len_buf;
+
+	for (i = 0; i < count; i++)
+		device_name[i] = response.fap.params[i];
+
+	return count;
+}
+
+static char *hidpp_get_device_name(struct hidpp_device *hidpp)
+{
+	u8 feature_type;
+	u8 feature_index;
+	u8 __name_length;
+	char *name;
+	unsigned index = 0;
+	int ret;
+
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
+		&feature_index, &feature_type);
+	if (ret)
+		return NULL;
+
+	ret = hidpp_devicenametype_get_count(hidpp, feature_index,
+		&__name_length);
+	if (ret)
+		return NULL;
+
+	name = kzalloc(__name_length + 1, GFP_KERNEL);
+	if (!name)
+		return NULL;
+
+	while (index < __name_length) {
+		ret = hidpp_devicenametype_get_device_name(hidpp,
+			feature_index, index, name + index,
+			__name_length - index);
+		if (ret <= 0) {
+			kfree(name);
+			return NULL;
+		}
+		index += ret;
+	}
+
+	/* include the terminating '\0' */
+	hidpp_prefix_name(&name, __name_length + 1);
+
+	return name;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x1000: Battery level status                                               */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_BATTERY_LEVEL_STATUS				0x1000
+
+#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS	0x00
+#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY		0x10
+
+#define EVENT_BATTERY_LEVEL_STATUS_BROADCAST			0x00
+
+#define FLAG_BATTERY_LEVEL_DISABLE_OSD				BIT(0)
+#define FLAG_BATTERY_LEVEL_MILEAGE				BIT(1)
+#define FLAG_BATTERY_LEVEL_RECHARGEABLE				BIT(2)
+
+static int hidpp_map_battery_level(int capacity)
+{
+	if (capacity < 11)
+		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+	else if (capacity < 31)
+		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+	else if (capacity < 81)
+		return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+	return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+}
+
+static int hidpp20_batterylevel_map_status_capacity(u8 data[3], int *capacity,
+						    int *next_capacity,
+						    int *level)
+{
+	int status;
+
+	*capacity = data[0];
+	*next_capacity = data[1];
+	*level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+
+	/* When discharging, we can rely on the device reported capacity.
+	 * For all other states the device reports 0 (unknown).
+	 */
+	switch (data[2]) {
+		case 0: /* discharging (in use) */
+			status = POWER_SUPPLY_STATUS_DISCHARGING;
+			*level = hidpp_map_battery_level(*capacity);
+			break;
+		case 1: /* recharging */
+			status = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+		case 2: /* charge in final stage */
+			status = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+		case 3: /* charge complete */
+			status = POWER_SUPPLY_STATUS_FULL;
+			*level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+			*capacity = 100;
+			break;
+		case 4: /* recharging below optimal speed */
+			status = POWER_SUPPLY_STATUS_CHARGING;
+			break;
+		/* 5 = invalid battery type
+		   6 = thermal error
+		   7 = other charging error */
+		default:
+			status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			break;
+	}
+
+	return status;
+}
+
+static int hidpp20_batterylevel_get_battery_capacity(struct hidpp_device *hidpp,
+						     u8 feature_index,
+						     int *status,
+						     int *capacity,
+						     int *next_capacity,
+						     int *level)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+					  CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
+					  NULL, 0, &response);
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	*status = hidpp20_batterylevel_map_status_capacity(params, capacity,
+							   next_capacity,
+							   level);
+
+	return 0;
+}
+
+static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
+						  u8 feature_index)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+	unsigned int level_count, flags;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+					  CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY,
+					  NULL, 0, &response);
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	level_count = params[0];
+	flags = params[1];
+
+	if (level_count < 10 || !(flags & FLAG_BATTERY_LEVEL_MILEAGE))
+		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
+	else
+		hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
+
+	return 0;
+}
+
+static int hidpp20_query_battery_info(struct hidpp_device *hidpp)
+{
+	u8 feature_type;
+	int ret;
+	int status, capacity, next_capacity, level;
+
+	if (hidpp->battery.feature_index == 0xff) {
+		ret = hidpp_root_get_feature(hidpp,
+					     HIDPP_PAGE_BATTERY_LEVEL_STATUS,
+					     &hidpp->battery.feature_index,
+					     &feature_type);
+		if (ret)
+			return ret;
+	}
+
+	ret = hidpp20_batterylevel_get_battery_capacity(hidpp,
+						hidpp->battery.feature_index,
+						&status, &capacity,
+						&next_capacity, &level);
+	if (ret)
+		return ret;
+
+	ret = hidpp20_batterylevel_get_battery_info(hidpp,
+						hidpp->battery.feature_index);
+	if (ret)
+		return ret;
+
+	hidpp->battery.status = status;
+	hidpp->battery.capacity = capacity;
+	hidpp->battery.level = level;
+	/* the capacity is only available when discharging or full */
+	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
+				status == POWER_SUPPLY_STATUS_FULL;
+
+	return 0;
+}
+
+static int hidpp20_battery_event(struct hidpp_device *hidpp,
+				 u8 *data, int size)
+{
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	int status, capacity, next_capacity, level;
+	bool changed;
+
+	if (report->fap.feature_index != hidpp->battery.feature_index ||
+	    report->fap.funcindex_clientid != EVENT_BATTERY_LEVEL_STATUS_BROADCAST)
+		return 0;
+
+	status = hidpp20_batterylevel_map_status_capacity(report->fap.params,
+							  &capacity,
+							  &next_capacity,
+							  &level);
+
+	/* the capacity is only available when discharging or full */
+	hidpp->battery.online = status == POWER_SUPPLY_STATUS_DISCHARGING ||
+				status == POWER_SUPPLY_STATUS_FULL;
+
+	changed = capacity != hidpp->battery.capacity ||
+		  level != hidpp->battery.level ||
+		  status != hidpp->battery.status;
+
+	if (changed) {
+		hidpp->battery.level = level;
+		hidpp->battery.capacity = capacity;
+		hidpp->battery.status = status;
+		if (hidpp->battery.ps)
+			power_supply_changed(hidpp->battery.ps);
+	}
+
+	return 0;
+}
+
+static enum power_supply_property hidpp_battery_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_MANUFACTURER,
+	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+	0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY, */
+	0, /* placeholder for POWER_SUPPLY_PROP_CAPACITY_LEVEL, */
+};
+
+static int hidpp_battery_get_property(struct power_supply *psy,
+				      enum power_supply_property psp,
+				      union power_supply_propval *val)
+{
+	struct hidpp_device *hidpp = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch(psp) {
+		case POWER_SUPPLY_PROP_STATUS:
+			val->intval = hidpp->battery.status;
+			break;
+		case POWER_SUPPLY_PROP_CAPACITY:
+			val->intval = hidpp->battery.capacity;
+			break;
+		case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+			val->intval = hidpp->battery.level;
+			break;
+		case POWER_SUPPLY_PROP_SCOPE:
+			val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+			break;
+		case POWER_SUPPLY_PROP_ONLINE:
+			val->intval = hidpp->battery.online;
+			break;
+		case POWER_SUPPLY_PROP_MODEL_NAME:
+			if (!strncmp(hidpp->name, "Logitech ", 9))
+				val->strval = hidpp->name + 9;
+			else
+				val->strval = hidpp->name;
+			break;
+		case POWER_SUPPLY_PROP_MANUFACTURER:
+			val->strval = "Logitech";
+			break;
+		case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+			val->strval = hidpp->hid_dev->uniq;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+	}
+
+	return ret;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x4301: Solar Keyboard                                                     */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_SOLAR_KEYBOARD			0x4301
+
+#define CMD_SOLAR_SET_LIGHT_MEASURE			0x00
+
+#define EVENT_SOLAR_BATTERY_BROADCAST			0x00
+#define EVENT_SOLAR_BATTERY_LIGHT_MEASURE		0x10
+#define EVENT_SOLAR_CHECK_LIGHT_BUTTON			0x20
+
+static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	u8 params[2] = { 1, 1 };
+	u8 feature_type;
+	int ret;
+
+	if (hidpp->battery.feature_index == 0xff) {
+		ret = hidpp_root_get_feature(hidpp,
+					     HIDPP_PAGE_SOLAR_KEYBOARD,
+					     &hidpp->battery.solar_feature_index,
+					     &feature_type);
+		if (ret)
+			return ret;
+	}
+
+	ret = hidpp_send_fap_command_sync(hidpp,
+					  hidpp->battery.solar_feature_index,
+					  CMD_SOLAR_SET_LIGHT_MEASURE,
+					  params, 2, &response);
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
+
+	return 0;
+}
+
+static int hidpp_solar_battery_event(struct hidpp_device *hidpp,
+				     u8 *data, int size)
+{
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	int capacity, lux, status;
+	u8 function;
+
+	function = report->fap.funcindex_clientid;
+
+
+	if (report->fap.feature_index != hidpp->battery.solar_feature_index ||
+	    !(function == EVENT_SOLAR_BATTERY_BROADCAST ||
+	      function == EVENT_SOLAR_BATTERY_LIGHT_MEASURE ||
+	      function == EVENT_SOLAR_CHECK_LIGHT_BUTTON))
+		return 0;
+
+	capacity = report->fap.params[0];
+
+	switch (function) {
+	case EVENT_SOLAR_BATTERY_LIGHT_MEASURE:
+		lux = (report->fap.params[1] << 8) | report->fap.params[2];
+		if (lux > 200)
+			status = POWER_SUPPLY_STATUS_CHARGING;
+		else
+			status = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	case EVENT_SOLAR_CHECK_LIGHT_BUTTON:
+	default:
+		if (capacity < hidpp->battery.capacity)
+			status = POWER_SUPPLY_STATUS_DISCHARGING;
+		else
+			status = POWER_SUPPLY_STATUS_CHARGING;
+
+	}
+
+	if (capacity == 100)
+		status = POWER_SUPPLY_STATUS_FULL;
+
+	hidpp->battery.online = true;
+	if (capacity != hidpp->battery.capacity ||
+	    status != hidpp->battery.status) {
+		hidpp->battery.capacity = capacity;
+		hidpp->battery.status = status;
+		if (hidpp->battery.ps)
+			power_supply_changed(hidpp->battery.ps);
+	}
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x6010: Touchpad FW items                                                  */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_TOUCHPAD_FW_ITEMS			0x6010
+
+#define CMD_TOUCHPAD_FW_ITEMS_SET			0x10
+
+struct hidpp_touchpad_fw_items {
+	uint8_t presence;
+	uint8_t desired_state;
+	uint8_t state;
+	uint8_t persistent;
+};
+
+/**
+ * send a set state command to the device by reading the current items->state
+ * field. items is then filled with the current state.
+ */
+static int hidpp_touchpad_fw_items_set(struct hidpp_device *hidpp,
+				       u8 feature_index,
+				       struct hidpp_touchpad_fw_items *items)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_TOUCHPAD_FW_ITEMS_SET, &items->state, 1, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	items->presence = params[0];
+	items->desired_state = params[1];
+	items->state = params[2];
+	items->persistent = params[3];
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x6100: TouchPadRawXY                                                      */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_TOUCHPAD_RAW_XY			0x6100
+
+#define CMD_TOUCHPAD_GET_RAW_INFO			0x01
+#define CMD_TOUCHPAD_SET_RAW_REPORT_STATE		0x21
+
+#define EVENT_TOUCHPAD_RAW_XY				0x00
+
+#define TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT		0x01
+#define TOUCHPAD_RAW_XY_ORIGIN_UPPER_LEFT		0x03
+
+struct hidpp_touchpad_raw_info {
+	u16 x_size;
+	u16 y_size;
+	u8 z_range;
+	u8 area_range;
+	u8 timestamp_unit;
+	u8 maxcontacts;
+	u8 origin;
+	u16 res;
+};
+
+struct hidpp_touchpad_raw_xy_finger {
+	u8 contact_type;
+	u8 contact_status;
+	u16 x;
+	u16 y;
+	u8 z;
+	u8 area;
+	u8 finger_id;
+};
+
+struct hidpp_touchpad_raw_xy {
+	u16 timestamp;
+	struct hidpp_touchpad_raw_xy_finger fingers[2];
+	u8 spurious_flag;
+	u8 end_of_frame;
+	u8 finger_count;
+	u8 button;
+};
+
+static int hidpp_touchpad_get_raw_info(struct hidpp_device *hidpp,
+	u8 feature_index, struct hidpp_touchpad_raw_info *raw_info)
+{
+	struct hidpp_report response;
+	int ret;
+	u8 *params = (u8 *)response.fap.params;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+		CMD_TOUCHPAD_GET_RAW_INFO, NULL, 0, &response);
+
+	if (ret > 0) {
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, ret);
+		return -EPROTO;
+	}
+	if (ret)
+		return ret;
+
+	raw_info->x_size = get_unaligned_be16(&params[0]);
+	raw_info->y_size = get_unaligned_be16(&params[2]);
+	raw_info->z_range = params[4];
+	raw_info->area_range = params[5];
+	raw_info->maxcontacts = params[7];
+	raw_info->origin = params[8];
+	/* res is given in unit per inch */
+	raw_info->res = get_unaligned_be16(&params[13]) * 2 / 51;
+
+	return ret;
+}
+
+static int hidpp_touchpad_set_raw_report_state(struct hidpp_device *hidpp_dev,
+		u8 feature_index, bool send_raw_reports,
+		bool sensor_enhanced_settings)
+{
+	struct hidpp_report response;
+
+	/*
+	 * Params:
+	 *   bit 0 - enable raw
+	 *   bit 1 - 16bit Z, no area
+	 *   bit 2 - enhanced sensitivity
+	 *   bit 3 - width, height (4 bits each) instead of area
+	 *   bit 4 - send raw + gestures (degrades smoothness)
+	 *   remaining bits - reserved
+	 */
+	u8 params = send_raw_reports | (sensor_enhanced_settings << 2);
+
+	return hidpp_send_fap_command_sync(hidpp_dev, feature_index,
+		CMD_TOUCHPAD_SET_RAW_REPORT_STATE, &params, 1, &response);
+}
+
+static void hidpp_touchpad_touch_event(u8 *data,
+	struct hidpp_touchpad_raw_xy_finger *finger)
+{
+	u8 x_m = data[0] << 2;
+	u8 y_m = data[2] << 2;
+
+	finger->x = x_m << 6 | data[1];
+	finger->y = y_m << 6 | data[3];
+
+	finger->contact_type = data[0] >> 6;
+	finger->contact_status = data[2] >> 6;
+
+	finger->z = data[4];
+	finger->area = data[5];
+	finger->finger_id = data[6] >> 4;
+}
+
+static void hidpp_touchpad_raw_xy_event(struct hidpp_device *hidpp_dev,
+		u8 *data, struct hidpp_touchpad_raw_xy *raw_xy)
+{
+	memset(raw_xy, 0, sizeof(struct hidpp_touchpad_raw_xy));
+	raw_xy->end_of_frame = data[8] & 0x01;
+	raw_xy->spurious_flag = (data[8] >> 1) & 0x01;
+	raw_xy->finger_count = data[15] & 0x0f;
+	raw_xy->button = (data[8] >> 2) & 0x01;
+
+	if (raw_xy->finger_count) {
+		hidpp_touchpad_touch_event(&data[2], &raw_xy->fingers[0]);
+		hidpp_touchpad_touch_event(&data[9], &raw_xy->fingers[1]);
+	}
+}
+
+/* -------------------------------------------------------------------------- */
+/* 0x8123: Force feedback support                                             */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_FF_GET_INFO		0x01
+#define HIDPP_FF_RESET_ALL		0x11
+#define HIDPP_FF_DOWNLOAD_EFFECT	0x21
+#define HIDPP_FF_SET_EFFECT_STATE	0x31
+#define HIDPP_FF_DESTROY_EFFECT		0x41
+#define HIDPP_FF_GET_APERTURE		0x51
+#define HIDPP_FF_SET_APERTURE		0x61
+#define HIDPP_FF_GET_GLOBAL_GAINS	0x71
+#define HIDPP_FF_SET_GLOBAL_GAINS	0x81
+
+#define HIDPP_FF_EFFECT_STATE_GET	0x00
+#define HIDPP_FF_EFFECT_STATE_STOP	0x01
+#define HIDPP_FF_EFFECT_STATE_PLAY	0x02
+#define HIDPP_FF_EFFECT_STATE_PAUSE	0x03
+
+#define HIDPP_FF_EFFECT_CONSTANT	0x00
+#define HIDPP_FF_EFFECT_PERIODIC_SINE		0x01
+#define HIDPP_FF_EFFECT_PERIODIC_SQUARE		0x02
+#define HIDPP_FF_EFFECT_PERIODIC_TRIANGLE	0x03
+#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP	0x04
+#define HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN	0x05
+#define HIDPP_FF_EFFECT_SPRING		0x06
+#define HIDPP_FF_EFFECT_DAMPER		0x07
+#define HIDPP_FF_EFFECT_FRICTION	0x08
+#define HIDPP_FF_EFFECT_INERTIA		0x09
+#define HIDPP_FF_EFFECT_RAMP		0x0A
+
+#define HIDPP_FF_EFFECT_AUTOSTART	0x80
+
+#define HIDPP_FF_EFFECTID_NONE		-1
+#define HIDPP_FF_EFFECTID_AUTOCENTER	-2
+
+#define HIDPP_FF_MAX_PARAMS	20
+#define HIDPP_FF_RESERVED_SLOTS	1
+
+struct hidpp_ff_private_data {
+	struct hidpp_device *hidpp;
+	u8 feature_index;
+	u8 version;
+	u16 gain;
+	s16 range;
+	u8 slot_autocenter;
+	u8 num_effects;
+	int *effect_ids;
+	struct workqueue_struct *wq;
+	atomic_t workqueue_size;
+};
+
+struct hidpp_ff_work_data {
+	struct work_struct work;
+	struct hidpp_ff_private_data *data;
+	int effect_id;
+	u8 command;
+	u8 params[HIDPP_FF_MAX_PARAMS];
+	u8 size;
+};
+
+static const signed short hiddpp_ff_effects[] = {
+	FF_CONSTANT,
+	FF_PERIODIC,
+	FF_SINE,
+	FF_SQUARE,
+	FF_SAW_UP,
+	FF_SAW_DOWN,
+	FF_TRIANGLE,
+	FF_SPRING,
+	FF_DAMPER,
+	FF_AUTOCENTER,
+	FF_GAIN,
+	-1
+};
+
+static const signed short hiddpp_ff_effects_v2[] = {
+	FF_RAMP,
+	FF_FRICTION,
+	FF_INERTIA,
+	-1
+};
+
+static const u8 HIDPP_FF_CONDITION_CMDS[] = {
+	HIDPP_FF_EFFECT_SPRING,
+	HIDPP_FF_EFFECT_FRICTION,
+	HIDPP_FF_EFFECT_DAMPER,
+	HIDPP_FF_EFFECT_INERTIA
+};
+
+static const char *HIDPP_FF_CONDITION_NAMES[] = {
+	"spring",
+	"friction",
+	"damper",
+	"inertia"
+};
+
+
+static u8 hidpp_ff_find_effect(struct hidpp_ff_private_data *data, int effect_id)
+{
+	int i;
+
+	for (i = 0; i < data->num_effects; i++)
+		if (data->effect_ids[i] == effect_id)
+			return i+1;
+
+	return 0;
+}
+
+static void hidpp_ff_work_handler(struct work_struct *w)
+{
+	struct hidpp_ff_work_data *wd = container_of(w, struct hidpp_ff_work_data, work);
+	struct hidpp_ff_private_data *data = wd->data;
+	struct hidpp_report response;
+	u8 slot;
+	int ret;
+
+	/* add slot number if needed */
+	switch (wd->effect_id) {
+	case HIDPP_FF_EFFECTID_AUTOCENTER:
+		wd->params[0] = data->slot_autocenter;
+		break;
+	case HIDPP_FF_EFFECTID_NONE:
+		/* leave slot as zero */
+		break;
+	default:
+		/* find current slot for effect */
+		wd->params[0] = hidpp_ff_find_effect(data, wd->effect_id);
+		break;
+	}
+
+	/* send command and wait for reply */
+	ret = hidpp_send_fap_command_sync(data->hidpp, data->feature_index,
+		wd->command, wd->params, wd->size, &response);
+
+	if (ret) {
+		hid_err(data->hidpp->hid_dev, "Failed to send command to device!\n");
+		goto out;
+	}
+
+	/* parse return data */
+	switch (wd->command) {
+	case HIDPP_FF_DOWNLOAD_EFFECT:
+		slot = response.fap.params[0];
+		if (slot > 0 && slot <= data->num_effects) {
+			if (wd->effect_id >= 0)
+				/* regular effect uploaded */
+				data->effect_ids[slot-1] = wd->effect_id;
+			else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
+				/* autocenter spring uploaded */
+				data->slot_autocenter = slot;
+		}
+		break;
+	case HIDPP_FF_DESTROY_EFFECT:
+		if (wd->effect_id >= 0)
+			/* regular effect destroyed */
+			data->effect_ids[wd->params[0]-1] = -1;
+		else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
+			/* autocenter spring destoyed */
+			data->slot_autocenter = 0;
+		break;
+	case HIDPP_FF_SET_GLOBAL_GAINS:
+		data->gain = (wd->params[0] << 8) + wd->params[1];
+		break;
+	case HIDPP_FF_SET_APERTURE:
+		data->range = (wd->params[0] << 8) + wd->params[1];
+		break;
+	default:
+		/* no action needed */
+		break;
+	}
+
+out:
+	atomic_dec(&data->workqueue_size);
+	kfree(wd);
+}
+
+static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size)
+{
+	struct hidpp_ff_work_data *wd = kzalloc(sizeof(*wd), GFP_KERNEL);
+	int s;
+
+	if (!wd)
+		return -ENOMEM;
+
+	INIT_WORK(&wd->work, hidpp_ff_work_handler);
+
+	wd->data = data;
+	wd->effect_id = effect_id;
+	wd->command = command;
+	wd->size = size;
+	memcpy(wd->params, params, size);
+
+	atomic_inc(&data->workqueue_size);
+	queue_work(data->wq, &wd->work);
+
+	/* warn about excessive queue size */
+	s = atomic_read(&data->workqueue_size);
+	if (s >= 20 && s % 20 == 0)
+		hid_warn(data->hidpp->hid_dev, "Force feedback command queue contains %d commands, causing substantial delays!", s);
+
+	return 0;
+}
+
+static int hidpp_ff_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[20];
+	u8 size;
+	int force;
+
+	/* set common parameters */
+	params[2] = effect->replay.length >> 8;
+	params[3] = effect->replay.length & 255;
+	params[4] = effect->replay.delay >> 8;
+	params[5] = effect->replay.delay & 255;
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		force = (effect->u.constant.level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[1] = HIDPP_FF_EFFECT_CONSTANT;
+		params[6] = force >> 8;
+		params[7] = force & 255;
+		params[8] = effect->u.constant.envelope.attack_level >> 7;
+		params[9] = effect->u.constant.envelope.attack_length >> 8;
+		params[10] = effect->u.constant.envelope.attack_length & 255;
+		params[11] = effect->u.constant.envelope.fade_level >> 7;
+		params[12] = effect->u.constant.envelope.fade_length >> 8;
+		params[13] = effect->u.constant.envelope.fade_length & 255;
+		size = 14;
+		dbg_hid("Uploading constant force level=%d in dir %d = %d\n",
+				effect->u.constant.level,
+				effect->direction, force);
+		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+				effect->u.constant.envelope.attack_level,
+				effect->u.constant.envelope.attack_length,
+				effect->u.constant.envelope.fade_level,
+				effect->u.constant.envelope.fade_length);
+		break;
+	case FF_PERIODIC:
+	{
+		switch (effect->u.periodic.waveform) {
+		case FF_SINE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SINE;
+			break;
+		case FF_SQUARE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SQUARE;
+			break;
+		case FF_SAW_UP:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHUP;
+			break;
+		case FF_SAW_DOWN:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_SAWTOOTHDOWN;
+			break;
+		case FF_TRIANGLE:
+			params[1] = HIDPP_FF_EFFECT_PERIODIC_TRIANGLE;
+			break;
+		default:
+			hid_err(data->hidpp->hid_dev, "Unexpected periodic waveform type %i!\n", effect->u.periodic.waveform);
+			return -EINVAL;
+		}
+		force = (effect->u.periodic.magnitude * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[6] = effect->u.periodic.magnitude >> 8;
+		params[7] = effect->u.periodic.magnitude & 255;
+		params[8] = effect->u.periodic.offset >> 8;
+		params[9] = effect->u.periodic.offset & 255;
+		params[10] = effect->u.periodic.period >> 8;
+		params[11] = effect->u.periodic.period & 255;
+		params[12] = effect->u.periodic.phase >> 8;
+		params[13] = effect->u.periodic.phase & 255;
+		params[14] = effect->u.periodic.envelope.attack_level >> 7;
+		params[15] = effect->u.periodic.envelope.attack_length >> 8;
+		params[16] = effect->u.periodic.envelope.attack_length & 255;
+		params[17] = effect->u.periodic.envelope.fade_level >> 7;
+		params[18] = effect->u.periodic.envelope.fade_length >> 8;
+		params[19] = effect->u.periodic.envelope.fade_length & 255;
+		size = 20;
+		dbg_hid("Uploading periodic force mag=%d/dir=%d, offset=%d, period=%d ms, phase=%d\n",
+				effect->u.periodic.magnitude, effect->direction,
+				effect->u.periodic.offset,
+				effect->u.periodic.period,
+				effect->u.periodic.phase);
+		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+				effect->u.periodic.envelope.attack_level,
+				effect->u.periodic.envelope.attack_length,
+				effect->u.periodic.envelope.fade_level,
+				effect->u.periodic.envelope.fade_length);
+		break;
+	}
+	case FF_RAMP:
+		params[1] = HIDPP_FF_EFFECT_RAMP;
+		force = (effect->u.ramp.start_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[6] = force >> 8;
+		params[7] = force & 255;
+		force = (effect->u.ramp.end_level * fixp_sin16((effect->direction * 360) >> 16)) >> 15;
+		params[8] = force >> 8;
+		params[9] = force & 255;
+		params[10] = effect->u.ramp.envelope.attack_level >> 7;
+		params[11] = effect->u.ramp.envelope.attack_length >> 8;
+		params[12] = effect->u.ramp.envelope.attack_length & 255;
+		params[13] = effect->u.ramp.envelope.fade_level >> 7;
+		params[14] = effect->u.ramp.envelope.fade_length >> 8;
+		params[15] = effect->u.ramp.envelope.fade_length & 255;
+		size = 16;
+		dbg_hid("Uploading ramp force level=%d -> %d in dir %d = %d\n",
+				effect->u.ramp.start_level,
+				effect->u.ramp.end_level,
+				effect->direction, force);
+		dbg_hid("          envelope attack=(%d, %d ms) fade=(%d, %d ms)\n",
+				effect->u.ramp.envelope.attack_level,
+				effect->u.ramp.envelope.attack_length,
+				effect->u.ramp.envelope.fade_level,
+				effect->u.ramp.envelope.fade_length);
+		break;
+	case FF_FRICTION:
+	case FF_INERTIA:
+	case FF_SPRING:
+	case FF_DAMPER:
+		params[1] = HIDPP_FF_CONDITION_CMDS[effect->type - FF_SPRING];
+		params[6] = effect->u.condition[0].left_saturation >> 9;
+		params[7] = (effect->u.condition[0].left_saturation >> 1) & 255;
+		params[8] = effect->u.condition[0].left_coeff >> 8;
+		params[9] = effect->u.condition[0].left_coeff & 255;
+		params[10] = effect->u.condition[0].deadband >> 9;
+		params[11] = (effect->u.condition[0].deadband >> 1) & 255;
+		params[12] = effect->u.condition[0].center >> 8;
+		params[13] = effect->u.condition[0].center & 255;
+		params[14] = effect->u.condition[0].right_coeff >> 8;
+		params[15] = effect->u.condition[0].right_coeff & 255;
+		params[16] = effect->u.condition[0].right_saturation >> 9;
+		params[17] = (effect->u.condition[0].right_saturation >> 1) & 255;
+		size = 18;
+		dbg_hid("Uploading %s force left coeff=%d, left sat=%d, right coeff=%d, right sat=%d\n",
+				HIDPP_FF_CONDITION_NAMES[effect->type - FF_SPRING],
+				effect->u.condition[0].left_coeff,
+				effect->u.condition[0].left_saturation,
+				effect->u.condition[0].right_coeff,
+				effect->u.condition[0].right_saturation);
+		dbg_hid("          deadband=%d, center=%d\n",
+				effect->u.condition[0].deadband,
+				effect->u.condition[0].center);
+		break;
+	default:
+		hid_err(data->hidpp->hid_dev, "Unexpected force type %i!\n", effect->type);
+		return -EINVAL;
+	}
+
+	return hidpp_ff_queue_work(data, effect->id, HIDPP_FF_DOWNLOAD_EFFECT, params, size);
+}
+
+static int hidpp_ff_playback(struct input_dev *dev, int effect_id, int value)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[2];
+
+	params[1] = value ? HIDPP_FF_EFFECT_STATE_PLAY : HIDPP_FF_EFFECT_STATE_STOP;
+
+	dbg_hid("St%sing playback of effect %d.\n", value?"art":"opp", effect_id);
+
+	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_SET_EFFECT_STATE, params, ARRAY_SIZE(params));
+}
+
+static int hidpp_ff_erase_effect(struct input_dev *dev, int effect_id)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 slot = 0;
+
+	dbg_hid("Erasing effect %d.\n", effect_id);
+
+	return hidpp_ff_queue_work(data, effect_id, HIDPP_FF_DESTROY_EFFECT, &slot, 1);
+}
+
+static void hidpp_ff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[18];
+
+	dbg_hid("Setting autocenter to %d.\n", magnitude);
+
+	/* start a standard spring effect */
+	params[1] = HIDPP_FF_EFFECT_SPRING | HIDPP_FF_EFFECT_AUTOSTART;
+	/* zero delay and duration */
+	params[2] = params[3] = params[4] = params[5] = 0;
+	/* set coeff to 25% of saturation */
+	params[8] = params[14] = magnitude >> 11;
+	params[9] = params[15] = (magnitude >> 3) & 255;
+	params[6] = params[16] = magnitude >> 9;
+	params[7] = params[17] = (magnitude >> 1) & 255;
+	/* zero deadband and center */
+	params[10] = params[11] = params[12] = params[13] = 0;
+
+	hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_AUTOCENTER, HIDPP_FF_DOWNLOAD_EFFECT, params, ARRAY_SIZE(params));
+}
+
+static void hidpp_ff_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct hidpp_ff_private_data *data = dev->ff->private;
+	u8 params[4];
+
+	dbg_hid("Setting gain to %d.\n", gain);
+
+	params[0] = gain >> 8;
+	params[1] = gain & 255;
+	params[2] = 0; /* no boost */
+	params[3] = 0;
+
+	hidpp_ff_queue_work(data, HIDPP_FF_EFFECTID_NONE, HIDPP_FF_SET_GLOBAL_GAINS, params, ARRAY_SIZE(params));
+}
+
+static ssize_t hidpp_ff_range_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *idev = hidinput->input;
+	struct hidpp_ff_private_data *data = idev->ff->private;
+
+	return scnprintf(buf, PAGE_SIZE, "%u\n", data->range);
+}
+
+static ssize_t hidpp_ff_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hid = to_hid_device(dev);
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *idev = hidinput->input;
+	struct hidpp_ff_private_data *data = idev->ff->private;
+	u8 params[2];
+	int range = simple_strtoul(buf, NULL, 10);
+
+	range = clamp(range, 180, 900);
+
+	params[0] = range >> 8;
+	params[1] = range & 0x00FF;
+
+	hidpp_ff_queue_work(data, -1, HIDPP_FF_SET_APERTURE, params, ARRAY_SIZE(params));
+
+	return count;
+}
+
+static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, hidpp_ff_range_show, hidpp_ff_range_store);
+
+static void hidpp_ff_destroy(struct ff_device *ff)
+{
+	struct hidpp_ff_private_data *data = ff->private;
+
+	kfree(data->effect_ids);
+}
+
+static int hidpp_ff_init(struct hidpp_device *hidpp, u8 feature_index)
+{
+	struct hid_device *hid = hidpp->hid_dev;
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
+	const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
+	struct ff_device *ff;
+	struct hidpp_report response;
+	struct hidpp_ff_private_data *data;
+	int error, j, num_slots;
+	u8 version;
+
+	if (!dev) {
+		hid_err(hid, "Struct input_dev not set!\n");
+		return -EINVAL;
+	}
+
+	/* Get firmware release */
+	version = bcdDevice & 255;
+
+	/* Set supported force feedback capabilities */
+	for (j = 0; hiddpp_ff_effects[j] >= 0; j++)
+		set_bit(hiddpp_ff_effects[j], dev->ffbit);
+	if (version > 1)
+		for (j = 0; hiddpp_ff_effects_v2[j] >= 0; j++)
+			set_bit(hiddpp_ff_effects_v2[j], dev->ffbit);
+
+	/* Read number of slots available in device */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_INFO, NULL, 0, &response);
+	if (error) {
+		if (error < 0)
+			return error;
+		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
+			__func__, error);
+		return -EPROTO;
+	}
+
+	num_slots = response.fap.params[0] - HIDPP_FF_RESERVED_SLOTS;
+
+	error = input_ff_create(dev, num_slots);
+
+	if (error) {
+		hid_err(dev, "Failed to create FF device!\n");
+		return error;
+	}
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
+	if (!data->effect_ids) {
+		kfree(data);
+		return -ENOMEM;
+	}
+	data->hidpp = hidpp;
+	data->feature_index = feature_index;
+	data->version = version;
+	data->slot_autocenter = 0;
+	data->num_effects = num_slots;
+	for (j = 0; j < num_slots; j++)
+		data->effect_ids[j] = -1;
+
+	ff = dev->ff;
+	ff->private = data;
+
+	ff->upload = hidpp_ff_upload_effect;
+	ff->erase = hidpp_ff_erase_effect;
+	ff->playback = hidpp_ff_playback;
+	ff->set_gain = hidpp_ff_set_gain;
+	ff->set_autocenter = hidpp_ff_set_autocenter;
+	ff->destroy = hidpp_ff_destroy;
+
+
+	/* reset all forces */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_RESET_ALL, NULL, 0, &response);
+
+	/* Read current Range */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_APERTURE, NULL, 0, &response);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Failed to read range from device!\n");
+	data->range = error ? 900 : get_unaligned_be16(&response.fap.params[0]);
+
+	/* Create sysfs interface */
+	error = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d!\n", error);
+
+	/* Read the current gain values */
+	error = hidpp_send_fap_command_sync(hidpp, feature_index,
+		HIDPP_FF_GET_GLOBAL_GAINS, NULL, 0, &response);
+	if (error)
+		hid_warn(hidpp->hid_dev, "Failed to read gain values from device!\n");
+	data->gain = error ? 0xffff : get_unaligned_be16(&response.fap.params[0]);
+	/* ignore boost value at response.fap.params[2] */
+
+	/* init the hardware command queue */
+	data->wq = create_singlethread_workqueue("hidpp-ff-sendqueue");
+	atomic_set(&data->workqueue_size, 0);
+
+	/* initialize with zero autocenter to get wheel in usable state */
+	hidpp_ff_set_autocenter(dev, 0);
+
+	hid_info(hid, "Force feedback support loaded (firmware release %d).\n",
+		 version);
+
+	return 0;
+}
+
+static int hidpp_ff_deinit(struct hid_device *hid)
+{
+	struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct hidpp_ff_private_data *data;
+
+	if (!dev) {
+		hid_err(hid, "Struct input_dev not found!\n");
+		return -EINVAL;
+	}
+
+	hid_info(hid, "Unloading HID++ force feedback.\n");
+	data = dev->ff->private;
+	if (!data) {
+		hid_err(hid, "Private data not found!\n");
+		return -EINVAL;
+	}
+
+	destroy_workqueue(data->wq);
+	device_remove_file(&hid->dev, &dev_attr_range);
+
+	return 0;
+}
+
+
+/* ************************************************************************** */
+/*                                                                            */
+/* Device Support                                                             */
+/*                                                                            */
+/* ************************************************************************** */
+
+/* -------------------------------------------------------------------------- */
+/* Touchpad HID++ devices                                                     */
+/* -------------------------------------------------------------------------- */
+
+#define WTP_MANUAL_RESOLUTION				39
+
+struct wtp_data {
+	struct input_dev *input;
+	u16 x_size, y_size;
+	u8 finger_count;
+	u8 mt_feature_index;
+	u8 button_feature_index;
+	u8 maxcontacts;
+	bool flip_y;
+	unsigned int resolution;
+};
+
+static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+static void wtp_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input_dev, bool origin_is_hid_core)
+{
+	struct wtp_data *wd = hidpp->private_data;
+
+	__set_bit(EV_ABS, input_dev->evbit);
+	__set_bit(EV_KEY, input_dev->evbit);
+	__clear_bit(EV_REL, input_dev->evbit);
+	__clear_bit(EV_LED, input_dev->evbit);
+
+	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, wd->x_size, 0, 0);
+	input_abs_set_res(input_dev, ABS_MT_POSITION_X, wd->resolution);
+	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, wd->y_size, 0, 0);
+	input_abs_set_res(input_dev, ABS_MT_POSITION_Y, wd->resolution);
+
+	/* Max pressure is not given by the devices, pick one */
+	input_set_abs_params(input_dev, ABS_MT_PRESSURE, 0, 50, 0, 0);
+
+	input_set_capability(input_dev, EV_KEY, BTN_LEFT);
+
+	if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)
+		input_set_capability(input_dev, EV_KEY, BTN_RIGHT);
+	else
+		__set_bit(INPUT_PROP_BUTTONPAD, input_dev->propbit);
+
+	input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER |
+		INPUT_MT_DROP_UNUSED);
+
+	wd->input = input_dev;
+}
+
+static void wtp_touch_event(struct wtp_data *wd,
+	struct hidpp_touchpad_raw_xy_finger *touch_report)
+{
+	int slot;
+
+	if (!touch_report->finger_id || touch_report->contact_type)
+		/* no actual data */
+		return;
+
+	slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id);
+
+	input_mt_slot(wd->input, slot);
+	input_mt_report_slot_state(wd->input, MT_TOOL_FINGER,
+					touch_report->contact_status);
+	if (touch_report->contact_status) {
+		input_event(wd->input, EV_ABS, ABS_MT_POSITION_X,
+				touch_report->x);
+		input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y,
+				wd->flip_y ? wd->y_size - touch_report->y :
+					     touch_report->y);
+		input_event(wd->input, EV_ABS, ABS_MT_PRESSURE,
+				touch_report->area);
+	}
+}
+
+static void wtp_send_raw_xy_event(struct hidpp_device *hidpp,
+		struct hidpp_touchpad_raw_xy *raw)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	int i;
+
+	for (i = 0; i < 2; i++)
+		wtp_touch_event(wd, &(raw->fingers[i]));
+
+	if (raw->end_of_frame &&
+	    !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS))
+		input_event(wd->input, EV_KEY, BTN_LEFT, raw->button);
+
+	if (raw->end_of_frame || raw->finger_count <= 2) {
+		input_mt_sync_frame(wd->input);
+		input_sync(wd->input);
+	}
+}
+
+static int wtp_mouse_raw_xy_event(struct hidpp_device *hidpp, u8 *data)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	u8 c1_area = ((data[7] & 0xf) * (data[7] & 0xf) +
+		      (data[7] >> 4) * (data[7] >> 4)) / 2;
+	u8 c2_area = ((data[13] & 0xf) * (data[13] & 0xf) +
+		      (data[13] >> 4) * (data[13] >> 4)) / 2;
+	struct hidpp_touchpad_raw_xy raw = {
+		.timestamp = data[1],
+		.fingers = {
+			{
+				.contact_type = 0,
+				.contact_status = !!data[7],
+				.x = get_unaligned_le16(&data[3]),
+				.y = get_unaligned_le16(&data[5]),
+				.z = c1_area,
+				.area = c1_area,
+				.finger_id = data[2],
+			}, {
+				.contact_type = 0,
+				.contact_status = !!data[13],
+				.x = get_unaligned_le16(&data[9]),
+				.y = get_unaligned_le16(&data[11]),
+				.z = c2_area,
+				.area = c2_area,
+				.finger_id = data[8],
+			}
+		},
+		.finger_count = wd->maxcontacts,
+		.spurious_flag = 0,
+		.end_of_frame = (data[0] >> 7) == 0,
+		.button = data[0] & 0x01,
+	};
+
+	wtp_send_raw_xy_event(hidpp, &raw);
+
+	return 1;
+}
+
+static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd = hidpp->private_data;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	struct hidpp_touchpad_raw_xy raw;
+
+	if (!wd || !wd->input)
+		return 1;
+
+	switch (data[0]) {
+	case 0x02:
+		if (size < 2) {
+			hid_err(hdev, "Received HID report of bad size (%d)",
+				size);
+			return 1;
+		}
+		if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) {
+			input_event(wd->input, EV_KEY, BTN_LEFT,
+					!!(data[1] & 0x01));
+			input_event(wd->input, EV_KEY, BTN_RIGHT,
+					!!(data[1] & 0x02));
+			input_sync(wd->input);
+			return 0;
+		} else {
+			if (size < 21)
+				return 1;
+			return wtp_mouse_raw_xy_event(hidpp, &data[7]);
+		}
+	case REPORT_ID_HIDPP_LONG:
+		/* size is already checked in hidpp_raw_event. */
+		if ((report->fap.feature_index != wd->mt_feature_index) ||
+		    (report->fap.funcindex_clientid != EVENT_TOUCHPAD_RAW_XY))
+			return 1;
+		hidpp_touchpad_raw_xy_event(hidpp, data + 4, &raw);
+
+		wtp_send_raw_xy_event(hidpp, &raw);
+		return 0;
+	}
+
+	return 0;
+}
+
+static int wtp_get_config(struct hidpp_device *hidpp)
+{
+	struct wtp_data *wd = hidpp->private_data;
+	struct hidpp_touchpad_raw_info raw_info = {0};
+	u8 feature_type;
+	int ret;
+
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
+		&wd->mt_feature_index, &feature_type);
+	if (ret)
+		/* means that the device is not powered up */
+		return ret;
+
+	ret = hidpp_touchpad_get_raw_info(hidpp, wd->mt_feature_index,
+		&raw_info);
+	if (ret)
+		return ret;
+
+	wd->x_size = raw_info.x_size;
+	wd->y_size = raw_info.y_size;
+	wd->maxcontacts = raw_info.maxcontacts;
+	wd->flip_y = raw_info.origin == TOUCHPAD_RAW_XY_ORIGIN_LOWER_LEFT;
+	wd->resolution = raw_info.res;
+	if (!wd->resolution)
+		wd->resolution = WTP_MANUAL_RESOLUTION;
+
+	return 0;
+}
+
+static int wtp_allocate(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd;
+
+	wd = devm_kzalloc(&hdev->dev, sizeof(struct wtp_data),
+			GFP_KERNEL);
+	if (!wd)
+		return -ENOMEM;
+
+	hidpp->private_data = wd;
+
+	return 0;
+};
+
+static int wtp_connect(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct wtp_data *wd = hidpp->private_data;
+	int ret;
+
+	if (!wd->x_size) {
+		ret = wtp_get_config(hidpp);
+		if (ret) {
+			hid_err(hdev, "Can not get wtp config: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return hidpp_touchpad_set_raw_report_state(hidpp, wd->mt_feature_index,
+			true, true);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech M560 devices                                                     */
+/* ------------------------------------------------------------------------- */
+
+/*
+ * Logitech M560 protocol overview
+ *
+ * The Logitech M560 mouse, is designed for windows 8. When the middle and/or
+ * the sides buttons are pressed, it sends some keyboard keys events
+ * instead of buttons ones.
+ * To complicate things further, the middle button keys sequence
+ * is different from the odd press and the even press.
+ *
+ * forward button -> Super_R
+ * backward button -> Super_L+'d' (press only)
+ * middle button -> 1st time: Alt_L+SuperL+XF86TouchpadOff (press only)
+ *                  2nd time: left-click (press only)
+ * NB: press-only means that when the button is pressed, the
+ * KeyPress/ButtonPress and KeyRelease/ButtonRelease events are generated
+ * together sequentially; instead when the button is released, no event is
+ * generated !
+ *
+ * With the command
+ *	10<xx>0a 3500af03 (where <xx> is the mouse id),
+ * the mouse reacts differently:
+ * - it never sends a keyboard key event
+ * - for the three mouse button it sends:
+ *	middle button               press   11<xx>0a 3500af00...
+ *	side 1 button (forward)     press   11<xx>0a 3500b000...
+ *	side 2 button (backward)    press   11<xx>0a 3500ae00...
+ *	middle/side1/side2 button   release 11<xx>0a 35000000...
+ */
+
+static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03};
+
+struct m560_private_data {
+	struct input_dev *input;
+};
+
+/* how buttons are mapped in the report */
+#define M560_MOUSE_BTN_LEFT		0x01
+#define M560_MOUSE_BTN_RIGHT		0x02
+#define M560_MOUSE_BTN_WHEEL_LEFT	0x08
+#define M560_MOUSE_BTN_WHEEL_RIGHT	0x10
+
+#define M560_SUB_ID			0x0a
+#define M560_BUTTON_MODE_REGISTER	0x35
+
+static int m560_send_config_command(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_report response;
+	struct hidpp_device *hidpp_dev;
+
+	hidpp_dev = hid_get_drvdata(hdev);
+
+	return hidpp_send_rap_command_sync(
+		hidpp_dev,
+		REPORT_ID_HIDPP_SHORT,
+		M560_SUB_ID,
+		M560_BUTTON_MODE_REGISTER,
+		(u8 *)m560_config_parameter,
+		sizeof(m560_config_parameter),
+		&response
+	);
+}
+
+static int m560_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *d;
+
+	d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data),
+			GFP_KERNEL);
+	if (!d)
+		return -ENOMEM;
+
+	hidpp->private_data = d;
+
+	return 0;
+};
+
+static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct m560_private_data *mydata = hidpp->private_data;
+
+	/* sanity check */
+	if (!mydata || !mydata->input) {
+		hid_err(hdev, "error in parameter\n");
+		return -EINVAL;
+	}
+
+	if (size < 7) {
+		hid_err(hdev, "error in report\n");
+		return 0;
+	}
+
+	if (data[0] == REPORT_ID_HIDPP_LONG &&
+	    data[2] == M560_SUB_ID && data[6] == 0x00) {
+		/*
+		 * m560 mouse report for middle, forward and backward button
+		 *
+		 * data[0] = 0x11
+		 * data[1] = device-id
+		 * data[2] = 0x0a
+		 * data[5] = 0xaf -> middle
+		 *	     0xb0 -> forward
+		 *	     0xae -> backward
+		 *	     0x00 -> release all
+		 * data[6] = 0x00
+		 */
+
+		switch (data[5]) {
+		case 0xaf:
+			input_report_key(mydata->input, BTN_MIDDLE, 1);
+			break;
+		case 0xb0:
+			input_report_key(mydata->input, BTN_FORWARD, 1);
+			break;
+		case 0xae:
+			input_report_key(mydata->input, BTN_BACK, 1);
+			break;
+		case 0x00:
+			input_report_key(mydata->input, BTN_BACK, 0);
+			input_report_key(mydata->input, BTN_FORWARD, 0);
+			input_report_key(mydata->input, BTN_MIDDLE, 0);
+			break;
+		default:
+			hid_err(hdev, "error in report\n");
+			return 0;
+		}
+		input_sync(mydata->input);
+
+	} else if (data[0] == 0x02) {
+		/*
+		 * Logitech M560 mouse report
+		 *
+		 * data[0] = type (0x02)
+		 * data[1..2] = buttons
+		 * data[3..5] = xy
+		 * data[6] = wheel
+		 */
+
+		int v;
+
+		input_report_key(mydata->input, BTN_LEFT,
+			!!(data[1] & M560_MOUSE_BTN_LEFT));
+		input_report_key(mydata->input, BTN_RIGHT,
+			!!(data[1] & M560_MOUSE_BTN_RIGHT));
+
+		if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT)
+			input_report_rel(mydata->input, REL_HWHEEL, -1);
+		else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT)
+			input_report_rel(mydata->input, REL_HWHEEL, 1);
+
+		v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
+		input_report_rel(mydata->input, REL_X, v);
+
+		v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
+		input_report_rel(mydata->input, REL_Y, v);
+
+		v = hid_snto32(data[6], 8);
+		input_report_rel(mydata->input, REL_WHEEL, v);
+
+		input_sync(mydata->input);
+	}
+
+	return 1;
+}
+
+static void m560_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input_dev, bool origin_is_hid_core)
+{
+	struct m560_private_data *mydata = hidpp->private_data;
+
+	mydata->input = input_dev;
+
+	__set_bit(EV_KEY, mydata->input->evbit);
+	__set_bit(BTN_MIDDLE, mydata->input->keybit);
+	__set_bit(BTN_RIGHT, mydata->input->keybit);
+	__set_bit(BTN_LEFT, mydata->input->keybit);
+	__set_bit(BTN_BACK, mydata->input->keybit);
+	__set_bit(BTN_FORWARD, mydata->input->keybit);
+
+	__set_bit(EV_REL, mydata->input->evbit);
+	__set_bit(REL_X, mydata->input->relbit);
+	__set_bit(REL_Y, mydata->input->relbit);
+	__set_bit(REL_WHEEL, mydata->input->relbit);
+	__set_bit(REL_HWHEEL, mydata->input->relbit);
+}
+
+static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	return -1;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech K400 devices                                                     */
+/* ------------------------------------------------------------------------- */
+
+/*
+ * The Logitech K400 keyboard has an embedded touchpad which is seen
+ * as a mouse from the OS point of view. There is a hardware shortcut to disable
+ * tap-to-click but the setting is not remembered accross reset, annoying some
+ * users.
+ *
+ * We can toggle this feature from the host by using the feature 0x6010:
+ * Touchpad FW items
+ */
+
+struct k400_private_data {
+	u8 feature_index;
+};
+
+static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
+{
+	struct k400_private_data *k400 = hidpp->private_data;
+	struct hidpp_touchpad_fw_items items = {};
+	int ret;
+	u8 feature_type;
+
+	if (!k400->feature_index) {
+		ret = hidpp_root_get_feature(hidpp,
+			HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
+			&k400->feature_index, &feature_type);
+		if (ret)
+			/* means that the device is not powered up */
+			return ret;
+	}
+
+	ret = hidpp_touchpad_fw_items_set(hidpp, k400->feature_index, &items);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int k400_allocate(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct k400_private_data *k400;
+
+	k400 = devm_kzalloc(&hdev->dev, sizeof(struct k400_private_data),
+			    GFP_KERNEL);
+	if (!k400)
+		return -ENOMEM;
+
+	hidpp->private_data = k400;
+
+	return 0;
+};
+
+static int k400_connect(struct hid_device *hdev, bool connected)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (!disable_tap_to_click)
+		return 0;
+
+	return k400_disable_tap_to_click(hidpp);
+}
+
+/* ------------------------------------------------------------------------- */
+/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
+/* ------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123
+
+static int g920_get_config(struct hidpp_device *hidpp)
+{
+	u8 feature_type;
+	u8 feature_index;
+	int ret;
+
+	/* Find feature and store for later use */
+	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
+		&feature_index, &feature_type);
+	if (ret)
+		return ret;
+
+	ret = hidpp_ff_init(hidpp, feature_index);
+	if (ret)
+		hid_warn(hidpp->hid_dev, "Unable to initialize force feedback support, errno %d\n",
+				ret);
+
+	return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Generic HID++ devices                                                      */
+/* -------------------------------------------------------------------------- */
+
+static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		return wtp_input_mapping(hdev, hi, field, usage, bit, max);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 &&
+			field->application != HID_GD_MOUSE)
+		return m560_input_mapping(hdev, hi, field, usage, bit, max);
+
+	return 0;
+}
+
+static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	/* Ensure that Logitech G920 is not given a default fuzz/flat value */
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		if (usage->type == EV_ABS && (usage->code == ABS_X ||
+				usage->code == ABS_Y || usage->code == ABS_Z ||
+				usage->code == ABS_RZ)) {
+			field->application = HID_GD_MULTIAXIS;
+		}
+	}
+
+	return 0;
+}
+
+
+static void hidpp_populate_input(struct hidpp_device *hidpp,
+		struct input_dev *input, bool origin_is_hid_core)
+{
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		wtp_populate_input(hidpp, input, origin_is_hid_core);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		m560_populate_input(hidpp, input, origin_is_hid_core);
+}
+
+static int hidpp_input_configured(struct hid_device *hdev,
+				struct hid_input *hidinput)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	struct input_dev *input = hidinput->input;
+
+	hidpp_populate_input(hidpp, input, true);
+
+	return 0;
+}
+
+static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
+		int size)
+{
+	struct hidpp_report *question = hidpp->send_receive_buf;
+	struct hidpp_report *answer = hidpp->send_receive_buf;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	int ret;
+
+	/*
+	 * If the mutex is locked then we have a pending answer from a
+	 * previously sent command.
+	 */
+	if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
+		/*
+		 * Check for a correct hidpp20 answer or the corresponding
+		 * error
+		 */
+		if (hidpp_match_answer(question, report) ||
+				hidpp_match_error(question, report)) {
+			*answer = *report;
+			hidpp->answer_available = true;
+			wake_up(&hidpp->wait);
+			/*
+			 * This was an answer to a command that this driver sent
+			 * We return 1 to hid-core to avoid forwarding the
+			 * command upstream as it has been treated by the driver
+			 */
+
+			return 1;
+		}
+	}
+
+	if (unlikely(hidpp_report_is_connect_event(report))) {
+		atomic_set(&hidpp->connected,
+				!(report->rap.params[0] & (1 << 6)));
+		if (schedule_work(&hidpp->work) == 0)
+			dbg_hid("%s: connect event already queued\n", __func__);
+		return 1;
+	}
+
+	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
+		ret = hidpp20_battery_event(hidpp, data, size);
+		if (ret != 0)
+			return ret;
+		ret = hidpp_solar_battery_event(hidpp, data, size);
+		if (ret != 0)
+			return ret;
+	}
+
+	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
+		ret = hidpp10_battery_event(hidpp, data, size);
+		if (ret != 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	int ret = 0;
+
+	/* Generic HID++ processing. */
+	switch (data[0]) {
+	case REPORT_ID_HIDPP_VERY_LONG:
+		if (size != HIDPP_REPORT_VERY_LONG_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	case REPORT_ID_HIDPP_LONG:
+		if (size != HIDPP_REPORT_LONG_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	case REPORT_ID_HIDPP_SHORT:
+		if (size != HIDPP_REPORT_SHORT_LENGTH) {
+			hid_err(hdev, "received hid++ report of bad size (%d)",
+				size);
+			return 1;
+		}
+		ret = hidpp_raw_hidpp_event(hidpp, data, size);
+		break;
+	}
+
+	/* If no report is available for further processing, skip calling
+	 * raw_event of subclasses. */
+	if (ret != 0)
+		return ret;
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)
+		return wtp_raw_event(hdev, data, size);
+	else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560)
+		return m560_raw_event(hdev, data, size);
+
+	return 0;
+}
+
+static int hidpp_initialize_battery(struct hidpp_device *hidpp)
+{
+	static atomic_t battery_no = ATOMIC_INIT(0);
+	struct power_supply_config cfg = { .drv_data = hidpp };
+	struct power_supply_desc *desc = &hidpp->battery.desc;
+	enum power_supply_property *battery_props;
+	struct hidpp_battery *battery;
+	unsigned int num_battery_props;
+	unsigned long n;
+	int ret;
+
+	if (hidpp->battery.ps)
+		return 0;
+
+	hidpp->battery.feature_index = 0xff;
+	hidpp->battery.solar_feature_index = 0xff;
+
+	if (hidpp->protocol_major >= 2) {
+		if (hidpp->quirks & HIDPP_QUIRK_CLASS_K750)
+			ret = hidpp_solar_request_battery_event(hidpp);
+		else
+			ret = hidpp20_query_battery_info(hidpp);
+
+		if (ret)
+			return ret;
+		hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_BATTERY;
+	} else {
+		ret = hidpp10_query_battery_status(hidpp);
+		if (ret) {
+			ret = hidpp10_query_battery_mileage(hidpp);
+			if (ret)
+				return -ENOENT;
+			hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_MILEAGE;
+		} else {
+			hidpp->capabilities |= HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS;
+		}
+		hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_BATTERY;
+	}
+
+	battery_props = devm_kmemdup(&hidpp->hid_dev->dev,
+				     hidpp_battery_props,
+				     sizeof(hidpp_battery_props),
+				     GFP_KERNEL);
+	if (!battery_props)
+		return -ENOMEM;
+
+	num_battery_props = ARRAY_SIZE(hidpp_battery_props) - 2;
+
+	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
+		battery_props[num_battery_props++] =
+				POWER_SUPPLY_PROP_CAPACITY;
+
+	if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_LEVEL_STATUS)
+		battery_props[num_battery_props++] =
+				POWER_SUPPLY_PROP_CAPACITY_LEVEL;
+
+	battery = &hidpp->battery;
+
+	n = atomic_inc_return(&battery_no) - 1;
+	desc->properties = battery_props;
+	desc->num_properties = num_battery_props;
+	desc->get_property = hidpp_battery_get_property;
+	sprintf(battery->name, "hidpp_battery_%ld", n);
+	desc->name = battery->name;
+	desc->type = POWER_SUPPLY_TYPE_BATTERY;
+	desc->use_for_apm = 0;
+
+	battery->ps = devm_power_supply_register(&hidpp->hid_dev->dev,
+						 &battery->desc,
+						 &cfg);
+	if (IS_ERR(battery->ps))
+		return PTR_ERR(battery->ps);
+
+	power_supply_powers(battery->ps, &hidpp->hid_dev->dev);
+
+	return ret;
+}
+
+static void hidpp_overwrite_name(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+	char *name;
+
+	if (hidpp->protocol_major < 2)
+		return;
+
+	name = hidpp_get_device_name(hidpp);
+
+	if (!name) {
+		hid_err(hdev, "unable to retrieve the name of the device");
+	} else {
+		dbg_hid("HID++: Got name: %s\n", name);
+		snprintf(hdev->name, sizeof(hdev->name), "%s", name);
+	}
+
+	kfree(name);
+}
+
+static int hidpp_input_open(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	return hid_hw_open(hid);
+}
+
+static void hidpp_input_close(struct input_dev *dev)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+
+	hid_hw_close(hid);
+}
+
+static struct input_dev *hidpp_allocate_input(struct hid_device *hdev)
+{
+	struct input_dev *input_dev = devm_input_allocate_device(&hdev->dev);
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	if (!input_dev)
+		return NULL;
+
+	input_set_drvdata(input_dev, hdev);
+	input_dev->open = hidpp_input_open;
+	input_dev->close = hidpp_input_close;
+
+	input_dev->name = hidpp->name;
+	input_dev->phys = hdev->phys;
+	input_dev->uniq = hdev->uniq;
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor  = hdev->vendor;
+	input_dev->id.product = hdev->product;
+	input_dev->id.version = hdev->version;
+	input_dev->dev.parent = &hdev->dev;
+
+	return input_dev;
+}
+
+static void hidpp_connect_event(struct hidpp_device *hidpp)
+{
+	struct hid_device *hdev = hidpp->hid_dev;
+	int ret = 0;
+	bool connected = atomic_read(&hidpp->connected);
+	struct input_dev *input;
+	char *name, *devm_name;
+
+	if (!connected) {
+		if (hidpp->battery.ps) {
+			hidpp->battery.online = false;
+			hidpp->battery.status = POWER_SUPPLY_STATUS_UNKNOWN;
+			hidpp->battery.level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+			power_supply_changed(hidpp->battery.ps);
+		}
+		return;
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+		ret = wtp_connect(hdev, connected);
+		if (ret)
+			return;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_send_config_command(hdev, connected);
+		if (ret)
+			return;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+		ret = k400_connect(hdev, connected);
+		if (ret)
+			return;
+	}
+
+	/* the device is already connected, we can ask for its name and
+	 * protocol */
+	if (!hidpp->protocol_major) {
+		ret = !hidpp_is_connected(hidpp);
+		if (ret) {
+			hid_err(hdev, "Can not get the protocol version.\n");
+			return;
+		}
+		hid_info(hdev, "HID++ %u.%u device connected.\n",
+			 hidpp->protocol_major, hidpp->protocol_minor);
+	}
+
+	if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) {
+		name = hidpp_get_device_name(hidpp);
+		if (!name) {
+			hid_err(hdev,
+				"unable to retrieve the name of the device");
+			return;
+		}
+
+		devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name);
+		kfree(name);
+		if (!devm_name)
+			return;
+
+		hidpp->name = devm_name;
+	}
+
+	hidpp_initialize_battery(hidpp);
+
+	/* forward current battery state */
+	if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
+		hidpp10_enable_battery_reporting(hidpp);
+		if (hidpp->capabilities & HIDPP_CAPABILITY_BATTERY_MILEAGE)
+			hidpp10_query_battery_mileage(hidpp);
+		else
+			hidpp10_query_battery_status(hidpp);
+	} else if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP20_BATTERY) {
+		hidpp20_query_battery_info(hidpp);
+	}
+	if (hidpp->battery.ps)
+		power_supply_changed(hidpp->battery.ps);
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input)
+		/* if the input nodes are already created, we can stop now */
+		return;
+
+	input = hidpp_allocate_input(hdev);
+	if (!input) {
+		hid_err(hdev, "cannot allocate new input device: %d\n", ret);
+		return;
+	}
+
+	hidpp_populate_input(hidpp, input, false);
+
+	ret = input_register_device(input);
+	if (ret)
+		input_free_device(input);
+
+	hidpp->delayed_input = input;
+}
+
+static DEVICE_ATTR(builtin_power_supply, 0000, NULL, NULL);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_builtin_power_supply.attr,
+	NULL
+};
+
+static const struct attribute_group ps_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
+static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct hidpp_device *hidpp;
+	int ret;
+	bool connected;
+	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+
+	hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device),
+			GFP_KERNEL);
+	if (!hidpp)
+		return -ENOMEM;
+
+	hidpp->hid_dev = hdev;
+	hidpp->name = hdev->name;
+	hid_set_drvdata(hdev, hidpp);
+
+	hidpp->quirks = id->driver_data;
+
+	if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE)
+		hidpp->quirks |= HIDPP_QUIRK_UNIFYING;
+
+	if (disable_raw_mode) {
+		hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
+		hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
+		ret = wtp_allocate(hdev, id);
+		if (ret)
+			goto allocate_fail;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) {
+		ret = m560_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
+	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) {
+		ret = k400_allocate(hdev);
+		if (ret)
+			goto allocate_fail;
+	}
+
+	INIT_WORK(&hidpp->work, delayed_work_cb);
+	mutex_init(&hidpp->send_mutex);
+	init_waitqueue_head(&hidpp->wait);
+
+	/* indicates we are handling the battery properties in the kernel */
+	ret = sysfs_create_group(&hdev->dev.kobj, &ps_attribute_group);
+	if (ret)
+		hid_warn(hdev, "Cannot allocate sysfs group for %s\n",
+			 hdev->name);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "%s:parse failed\n", __func__);
+		goto hid_parse_fail;
+	}
+
+	if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
+		connect_mask &= ~HID_CONNECT_HIDINPUT;
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		ret = hid_hw_start(hdev, connect_mask);
+		if (ret) {
+			hid_err(hdev, "hw start failed\n");
+			goto hid_hw_start_fail;
+		}
+		ret = hid_hw_open(hdev);
+		if (ret < 0) {
+			dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n",
+				__func__, ret);
+			hid_hw_stop(hdev);
+			goto hid_hw_start_fail;
+		}
+	}
+
+
+	/* Allow incoming packets */
+	hid_device_io_start(hdev);
+
+	if (hidpp->quirks & HIDPP_QUIRK_UNIFYING)
+		hidpp_unifying_init(hidpp);
+
+	connected = hidpp_is_connected(hidpp);
+	atomic_set(&hidpp->connected, connected);
+	if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) {
+		if (!connected) {
+			ret = -ENODEV;
+			hid_err(hdev, "Device not connected");
+			goto hid_hw_open_failed;
+		}
+
+		hid_info(hdev, "HID++ %u.%u device connected.\n",
+			 hidpp->protocol_major, hidpp->protocol_minor);
+
+		hidpp_overwrite_name(hdev);
+	}
+
+	if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
+		ret = wtp_get_config(hidpp);
+		if (ret)
+			goto hid_hw_open_failed;
+	} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
+		ret = g920_get_config(hidpp);
+		if (ret)
+			goto hid_hw_open_failed;
+	}
+
+	/* Block incoming packets */
+	hid_device_io_stop(hdev);
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
+		ret = hid_hw_start(hdev, connect_mask);
+		if (ret) {
+			hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
+			goto hid_hw_start_fail;
+		}
+	}
+
+	/* Allow incoming packets */
+	hid_device_io_start(hdev);
+
+	hidpp_connect_event(hidpp);
+
+	return ret;
+
+hid_hw_open_failed:
+	hid_device_io_stop(hdev);
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		hid_hw_close(hdev);
+		hid_hw_stop(hdev);
+	}
+hid_hw_start_fail:
+hid_parse_fail:
+	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
+	cancel_work_sync(&hidpp->work);
+	mutex_destroy(&hidpp->send_mutex);
+allocate_fail:
+	hid_set_drvdata(hdev, NULL);
+	return ret;
+}
+
+static void hidpp_remove(struct hid_device *hdev)
+{
+	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+
+	sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
+
+	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
+		hidpp_ff_deinit(hdev);
+		hid_hw_close(hdev);
+	}
+	hid_hw_stop(hdev);
+	cancel_work_sync(&hidpp->work);
+	mutex_destroy(&hidpp->send_mutex);
+}
+
+static const struct hid_device_id hidpp_devices[] = {
+	{ /* wireless touchpad */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4011),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT |
+			 HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS },
+	{ /* wireless touchpad T650 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4101),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP | HIDPP_QUIRK_DELAYED_INIT },
+	{ /* wireless touchpad T651 */
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+		USB_DEVICE_ID_LOGITECH_T651),
+	  .driver_data = HIDPP_QUIRK_CLASS_WTP },
+	{ /* Mouse logitech M560 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x402d),
+	  .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
+	{ /* Keyboard logitech K400 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4024),
+	  .driver_data = HIDPP_QUIRK_CLASS_K400 },
+	{ /* Solar Keyboard Logitech K750 */
+	  HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, 0x4002),
+	  .driver_data = HIDPP_QUIRK_CLASS_K750 },
+
+	{ HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE,
+		USB_VENDOR_ID_LOGITECH, HID_ANY_ID)},
+
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
+		.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, hidpp_devices);
+
+static struct hid_driver hidpp_driver = {
+	.name = "logitech-hidpp-device",
+	.id_table = hidpp_devices,
+	.probe = hidpp_probe,
+	.remove = hidpp_remove,
+	.raw_event = hidpp_raw_event,
+	.input_configured = hidpp_input_configured,
+	.input_mapping = hidpp_input_mapping,
+	.input_mapped = hidpp_input_mapped,
+};
+
+module_hid_driver(hidpp_driver);
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
new file mode 100644
index 0000000..b454c43
--- /dev/null
+++ b/drivers/hid/hid-magicmouse.c
@@ -0,0 +1,596 @@
+/*
+ *   Apple "Magic" Wireless Mouse driver
+ *
+ *   Copyright (c) 2010 Michael Poole <mdpoole@troilus.org>
+ *   Copyright (c) 2010 Chase Douglas <chase.douglas@canonical.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input/mt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+static bool emulate_3button = true;
+module_param(emulate_3button, bool, 0644);
+MODULE_PARM_DESC(emulate_3button, "Emulate a middle button");
+
+static int middle_button_start = -350;
+static int middle_button_stop = +350;
+
+static bool emulate_scroll_wheel = true;
+module_param(emulate_scroll_wheel, bool, 0644);
+MODULE_PARM_DESC(emulate_scroll_wheel, "Emulate a scroll wheel");
+
+static unsigned int scroll_speed = 32;
+static int param_set_scroll_speed(const char *val,
+				  const struct kernel_param *kp) {
+	unsigned long speed;
+	if (!val || kstrtoul(val, 0, &speed) || speed > 63)
+		return -EINVAL;
+	scroll_speed = speed;
+	return 0;
+}
+module_param_call(scroll_speed, param_set_scroll_speed, param_get_uint, &scroll_speed, 0644);
+MODULE_PARM_DESC(scroll_speed, "Scroll speed, value from 0 (slow) to 63 (fast)");
+
+static bool scroll_acceleration = false;
+module_param(scroll_acceleration, bool, 0644);
+MODULE_PARM_DESC(scroll_acceleration, "Accelerate sequential scroll events");
+
+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");
+
+#define TRACKPAD_REPORT_ID 0x28
+#define MOUSE_REPORT_ID    0x29
+#define DOUBLE_REPORT_ID   0xf7
+/* These definitions are not precise, but they're close enough.  (Bits
+ * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
+ * to be some kind of bit mask -- 0x20 may be a near-field reading,
+ * and 0x40 is actual contact, and 0x10 may be a start/stop or change
+ * indication.)
+ */
+#define TOUCH_STATE_MASK  0xf0
+#define TOUCH_STATE_NONE  0x00
+#define TOUCH_STATE_START 0x30
+#define TOUCH_STATE_DRAG  0x40
+
+#define SCROLL_ACCEL_DEFAULT 7
+
+/* Touch surface information. Dimension is in hundredths of a mm, min and max
+ * are in units. */
+#define MOUSE_DIMENSION_X (float)9056
+#define MOUSE_MIN_X -1100
+#define MOUSE_MAX_X 1258
+#define MOUSE_RES_X ((MOUSE_MAX_X - MOUSE_MIN_X) / (MOUSE_DIMENSION_X / 100))
+#define MOUSE_DIMENSION_Y (float)5152
+#define MOUSE_MIN_Y -1589
+#define MOUSE_MAX_Y 2047
+#define MOUSE_RES_Y ((MOUSE_MAX_Y - MOUSE_MIN_Y) / (MOUSE_DIMENSION_Y / 100))
+
+#define TRACKPAD_DIMENSION_X (float)13000
+#define TRACKPAD_MIN_X -2909
+#define TRACKPAD_MAX_X 3167
+#define TRACKPAD_RES_X \
+	((TRACKPAD_MAX_X - TRACKPAD_MIN_X) / (TRACKPAD_DIMENSION_X / 100))
+#define TRACKPAD_DIMENSION_Y (float)11000
+#define TRACKPAD_MIN_Y -2456
+#define TRACKPAD_MAX_Y 2565
+#define TRACKPAD_RES_Y \
+	((TRACKPAD_MAX_Y - TRACKPAD_MIN_Y) / (TRACKPAD_DIMENSION_Y / 100))
+
+/**
+ * struct magicmouse_sc - Tracks Magic Mouse-specific data.
+ * @input: Input device through which we report events.
+ * @quirks: Currently unused.
+ * @ntouches: Number of touches in most recent touch report.
+ * @scroll_accel: Number of consecutive scroll motions.
+ * @scroll_jiffies: Time of last scroll motion.
+ * @touches: Most recent data for a touch, indexed by tracking ID.
+ * @tracking_ids: Mapping of current touch input data to @touches.
+ */
+struct magicmouse_sc {
+	struct input_dev *input;
+	unsigned long quirks;
+
+	int ntouches;
+	int scroll_accel;
+	unsigned long scroll_jiffies;
+
+	struct {
+		short x;
+		short y;
+		short scroll_x;
+		short scroll_y;
+		u8 size;
+	} touches[16];
+	int tracking_ids[16];
+};
+
+static int magicmouse_firm_touch(struct magicmouse_sc *msc)
+{
+	int touch = -1;
+	int ii;
+
+	/* If there is only one "firm" touch, set touch to its
+	 * tracking ID.
+	 */
+	for (ii = 0; ii < msc->ntouches; ii++) {
+		int idx = msc->tracking_ids[ii];
+		if (msc->touches[idx].size < 8) {
+			/* Ignore this touch. */
+		} else if (touch >= 0) {
+			touch = -1;
+			break;
+		} else {
+			touch = idx;
+		}
+	}
+
+	return touch;
+}
+
+static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
+{
+	int last_state = test_bit(BTN_LEFT, msc->input->key) << 0 |
+		test_bit(BTN_RIGHT, msc->input->key) << 1 |
+		test_bit(BTN_MIDDLE, msc->input->key) << 2;
+
+	if (emulate_3button) {
+		int id;
+
+		/* If some button was pressed before, keep it held
+		 * down.  Otherwise, if there's exactly one firm
+		 * touch, use that to override the mouse's guess.
+		 */
+		if (state == 0) {
+			/* The button was released. */
+		} else if (last_state != 0) {
+			state = last_state;
+		} else if ((id = magicmouse_firm_touch(msc)) >= 0) {
+			int x = msc->touches[id].x;
+			if (x < middle_button_start)
+				state = 1;
+			else if (x > middle_button_stop)
+				state = 2;
+			else
+				state = 4;
+		} /* else: we keep the mouse's guess */
+
+		input_report_key(msc->input, BTN_MIDDLE, state & 4);
+	}
+
+	input_report_key(msc->input, BTN_LEFT, state & 1);
+	input_report_key(msc->input, BTN_RIGHT, state & 2);
+
+	if (state != last_state)
+		msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
+}
+
+static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tdata)
+{
+	struct input_dev *input = msc->input;
+	int id, x, y, size, orientation, touch_major, touch_minor, state, down;
+
+	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+		id = (tdata[6] << 2 | tdata[5] >> 6) & 0xf;
+		x = (tdata[1] << 28 | tdata[0] << 20) >> 20;
+		y = -((tdata[2] << 24 | tdata[1] << 16) >> 20);
+		size = tdata[5] & 0x3f;
+		orientation = (tdata[6] >> 2) - 32;
+		touch_major = tdata[3];
+		touch_minor = tdata[4];
+		state = tdata[7] & TOUCH_STATE_MASK;
+		down = state != TOUCH_STATE_NONE;
+	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		id = (tdata[7] << 2 | tdata[6] >> 6) & 0xf;
+		x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
+		y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
+		size = tdata[6] & 0x3f;
+		orientation = (tdata[7] >> 2) - 32;
+		touch_major = tdata[4];
+		touch_minor = tdata[5];
+		state = tdata[8] & TOUCH_STATE_MASK;
+		down = state != TOUCH_STATE_NONE;
+	}
+
+	/* Store tracking ID and other fields. */
+	msc->tracking_ids[raw_id] = id;
+	msc->touches[id].x = x;
+	msc->touches[id].y = y;
+	msc->touches[id].size = size;
+
+	/* If requested, emulate a scroll wheel by detecting small
+	 * vertical touch motions.
+	 */
+	if (emulate_scroll_wheel) {
+		unsigned long now = jiffies;
+		int step_x = msc->touches[id].scroll_x - x;
+		int step_y = msc->touches[id].scroll_y - y;
+
+		/* Calculate and apply the scroll motion. */
+		switch (state) {
+		case TOUCH_STATE_START:
+			msc->touches[id].scroll_x = x;
+			msc->touches[id].scroll_y = y;
+
+			/* Reset acceleration after half a second. */
+			if (scroll_acceleration && time_before(now,
+						msc->scroll_jiffies + HZ / 2))
+				msc->scroll_accel = max_t(int,
+						msc->scroll_accel - 1, 1);
+			else
+				msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
+
+			break;
+		case TOUCH_STATE_DRAG:
+			step_x /= (64 - (int)scroll_speed) * msc->scroll_accel;
+			if (step_x != 0) {
+				msc->touches[id].scroll_x -= step_x *
+					(64 - scroll_speed) * msc->scroll_accel;
+				msc->scroll_jiffies = now;
+				input_report_rel(input, REL_HWHEEL, -step_x);
+			}
+
+			step_y /= (64 - (int)scroll_speed) * msc->scroll_accel;
+			if (step_y != 0) {
+				msc->touches[id].scroll_y -= step_y *
+					(64 - scroll_speed) * msc->scroll_accel;
+				msc->scroll_jiffies = now;
+				input_report_rel(input, REL_WHEEL, step_y);
+			}
+			break;
+		}
+	}
+
+	if (down)
+		msc->ntouches++;
+
+	input_mt_slot(input, id);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER, down);
+
+	/* Generate the input events for this touch. */
+	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);
+		input_report_abs(input, ABS_MT_POSITION_X, x);
+		input_report_abs(input, ABS_MT_POSITION_Y, y);
+
+		if (report_undeciphered) {
+			if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
+				input_event(input, EV_MSC, MSC_RAW, tdata[7]);
+			else /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+				input_event(input, EV_MSC, MSC_RAW, tdata[8]);
+		}
+	}
+}
+
+static int magicmouse_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+	struct input_dev *input = msc->input;
+	int x = 0, y = 0, ii, clicks = 0, npoints;
+
+	switch (data[0]) {
+	case TRACKPAD_REPORT_ID:
+		/* Expect four bytes of prefix, and N*9 bytes of touch data. */
+		if (size < 4 || ((size - 4) % 9) != 0)
+			return 0;
+		npoints = (size - 4) / 9;
+		if (npoints > 15) {
+			hid_warn(hdev, "invalid size value (%d) for TRACKPAD_REPORT_ID\n",
+					size);
+			return 0;
+		}
+		msc->ntouches = 0;
+		for (ii = 0; ii < npoints; ii++)
+			magicmouse_emit_touch(msc, ii, data + ii * 9 + 4);
+
+		clicks = data[1];
+
+		/* The following bits provide a device specific timestamp. They
+		 * are unused here.
+		 *
+		 * ts = data[1] >> 6 | data[2] << 2 | data[3] << 10;
+		 */
+		break;
+	case MOUSE_REPORT_ID:
+		/* Expect six bytes of prefix, and N*8 bytes of touch data. */
+		if (size < 6 || ((size - 6) % 8) != 0)
+			return 0;
+		npoints = (size - 6) / 8;
+		if (npoints > 15) {
+			hid_warn(hdev, "invalid size value (%d) for MOUSE_REPORT_ID\n",
+					size);
+			return 0;
+		}
+		msc->ntouches = 0;
+		for (ii = 0; ii < npoints; ii++)
+			magicmouse_emit_touch(msc, ii, data + ii * 8 + 6);
+
+		/* When emulating three-button mode, it is important
+		 * to have the current touch information before
+		 * generating a click event.
+		 */
+		x = (int)(((data[3] & 0x0c) << 28) | (data[1] << 22)) >> 22;
+		y = (int)(((data[3] & 0x30) << 26) | (data[2] << 22)) >> 22;
+		clicks = data[3];
+
+		/* The following bits provide a device specific timestamp. They
+		 * are unused here.
+		 *
+		 * ts = data[3] >> 6 | data[4] << 2 | data[5] << 10;
+		 */
+		break;
+	case DOUBLE_REPORT_ID:
+		/* Sometimes the trackpad sends two touch reports in one
+		 * packet.
+		 */
+		magicmouse_raw_event(hdev, report, data + 2, data[1]);
+		magicmouse_raw_event(hdev, report, data + 2 + data[1],
+			size - 2 - data[1]);
+		break;
+	default:
+		return 0;
+	}
+
+	if (input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE) {
+		magicmouse_emit_buttons(msc, clicks & 3);
+		input_report_rel(input, REL_X, x);
+		input_report_rel(input, REL_Y, y);
+	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		input_report_key(input, BTN_MOUSE, clicks & 1);
+		input_mt_report_pointer_emulation(input, true);
+	}
+
+	input_sync(input);
+	return 1;
+}
+
+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) {
+		__set_bit(BTN_LEFT, input->keybit);
+		__set_bit(BTN_RIGHT, input->keybit);
+		if (emulate_3button)
+			__set_bit(BTN_MIDDLE, input->keybit);
+
+		__set_bit(EV_REL, input->evbit);
+		__set_bit(REL_X, input->relbit);
+		__set_bit(REL_Y, input->relbit);
+		if (emulate_scroll_wheel) {
+			__set_bit(REL_WHEEL, input->relbit);
+			__set_bit(REL_HWHEEL, input->relbit);
+		}
+	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		/* input->keybit is initialized with incorrect button info
+		 * for Magic Trackpad. There really is only one physical
+		 * button (BTN_LEFT == BTN_MOUSE). Make sure we don't
+		 * advertise buttons that don't exist...
+		 */
+		__clear_bit(BTN_RIGHT, input->keybit);
+		__clear_bit(BTN_MIDDLE, input->keybit);
+		__set_bit(BTN_MOUSE, input->keybit);
+		__set_bit(BTN_TOOL_FINGER, input->keybit);
+		__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);
+	}
+
+
+	__set_bit(EV_ABS, input->evbit);
+
+	error = input_mt_init_slots(input, 16, 0);
+	if (error)
+		return error;
+	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,
+			     4, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 255 << 2,
+			     4, 0);
+	input_set_abs_params(input, ABS_MT_ORIENTATION, -31, 32, 1, 0);
+
+	/* Note: Touch Y position from the device is inverted relative
+	 * to how pointer motion is reported (and relative to how USB
+	 * 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_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_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,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+	if (!msc->input)
+		msc->input = hi->input;
+
+	/* Magic Trackpad does not give relative data after switching to MT */
+	if (hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD &&
+	    field->flags & HID_MAIN_ITEM_RELATIVE)
+		return -1;
+
+	return 0;
+}
+
+static int magicmouse_input_configured(struct hid_device *hdev,
+		struct hid_input *hi)
+
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+	int ret;
+
+	ret = magicmouse_setup_input(msc->input, hdev);
+	if (ret) {
+		hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
+		/* clean msc->input to notify probe() of the failure */
+		msc->input = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+
+static int magicmouse_probe(struct hid_device *hdev,
+	const struct hid_device_id *id)
+{
+	const u8 feature[] = { 0xd7, 0x01 };
+	u8 *buf;
+	struct magicmouse_sc *msc;
+	struct hid_report *report;
+	int ret;
+
+	msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
+	if (msc == NULL) {
+		hid_err(hdev, "can't alloc magicmouse descriptor\n");
+		return -ENOMEM;
+	}
+
+	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
+
+	msc->quirks = id->driver_data;
+	hid_set_drvdata(hdev, msc);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "magicmouse hid parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "magicmouse hw start failed\n");
+		return ret;
+	}
+
+	if (!msc->input) {
+		hid_err(hdev, "magicmouse input not registered\n");
+		ret = -ENOMEM;
+		goto err_stop_hw;
+	}
+
+	if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE)
+		report = hid_register_report(hdev, HID_INPUT_REPORT,
+			MOUSE_REPORT_ID, 0);
+	else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
+		report = hid_register_report(hdev, HID_INPUT_REPORT,
+			TRACKPAD_REPORT_ID, 0);
+		report = hid_register_report(hdev, HID_INPUT_REPORT,
+			DOUBLE_REPORT_ID, 0);
+	}
+
+	if (!report) {
+		hid_err(hdev, "unable to register touch report\n");
+		ret = -ENOMEM;
+		goto err_stop_hw;
+	}
+	report->size = 6;
+
+	buf = kmemdup(feature, sizeof(feature), GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto err_stop_hw;
+	}
+
+	/*
+	 * Some devices repond with 'invalid report id' when feature
+	 * report switching it into multitouch mode is sent to it.
+	 *
+	 * This results in -EIO from the _raw low-level transport callback,
+	 * but there seems to be no other way of switching the mode.
+	 * Thus the super-ugly hacky success check below.
+	 */
+	ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(feature),
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	kfree(buf);
+	if (ret != -EIO && ret != sizeof(feature)) {
+		hid_err(hdev, "unable to request touch data (%d)\n", ret);
+		goto err_stop_hw;
+	}
+
+	return 0;
+err_stop_hw:
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static const struct hid_device_id magic_mice[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_MAGICMOUSE), .driver_data = 0 },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
+		USB_DEVICE_ID_APPLE_MAGICTRACKPAD), .driver_data = 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, magic_mice);
+
+static struct hid_driver magicmouse_driver = {
+	.name = "magicmouse",
+	.id_table = magic_mice,
+	.probe = magicmouse_probe,
+	.raw_event = magicmouse_raw_event,
+	.input_mapping = magicmouse_input_mapping,
+	.input_configured = magicmouse_input_configured,
+};
+module_hid_driver(magicmouse_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-mf.c b/drivers/hid/hid-mf.c
new file mode 100644
index 0000000..03f1051
--- /dev/null
+++ b/drivers/hid/hid-mf.c
@@ -0,0 +1,175 @@
+/*
+ * Force feedback support for Mayflash game controller adapters.
+ *
+ * These devices are manufactured by Mayflash but identify themselves
+ * using the vendor ID of DragonRise Inc.
+ *
+ * Tested with:
+ * 0079:1801 "DragonRise Inc. Mayflash PS3 Game Controller Adapter"
+ * 0079:1803 "DragonRise Inc. Mayflash Wireless Sensor DolphinBar"
+ * 0079:1843 "DragonRise Inc. Mayflash GameCube Game Controller Adapter"
+ * 0079:1844 "DragonRise Inc. Mayflash GameCube Game Controller Adapter (v04)"
+ *
+ * The following adapters probably work too, but need to be tested:
+ * 0079:1800 "DragonRise Inc. Mayflash WIIU Game Controller Adapter"
+ *
+ * Copyright (c) 2016-2017 Marcel Hasler <mahasler@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+struct mf_device {
+	struct hid_report *report;
+};
+
+static int mf_play(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct mf_device *mf = data;
+	int strong, weak;
+
+	strong = effect->u.rumble.strong_magnitude;
+	weak = effect->u.rumble.weak_magnitude;
+
+	dbg_hid("Called with 0x%04x 0x%04x.\n", strong, weak);
+
+	strong = strong * 0xff / 0xffff;
+	weak = weak * 0xff / 0xffff;
+
+	dbg_hid("Running with 0x%02x 0x%02x.\n", strong, weak);
+
+	mf->report->field[0]->value[0] = weak;
+	mf->report->field[0]->value[1] = strong;
+	hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int mf_init(struct hid_device *hid)
+{
+	struct mf_device *mf;
+
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+
+	struct list_head *report_ptr;
+	struct hid_report *report;
+
+	struct list_head *input_ptr = &hid->inputs;
+	struct hid_input *input;
+
+	struct input_dev *dev;
+
+	int error;
+
+	/* Setup each of the four inputs */
+	list_for_each(report_ptr, report_list) {
+		report = list_entry(report_ptr, struct hid_report, list);
+
+		if (report->maxfield < 1 || report->field[0]->report_count < 2) {
+			hid_err(hid, "Invalid report, this should never happen!\n");
+			return -ENODEV;
+		}
+
+		if (list_is_last(input_ptr, &hid->inputs)) {
+			hid_err(hid, "Missing input, this should never happen!\n");
+			return -ENODEV;
+		}
+
+		input_ptr = input_ptr->next;
+		input = list_entry(input_ptr, struct hid_input, list);
+
+		mf = kzalloc(sizeof(struct mf_device), GFP_KERNEL);
+		if (!mf)
+			return -ENOMEM;
+
+		dev = input->input;
+		set_bit(FF_RUMBLE, dev->ffbit);
+
+		error = input_ff_create_memless(dev, mf, mf_play);
+		if (error) {
+			kfree(mf);
+			return error;
+		}
+
+		mf->report = report;
+		mf->report->field[0]->value[0] = 0x00;
+		mf->report->field[0]->value[1] = 0x00;
+		hid_hw_request(hid, mf->report, HID_REQ_SET_REPORT);
+	}
+
+	hid_info(hid, "Force feedback for HJZ Mayflash game controller "
+		      "adapters by Marcel Hasler <mahasler@gmail.com>\n");
+
+	return 0;
+}
+
+static int mf_probe(struct hid_device *hid, const struct hid_device_id *id)
+{
+	int error;
+
+	dev_dbg(&hid->dev, "Mayflash HID hardware probe...\n");
+
+	/* Apply quirks as needed */
+	hid->quirks |= id->driver_data;
+
+	error = hid_parse(hid);
+	if (error) {
+		hid_err(hid, "HID parse failed.\n");
+		return error;
+	}
+
+	error = hid_hw_start(hid, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (error) {
+		hid_err(hid, "HID hw start failed\n");
+		return error;
+	}
+
+	error = mf_init(hid);
+	if (error) {
+		hid_err(hid, "Force feedback init failed.\n");
+		hid_hw_stop(hid);
+		return error;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id mf_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3),
+		.driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR),
+		.driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1),
+		.driver_data = HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE2),
+		.driver_data = 0 }, /* No quirk required */
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, mf_devices);
+
+static struct hid_driver mf_driver = {
+	.name = "hid_mf",
+	.id_table = mf_devices,
+	.probe = mf_probe,
+};
+module_hid_driver(mf_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-microsoft.c b/drivers/hid/hid-microsoft.c
new file mode 100644
index 0000000..72d9836
--- /dev/null
+++ b/drivers/hid/hid-microsoft.c
@@ -0,0 +1,336 @@
+/*
+ *  HID driver for some microsoft "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#define MS_HIDINPUT		BIT(0)
+#define MS_ERGONOMY		BIT(1)
+#define MS_PRESENTER		BIT(2)
+#define MS_RDESC		BIT(3)
+#define MS_NOGET		BIT(4)
+#define MS_DUPLICATE_USAGES	BIT(5)
+#define MS_SURFACE_DIAL		BIT(6)
+
+static __u8 *ms_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	/*
+	 * Microsoft Wireless Desktop Receiver (Model 1028) has
+	 * 'Usage Min/Max' where it ought to have 'Physical Min/Max'
+	 */
+	if ((quirks & MS_RDESC) && *rsize == 571 && rdesc[557] == 0x19 &&
+			rdesc[559] == 0x29) {
+		hid_info(hdev, "fixing up Microsoft Wireless Receiver Model 1028 report descriptor\n");
+		rdesc[557] = 0x35;
+		rdesc[559] = 0x45;
+	}
+	return rdesc;
+}
+
+#define ms_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int ms_ergonomy_kb_quirk(struct hid_input *hi, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct input_dev *input = hi->input;
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		switch (usage->hid & HID_USAGE) {
+		/*
+		 * Microsoft uses these 2 reserved usage ids for 2 keys on
+		 * the MS office kb labelled "Office Home" and "Task Pane".
+		 */
+		case 0x29d:
+			ms_map_key_clear(KEY_PROG1);
+			return 1;
+		case 0x29e:
+			ms_map_key_clear(KEY_PROG2);
+			return 1;
+		}
+		return 0;
+	}
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0xfd06: ms_map_key_clear(KEY_CHAT);	break;
+	case 0xfd07: ms_map_key_clear(KEY_PHONE);	break;
+	case 0xff00:
+		/* Special keypad keys */
+		ms_map_key_clear(KEY_KPEQUAL);
+		set_bit(KEY_KPLEFTPAREN, input->keybit);
+		set_bit(KEY_KPRIGHTPAREN, input->keybit);
+		break;
+	case 0xff01:
+		/* Scroll wheel */
+		hid_map_usage_clear(hi, usage, bit, max, EV_REL, REL_WHEEL);
+		break;
+	case 0xff02:
+		/*
+		 * This byte contains a copy of the modifier keys byte of a
+		 * standard hid keyboard report, as send by interface 0
+		 * (this usage is found on interface 1).
+		 *
+		 * This byte only gets send when another key in the same report
+		 * changes state, and as such is useless, ignore it.
+		 */
+		return -1;
+	case 0xff05:
+		set_bit(EV_REP, input->evbit);
+		ms_map_key_clear(KEY_F13);
+		set_bit(KEY_F14, input->keybit);
+		set_bit(KEY_F15, input->keybit);
+		set_bit(KEY_F16, input->keybit);
+		set_bit(KEY_F17, input->keybit);
+		set_bit(KEY_F18, input->keybit);
+		break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int ms_presenter_8k_quirk(struct hid_input *hi, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_MSVENDOR)
+		return 0;
+
+	set_bit(EV_REP, hi->input->evbit);
+	switch (usage->hid & HID_USAGE) {
+	case 0xfd08: ms_map_key_clear(KEY_FORWARD);	break;
+	case 0xfd09: ms_map_key_clear(KEY_BACK);	break;
+	case 0xfd0b: ms_map_key_clear(KEY_PLAYPAUSE);	break;
+	case 0xfd0e: ms_map_key_clear(KEY_CLOSE);	break;
+	case 0xfd0f: ms_map_key_clear(KEY_PLAY);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static int ms_surface_dial_quirk(struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	switch (usage->hid & HID_USAGE_PAGE) {
+	case 0xff070000:
+		/* fall-through */
+	case HID_UP_DIGITIZER:
+		/* ignore those axis */
+		return -1;
+	case HID_UP_GENDESK:
+		switch (usage->hid) {
+		case HID_GD_X:
+			/* fall-through */
+		case HID_GD_Y:
+			/* fall-through */
+		case HID_GD_RFKILL_BTN:
+			/* ignore those axis */
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int ms_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	if (quirks & MS_ERGONOMY) {
+		int ret = ms_ergonomy_kb_quirk(hi, usage, bit, max);
+		if (ret)
+			return ret;
+	}
+
+	if ((quirks & MS_PRESENTER) &&
+			ms_presenter_8k_quirk(hi, usage, bit, max))
+		return 1;
+
+	if (quirks & MS_SURFACE_DIAL) {
+		int ret = ms_surface_dial_quirk(hi, field, usage, bit, max);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int ms_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+
+	if (quirks & MS_DUPLICATE_USAGES)
+		clear_bit(usage->code, *bit);
+
+	return 0;
+}
+
+static int ms_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	unsigned long quirks = (unsigned long)hid_get_drvdata(hdev);
+	struct input_dev *input;
+
+	if (!(hdev->claimed & HID_CLAIMED_INPUT) || !field->hidinput ||
+			!usage->type)
+		return 0;
+
+	input = field->hidinput->input;
+
+	/* Handling MS keyboards special buttons */
+	if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff00)) {
+		/* Special keypad keys */
+		input_report_key(input, KEY_KPEQUAL, value & 0x01);
+		input_report_key(input, KEY_KPLEFTPAREN, value & 0x02);
+		input_report_key(input, KEY_KPRIGHTPAREN, value & 0x04);
+		return 1;
+	}
+
+	if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff01)) {
+		/* Scroll wheel */
+		int step = ((value & 0x60) >> 5) + 1;
+
+		switch (value & 0x1f) {
+		case 0x01:
+			input_report_rel(input, REL_WHEEL, step);
+			break;
+		case 0x1f:
+			input_report_rel(input, REL_WHEEL, -step);
+			break;
+		}
+		return 1;
+	}
+
+	if (quirks & MS_ERGONOMY && usage->hid == (HID_UP_MSVENDOR | 0xff05)) {
+		static unsigned int last_key = 0;
+		unsigned int key = 0;
+		switch (value) {
+		case 0x01: key = KEY_F14; break;
+		case 0x02: key = KEY_F15; break;
+		case 0x04: key = KEY_F16; break;
+		case 0x08: key = KEY_F17; break;
+		case 0x10: key = KEY_F18; break;
+		}
+		if (key) {
+			input_event(input, usage->type, key, 1);
+			last_key = key;
+		} else
+			input_event(input, usage->type, last_key, 0);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int ms_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	unsigned long quirks = id->driver_data;
+	int ret;
+
+	hid_set_drvdata(hdev, (void *)quirks);
+
+	if (quirks & MS_NOGET)
+		hdev->quirks |= HID_QUIRK_NOGET;
+
+	if (quirks & MS_SURFACE_DIAL)
+		hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT | ((quirks & MS_HIDINPUT) ?
+				HID_CONNECT_HIDINPUT_FORCE : 0));
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static const struct hid_device_id ms_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV),
+		.driver_data = MS_HIDINPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K_JP),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE7K),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K),
+		.driver_data = MS_ERGONOMY | MS_RDESC },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB),
+		.driver_data = MS_PRESENTER },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_600),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1),
+		.driver_data = MS_ERGONOMY },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0),
+		.driver_data = MS_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500),
+		.driver_data = MS_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER),
+		.driver_data = MS_HIDINPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_KEYBOARD),
+		.driver_data = MS_ERGONOMY},
+
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT),
+		.driver_data = MS_PRESENTER },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, 0x091B),
+		.driver_data = MS_SURFACE_DIAL },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ms_devices);
+
+static struct hid_driver ms_driver = {
+	.name = "microsoft",
+	.id_table = ms_devices,
+	.report_fixup = ms_report_fixup,
+	.input_mapping = ms_input_mapping,
+	.input_mapped = ms_input_mapped,
+	.event = ms_event,
+	.probe = ms_probe,
+};
+module_hid_driver(ms_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-monterey.c b/drivers/hid/hid-monterey.c
new file mode 100644
index 0000000..25daf28
--- /dev/null
+++ b/drivers/hid/hid-monterey.c
@@ -0,0 +1,68 @@
+/*
+ *  HID driver for some monterey "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+static __u8 *mr_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 31 && rdesc[29] == 0x05 && rdesc[30] == 0x09) {
+		hid_info(hdev, "fixing up button/consumer in HID report descriptor\n");
+		rdesc[30] = 0x0c;
+	}
+	return rdesc;
+}
+
+#define mr_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int mr_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x156: mr_map_key_clear(KEY_WORDPROCESSOR);	break;
+	case 0x157: mr_map_key_clear(KEY_SPREADSHEET);		break;
+	case 0x158: mr_map_key_clear(KEY_PRESENTATION);		break;
+	case 0x15c: mr_map_key_clear(KEY_STOP);			break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static const struct hid_device_id mr_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, mr_devices);
+
+static struct hid_driver mr_driver = {
+	.name = "monterey",
+	.id_table = mr_devices,
+	.report_fixup = mr_report_fixup,
+	.input_mapping = mr_input_mapping,
+};
+module_hid_driver(mr_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
new file mode 100644
index 0000000..2faf542
--- /dev/null
+++ b/drivers/hid/hid-multitouch.c
@@ -0,0 +1,2125 @@
+/*
+ *  HID driver for multitouch panels
+ *
+ *  Copyright (c) 2010-2012 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010-2013 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2010-2012 Ecole Nationale de l'Aviation Civile, France
+ *  Copyright (c) 2012-2013 Red Hat, Inc
+ *
+ *  This code is partly based on hid-egalax.c:
+ *
+ *  Copyright (c) 2010 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010 Henrik Rydberg <rydberg@euromail.se>
+ *  Copyright (c) 2010 Canonical, Ltd.
+ *
+ *  This code is partly based on hid-3m-pct.c:
+ *
+ *  Copyright (c) 2009-2010 Stephane Chatty <chatty@enac.fr>
+ *  Copyright (c) 2010      Henrik Rydberg <rydberg@euromail.se>
+ *  Copyright (c) 2010      Canonical, Ltd.
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * This driver is regularly tested thanks to the test suite in hid-tools[1].
+ * Please run these regression tests before patching this module so that
+ * your patch won't break existing known devices.
+ *
+ * [1] https://gitlab.freedesktop.org/libevdev/hid-tools
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input/mt.h>
+#include <linux/jiffies.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+
+
+MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_DESCRIPTION("HID multitouch panels");
+MODULE_LICENSE("GPL");
+
+#include "hid-ids.h"
+
+/* quirks to control the device */
+#define MT_QUIRK_NOT_SEEN_MEANS_UP	BIT(0)
+#define MT_QUIRK_SLOT_IS_CONTACTID	BIT(1)
+#define MT_QUIRK_CYPRESS		BIT(2)
+#define MT_QUIRK_SLOT_IS_CONTACTNUMBER	BIT(3)
+#define MT_QUIRK_ALWAYS_VALID		BIT(4)
+#define MT_QUIRK_VALID_IS_INRANGE	BIT(5)
+#define MT_QUIRK_VALID_IS_CONFIDENCE	BIT(6)
+#define MT_QUIRK_CONFIDENCE		BIT(7)
+#define MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE	BIT(8)
+#define MT_QUIRK_NO_AREA		BIT(9)
+#define MT_QUIRK_IGNORE_DUPLICATES	BIT(10)
+#define MT_QUIRK_HOVERING		BIT(11)
+#define MT_QUIRK_CONTACT_CNT_ACCURATE	BIT(12)
+#define MT_QUIRK_FORCE_GET_FEATURE	BIT(13)
+#define MT_QUIRK_FIX_CONST_CONTACT_ID	BIT(14)
+#define MT_QUIRK_TOUCH_SIZE_SCALING	BIT(15)
+#define MT_QUIRK_STICKY_FINGERS		BIT(16)
+#define MT_QUIRK_ASUS_CUSTOM_UP		BIT(17)
+#define MT_QUIRK_WIN8_PTP_BUTTONS	BIT(18)
+
+#define MT_INPUTMODE_TOUCHSCREEN	0x02
+#define MT_INPUTMODE_TOUCHPAD		0x03
+
+#define MT_BUTTONTYPE_CLICKPAD		0
+
+enum latency_mode {
+	HID_LATENCY_NORMAL = 0,
+	HID_LATENCY_HIGH = 1,
+};
+
+#define MT_IO_FLAGS_RUNNING		0
+#define MT_IO_FLAGS_ACTIVE_SLOTS	1
+#define MT_IO_FLAGS_PENDING_SLOTS	2
+
+static const bool mtrue = true;		/* default for true */
+static const bool mfalse;		/* default for false */
+static const __s32 mzero;		/* default for 0 */
+
+#define DEFAULT_TRUE	((void *)&mtrue)
+#define DEFAULT_FALSE	((void *)&mfalse)
+#define DEFAULT_ZERO	((void *)&mzero)
+
+struct mt_usages {
+	struct list_head list;
+	__s32 *x, *y, *cx, *cy, *p, *w, *h, *a;
+	__s32 *contactid;	/* the device ContactID assigned to this slot */
+	bool *tip_state;	/* is the touch valid? */
+	bool *inrange_state;	/* is the finger in proximity of the sensor? */
+	bool *confidence_state;	/* is the touch made by a finger? */
+};
+
+struct mt_application {
+	struct list_head list;
+	unsigned int application;
+	struct list_head mt_usages;	/* mt usages list */
+
+	__s32 quirks;
+
+	__s32 *scantime;		/* scantime reported */
+	__s32 scantime_logical_max;	/* max value for raw scantime */
+
+	__s32 *raw_cc;			/* contact count in the report */
+	int left_button_state;		/* left button state */
+	unsigned int mt_flags;		/* flags to pass to input-mt */
+
+	unsigned long *pending_palm_slots;	/* slots where we reported palm
+						 * and need to release */
+
+	__u8 num_received;	/* how many contacts we received */
+	__u8 num_expected;	/* expected last contact index */
+	__u8 buttons_count;	/* number of physical buttons per touchpad */
+	__u8 touches_by_report;	/* how many touches are present in one report:
+				 * 1 means we should use a serial protocol
+				 * > 1 means hybrid (multitouch) protocol
+				 */
+
+	__s32 dev_time;		/* the scan time provided by the device */
+	unsigned long jiffies;	/* the frame's jiffies */
+	int timestamp;		/* the timestamp to be sent */
+	int prev_scantime;		/* scantime reported previously */
+
+	bool have_contact_count;
+};
+
+struct mt_class {
+	__s32 name;	/* MT_CLS */
+	__s32 quirks;
+	__s32 sn_move;	/* Signal/noise ratio for move events */
+	__s32 sn_width;	/* Signal/noise ratio for width events */
+	__s32 sn_height;	/* Signal/noise ratio for height events */
+	__s32 sn_pressure;	/* Signal/noise ratio for pressure events */
+	__u8 maxcontacts;
+	bool is_indirect;	/* true for touchpads */
+	bool export_all_inputs;	/* do not ignore mouse, keyboards, etc... */
+};
+
+struct mt_report_data {
+	struct list_head list;
+	struct hid_report *report;
+	struct mt_application *application;
+	bool is_mt_collection;
+};
+
+struct mt_device {
+	struct mt_class mtclass;	/* our mt device class */
+	struct timer_list release_timer;	/* to release sticky fingers */
+	struct hid_device *hdev;	/* hid_device we're attached to */
+	unsigned long mt_io_flags;	/* mt flags (MT_IO_FLAGS_*) */
+	__u8 inputmode_value;	/* InputMode HID feature value */
+	__u8 maxcontacts;
+	bool is_buttonpad;	/* is this device a button pad? */
+	bool serial_maybe;	/* need to check for serial protocol */
+
+	struct list_head applications;
+	struct list_head reports;
+};
+
+static void mt_post_parse_default_settings(struct mt_device *td,
+					   struct mt_application *app);
+static void mt_post_parse(struct mt_device *td, struct mt_application *app);
+
+/* classes of device behavior */
+#define MT_CLS_DEFAULT				0x0001
+
+#define MT_CLS_SERIAL				0x0002
+#define MT_CLS_CONFIDENCE			0x0003
+#define MT_CLS_CONFIDENCE_CONTACT_ID		0x0004
+#define MT_CLS_CONFIDENCE_MINUS_ONE		0x0005
+#define MT_CLS_DUAL_INRANGE_CONTACTID		0x0006
+#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER	0x0007
+/* reserved					0x0008 */
+#define MT_CLS_INRANGE_CONTACTNUMBER		0x0009
+#define MT_CLS_NSMU				0x000a
+/* reserved					0x0010 */
+/* reserved					0x0011 */
+#define MT_CLS_WIN_8				0x0012
+#define MT_CLS_EXPORT_ALL_INPUTS		0x0013
+#define MT_CLS_WIN_8_DUAL			0x0014
+
+/* vendor specific classes */
+#define MT_CLS_3M				0x0101
+/* reserved					0x0102 */
+#define MT_CLS_EGALAX				0x0103
+#define MT_CLS_EGALAX_SERIAL			0x0104
+#define MT_CLS_TOPSEED				0x0105
+#define MT_CLS_PANASONIC			0x0106
+#define MT_CLS_FLATFROG				0x0107
+#define MT_CLS_GENERALTOUCH_TWOFINGERS		0x0108
+#define MT_CLS_GENERALTOUCH_PWT_TENFINGERS	0x0109
+#define MT_CLS_LG				0x010a
+#define MT_CLS_ASUS				0x010b
+#define MT_CLS_VTL				0x0110
+#define MT_CLS_GOOGLE				0x0111
+#define MT_CLS_RAZER_BLADE_STEALTH		0x0112
+
+#define MT_DEFAULT_MAXCONTACT	10
+#define MT_MAX_MAXCONTACT	250
+
+/*
+ * Resync device and local timestamps after that many microseconds without
+ * receiving data.
+ */
+#define MAX_TIMESTAMP_INTERVAL	1000000
+
+#define MT_USB_DEVICE(v, p)	HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH, v, p)
+#define MT_BT_DEVICE(v, p)	HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH, v, p)
+
+/*
+ * these device-dependent functions determine what slot corresponds
+ * to a valid contact that was just read.
+ */
+
+static int cypress_compute_slot(struct mt_application *application,
+				struct mt_usages *slot)
+{
+	if (*slot->contactid != 0 || application->num_received == 0)
+		return *slot->contactid;
+	else
+		return -1;
+}
+
+static const struct mt_class mt_classes[] = {
+	{ .name = MT_CLS_DEFAULT,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_CONTACT_CNT_ACCURATE },
+	{ .name = MT_CLS_NSMU,
+		.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP },
+	{ .name = MT_CLS_SERIAL,
+		.quirks = MT_QUIRK_ALWAYS_VALID},
+	{ .name = MT_CLS_CONFIDENCE,
+		.quirks = MT_QUIRK_VALID_IS_CONFIDENCE },
+	{ .name = MT_CLS_CONFIDENCE_CONTACT_ID,
+		.quirks = MT_QUIRK_VALID_IS_CONFIDENCE |
+			MT_QUIRK_SLOT_IS_CONTACTID },
+	{ .name = MT_CLS_CONFIDENCE_MINUS_ONE,
+		.quirks = MT_QUIRK_VALID_IS_CONFIDENCE |
+			MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE },
+	{ .name = MT_CLS_DUAL_INRANGE_CONTACTID,
+		.quirks = MT_QUIRK_VALID_IS_INRANGE |
+			MT_QUIRK_SLOT_IS_CONTACTID,
+		.maxcontacts = 2 },
+	{ .name = MT_CLS_DUAL_INRANGE_CONTACTNUMBER,
+		.quirks = MT_QUIRK_VALID_IS_INRANGE |
+			MT_QUIRK_SLOT_IS_CONTACTNUMBER,
+		.maxcontacts = 2 },
+	{ .name = MT_CLS_INRANGE_CONTACTNUMBER,
+		.quirks = MT_QUIRK_VALID_IS_INRANGE |
+			MT_QUIRK_SLOT_IS_CONTACTNUMBER },
+	{ .name = MT_CLS_WIN_8,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_IGNORE_DUPLICATES |
+			MT_QUIRK_HOVERING |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_STICKY_FINGERS |
+			MT_QUIRK_WIN8_PTP_BUTTONS },
+	{ .name = MT_CLS_EXPORT_ALL_INPUTS,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_CONTACT_CNT_ACCURATE,
+		.export_all_inputs = true },
+	{ .name = MT_CLS_WIN_8_DUAL,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_IGNORE_DUPLICATES |
+			MT_QUIRK_HOVERING |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_WIN8_PTP_BUTTONS,
+		.export_all_inputs = true },
+
+	/*
+	 * vendor specific classes
+	 */
+	{ .name = MT_CLS_3M,
+		.quirks = MT_QUIRK_VALID_IS_CONFIDENCE |
+			MT_QUIRK_SLOT_IS_CONTACTID |
+			MT_QUIRK_TOUCH_SIZE_SCALING,
+		.sn_move = 2048,
+		.sn_width = 128,
+		.sn_height = 128,
+		.maxcontacts = 60,
+	},
+	{ .name = MT_CLS_EGALAX,
+		.quirks =  MT_QUIRK_SLOT_IS_CONTACTID |
+			MT_QUIRK_VALID_IS_INRANGE,
+		.sn_move = 4096,
+		.sn_pressure = 32,
+	},
+	{ .name = MT_CLS_EGALAX_SERIAL,
+		.quirks =  MT_QUIRK_SLOT_IS_CONTACTID |
+			MT_QUIRK_ALWAYS_VALID,
+		.sn_move = 4096,
+		.sn_pressure = 32,
+	},
+	{ .name = MT_CLS_TOPSEED,
+		.quirks = MT_QUIRK_ALWAYS_VALID,
+		.is_indirect = true,
+		.maxcontacts = 2,
+	},
+	{ .name = MT_CLS_PANASONIC,
+		.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP,
+		.maxcontacts = 4 },
+	{ .name	= MT_CLS_GENERALTOUCH_TWOFINGERS,
+		.quirks	= MT_QUIRK_NOT_SEEN_MEANS_UP |
+			MT_QUIRK_VALID_IS_INRANGE |
+			MT_QUIRK_SLOT_IS_CONTACTID,
+		.maxcontacts = 2
+	},
+	{ .name	= MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		.quirks	= MT_QUIRK_NOT_SEEN_MEANS_UP |
+			MT_QUIRK_SLOT_IS_CONTACTID
+	},
+
+	{ .name = MT_CLS_FLATFROG,
+		.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
+			MT_QUIRK_NO_AREA,
+		.sn_move = 2048,
+		.maxcontacts = 40,
+	},
+	{ .name = MT_CLS_LG,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_FIX_CONST_CONTACT_ID |
+			MT_QUIRK_IGNORE_DUPLICATES |
+			MT_QUIRK_HOVERING |
+			MT_QUIRK_CONTACT_CNT_ACCURATE },
+	{ .name = MT_CLS_ASUS,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_ASUS_CUSTOM_UP },
+	{ .name = MT_CLS_VTL,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_FORCE_GET_FEATURE,
+	},
+	{ .name = MT_CLS_GOOGLE,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_SLOT_IS_CONTACTID |
+			MT_QUIRK_HOVERING
+	},
+	{ .name = MT_CLS_RAZER_BLADE_STEALTH,
+		.quirks = MT_QUIRK_ALWAYS_VALID |
+			MT_QUIRK_IGNORE_DUPLICATES |
+			MT_QUIRK_HOVERING |
+			MT_QUIRK_CONTACT_CNT_ACCURATE |
+			MT_QUIRK_WIN8_PTP_BUTTONS,
+	},
+	{ }
+};
+
+static ssize_t mt_show_quirks(struct device *dev,
+			   struct device_attribute *attr,
+			   char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct mt_device *td = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%u\n", td->mtclass.quirks);
+}
+
+static ssize_t mt_set_quirks(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_application *application;
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	td->mtclass.quirks = val;
+
+	list_for_each_entry(application, &td->applications, list) {
+		application->quirks = val;
+		if (!application->have_contact_count)
+			application->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
+	}
+
+	return count;
+}
+
+static DEVICE_ATTR(quirks, S_IWUSR | S_IRUGO, mt_show_quirks, mt_set_quirks);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_quirks.attr,
+	NULL
+};
+
+static const struct attribute_group mt_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
+static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
+{
+	int ret;
+	u32 size = hid_report_len(report);
+	u8 *buf;
+
+	/*
+	 * Do not fetch the feature report if the device has been explicitly
+	 * marked as non-capable.
+	 */
+	if (hdev->quirks & HID_QUIRK_NO_INIT_REPORTS)
+		return;
+
+	buf = hid_alloc_report_buf(report, GFP_KERNEL);
+	if (!buf)
+		return;
+
+	ret = hid_hw_raw_request(hdev, report->id, buf, size,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		dev_warn(&hdev->dev, "failed to fetch feature %d\n",
+			 report->id);
+	} else {
+		ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
+					   size, 0);
+		if (ret)
+			dev_warn(&hdev->dev, "failed to report feature\n");
+	}
+
+	kfree(buf);
+}
+
+static void mt_feature_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+
+	switch (usage->hid) {
+	case HID_DG_CONTACTMAX:
+		mt_get_feature(hdev, field->report);
+
+		td->maxcontacts = field->value[0];
+		if (!td->maxcontacts &&
+		    field->logical_maximum <= MT_MAX_MAXCONTACT)
+			td->maxcontacts = field->logical_maximum;
+		if (td->mtclass.maxcontacts)
+			/* check if the maxcontacts is given by the class */
+			td->maxcontacts = td->mtclass.maxcontacts;
+
+		break;
+	case HID_DG_BUTTONTYPE:
+		if (usage->usage_index >= field->report_count) {
+			dev_err(&hdev->dev, "HID_DG_BUTTONTYPE out of range\n");
+			break;
+		}
+
+		mt_get_feature(hdev, field->report);
+		if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD)
+			td->is_buttonpad = true;
+
+		break;
+	case 0xff0000c5:
+		/* Retrieve the Win8 blob once to enable some devices */
+		if (usage->usage_index == 0)
+			mt_get_feature(hdev, field->report);
+		break;
+	}
+}
+
+static void set_abs(struct input_dev *input, unsigned int code,
+		struct hid_field *field, int snratio)
+{
+	int fmin = field->logical_minimum;
+	int fmax = field->logical_maximum;
+	int fuzz = snratio ? (fmax - fmin) / snratio : 0;
+	input_set_abs_params(input, code, fmin, fmax, fuzz, 0);
+	input_abs_set_res(input, code, hidinput_calc_abs_res(field, code));
+}
+
+static struct mt_usages *mt_allocate_usage(struct hid_device *hdev,
+					   struct mt_application *application)
+{
+	struct mt_usages *usage;
+
+	usage = devm_kzalloc(&hdev->dev, sizeof(*usage), GFP_KERNEL);
+	if (!usage)
+		return NULL;
+
+	/* set some defaults so we do not need to check for null pointers */
+	usage->x = DEFAULT_ZERO;
+	usage->y = DEFAULT_ZERO;
+	usage->cx = DEFAULT_ZERO;
+	usage->cy = DEFAULT_ZERO;
+	usage->p = DEFAULT_ZERO;
+	usage->w = DEFAULT_ZERO;
+	usage->h = DEFAULT_ZERO;
+	usage->a = DEFAULT_ZERO;
+	usage->contactid = DEFAULT_ZERO;
+	usage->tip_state = DEFAULT_FALSE;
+	usage->inrange_state = DEFAULT_FALSE;
+	usage->confidence_state = DEFAULT_TRUE;
+
+	list_add_tail(&usage->list, &application->mt_usages);
+
+	return usage;
+}
+
+static struct mt_application *mt_allocate_application(struct mt_device *td,
+						      unsigned int application)
+{
+	struct mt_application *mt_application;
+
+	mt_application = devm_kzalloc(&td->hdev->dev, sizeof(*mt_application),
+				      GFP_KERNEL);
+	if (!mt_application)
+		return NULL;
+
+	mt_application->application = application;
+	INIT_LIST_HEAD(&mt_application->mt_usages);
+
+	if (application == HID_DG_TOUCHSCREEN)
+		mt_application->mt_flags |= INPUT_MT_DIRECT;
+
+	/*
+	 * Model touchscreens providing buttons as touchpads.
+	 */
+	if (application == HID_DG_TOUCHPAD) {
+		mt_application->mt_flags |= INPUT_MT_POINTER;
+		td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
+	}
+
+	mt_application->scantime = DEFAULT_ZERO;
+	mt_application->raw_cc = DEFAULT_ZERO;
+	mt_application->quirks = td->mtclass.quirks;
+
+	list_add_tail(&mt_application->list, &td->applications);
+
+	return mt_application;
+}
+
+static struct mt_application *mt_find_application(struct mt_device *td,
+						  unsigned int application)
+{
+	struct mt_application *tmp, *mt_application = NULL;
+
+	list_for_each_entry(tmp, &td->applications, list) {
+		if (application == tmp->application) {
+			mt_application = tmp;
+			break;
+		}
+	}
+
+	if (!mt_application)
+		mt_application = mt_allocate_application(td, application);
+
+	return mt_application;
+}
+
+static struct mt_report_data *mt_allocate_report_data(struct mt_device *td,
+						      struct hid_report *report)
+{
+	struct mt_report_data *rdata;
+	struct hid_field *field;
+	int r, n;
+
+	rdata = devm_kzalloc(&td->hdev->dev, sizeof(*rdata), GFP_KERNEL);
+	if (!rdata)
+		return NULL;
+
+	rdata->report = report;
+	rdata->application = mt_find_application(td, report->application);
+
+	if (!rdata->application) {
+		devm_kfree(&td->hdev->dev, rdata);
+		return NULL;
+	}
+
+	for (r = 0; r < report->maxfield; r++) {
+		field = report->field[r];
+
+		if (!(HID_MAIN_ITEM_VARIABLE & field->flags))
+			continue;
+
+		for (n = 0; n < field->report_count; n++) {
+			if (field->usage[n].hid == HID_DG_CONTACTID)
+				rdata->is_mt_collection = true;
+		}
+	}
+
+	list_add_tail(&rdata->list, &td->reports);
+
+	return rdata;
+}
+
+static struct mt_report_data *mt_find_report_data(struct mt_device *td,
+						  struct hid_report *report)
+{
+	struct mt_report_data *tmp, *rdata = NULL;
+
+	list_for_each_entry(tmp, &td->reports, list) {
+		if (report == tmp->report) {
+			rdata = tmp;
+			break;
+		}
+	}
+
+	if (!rdata)
+		rdata = mt_allocate_report_data(td, report);
+
+	return rdata;
+}
+
+static void mt_store_field(struct hid_device *hdev,
+			   struct mt_application *application,
+			   __s32 *value,
+			   size_t offset)
+{
+	struct mt_usages *usage;
+	__s32 **target;
+
+	if (list_empty(&application->mt_usages))
+		usage = mt_allocate_usage(hdev, application);
+	else
+		usage = list_last_entry(&application->mt_usages,
+					struct mt_usages,
+					list);
+
+	if (!usage)
+		return;
+
+	target = (__s32 **)((char *)usage + offset);
+
+	/* the value has already been filled, create a new slot */
+	if (*target != DEFAULT_TRUE &&
+	    *target != DEFAULT_FALSE &&
+	    *target != DEFAULT_ZERO) {
+		usage = mt_allocate_usage(hdev, application);
+		if (!usage)
+			return;
+
+		target = (__s32 **)((char *)usage + offset);
+	}
+
+	*target = value;
+}
+
+#define MT_STORE_FIELD(__name)						\
+	mt_store_field(hdev, app,					\
+		       &field->value[usage->usage_index],		\
+		       offsetof(struct mt_usages, __name))
+
+static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max, struct mt_application *app)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_class *cls = &td->mtclass;
+	int code;
+	struct hid_usage *prev_usage = NULL;
+
+	/*
+	 * Model touchscreens providing buttons as touchpads.
+	 */
+	if (field->application == HID_DG_TOUCHSCREEN &&
+	    (usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		app->mt_flags |= INPUT_MT_POINTER;
+		td->inputmode_value = MT_INPUTMODE_TOUCHPAD;
+	}
+
+	/* count the buttons on touchpads */
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
+		app->buttons_count++;
+
+	if (usage->usage_index)
+		prev_usage = &field->usage[usage->usage_index - 1];
+
+	switch (usage->hid & HID_USAGE_PAGE) {
+
+	case HID_UP_GENDESK:
+		switch (usage->hid) {
+		case HID_GD_X:
+			if (prev_usage && (prev_usage->hid == usage->hid)) {
+				code = ABS_MT_TOOL_X;
+				MT_STORE_FIELD(cx);
+			} else {
+				code = ABS_MT_POSITION_X;
+				MT_STORE_FIELD(x);
+			}
+
+			set_abs(hi->input, code, field, cls->sn_move);
+
+			/*
+			 * A system multi-axis that exports X and Y has a high
+			 * chance of being used directly on a surface
+			 */
+			if (field->application == HID_GD_SYSTEM_MULTIAXIS) {
+				__set_bit(INPUT_PROP_DIRECT,
+					  hi->input->propbit);
+				input_set_abs_params(hi->input,
+						     ABS_MT_TOOL_TYPE,
+						     MT_TOOL_DIAL,
+						     MT_TOOL_DIAL, 0, 0);
+			}
+
+			return 1;
+		case HID_GD_Y:
+			if (prev_usage && (prev_usage->hid == usage->hid)) {
+				code = ABS_MT_TOOL_Y;
+				MT_STORE_FIELD(cy);
+			} else {
+				code = ABS_MT_POSITION_Y;
+				MT_STORE_FIELD(y);
+			}
+
+			set_abs(hi->input, code, field, cls->sn_move);
+
+			return 1;
+		}
+		return 0;
+
+	case HID_UP_DIGITIZER:
+		switch (usage->hid) {
+		case HID_DG_INRANGE:
+			if (app->quirks & MT_QUIRK_HOVERING) {
+				input_set_abs_params(hi->input,
+					ABS_MT_DISTANCE, 0, 1, 0, 0);
+			}
+			MT_STORE_FIELD(inrange_state);
+			return 1;
+		case HID_DG_CONFIDENCE:
+			if ((cls->name == MT_CLS_WIN_8 ||
+				cls->name == MT_CLS_WIN_8_DUAL) &&
+				(field->application == HID_DG_TOUCHPAD ||
+				 field->application == HID_DG_TOUCHSCREEN))
+				app->quirks |= MT_QUIRK_CONFIDENCE;
+
+			if (app->quirks & MT_QUIRK_CONFIDENCE)
+				input_set_abs_params(hi->input,
+						     ABS_MT_TOOL_TYPE,
+						     MT_TOOL_FINGER,
+						     MT_TOOL_PALM, 0, 0);
+
+			MT_STORE_FIELD(confidence_state);
+			return 1;
+		case HID_DG_TIPSWITCH:
+			if (field->application != HID_GD_SYSTEM_MULTIAXIS)
+				input_set_capability(hi->input,
+						     EV_KEY, BTN_TOUCH);
+			MT_STORE_FIELD(tip_state);
+			return 1;
+		case HID_DG_CONTACTID:
+			MT_STORE_FIELD(contactid);
+			app->touches_by_report++;
+			return 1;
+		case HID_DG_WIDTH:
+			if (!(app->quirks & MT_QUIRK_NO_AREA))
+				set_abs(hi->input, ABS_MT_TOUCH_MAJOR, field,
+					cls->sn_width);
+			MT_STORE_FIELD(w);
+			return 1;
+		case HID_DG_HEIGHT:
+			if (!(app->quirks & MT_QUIRK_NO_AREA)) {
+				set_abs(hi->input, ABS_MT_TOUCH_MINOR, field,
+					cls->sn_height);
+
+				/*
+				 * Only set ABS_MT_ORIENTATION if it is not
+				 * already set by the HID_DG_AZIMUTH usage.
+				 */
+				if (!test_bit(ABS_MT_ORIENTATION,
+						hi->input->absbit))
+					input_set_abs_params(hi->input,
+						ABS_MT_ORIENTATION, 0, 1, 0, 0);
+			}
+			MT_STORE_FIELD(h);
+			return 1;
+		case HID_DG_TIPPRESSURE:
+			set_abs(hi->input, ABS_MT_PRESSURE, field,
+				cls->sn_pressure);
+			MT_STORE_FIELD(p);
+			return 1;
+		case HID_DG_SCANTIME:
+			input_set_capability(hi->input, EV_MSC, MSC_TIMESTAMP);
+			app->scantime = &field->value[usage->usage_index];
+			app->scantime_logical_max = field->logical_maximum;
+			return 1;
+		case HID_DG_CONTACTCOUNT:
+			app->have_contact_count = true;
+			app->raw_cc = &field->value[usage->usage_index];
+			return 1;
+		case HID_DG_AZIMUTH:
+			/*
+			 * Azimuth has the range of [0, MAX) representing a full
+			 * revolution. Set ABS_MT_ORIENTATION to a quarter of
+			 * MAX according the definition of ABS_MT_ORIENTATION
+			 */
+			input_set_abs_params(hi->input, ABS_MT_ORIENTATION,
+				-field->logical_maximum / 4,
+				field->logical_maximum / 4,
+				cls->sn_move ?
+				field->logical_maximum / cls->sn_move : 0, 0);
+			MT_STORE_FIELD(a);
+			return 1;
+		case HID_DG_CONTACTMAX:
+			/* contact max are global to the report */
+			return -1;
+		case HID_DG_TOUCH:
+			/* Legacy devices use TIPSWITCH and not TOUCH.
+			 * Let's just ignore this field. */
+			return -1;
+		}
+		/* let hid-input decide for the others */
+		return 0;
+
+	case HID_UP_BUTTON:
+		code = BTN_MOUSE + ((usage->hid - 1) & HID_USAGE);
+		/*
+		 * MS PTP spec says that external buttons left and right have
+		 * usages 2 and 3.
+		 */
+		if ((app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS) &&
+		    field->application == HID_DG_TOUCHPAD &&
+		    (usage->hid & HID_USAGE) > 1)
+			code--;
+
+		if (field->application == HID_GD_SYSTEM_MULTIAXIS)
+			code = BTN_0  + ((usage->hid - 1) & HID_USAGE);
+
+		hid_map_usage(hi, usage, bit, max, EV_KEY, code);
+		input_set_capability(hi->input, EV_KEY, code);
+		return 1;
+
+	case 0xff000000:
+		/* we do not want to map these: no input-oriented meaning */
+		return -1;
+	}
+
+	return 0;
+}
+
+static int mt_compute_slot(struct mt_device *td, struct mt_application *app,
+			   struct mt_usages *slot,
+			   struct input_dev *input)
+{
+	__s32 quirks = app->quirks;
+
+	if (quirks & MT_QUIRK_SLOT_IS_CONTACTID)
+		return *slot->contactid;
+
+	if (quirks & MT_QUIRK_CYPRESS)
+		return cypress_compute_slot(app, slot);
+
+	if (quirks & MT_QUIRK_SLOT_IS_CONTACTNUMBER)
+		return app->num_received;
+
+	if (quirks & MT_QUIRK_SLOT_IS_CONTACTID_MINUS_ONE)
+		return *slot->contactid - 1;
+
+	return input_mt_get_slot_by_key(input, *slot->contactid);
+}
+
+static void mt_release_pending_palms(struct mt_device *td,
+				     struct mt_application *app,
+				     struct input_dev *input)
+{
+	int slotnum;
+	bool need_sync = false;
+
+	for_each_set_bit(slotnum, app->pending_palm_slots, td->maxcontacts) {
+		clear_bit(slotnum, app->pending_palm_slots);
+
+		input_mt_slot(input, slotnum);
+		input_mt_report_slot_state(input, MT_TOOL_PALM, false);
+
+		need_sync = true;
+	}
+
+	if (need_sync) {
+		input_mt_sync_frame(input);
+		input_sync(input);
+	}
+}
+
+/*
+ * this function is called when a whole packet has been received and processed,
+ * so that it can decide what to send to the input layer.
+ */
+static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
+			  struct input_dev *input)
+{
+	if (app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS)
+		input_event(input, EV_KEY, BTN_LEFT, app->left_button_state);
+
+	input_mt_sync_frame(input);
+	input_event(input, EV_MSC, MSC_TIMESTAMP, app->timestamp);
+	input_sync(input);
+
+	mt_release_pending_palms(td, app, input);
+
+	app->num_received = 0;
+	app->left_button_state = 0;
+
+	if (test_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags))
+		set_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
+	else
+		clear_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags);
+	clear_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags);
+}
+
+static int mt_compute_timestamp(struct mt_application *app, __s32 value)
+{
+	long delta = value - app->prev_scantime;
+	unsigned long jdelta = jiffies_to_usecs(jiffies - app->jiffies);
+
+	app->jiffies = jiffies;
+
+	if (delta < 0)
+		delta += app->scantime_logical_max;
+
+	/* HID_DG_SCANTIME is expressed in 100us, we want it in us. */
+	delta *= 100;
+
+	if (jdelta > MAX_TIMESTAMP_INTERVAL)
+		/* No data received for a while, resync the timestamp. */
+		return 0;
+	else
+		return app->timestamp + delta;
+}
+
+static int mt_touch_event(struct hid_device *hid, struct hid_field *field,
+				struct hid_usage *usage, __s32 value)
+{
+	/* we will handle the hidinput part later, now remains hiddev */
+	if (hid->claimed & HID_CLAIMED_HIDDEV && hid->hiddev_hid_event)
+		hid->hiddev_hid_event(hid, field, usage, value);
+
+	return 1;
+}
+
+static int mt_process_slot(struct mt_device *td, struct input_dev *input,
+			    struct mt_application *app,
+			    struct mt_usages *slot)
+{
+	struct input_mt *mt = input->mt;
+	__s32 quirks = app->quirks;
+	bool valid = true;
+	bool confidence_state = true;
+	bool inrange_state = false;
+	int active;
+	int slotnum;
+	int tool = MT_TOOL_FINGER;
+
+	if (!slot)
+		return -EINVAL;
+
+	if ((quirks & MT_QUIRK_CONTACT_CNT_ACCURATE) &&
+	    app->num_received >= app->num_expected)
+		return -EAGAIN;
+
+	if (!(quirks & MT_QUIRK_ALWAYS_VALID)) {
+		if (quirks & MT_QUIRK_VALID_IS_INRANGE)
+			valid = *slot->inrange_state;
+		if (quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
+			valid = *slot->tip_state;
+		if (quirks & MT_QUIRK_VALID_IS_CONFIDENCE)
+			valid = *slot->confidence_state;
+
+		if (!valid)
+			return 0;
+	}
+
+	slotnum = mt_compute_slot(td, app, slot, input);
+	if (slotnum < 0 || slotnum >= td->maxcontacts)
+		return 0;
+
+	if ((quirks & MT_QUIRK_IGNORE_DUPLICATES) && mt) {
+		struct input_mt_slot *i_slot = &mt->slots[slotnum];
+
+		if (input_mt_is_active(i_slot) &&
+		    input_mt_is_used(mt, i_slot))
+			return -EAGAIN;
+	}
+
+	if (quirks & MT_QUIRK_CONFIDENCE)
+		confidence_state = *slot->confidence_state;
+
+	if (quirks & MT_QUIRK_HOVERING)
+		inrange_state = *slot->inrange_state;
+
+	active = *slot->tip_state || inrange_state;
+
+	if (app->application == HID_GD_SYSTEM_MULTIAXIS)
+		tool = MT_TOOL_DIAL;
+	else if (unlikely(!confidence_state)) {
+		tool = MT_TOOL_PALM;
+		if (!active &&
+		    input_mt_is_active(&mt->slots[slotnum])) {
+			/*
+			 * The non-confidence was reported for
+			 * previously valid contact that is also no
+			 * longer valid. We can't simply report
+			 * lift-off as userspace will not be aware
+			 * of non-confidence, so we need to split
+			 * it into 2 events: active MT_TOOL_PALM
+			 * and a separate liftoff.
+			 */
+			active = true;
+			set_bit(slotnum, app->pending_palm_slots);
+		}
+	}
+
+	input_mt_slot(input, slotnum);
+	input_mt_report_slot_state(input, tool, active);
+	if (active) {
+		/* this finger is in proximity of the sensor */
+		int wide = (*slot->w > *slot->h);
+		int major = max(*slot->w, *slot->h);
+		int minor = min(*slot->w, *slot->h);
+		int orientation = wide;
+		int max_azimuth;
+		int azimuth;
+
+		if (slot->a != DEFAULT_ZERO) {
+			/*
+			 * Azimuth is counter-clockwise and ranges from [0, MAX)
+			 * (a full revolution). Convert it to clockwise ranging
+			 * [-MAX/2, MAX/2].
+			 *
+			 * Note that ABS_MT_ORIENTATION require us to report
+			 * the limit of [-MAX/4, MAX/4], but the value can go
+			 * out of range to [-MAX/2, MAX/2] to report an upside
+			 * down ellipsis.
+			 */
+			azimuth = *slot->a;
+			max_azimuth = input_abs_get_max(input,
+							ABS_MT_ORIENTATION);
+			if (azimuth > max_azimuth * 2)
+				azimuth -= max_azimuth * 4;
+			orientation = -azimuth;
+		}
+
+		if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) {
+			/*
+			 * divided by two to match visual scale of touch
+			 * for devices with this quirk
+			 */
+			major = major >> 1;
+			minor = minor >> 1;
+		}
+
+		input_event(input, EV_ABS, ABS_MT_POSITION_X, *slot->x);
+		input_event(input, EV_ABS, ABS_MT_POSITION_Y, *slot->y);
+		input_event(input, EV_ABS, ABS_MT_TOOL_X, *slot->cx);
+		input_event(input, EV_ABS, ABS_MT_TOOL_Y, *slot->cy);
+		input_event(input, EV_ABS, ABS_MT_DISTANCE, !*slot->tip_state);
+		input_event(input, EV_ABS, ABS_MT_ORIENTATION, orientation);
+		input_event(input, EV_ABS, ABS_MT_PRESSURE, *slot->p);
+		input_event(input, EV_ABS, ABS_MT_TOUCH_MAJOR, major);
+		input_event(input, EV_ABS, ABS_MT_TOUCH_MINOR, minor);
+
+		set_bit(MT_IO_FLAGS_ACTIVE_SLOTS, &td->mt_io_flags);
+	}
+
+	return 0;
+}
+
+static void mt_process_mt_event(struct hid_device *hid,
+				struct mt_application *app,
+				struct hid_field *field,
+				struct hid_usage *usage,
+				__s32 value,
+				bool first_packet)
+{
+	__s32 quirks = app->quirks;
+	struct input_dev *input = field->hidinput->input;
+
+	if (!usage->type || !(hid->claimed & HID_CLAIMED_INPUT))
+		return;
+
+	if (quirks & MT_QUIRK_WIN8_PTP_BUTTONS) {
+
+		/*
+		 * For Win8 PTP touchpads we should only look at
+		 * non finger/touch events in the first_packet of a
+		 * (possible) multi-packet frame.
+		 */
+		if (!first_packet)
+			return;
+
+		/*
+		 * For Win8 PTP touchpads we map both the clickpad click
+		 * and any "external" left buttons to BTN_LEFT if a
+		 * device claims to have both we need to report 1 for
+		 * BTN_LEFT if either is pressed, so we or all values
+		 * together and report the result in mt_sync_frame().
+		 */
+		if (usage->type == EV_KEY && usage->code == BTN_LEFT) {
+			app->left_button_state |= value;
+			return;
+		}
+	}
+
+	input_event(input, usage->type, usage->code, value);
+}
+
+static void mt_touch_report(struct hid_device *hid,
+			    struct mt_report_data *rdata)
+{
+	struct mt_device *td = hid_get_drvdata(hid);
+	struct hid_report *report = rdata->report;
+	struct mt_application *app = rdata->application;
+	struct hid_field *field;
+	struct input_dev *input;
+	struct mt_usages *slot;
+	bool first_packet;
+	unsigned count;
+	int r, n;
+	int scantime = 0;
+	int contact_count = -1;
+
+	/* sticky fingers release in progress, abort */
+	if (test_and_set_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+		return;
+
+	scantime = *app->scantime;
+	app->timestamp = mt_compute_timestamp(app, scantime);
+	if (app->raw_cc != DEFAULT_ZERO)
+		contact_count = *app->raw_cc;
+
+	/*
+	 * Includes multi-packet support where subsequent
+	 * packets are sent with zero contactcount.
+	 */
+	if (contact_count >= 0) {
+		/*
+		 * For Win8 PTPs the first packet (td->num_received == 0) may
+		 * have a contactcount of 0 if there only is a button event.
+		 * We double check that this is not a continuation packet
+		 * of a possible multi-packet frame be checking that the
+		 * timestamp has changed.
+		 */
+		if ((app->quirks & MT_QUIRK_WIN8_PTP_BUTTONS) &&
+		    app->num_received == 0 &&
+		    app->prev_scantime != scantime)
+			app->num_expected = contact_count;
+		/* A non 0 contact count always indicates a first packet */
+		else if (contact_count)
+			app->num_expected = contact_count;
+	}
+	app->prev_scantime = scantime;
+
+	first_packet = app->num_received == 0;
+
+	input = report->field[0]->hidinput->input;
+
+	list_for_each_entry(slot, &app->mt_usages, list) {
+		if (!mt_process_slot(td, input, app, slot))
+			app->num_received++;
+	}
+
+	for (r = 0; r < report->maxfield; r++) {
+		field = report->field[r];
+		count = field->report_count;
+
+		if (!(HID_MAIN_ITEM_VARIABLE & field->flags))
+			continue;
+
+		for (n = 0; n < count; n++)
+			mt_process_mt_event(hid, app, field,
+					    &field->usage[n], field->value[n],
+					    first_packet);
+	}
+
+	if (app->num_received >= app->num_expected)
+		mt_sync_frame(td, app, input);
+
+	/*
+	 * Windows 8 specs says 2 things:
+	 * - once a contact has been reported, it has to be reported in each
+	 *   subsequent report
+	 * - the report rate when fingers are present has to be at least
+	 *   the refresh rate of the screen, 60 or 120 Hz
+	 *
+	 * I interprete this that the specification forces a report rate of
+	 * at least 60 Hz for a touchscreen to be certified.
+	 * Which means that if we do not get a report whithin 16 ms, either
+	 * something wrong happens, either the touchscreen forgets to send
+	 * a release. Taking a reasonable margin allows to remove issues
+	 * with USB communication or the load of the machine.
+	 *
+	 * Given that Win 8 devices are forced to send a release, this will
+	 * only affect laggish machines and the ones that have a firmware
+	 * defect.
+	 */
+	if (app->quirks & MT_QUIRK_STICKY_FINGERS) {
+		if (test_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags))
+			mod_timer(&td->release_timer,
+				  jiffies + msecs_to_jiffies(100));
+		else
+			del_timer(&td->release_timer);
+	}
+
+	clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+}
+
+static int mt_touch_input_configured(struct hid_device *hdev,
+				     struct hid_input *hi,
+				     struct mt_application *app)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_class *cls = &td->mtclass;
+	struct input_dev *input = hi->input;
+	int ret;
+
+	if (!td->maxcontacts)
+		td->maxcontacts = MT_DEFAULT_MAXCONTACT;
+
+	mt_post_parse(td, app);
+	if (td->serial_maybe)
+		mt_post_parse_default_settings(td, app);
+
+	if (cls->is_indirect)
+		app->mt_flags |= INPUT_MT_POINTER;
+
+	if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
+		app->mt_flags |= INPUT_MT_DROP_UNUSED;
+
+	/* check for clickpads */
+	if ((app->mt_flags & INPUT_MT_POINTER) &&
+	    (app->buttons_count == 1))
+		td->is_buttonpad = true;
+
+	if (td->is_buttonpad)
+		__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+
+	app->pending_palm_slots = devm_kcalloc(&hi->input->dev,
+					       BITS_TO_LONGS(td->maxcontacts),
+					       sizeof(long),
+					       GFP_KERNEL);
+	if (!app->pending_palm_slots)
+		return -ENOMEM;
+
+	ret = input_mt_init_slots(input, td->maxcontacts, app->mt_flags);
+	if (ret)
+		return ret;
+
+	app->mt_flags = 0;
+	return 0;
+}
+
+#define mt_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, \
+						    max, EV_KEY, (c))
+static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_application *application;
+	struct mt_report_data *rdata;
+
+	rdata = mt_find_report_data(td, field->report);
+	if (!rdata) {
+		hid_err(hdev, "failed to allocate data for report\n");
+		return 0;
+	}
+
+	application = rdata->application;
+
+	/*
+	 * If mtclass.export_all_inputs is not set, only map fields from
+	 * TouchScreen or TouchPad collections. We need to ignore fields
+	 * that belong to other collections such as Mouse that might have
+	 * the same GenericDesktop usages.
+	 */
+	if (!td->mtclass.export_all_inputs &&
+	    field->application != HID_DG_TOUCHSCREEN &&
+	    field->application != HID_DG_PEN &&
+	    field->application != HID_DG_TOUCHPAD &&
+	    field->application != HID_GD_KEYBOARD &&
+	    field->application != HID_GD_SYSTEM_CONTROL &&
+	    field->application != HID_CP_CONSUMER_CONTROL &&
+	    field->application != HID_GD_WIRELESS_RADIO_CTLS &&
+	    field->application != HID_GD_SYSTEM_MULTIAXIS &&
+	    !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS &&
+	      application->quirks & MT_QUIRK_ASUS_CUSTOM_UP))
+		return -1;
+
+	/*
+	 * Some Asus keyboard+touchpad devices have the hotkeys defined in the
+	 * touchpad report descriptor. We need to treat these as an array to
+	 * map usages to input keys.
+	 */
+	if (field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS &&
+	    application->quirks & MT_QUIRK_ASUS_CUSTOM_UP &&
+	    (usage->hid & HID_USAGE_PAGE) == HID_UP_CUSTOM) {
+		set_bit(EV_REP, hi->input->evbit);
+		if (field->flags & HID_MAIN_ITEM_VARIABLE)
+			field->flags &= ~HID_MAIN_ITEM_VARIABLE;
+		switch (usage->hid & HID_USAGE) {
+		case 0x10: mt_map_key_clear(KEY_BRIGHTNESSDOWN);	break;
+		case 0x20: mt_map_key_clear(KEY_BRIGHTNESSUP);		break;
+		case 0x35: mt_map_key_clear(KEY_DISPLAY_OFF);		break;
+		case 0x6b: mt_map_key_clear(KEY_F21);			break;
+		case 0x6c: mt_map_key_clear(KEY_SLEEP);			break;
+		default:
+			return -1;
+		}
+		return 1;
+	}
+
+	if (rdata->is_mt_collection)
+		return mt_touch_input_mapping(hdev, hi, field, usage, bit, max,
+					      application);
+
+	/* let hid-core decide for the others */
+	return 0;
+}
+
+static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_report_data *rdata;
+
+	rdata = mt_find_report_data(td, field->report);
+	if (rdata && rdata->is_mt_collection) {
+		/* We own these mappings, tell hid-input to ignore them */
+		return -1;
+	}
+
+	/* let hid-core decide for the others */
+	return 0;
+}
+
+static int mt_event(struct hid_device *hid, struct hid_field *field,
+				struct hid_usage *usage, __s32 value)
+{
+	struct mt_device *td = hid_get_drvdata(hid);
+	struct mt_report_data *rdata;
+
+	rdata = mt_find_report_data(td, field->report);
+	if (rdata && rdata->is_mt_collection)
+		return mt_touch_event(hid, field, usage, value);
+
+	return 0;
+}
+
+static void mt_report(struct hid_device *hid, struct hid_report *report)
+{
+	struct mt_device *td = hid_get_drvdata(hid);
+	struct hid_field *field = report->field[0];
+	struct mt_report_data *rdata;
+
+	if (!(hid->claimed & HID_CLAIMED_INPUT))
+		return;
+
+	rdata = mt_find_report_data(td, report);
+	if (rdata && rdata->is_mt_collection)
+		return mt_touch_report(hid, rdata);
+
+	if (field && field->hidinput && field->hidinput->input)
+		input_sync(field->hidinput->input);
+}
+
+static bool mt_need_to_apply_feature(struct hid_device *hdev,
+				     struct hid_field *field,
+				     struct hid_usage *usage,
+				     enum latency_mode latency,
+				     bool surface_switch,
+				     bool button_switch,
+				     bool *inputmode_found)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	struct mt_class *cls = &td->mtclass;
+	struct hid_report *report = field->report;
+	unsigned int index = usage->usage_index;
+	char *buf;
+	u32 report_len;
+	int max;
+
+	switch (usage->hid) {
+	case HID_DG_INPUTMODE:
+		/*
+		 * Some elan panels wrongly declare 2 input mode features,
+		 * and silently ignore when we set the value in the second
+		 * field. Skip the second feature and hope for the best.
+		 */
+		if (*inputmode_found)
+			return false;
+
+		if (cls->quirks & MT_QUIRK_FORCE_GET_FEATURE) {
+			report_len = hid_report_len(report);
+			buf = hid_alloc_report_buf(report, GFP_KERNEL);
+			if (!buf) {
+				hid_err(hdev,
+					"failed to allocate buffer for report\n");
+				return false;
+			}
+			hid_hw_raw_request(hdev, report->id, buf, report_len,
+					   HID_FEATURE_REPORT,
+					   HID_REQ_GET_REPORT);
+			kfree(buf);
+		}
+
+		field->value[index] = td->inputmode_value;
+		*inputmode_found = true;
+		return true;
+
+	case HID_DG_CONTACTMAX:
+		if (cls->maxcontacts) {
+			max = min_t(int, field->logical_maximum,
+				    cls->maxcontacts);
+			if (field->value[index] != max) {
+				field->value[index] = max;
+				return true;
+			}
+		}
+		break;
+
+	case HID_DG_LATENCYMODE:
+		field->value[index] = latency;
+		return true;
+
+	case HID_DG_SURFACESWITCH:
+		field->value[index] = surface_switch;
+		return true;
+
+	case HID_DG_BUTTONSWITCH:
+		field->value[index] = button_switch;
+		return true;
+	}
+
+	return false; /* no need to update the report */
+}
+
+static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
+			 bool surface_switch, bool button_switch)
+{
+	struct hid_report_enum *rep_enum;
+	struct hid_report *rep;
+	struct hid_usage *usage;
+	int i, j;
+	bool update_report;
+	bool inputmode_found = false;
+
+	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(rep, &rep_enum->report_list, list) {
+		update_report = false;
+
+		for (i = 0; i < rep->maxfield; i++) {
+			/* Ignore if report count is out of bounds. */
+			if (rep->field[i]->report_count < 1)
+				continue;
+
+			for (j = 0; j < rep->field[i]->maxusage; j++) {
+				usage = &rep->field[i]->usage[j];
+
+				if (mt_need_to_apply_feature(hdev,
+							     rep->field[i],
+							     usage,
+							     latency,
+							     surface_switch,
+							     button_switch,
+							     &inputmode_found))
+					update_report = true;
+			}
+		}
+
+		if (update_report)
+			hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
+	}
+}
+
+static void mt_post_parse_default_settings(struct mt_device *td,
+					   struct mt_application *app)
+{
+	__s32 quirks = app->quirks;
+
+	/* unknown serial device needs special quirks */
+	if (list_is_singular(&app->mt_usages)) {
+		quirks |= MT_QUIRK_ALWAYS_VALID;
+		quirks &= ~MT_QUIRK_NOT_SEEN_MEANS_UP;
+		quirks &= ~MT_QUIRK_VALID_IS_INRANGE;
+		quirks &= ~MT_QUIRK_VALID_IS_CONFIDENCE;
+		quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
+	}
+
+	app->quirks = quirks;
+}
+
+static void mt_post_parse(struct mt_device *td, struct mt_application *app)
+{
+	if (!app->have_contact_count)
+		app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE;
+}
+
+static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+	char *name;
+	const char *suffix = NULL;
+	unsigned int application = 0;
+	struct mt_report_data *rdata;
+	struct mt_application *mt_application = NULL;
+	struct hid_report *report;
+	int ret;
+
+	list_for_each_entry(report, &hi->reports, hidinput_list) {
+		application = report->application;
+		rdata = mt_find_report_data(td, report);
+		if (!rdata) {
+			hid_err(hdev, "failed to allocate data for report\n");
+			return -ENOMEM;
+		}
+
+		mt_application = rdata->application;
+
+		if (rdata->is_mt_collection) {
+			ret = mt_touch_input_configured(hdev, hi,
+							mt_application);
+			if (ret)
+				return ret;
+		}
+
+		/*
+		 * some egalax touchscreens have "application == DG_TOUCHSCREEN"
+		 * for the stylus. Check this first, and then rely on
+		 * the application field.
+		 */
+		if (report->field[0]->physical == HID_DG_STYLUS) {
+			suffix = "Pen";
+			/* force BTN_STYLUS to allow tablet matching in udev */
+			__set_bit(BTN_STYLUS, hi->input->keybit);
+		}
+	}
+
+	if (!suffix) {
+		switch (application) {
+		case HID_GD_KEYBOARD:
+		case HID_GD_KEYPAD:
+		case HID_GD_MOUSE:
+		case HID_DG_TOUCHPAD:
+		case HID_GD_SYSTEM_CONTROL:
+		case HID_CP_CONSUMER_CONTROL:
+		case HID_GD_WIRELESS_RADIO_CTLS:
+		case HID_GD_SYSTEM_MULTIAXIS:
+			/* already handled by hid core */
+			break;
+		case HID_DG_TOUCHSCREEN:
+			/* we do not set suffix = "Touchscreen" */
+			hi->input->name = hdev->name;
+			break;
+		case HID_DG_STYLUS:
+			/* force BTN_STYLUS to allow tablet matching in udev */
+			__set_bit(BTN_STYLUS, hi->input->keybit);
+			break;
+		case HID_VD_ASUS_CUSTOM_MEDIA_KEYS:
+			suffix = "Custom Media Keys";
+			break;
+		default:
+			suffix = "UNKNOWN";
+			break;
+		}
+	}
+
+	if (suffix) {
+		name = devm_kzalloc(&hi->input->dev,
+				    strlen(hdev->name) + strlen(suffix) + 2,
+				    GFP_KERNEL);
+		if (name) {
+			sprintf(name, "%s %s", hdev->name, suffix);
+			hi->input->name = name;
+		}
+	}
+
+	return 0;
+}
+
+static void mt_fix_const_field(struct hid_field *field, unsigned int usage)
+{
+	if (field->usage[0].hid != usage ||
+	    !(field->flags & HID_MAIN_ITEM_CONSTANT))
+		return;
+
+	field->flags &= ~HID_MAIN_ITEM_CONSTANT;
+	field->flags |= HID_MAIN_ITEM_VARIABLE;
+}
+
+static void mt_fix_const_fields(struct hid_device *hdev, unsigned int usage)
+{
+	struct hid_report *report;
+	int i;
+
+	list_for_each_entry(report,
+			    &hdev->report_enum[HID_INPUT_REPORT].report_list,
+			    list) {
+
+		if (!report->maxfield)
+			continue;
+
+		for (i = 0; i < report->maxfield; i++)
+			if (report->field[i]->maxusage >= 1)
+				mt_fix_const_field(report->field[i], usage);
+	}
+}
+
+static void mt_release_contacts(struct hid_device *hid)
+{
+	struct hid_input *hidinput;
+	struct mt_application *application;
+	struct mt_device *td = hid_get_drvdata(hid);
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		struct input_dev *input_dev = hidinput->input;
+		struct input_mt *mt = input_dev->mt;
+		int i;
+
+		if (mt) {
+			for (i = 0; i < mt->num_slots; i++) {
+				input_mt_slot(input_dev, i);
+				input_mt_report_slot_state(input_dev,
+							   MT_TOOL_FINGER,
+							   false);
+			}
+			input_mt_sync_frame(input_dev);
+			input_sync(input_dev);
+		}
+	}
+
+	list_for_each_entry(application, &td->applications, list) {
+		application->num_received = 0;
+	}
+}
+
+static void mt_expired_timeout(struct timer_list *t)
+{
+	struct mt_device *td = from_timer(td, t, release_timer);
+	struct hid_device *hdev = td->hdev;
+
+	/*
+	 * An input report came in just before we release the sticky fingers,
+	 * it will take care of the sticky fingers.
+	 */
+	if (test_and_set_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags))
+		return;
+	if (test_bit(MT_IO_FLAGS_PENDING_SLOTS, &td->mt_io_flags))
+		mt_release_contacts(hdev);
+	clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+}
+
+static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret, i;
+	struct mt_device *td;
+	const struct mt_class *mtclass = mt_classes; /* MT_CLS_DEFAULT */
+
+	for (i = 0; mt_classes[i].name ; i++) {
+		if (id->driver_data == mt_classes[i].name) {
+			mtclass = &(mt_classes[i]);
+			break;
+		}
+	}
+
+	td = devm_kzalloc(&hdev->dev, sizeof(struct mt_device), GFP_KERNEL);
+	if (!td) {
+		dev_err(&hdev->dev, "cannot allocate multitouch data\n");
+		return -ENOMEM;
+	}
+	td->hdev = hdev;
+	td->mtclass = *mtclass;
+	td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
+	hid_set_drvdata(hdev, td);
+
+	INIT_LIST_HEAD(&td->applications);
+	INIT_LIST_HEAD(&td->reports);
+
+	if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
+		td->serial_maybe = true;
+
+	/* This allows the driver to correctly support devices
+	 * that emit events over several HID messages.
+	 */
+	hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;
+
+	/*
+	 * This allows the driver to handle different input sensors
+	 * that emits events through different applications on the same HID
+	 * device.
+	 */
+	hdev->quirks |= HID_QUIRK_INPUT_PER_APP;
+
+	if (id->group != HID_GROUP_MULTITOUCH_WIN_8)
+		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+	timer_setup(&td->release_timer, mt_expired_timeout, 0);
+
+	ret = hid_parse(hdev);
+	if (ret != 0)
+		return ret;
+
+	if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID)
+		mt_fix_const_fields(hdev, HID_DG_CONTACTID);
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		return ret;
+
+	ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group);
+	if (ret)
+		dev_warn(&hdev->dev, "Cannot allocate sysfs group for %s\n",
+				hdev->name);
+
+	mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int mt_reset_resume(struct hid_device *hdev)
+{
+	mt_release_contacts(hdev);
+	mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
+	return 0;
+}
+
+static int mt_resume(struct hid_device *hdev)
+{
+	/* Some Elan legacy devices require SET_IDLE to be set on resume.
+	 * It should be safe to send it to other devices too.
+	 * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */
+
+	hid_hw_idle(hdev, 0, 0, HID_REQ_SET_IDLE);
+
+	return 0;
+}
+#endif
+
+static void mt_remove(struct hid_device *hdev)
+{
+	struct mt_device *td = hid_get_drvdata(hdev);
+
+	del_timer_sync(&td->release_timer);
+
+	sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group);
+	hid_hw_stop(hdev);
+}
+
+/*
+ * This list contains only:
+ * - VID/PID of products not working with the default multitouch handling
+ * - 2 generic rules.
+ * So there is no point in adding here any device with MT_CLS_DEFAULT.
+ */
+static const struct hid_device_id mt_devices[] = {
+
+	/* 3M panels */
+	{ .driver_data = MT_CLS_3M,
+		MT_USB_DEVICE(USB_VENDOR_ID_3M,
+			USB_DEVICE_ID_3M1968) },
+	{ .driver_data = MT_CLS_3M,
+		MT_USB_DEVICE(USB_VENDOR_ID_3M,
+			USB_DEVICE_ID_3M2256) },
+	{ .driver_data = MT_CLS_3M,
+		MT_USB_DEVICE(USB_VENDOR_ID_3M,
+			USB_DEVICE_ID_3M3266) },
+
+	/* Alps devices */
+	{ .driver_data = MT_CLS_WIN_8_DUAL,
+		HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+			USB_VENDOR_ID_ALPS_JP,
+			HID_DEVICE_ID_ALPS_U1_DUAL_PTP) },
+	{ .driver_data = MT_CLS_WIN_8_DUAL,
+		HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+			USB_VENDOR_ID_ALPS_JP,
+			HID_DEVICE_ID_ALPS_U1_DUAL_3BTN_PTP) },
+
+	/* Lenovo X1 TAB Gen 2 */
+	{ .driver_data = MT_CLS_WIN_8_DUAL,
+		HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+			   USB_VENDOR_ID_LENOVO,
+			   USB_DEVICE_ID_LENOVO_X1_TAB) },
+
+	/* Anton devices */
+	{ .driver_data = MT_CLS_EXPORT_ALL_INPUTS,
+		MT_USB_DEVICE(USB_VENDOR_ID_ANTON,
+			USB_DEVICE_ID_ANTON_TOUCH_PAD) },
+
+	/* Asus T304UA */
+	{ .driver_data = MT_CLS_ASUS,
+		HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
+			USB_VENDOR_ID_ASUSTEK,
+			USB_DEVICE_ID_ASUSTEK_T304_KEYBOARD) },
+
+	/* Atmel panels */
+	{ .driver_data = MT_CLS_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_ATMEL,
+			USB_DEVICE_ID_ATMEL_MXT_DIGITIZER) },
+
+	/* Baanto multitouch devices */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_BAANTO,
+			USB_DEVICE_ID_BAANTO_MT_190W2) },
+
+	/* Cando panels */
+	{ .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER,
+		MT_USB_DEVICE(USB_VENDOR_ID_CANDO,
+			USB_DEVICE_ID_CANDO_MULTI_TOUCH) },
+	{ .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER,
+		MT_USB_DEVICE(USB_VENDOR_ID_CANDO,
+			USB_DEVICE_ID_CANDO_MULTI_TOUCH_15_6) },
+
+	/* Chunghwa Telecom touch panels */
+	{  .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_CHUNGHWAT,
+			USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH) },
+
+	/* Cirque devices */
+	{ .driver_data = MT_CLS_WIN_8_DUAL,
+		HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+			I2C_VENDOR_ID_CIRQUE,
+			I2C_PRODUCT_ID_CIRQUE_121F) },
+
+	/* CJTouch panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_CJTOUCH,
+			USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0020) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_CJTOUCH,
+			USB_DEVICE_ID_CJTOUCH_MULTI_TOUCH_0040) },
+
+	/* CVTouch panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_CVTOUCH,
+			USB_DEVICE_ID_CVTOUCH_SCREEN) },
+
+	/* eGalax devices (resistive) */
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480D) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_480E) },
+
+	/* eGalax devices (capacitive) */
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7207) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_720C) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7224) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_722A) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_725E) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7262) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_726B) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72A1) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72AA) },
+	{ .driver_data = MT_CLS_EGALAX,
+		HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72C4) },
+	{ .driver_data = MT_CLS_EGALAX,
+		HID_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72D0) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_72FA) },
+	{ .driver_data = MT_CLS_EGALAX,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7302) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7) },
+	{ .driver_data = MT_CLS_EGALAX_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
+			USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) },
+
+	/* Elitegroup panel */
+	{ .driver_data = MT_CLS_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_ELITEGROUP,
+			USB_DEVICE_ID_ELITEGROUP_05D8) },
+
+	/* Flatfrog Panels */
+	{ .driver_data = MT_CLS_FLATFROG,
+		MT_USB_DEVICE(USB_VENDOR_ID_FLATFROG,
+			USB_DEVICE_ID_MULTITOUCH_3200) },
+
+	/* FocalTech Panels */
+	{ .driver_data = MT_CLS_SERIAL,
+		MT_USB_DEVICE(USB_VENDOR_ID_CYGNAL,
+			USB_DEVICE_ID_FOCALTECH_FTXXXX_MULTITOUCH) },
+
+	/* GeneralTouch panel */
+	{ .driver_data = MT_CLS_GENERALTOUCH_TWOFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PWT_TENFINGERS) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_TWOFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0101) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0102) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_0106) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_010A) },
+	{ .driver_data = MT_CLS_GENERALTOUCH_PWT_TENFINGERS,
+		MT_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH,
+			USB_DEVICE_ID_GENERAL_TOUCH_WIN8_PIT_E100) },
+
+	/* Gametel game controller */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_BT_DEVICE(USB_VENDOR_ID_FRUCTEL,
+			USB_DEVICE_ID_GAMETEL_MT_MODE) },
+
+	/* GoodTouch panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_GOODTOUCH,
+			USB_DEVICE_ID_GOODTOUCH_000f) },
+
+	/* Hanvon panels */
+	{ .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID,
+		MT_USB_DEVICE(USB_VENDOR_ID_HANVON_ALT,
+			USB_DEVICE_ID_HANVON_ALT_MULTITOUCH) },
+
+	/* Ilitek dual touch panel */
+	{  .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_ILITEK,
+			USB_DEVICE_ID_ILITEK_MULTITOUCH) },
+
+	/* LG Melfas panel */
+	{ .driver_data = MT_CLS_LG,
+		HID_USB_DEVICE(USB_VENDOR_ID_LG,
+			USB_DEVICE_ID_LG_MELFAS_MT) },
+
+	/* MosArt panels */
+	{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
+		MT_USB_DEVICE(USB_VENDOR_ID_ASUS,
+			USB_DEVICE_ID_ASUS_T91MT)},
+	{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
+		MT_USB_DEVICE(USB_VENDOR_ID_ASUS,
+			USB_DEVICE_ID_ASUSTEK_MULTITOUCH_YFO) },
+	{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
+		MT_USB_DEVICE(USB_VENDOR_ID_TURBOX,
+			USB_DEVICE_ID_TURBOX_TOUCHSCREEN_MOSART) },
+
+	/* Novatek Panel */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_NOVATEK,
+			USB_DEVICE_ID_NOVATEK_PCT) },
+
+	/* Ntrig Panel */
+	{ .driver_data = MT_CLS_NSMU,
+		HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+			USB_VENDOR_ID_NTRIG, 0x1b05) },
+
+	/* Panasonic panels */
+	{ .driver_data = MT_CLS_PANASONIC,
+		MT_USB_DEVICE(USB_VENDOR_ID_PANASONIC,
+			USB_DEVICE_ID_PANABOARD_UBT780) },
+	{ .driver_data = MT_CLS_PANASONIC,
+		MT_USB_DEVICE(USB_VENDOR_ID_PANASONIC,
+			USB_DEVICE_ID_PANABOARD_UBT880) },
+
+	/* PixArt optical touch screen */
+	{ .driver_data = MT_CLS_INRANGE_CONTACTNUMBER,
+		MT_USB_DEVICE(USB_VENDOR_ID_PIXART,
+			USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN) },
+	{ .driver_data = MT_CLS_INRANGE_CONTACTNUMBER,
+		MT_USB_DEVICE(USB_VENDOR_ID_PIXART,
+			USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1) },
+	{ .driver_data = MT_CLS_INRANGE_CONTACTNUMBER,
+		MT_USB_DEVICE(USB_VENDOR_ID_PIXART,
+			USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2) },
+
+	/* PixCir-based panels */
+	{ .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID,
+		MT_USB_DEVICE(USB_VENDOR_ID_CANDO,
+			USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH) },
+
+	/* Quanta-based panels */
+	{ .driver_data = MT_CLS_CONFIDENCE_CONTACT_ID,
+		MT_USB_DEVICE(USB_VENDOR_ID_QUANTA,
+			USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001) },
+
+	/* Razer touchpads */
+	{ .driver_data = MT_CLS_RAZER_BLADE_STEALTH,
+		HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+			USB_VENDOR_ID_SYNAPTICS, 0x8323) },
+
+	/* Stantum panels */
+	{ .driver_data = MT_CLS_CONFIDENCE,
+		MT_USB_DEVICE(USB_VENDOR_ID_STANTUM_STM,
+			USB_DEVICE_ID_MTP_STM)},
+
+	/* TopSeed panels */
+	{ .driver_data = MT_CLS_TOPSEED,
+		MT_USB_DEVICE(USB_VENDOR_ID_TOPSEED2,
+			USB_DEVICE_ID_TOPSEED2_PERIPAD_701) },
+
+	/* Touch International panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_TOUCH_INTL,
+			USB_DEVICE_ID_TOUCH_INTL_MULTI_TOUCH) },
+
+	/* Unitec panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_UNITEC,
+			USB_DEVICE_ID_UNITEC_USB_TOUCH_0709) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_UNITEC,
+			USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19) },
+
+	/* VTL panels */
+	{ .driver_data = MT_CLS_VTL,
+		MT_USB_DEVICE(USB_VENDOR_ID_VTL,
+			USB_DEVICE_ID_VTL_MULTITOUCH_FF3F) },
+
+	/* Wistron panels */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_WISTRON,
+			USB_DEVICE_ID_WISTRON_OPTICAL_TOUCH) },
+
+	/* XAT */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XAT,
+			USB_DEVICE_ID_XAT_CSR) },
+
+	/* Xiroku */
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_SPX) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_MPX) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_CSR) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_SPX1) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_MPX1) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_CSR1) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_SPX2) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_MPX2) },
+	{ .driver_data = MT_CLS_NSMU,
+		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+			USB_DEVICE_ID_XIROKU_CSR2) },
+
+	/* Google MT devices */
+	{ .driver_data = MT_CLS_GOOGLE,
+		HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE,
+			USB_DEVICE_ID_GOOGLE_TOUCH_ROSE) },
+
+	/* Generic MT device */
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH, HID_ANY_ID, HID_ANY_ID) },
+
+	/* Generic Win 8 certified MT device */
+	{  .driver_data = MT_CLS_WIN_8,
+		HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8,
+			HID_ANY_ID, HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, mt_devices);
+
+static const struct hid_usage_id mt_grabbed_usages[] = {
+	{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
+	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
+};
+
+static struct hid_driver mt_driver = {
+	.name = "hid-multitouch",
+	.id_table = mt_devices,
+	.probe = mt_probe,
+	.remove = mt_remove,
+	.input_mapping = mt_input_mapping,
+	.input_mapped = mt_input_mapped,
+	.input_configured = mt_input_configured,
+	.feature_mapping = mt_feature_mapping,
+	.usage_table = mt_grabbed_usages,
+	.event = mt_event,
+	.report = mt_report,
+#ifdef CONFIG_PM
+	.reset_resume = mt_reset_resume,
+	.resume = mt_resume,
+#endif
+};
+module_hid_driver(mt_driver);
diff --git a/drivers/hid/hid-nti.c b/drivers/hid/hid-nti.c
new file mode 100644
index 0000000..5bb827b
--- /dev/null
+++ b/drivers/hid/hid-nti.c
@@ -0,0 +1,59 @@
+/*
+ *  USB HID quirks support for Network Technologies, Inc. "USB-SUN" USB
+ *  adapter for pre-USB Sun keyboards
+ *
+ *  Copyright (c) 2011 Google, Inc.
+ *
+ * Based on HID apple driver by
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby <jirislaby@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Jonathan Klabunde Tomer <jktomer@google.com>");
+MODULE_DESCRIPTION("HID driver for Network Technologies USB-SUN keyboard adapter");
+
+/*
+ * NTI Sun keyboard adapter has wrong logical maximum in report descriptor
+ */
+static __u8 *nti_usbsun_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 60 && rdesc[53] == 0x65 && rdesc[59] == 0x65) {
+		hid_info(hdev, "fixing up NTI USB-SUN keyboard adapter report descriptor\n");
+		rdesc[53] = rdesc[59] = 0xe7;
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id nti_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, nti_devices);
+
+static struct hid_driver nti_driver = {
+	.name = "nti",
+	.id_table = nti_devices,
+	.report_fixup = nti_usbsun_report_fixup
+};
+
+module_hid_driver(nti_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ntrig.c b/drivers/hid/hid-ntrig.c
new file mode 100644
index 0000000..9bc6f48
--- /dev/null
+++ b/drivers/hid/hid-ntrig.c
@@ -0,0 +1,1036 @@
+/*
+ *  HID driver for N-Trig touchscreens
+ *
+ *  Copyright (c) 2008-2010 Rafi Rubin
+ *  Copyright (c) 2009-2010 Stephane Chatty
+ *
+ */
+
+/*
+ * 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/usb.h>
+#include "usbhid/usbhid.h"
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include "hid-ids.h"
+
+#define NTRIG_DUPLICATE_USAGES	0x001
+
+static unsigned int min_width;
+module_param(min_width, uint, 0644);
+MODULE_PARM_DESC(min_width, "Minimum touch contact width to accept.");
+
+static unsigned int min_height;
+module_param(min_height, uint, 0644);
+MODULE_PARM_DESC(min_height, "Minimum touch contact height to accept.");
+
+static unsigned int activate_slack = 1;
+module_param(activate_slack, uint, 0644);
+MODULE_PARM_DESC(activate_slack, "Number of touch frames to ignore at "
+		 "the start of touch input.");
+
+static unsigned int deactivate_slack = 4;
+module_param(deactivate_slack, uint, 0644);
+MODULE_PARM_DESC(deactivate_slack, "Number of empty frames to ignore before "
+		 "deactivating touch.");
+
+static unsigned int activation_width = 64;
+module_param(activation_width, uint, 0644);
+MODULE_PARM_DESC(activation_width, "Width threshold to immediately start "
+		 "processing touch events.");
+
+static unsigned int activation_height = 32;
+module_param(activation_height, uint, 0644);
+MODULE_PARM_DESC(activation_height, "Height threshold to immediately start "
+		 "processing touch events.");
+
+struct ntrig_data {
+	/* Incoming raw values for a single contact */
+	__u16 x, y, w, h;
+	__u16 id;
+
+	bool tipswitch;
+	bool confidence;
+	bool first_contact_touch;
+
+	bool reading_mt;
+
+	__u8 mt_footer[4];
+	__u8 mt_foot_count;
+
+	/* The current activation state. */
+	__s8 act_state;
+
+	/* Empty frames to ignore before recognizing the end of activity */
+	__s8 deactivate_slack;
+
+	/* Frames to ignore before acknowledging the start of activity */
+	__s8 activate_slack;
+
+	/* Minimum size contact to accept */
+	__u16 min_width;
+	__u16 min_height;
+
+	/* Threshold to override activation slack */
+	__u16 activation_width;
+	__u16 activation_height;
+
+	__u16 sensor_logical_width;
+	__u16 sensor_logical_height;
+	__u16 sensor_physical_width;
+	__u16 sensor_physical_height;
+};
+
+
+/*
+ * This function converts the 4 byte raw firmware code into
+ * a string containing 5 comma separated numbers.
+ */
+static int ntrig_version_string(unsigned char *raw, char *buf)
+{
+	__u8 a =  (raw[1] & 0x0e) >> 1;
+	__u8 b =  (raw[0] & 0x3c) >> 2;
+	__u8 c = ((raw[0] & 0x03) << 3) | ((raw[3] & 0xe0) >> 5);
+	__u8 d = ((raw[3] & 0x07) << 3) | ((raw[2] & 0xe0) >> 5);
+	__u8 e =   raw[2] & 0x07;
+
+	/*
+	 * As yet unmapped bits:
+	 * 0b11000000 0b11110001 0b00011000 0b00011000
+	 */
+
+	return sprintf(buf, "%u.%u.%u.%u.%u", a, b, c, d, e);
+}
+
+static inline int ntrig_get_mode(struct hid_device *hdev)
+{
+	struct hid_report *report = hdev->report_enum[HID_FEATURE_REPORT].
+				    report_id_hash[0x0d];
+
+	if (!report || report->maxfield < 1 ||
+	    report->field[0]->report_count < 1)
+		return -EINVAL;
+
+	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+	hid_hw_wait(hdev);
+	return (int)report->field[0]->value[0];
+}
+
+static inline void ntrig_set_mode(struct hid_device *hdev, const int mode)
+{
+	struct hid_report *report;
+	__u8 mode_commands[4] = { 0xe, 0xf, 0x1b, 0x10 };
+
+	if (mode < 0 || mode > 3)
+		return;
+
+	report = hdev->report_enum[HID_FEATURE_REPORT].
+		 report_id_hash[mode_commands[mode]];
+
+	if (!report)
+		return;
+
+	hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+}
+
+static void ntrig_report_version(struct hid_device *hdev)
+{
+	int ret;
+	char buf[20];
+	struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+	unsigned char *data = kmalloc(8, GFP_KERNEL);
+
+	if (!data)
+		goto err_free;
+
+	ret = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			      USB_REQ_CLEAR_FEATURE,
+			      USB_TYPE_CLASS | USB_RECIP_INTERFACE |
+			      USB_DIR_IN,
+			      0x30c, 1, data, 8,
+			      USB_CTRL_SET_TIMEOUT);
+
+	if (ret == 8) {
+		ret = ntrig_version_string(&data[2], buf);
+
+		hid_info(hdev, "Firmware version: %s (%02x%02x %02x%02x)\n",
+			 buf, data[2], data[3], data[4], data[5]);
+	}
+
+err_free:
+	kfree(data);
+}
+
+static ssize_t show_phys_width(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->sensor_physical_width);
+}
+
+static DEVICE_ATTR(sensor_physical_width, S_IRUGO, show_phys_width, NULL);
+
+static ssize_t show_phys_height(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->sensor_physical_height);
+}
+
+static DEVICE_ATTR(sensor_physical_height, S_IRUGO, show_phys_height, NULL);
+
+static ssize_t show_log_width(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->sensor_logical_width);
+}
+
+static DEVICE_ATTR(sensor_logical_width, S_IRUGO, show_log_width, NULL);
+
+static ssize_t show_log_height(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->sensor_logical_height);
+}
+
+static DEVICE_ATTR(sensor_logical_height, S_IRUGO, show_log_height, NULL);
+
+static ssize_t show_min_width(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->min_width *
+				    nd->sensor_physical_width /
+				    nd->sensor_logical_width);
+}
+
+static ssize_t set_min_width(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > nd->sensor_physical_width)
+		return -EINVAL;
+
+	nd->min_width = val * nd->sensor_logical_width /
+			      nd->sensor_physical_width;
+
+	return count;
+}
+
+static DEVICE_ATTR(min_width, S_IWUSR | S_IRUGO, show_min_width, set_min_width);
+
+static ssize_t show_min_height(struct device *dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->min_height *
+				    nd->sensor_physical_height /
+				    nd->sensor_logical_height);
+}
+
+static ssize_t set_min_height(struct device *dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > nd->sensor_physical_height)
+		return -EINVAL;
+
+	nd->min_height = val * nd->sensor_logical_height /
+			       nd->sensor_physical_height;
+
+	return count;
+}
+
+static DEVICE_ATTR(min_height, S_IWUSR | S_IRUGO, show_min_height,
+		   set_min_height);
+
+static ssize_t show_activate_slack(struct device *dev,
+				   struct device_attribute *attr,
+				   char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->activate_slack);
+}
+
+static ssize_t set_activate_slack(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > 0x7f)
+		return -EINVAL;
+
+	nd->activate_slack = val;
+
+	return count;
+}
+
+static DEVICE_ATTR(activate_slack, S_IWUSR | S_IRUGO, show_activate_slack,
+		   set_activate_slack);
+
+static ssize_t show_activation_width(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->activation_width *
+				    nd->sensor_physical_width /
+				    nd->sensor_logical_width);
+}
+
+static ssize_t set_activation_width(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > nd->sensor_physical_width)
+		return -EINVAL;
+
+	nd->activation_width = val * nd->sensor_logical_width /
+				     nd->sensor_physical_width;
+
+	return count;
+}
+
+static DEVICE_ATTR(activation_width, S_IWUSR | S_IRUGO, show_activation_width,
+		   set_activation_width);
+
+static ssize_t show_activation_height(struct device *dev,
+				      struct device_attribute *attr,
+				      char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", nd->activation_height *
+				    nd->sensor_physical_height /
+				    nd->sensor_logical_height);
+}
+
+static ssize_t set_activation_height(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	if (val > nd->sensor_physical_height)
+		return -EINVAL;
+
+	nd->activation_height = val * nd->sensor_logical_height /
+				      nd->sensor_physical_height;
+
+	return count;
+}
+
+static DEVICE_ATTR(activation_height, S_IWUSR | S_IRUGO,
+		   show_activation_height, set_activation_height);
+
+static ssize_t show_deactivate_slack(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	return sprintf(buf, "%d\n", -nd->deactivate_slack);
+}
+
+static ssize_t set_deactivate_slack(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	unsigned long val;
+
+	if (kstrtoul(buf, 0, &val))
+		return -EINVAL;
+
+	/*
+	 * No more than 8 terminal frames have been observed so far
+	 * and higher slack is highly likely to leave the single
+	 * touch emulation stuck down.
+	 */
+	if (val > 7)
+		return -EINVAL;
+
+	nd->deactivate_slack = -val;
+
+	return count;
+}
+
+static DEVICE_ATTR(deactivate_slack, S_IWUSR | S_IRUGO, show_deactivate_slack,
+		   set_deactivate_slack);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_sensor_physical_width.attr,
+	&dev_attr_sensor_physical_height.attr,
+	&dev_attr_sensor_logical_width.attr,
+	&dev_attr_sensor_logical_height.attr,
+	&dev_attr_min_height.attr,
+	&dev_attr_min_width.attr,
+	&dev_attr_activate_slack.attr,
+	&dev_attr_activation_width.attr,
+	&dev_attr_activation_height.attr,
+	&dev_attr_deactivate_slack.attr,
+	NULL
+};
+
+static const struct attribute_group ntrig_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
+/*
+ * this driver is aimed at two firmware versions in circulation:
+ *  - dual pen/finger single touch
+ *  - finger multitouch, pen not working
+ */
+
+static int ntrig_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+			       struct hid_field *field, struct hid_usage *usage,
+			       unsigned long **bit, int *max)
+{
+	struct ntrig_data *nd = hid_get_drvdata(hdev);
+
+	/* No special mappings needed for the pen and single touch */
+	if (field->physical)
+		return 0;
+
+	switch (usage->hid & HID_USAGE_PAGE) {
+	case HID_UP_GENDESK:
+		switch (usage->hid) {
+		case HID_GD_X:
+			hid_map_usage(hi, usage, bit, max,
+					EV_ABS, ABS_MT_POSITION_X);
+			input_set_abs_params(hi->input, ABS_X,
+					field->logical_minimum,
+					field->logical_maximum, 0, 0);
+
+			if (!nd->sensor_logical_width) {
+				nd->sensor_logical_width =
+					field->logical_maximum -
+					field->logical_minimum;
+				nd->sensor_physical_width =
+					field->physical_maximum -
+					field->physical_minimum;
+				nd->activation_width = activation_width *
+					nd->sensor_logical_width /
+					nd->sensor_physical_width;
+				nd->min_width = min_width *
+					nd->sensor_logical_width /
+					nd->sensor_physical_width;
+			}
+			return 1;
+		case HID_GD_Y:
+			hid_map_usage(hi, usage, bit, max,
+					EV_ABS, ABS_MT_POSITION_Y);
+			input_set_abs_params(hi->input, ABS_Y,
+					field->logical_minimum,
+					field->logical_maximum, 0, 0);
+
+			if (!nd->sensor_logical_height) {
+				nd->sensor_logical_height =
+					field->logical_maximum -
+					field->logical_minimum;
+				nd->sensor_physical_height =
+					field->physical_maximum -
+					field->physical_minimum;
+				nd->activation_height = activation_height *
+					nd->sensor_logical_height /
+					nd->sensor_physical_height;
+				nd->min_height = min_height *
+					nd->sensor_logical_height /
+					nd->sensor_physical_height;
+			}
+			return 1;
+		}
+		return 0;
+
+	case HID_UP_DIGITIZER:
+		switch (usage->hid) {
+		/* we do not want to map these for now */
+		case HID_DG_CONTACTID: /* Not trustworthy, squelch for now */
+		case HID_DG_INPUTMODE:
+		case HID_DG_DEVICEINDEX:
+		case HID_DG_CONTACTMAX:
+			return -1;
+
+		/* width/height mapped on TouchMajor/TouchMinor/Orientation */
+		case HID_DG_WIDTH:
+			hid_map_usage(hi, usage, bit, max,
+				      EV_ABS, ABS_MT_TOUCH_MAJOR);
+			return 1;
+		case HID_DG_HEIGHT:
+			hid_map_usage(hi, usage, bit, max,
+				      EV_ABS, ABS_MT_TOUCH_MINOR);
+			input_set_abs_params(hi->input, ABS_MT_ORIENTATION,
+					     0, 1, 0, 0);
+			return 1;
+		}
+		return 0;
+
+	case 0xff000000:
+		/* we do not want to map these: no input-oriented meaning */
+		return -1;
+	}
+
+	return 0;
+}
+
+static int ntrig_input_mapped(struct hid_device *hdev, struct hid_input *hi,
+			      struct hid_field *field, struct hid_usage *usage,
+			      unsigned long **bit, int *max)
+{
+	/* No special mappings needed for the pen and single touch */
+	if (field->physical)
+		return 0;
+
+	if (usage->type == EV_KEY || usage->type == EV_REL
+			|| usage->type == EV_ABS)
+		clear_bit(usage->code, *bit);
+
+	return 0;
+}
+
+/*
+ * this function is called upon all reports
+ * so that we can filter contact point information,
+ * decide whether we are in multi or single touch mode
+ * and call input_mt_sync after each point if necessary
+ */
+static int ntrig_event (struct hid_device *hid, struct hid_field *field,
+			struct hid_usage *usage, __s32 value)
+{
+	struct ntrig_data *nd = hid_get_drvdata(hid);
+	struct input_dev *input;
+
+	/* Skip processing if not a claimed input */
+	if (!(hid->claimed & HID_CLAIMED_INPUT))
+		goto not_claimed_input;
+
+	/* This function is being called before the structures are fully
+	 * initialized */
+	if(!(field->hidinput && field->hidinput->input))
+		return -EINVAL;
+
+	input = field->hidinput->input;
+
+	/* No special handling needed for the pen */
+	if (field->application == HID_DG_PEN)
+		return 0;
+
+	switch (usage->hid) {
+	case 0xff000001:
+		/* Tag indicating the start of a multitouch group */
+		nd->reading_mt = true;
+		nd->first_contact_touch = false;
+		break;
+	case HID_DG_TIPSWITCH:
+		nd->tipswitch = value;
+		/* Prevent emission of touch until validated */
+		return 1;
+	case HID_DG_CONFIDENCE:
+		nd->confidence = value;
+		break;
+	case HID_GD_X:
+		nd->x = value;
+		/* Clear the contact footer */
+		nd->mt_foot_count = 0;
+		break;
+	case HID_GD_Y:
+		nd->y = value;
+		break;
+	case HID_DG_CONTACTID:
+		nd->id = value;
+		break;
+	case HID_DG_WIDTH:
+		nd->w = value;
+		break;
+	case HID_DG_HEIGHT:
+		nd->h = value;
+		/*
+		 * when in single touch mode, this is the last
+		 * report received in a finger event. We want
+		 * to emit a normal (X, Y) position
+		 */
+		if (!nd->reading_mt) {
+			/*
+			 * TipSwitch indicates the presence of a
+			 * finger in single touch mode.
+			 */
+			input_report_key(input, BTN_TOUCH,
+					 nd->tipswitch);
+			input_report_key(input, BTN_TOOL_DOUBLETAP,
+					 nd->tipswitch);
+			input_event(input, EV_ABS, ABS_X, nd->x);
+			input_event(input, EV_ABS, ABS_Y, nd->y);
+		}
+		break;
+	case 0xff000002:
+		/*
+		 * we receive this when the device is in multitouch
+		 * mode. The first of the three values tagged with
+		 * this usage tells if the contact point is real
+		 * or a placeholder
+		 */
+
+		/* Shouldn't get more than 4 footer packets, so skip */
+		if (nd->mt_foot_count >= 4)
+			break;
+
+		nd->mt_footer[nd->mt_foot_count++] = value;
+
+		/* if the footer isn't complete break */
+		if (nd->mt_foot_count != 4)
+			break;
+
+		/* Pen activity signal. */
+		if (nd->mt_footer[2]) {
+			/*
+			 * When the pen deactivates touch, we see a
+			 * bogus frame with ContactCount > 0.
+			 * We can
+			 * save a bit of work by ensuring act_state < 0
+			 * even if deactivation slack is turned off.
+			 */
+			nd->act_state = deactivate_slack - 1;
+			nd->confidence = false;
+			break;
+		}
+
+		/*
+		 * The first footer value indicates the presence of a
+		 * finger.
+		 */
+		if (nd->mt_footer[0]) {
+			/*
+			 * We do not want to process contacts under
+			 * the size threshold, but do not want to
+			 * ignore them for activation state
+			 */
+			if (nd->w < nd->min_width ||
+			    nd->h < nd->min_height)
+				nd->confidence = false;
+		} else
+			break;
+
+		if (nd->act_state > 0) {
+			/*
+			 * Contact meets the activation size threshold
+			 */
+			if (nd->w >= nd->activation_width &&
+			    nd->h >= nd->activation_height) {
+				if (nd->id)
+					/*
+					 * first contact, activate now
+					 */
+					nd->act_state = 0;
+				else {
+					/*
+					 * avoid corrupting this frame
+					 * but ensure next frame will
+					 * be active
+					 */
+					nd->act_state = 1;
+					break;
+				}
+			} else
+				/*
+				 * Defer adjusting the activation state
+				 * until the end of the frame.
+				 */
+				break;
+		}
+
+		/* Discarding this contact */
+		if (!nd->confidence)
+			break;
+
+		/* emit a normal (X, Y) for the first point only */
+		if (nd->id == 0) {
+			/*
+			 * TipSwitch is superfluous in multitouch
+			 * mode.  The footer events tell us
+			 * if there is a finger on the screen or
+			 * not.
+			 */
+			nd->first_contact_touch = nd->confidence;
+			input_event(input, EV_ABS, ABS_X, nd->x);
+			input_event(input, EV_ABS, ABS_Y, nd->y);
+		}
+
+		/* Emit MT events */
+		input_event(input, EV_ABS, ABS_MT_POSITION_X, nd->x);
+		input_event(input, EV_ABS, ABS_MT_POSITION_Y, nd->y);
+
+		/*
+		 * Translate from height and width to size
+		 * and orientation.
+		 */
+		if (nd->w > nd->h) {
+			input_event(input, EV_ABS,
+					ABS_MT_ORIENTATION, 1);
+			input_event(input, EV_ABS,
+					ABS_MT_TOUCH_MAJOR, nd->w);
+			input_event(input, EV_ABS,
+					ABS_MT_TOUCH_MINOR, nd->h);
+		} else {
+			input_event(input, EV_ABS,
+					ABS_MT_ORIENTATION, 0);
+			input_event(input, EV_ABS,
+					ABS_MT_TOUCH_MAJOR, nd->h);
+			input_event(input, EV_ABS,
+					ABS_MT_TOUCH_MINOR, nd->w);
+		}
+		input_mt_sync(field->hidinput->input);
+		break;
+
+	case HID_DG_CONTACTCOUNT: /* End of a multitouch group */
+		if (!nd->reading_mt) /* Just to be sure */
+			break;
+
+		nd->reading_mt = false;
+
+
+		/*
+		 * Activation state machine logic:
+		 *
+		 * Fundamental states:
+		 *	state >  0: Inactive
+		 *	state <= 0: Active
+		 *	state <  -deactivate_slack:
+		 *		 Pen termination of touch
+		 *
+		 * Specific values of interest
+		 *	state == activate_slack
+		 *		 no valid input since the last reset
+		 *
+		 *	state == 0
+		 *		 general operational state
+		 *
+		 *	state == -deactivate_slack
+		 *		 read sufficient empty frames to accept
+		 *		 the end of input and reset
+		 */
+
+		if (nd->act_state > 0) { /* Currently inactive */
+			if (value)
+				/*
+				 * Consider each live contact as
+				 * evidence of intentional activity.
+				 */
+				nd->act_state = (nd->act_state > value)
+						? nd->act_state - value
+						: 0;
+			else
+				/*
+				 * Empty frame before we hit the
+				 * activity threshold, reset.
+				 */
+				nd->act_state = nd->activate_slack;
+
+			/*
+			 * Entered this block inactive and no
+			 * coordinates sent this frame, so hold off
+			 * on button state.
+			 */
+			break;
+		} else { /* Currently active */
+			if (value && nd->act_state >=
+				     nd->deactivate_slack)
+				/*
+				 * Live point: clear accumulated
+				 * deactivation count.
+				 */
+				nd->act_state = 0;
+			else if (nd->act_state <= nd->deactivate_slack)
+				/*
+				 * We've consumed the deactivation
+				 * slack, time to deactivate and reset.
+				 */
+				nd->act_state =
+					nd->activate_slack;
+			else { /* Move towards deactivation */
+				nd->act_state--;
+				break;
+			}
+		}
+
+		if (nd->first_contact_touch && nd->act_state <= 0) {
+			/*
+			 * Check to see if we're ready to start
+			 * emitting touch events.
+			 *
+			 * Note: activation slack will decrease over
+			 * the course of the frame, and it will be
+			 * inconsistent from the start to the end of
+			 * the frame.  However if the frame starts
+			 * with slack, first_contact_touch will still
+			 * be 0 and we will not get to this point.
+			 */
+			input_report_key(input, BTN_TOOL_DOUBLETAP, 1);
+			input_report_key(input, BTN_TOUCH, 1);
+		} else {
+			input_report_key(input, BTN_TOOL_DOUBLETAP, 0);
+			input_report_key(input, BTN_TOUCH, 0);
+		}
+		break;
+
+	default:
+		/* fall-back to the generic hidinput handling */
+		return 0;
+	}
+
+not_claimed_input:
+
+	/* we have handled the hidinput part, now remains hiddev */
+	if ((hid->claimed & HID_CLAIMED_HIDDEV) && hid->hiddev_hid_event)
+		hid->hiddev_hid_event(hid, field, usage, value);
+
+	return 1;
+}
+
+static int ntrig_input_configured(struct hid_device *hid,
+		struct hid_input *hidinput)
+
+{
+	struct input_dev *input = hidinput->input;
+
+	if (hidinput->report->maxfield < 1)
+		return 0;
+
+	switch (hidinput->report->field[0]->application) {
+	case HID_DG_PEN:
+		input->name = "N-Trig Pen";
+		break;
+	case HID_DG_TOUCHSCREEN:
+		/* These keys are redundant for fingers, clear them
+		 * to prevent incorrect identification */
+		__clear_bit(BTN_TOOL_PEN, input->keybit);
+		__clear_bit(BTN_TOOL_FINGER, input->keybit);
+		__clear_bit(BTN_0, input->keybit);
+		__set_bit(BTN_TOOL_DOUBLETAP, input->keybit);
+		/*
+		 * The physical touchscreen (single touch)
+		 * input has a value for physical, whereas
+		 * the multitouch only has logical input
+		 * fields.
+		 */
+		input->name = (hidinput->report->field[0]->physical) ?
+							"N-Trig Touchscreen" :
+							"N-Trig MultiTouch";
+		break;
+	}
+
+	return 0;
+}
+
+static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct ntrig_data *nd;
+	struct hid_report *report;
+
+	if (id->driver_data)
+		hdev->quirks |= HID_QUIRK_MULTI_INPUT
+				| HID_QUIRK_NO_INIT_REPORTS;
+
+	nd = kmalloc(sizeof(struct ntrig_data), GFP_KERNEL);
+	if (!nd) {
+		hid_err(hdev, "cannot allocate N-Trig data\n");
+		return -ENOMEM;
+	}
+
+	nd->reading_mt = false;
+	nd->min_width = 0;
+	nd->min_height = 0;
+	nd->activate_slack = activate_slack;
+	nd->act_state = activate_slack;
+	nd->deactivate_slack = -deactivate_slack;
+	nd->sensor_logical_width = 1;
+	nd->sensor_logical_height = 1;
+	nd->sensor_physical_width = 1;
+	nd->sensor_physical_height = 1;
+
+	hid_set_drvdata(hdev, nd);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	/* This is needed for devices with more recent firmware versions */
+	report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0x0a];
+	if (report) {
+		/* Let the device settle to ensure the wakeup message gets
+		 * through */
+		hid_hw_wait(hdev);
+		hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
+
+		/*
+		 * Sanity check: if the current mode is invalid reset it to
+		 * something reasonable.
+		 */
+		if (ntrig_get_mode(hdev) >= 4)
+			ntrig_set_mode(hdev, 3);
+	}
+
+	ntrig_report_version(hdev);
+
+	ret = sysfs_create_group(&hdev->dev.kobj,
+			&ntrig_attribute_group);
+	if (ret)
+		hid_err(hdev, "cannot create sysfs group\n");
+
+	return 0;
+err_free:
+	kfree(nd);
+	return ret;
+}
+
+static void ntrig_remove(struct hid_device *hdev)
+{
+	sysfs_remove_group(&hdev->dev.kobj,
+			   &ntrig_attribute_group);
+	hid_hw_stop(hdev);
+	kfree(hid_get_drvdata(hdev));
+}
+
+static const struct hid_device_id ntrig_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_3),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_4),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_5),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_6),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_7),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_8),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_9),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_10),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_11),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_12),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_13),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_14),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_15),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_16),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_17),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18),
+		.driver_data = NTRIG_DUPLICATE_USAGES },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ntrig_devices);
+
+static const struct hid_usage_id ntrig_grabbed_usages[] = {
+	{ HID_ANY_ID, HID_ANY_ID, HID_ANY_ID },
+	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1 }
+};
+
+static struct hid_driver ntrig_driver = {
+	.name = "ntrig",
+	.id_table = ntrig_devices,
+	.probe = ntrig_probe,
+	.remove = ntrig_remove,
+	.input_mapping = ntrig_input_mapping,
+	.input_mapped = ntrig_input_mapped,
+	.input_configured = ntrig_input_configured,
+	.usage_table = ntrig_grabbed_usages,
+	.event = ntrig_event,
+};
+module_hid_driver(ntrig_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-ortek.c b/drivers/hid/hid-ortek.c
new file mode 100644
index 0000000..8783a06
--- /dev/null
+++ b/drivers/hid/hid-ortek.c
@@ -0,0 +1,57 @@
+/*
+ *  HID driver for various devices which are apparently based on the same chipset
+ *  from certain vendor which produces chips that contain wrong LogicalMaximum
+ *  value in their HID report descriptor. Currently supported devices are:
+ *
+ *    Ortek PKB-1700
+ *    Ortek WKB-2000
+ *    iHome IMAC-A210S
+ *    Skycable wireless presenter
+ *
+ *  Copyright (c) 2010 Johnathon Harris <jmharris@gmail.com>
+ *  Copyright (c) 2011 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static __u8 *ortek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 56 && rdesc[54] == 0x25 && rdesc[55] == 0x01) {
+		hid_info(hdev, "Fixing up logical maximum in report descriptor (Ortek)\n");
+		rdesc[55] = 0x92;
+	} else if (*rsize >= 54 && rdesc[52] == 0x25 && rdesc[53] == 0x01) {
+		hid_info(hdev, "Fixing up logical maximum in report descriptor (Skycable)\n");
+		rdesc[53] = 0x65;
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id ortek_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_PKB1700) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_WKB2000) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ortek_devices);
+
+static struct hid_driver ortek_driver = {
+	.name = "ortek",
+	.id_table = ortek_devices,
+	.report_fixup = ortek_report_fixup
+};
+module_hid_driver(ortek_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-penmount.c b/drivers/hid/hid-penmount.c
new file mode 100644
index 0000000..d90383f
--- /dev/null
+++ b/drivers/hid/hid-penmount.c
@@ -0,0 +1,53 @@
+/*
+ *  HID driver for PenMount touchscreens
+ *
+ *  Copyright (c) 2014 Christian Gmeiner <christian.gmeiner <at> gmail.com>
+ *
+ *  based on hid-penmount copyrighted by
+ *    PenMount Touch Solutions <penmount <at> seed.net.tw>
+ */
+
+/*
+ * 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/hid.h>
+#include "hid-ids.h"
+
+static int penmount_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		if (((usage->hid - 1) & HID_USAGE) == 0) {
+			hid_map_usage(hi, usage, bit, max, EV_KEY, BTN_TOUCH);
+			return 1;
+		} else {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id penmount_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_6000) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, penmount_devices);
+
+static struct hid_driver penmount_driver = {
+	.name = "hid-penmount",
+	.id_table = penmount_devices,
+	.input_mapping = penmount_input_mapping,
+};
+
+module_hid_driver(penmount_driver);
+
+MODULE_AUTHOR("Christian Gmeiner <christian.gmeiner@gmail.com>");
+MODULE_DESCRIPTION("PenMount HID TouchScreen driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-petalynx.c b/drivers/hid/hid-petalynx.c
new file mode 100644
index 0000000..6aca4f2
--- /dev/null
+++ b/drivers/hid/hid-petalynx.c
@@ -0,0 +1,108 @@
+/*
+ *  HID driver for some petalynx "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+/* Petalynx Maxter Remote has maximum for consumer page set too low */
+static __u8 *pl_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 62 && rdesc[39] == 0x2a && rdesc[40] == 0xf5 &&
+			rdesc[41] == 0x00 && rdesc[59] == 0x26 &&
+			rdesc[60] == 0xf9 && rdesc[61] == 0x00) {
+		hid_info(hdev, "fixing up Petalynx Maxter Remote report descriptor\n");
+		rdesc[60] = 0xfa;
+		rdesc[40] = 0xfa;
+	}
+	return rdesc;
+}
+
+#define pl_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int pl_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_LOGIVENDOR) {
+		switch (usage->hid & HID_USAGE) {
+		case 0x05a: pl_map_key_clear(KEY_TEXT);		break;
+		case 0x05b: pl_map_key_clear(KEY_RED);		break;
+		case 0x05c: pl_map_key_clear(KEY_GREEN);	break;
+		case 0x05d: pl_map_key_clear(KEY_YELLOW);	break;
+		case 0x05e: pl_map_key_clear(KEY_BLUE);		break;
+		default:
+			return 0;
+		}
+		return 1;
+	}
+
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+		switch (usage->hid & HID_USAGE) {
+		case 0x0f6: pl_map_key_clear(KEY_NEXT);		break;
+		case 0x0fa: pl_map_key_clear(KEY_BACK);		break;
+		default:
+			return 0;
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
+static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	hdev->quirks |= HID_QUIRK_NOGET;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static const struct hid_device_id pl_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, pl_devices);
+
+static struct hid_driver pl_driver = {
+	.name = "petalynx",
+	.id_table = pl_devices,
+	.report_fixup = pl_report_fixup,
+	.input_mapping = pl_input_mapping,
+	.probe = pl_probe,
+};
+module_hid_driver(pl_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-picolcd.h b/drivers/hid/hid-picolcd.h
new file mode 100644
index 0000000..e56d847
--- /dev/null
+++ b/drivers/hid/hid-picolcd.h
@@ -0,0 +1,309 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#define PICOLCD_NAME "PicoLCD (graphic)"
+
+/* Report numbers */
+#define REPORT_ERROR_CODE      0x10 /* LCD: IN[16]  */
+#define   ERR_SUCCESS            0x00
+#define   ERR_PARAMETER_MISSING  0x01
+#define   ERR_DATA_MISSING       0x02
+#define   ERR_BLOCK_READ_ONLY    0x03
+#define   ERR_BLOCK_NOT_ERASABLE 0x04
+#define   ERR_BLOCK_TOO_BIG      0x05
+#define   ERR_SECTION_OVERFLOW   0x06
+#define   ERR_INVALID_CMD_LEN    0x07
+#define   ERR_INVALID_DATA_LEN   0x08
+#define REPORT_KEY_STATE       0x11 /* LCD: IN[2]   */
+#define REPORT_IR_DATA         0x21 /* LCD: IN[63]  */
+#define REPORT_EE_DATA         0x32 /* LCD: IN[63]  */
+#define REPORT_MEMORY          0x41 /* LCD: IN[63]  */
+#define REPORT_LED_STATE       0x81 /* LCD: OUT[1]  */
+#define REPORT_BRIGHTNESS      0x91 /* LCD: OUT[1]  */
+#define REPORT_CONTRAST        0x92 /* LCD: OUT[1]  */
+#define REPORT_RESET           0x93 /* LCD: OUT[2]  */
+#define REPORT_LCD_CMD         0x94 /* LCD: OUT[63] */
+#define REPORT_LCD_DATA        0x95 /* LCD: OUT[63] */
+#define REPORT_LCD_CMD_DATA    0x96 /* LCD: OUT[63] */
+#define	REPORT_EE_READ         0xa3 /* LCD: OUT[63] */
+#define REPORT_EE_WRITE        0xa4 /* LCD: OUT[63] */
+#define REPORT_ERASE_MEMORY    0xb2 /* LCD: OUT[2]  */
+#define REPORT_READ_MEMORY     0xb3 /* LCD: OUT[3]  */
+#define REPORT_WRITE_MEMORY    0xb4 /* LCD: OUT[63] */
+#define REPORT_SPLASH_RESTART  0xc1 /* LCD: OUT[1]  */
+#define REPORT_EXIT_KEYBOARD   0xef /* LCD: OUT[2]  */
+#define REPORT_VERSION         0xf1 /* LCD: IN[2],OUT[1]    Bootloader: IN[2],OUT[1]   */
+#define REPORT_BL_ERASE_MEMORY 0xf2 /*                      Bootloader: IN[36],OUT[4]  */
+#define REPORT_BL_READ_MEMORY  0xf3 /*                      Bootloader: IN[36],OUT[4]  */
+#define REPORT_BL_WRITE_MEMORY 0xf4 /*                      Bootloader: IN[36],OUT[36] */
+#define REPORT_DEVID           0xf5 /* LCD: IN[5], OUT[1]   Bootloader: IN[5],OUT[1]   */
+#define REPORT_SPLASH_SIZE     0xf6 /* LCD: IN[4], OUT[1]   */
+#define REPORT_HOOK_VERSION    0xf7 /* LCD: IN[2], OUT[1]   */
+#define REPORT_EXIT_FLASHER    0xff /*                      Bootloader: OUT[2]         */
+
+/* Description of in-progress IO operation, used for operations
+ * that trigger response from device */
+struct picolcd_pending {
+	struct hid_report *out_report;
+	struct hid_report *in_report;
+	struct completion ready;
+	int raw_size;
+	u8 raw_data[64];
+};
+
+
+#define PICOLCD_KEYS 17
+
+/* Per device data structure */
+struct picolcd_data {
+	struct hid_device *hdev;
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *debug_reset;
+	struct dentry *debug_eeprom;
+	struct dentry *debug_flash;
+	struct mutex mutex_flash;
+	int addr_sz;
+#endif
+	u8 version[2];
+	unsigned short opmode_delay;
+	/* input stuff */
+	u8 pressed_keys[2];
+	struct input_dev *input_keys;
+#ifdef CONFIG_HID_PICOLCD_CIR
+	struct rc_dev *rc_dev;
+#endif
+	unsigned short keycode[PICOLCD_KEYS];
+
+#ifdef CONFIG_HID_PICOLCD_FB
+	/* Framebuffer stuff */
+	struct fb_info *fb_info;
+#endif /* CONFIG_HID_PICOLCD_FB */
+#ifdef CONFIG_HID_PICOLCD_LCD
+	struct lcd_device *lcd;
+	u8 lcd_contrast;
+#endif /* CONFIG_HID_PICOLCD_LCD */
+#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
+	struct backlight_device *backlight;
+	u8 lcd_brightness;
+	u8 lcd_power;
+#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
+#ifdef CONFIG_HID_PICOLCD_LEDS
+	/* LED stuff */
+	u8 led_state;
+	struct led_classdev *led[8];
+#endif /* CONFIG_HID_PICOLCD_LEDS */
+
+	/* Housekeeping stuff */
+	spinlock_t lock;
+	struct mutex mutex;
+	struct picolcd_pending *pending;
+	int status;
+#define PICOLCD_BOOTLOADER 1
+#define PICOLCD_FAILED 2
+#define PICOLCD_CIR_SHUN 4
+};
+
+#ifdef CONFIG_HID_PICOLCD_FB
+struct picolcd_fb_data {
+	/* Framebuffer stuff */
+	spinlock_t lock;
+	struct picolcd_data *picolcd;
+	u8 update_rate;
+	u8 bpp;
+	u8 force;
+	u8 ready;
+	u8 *vbitmap;		/* local copy of what was sent to PicoLCD */
+	u8 *bitmap;		/* framebuffer */
+};
+#endif /* CONFIG_HID_PICOLCD_FB */
+
+/* Find a given report */
+#define picolcd_in_report(id, dev) picolcd_report(id, dev, HID_INPUT_REPORT)
+#define picolcd_out_report(id, dev) picolcd_report(id, dev, HID_OUTPUT_REPORT)
+
+struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir);
+
+#ifdef CONFIG_DEBUG_FS
+void picolcd_debug_out_report(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report);
+#define hid_hw_request(a, b, c) \
+	do { \
+		picolcd_debug_out_report(hid_get_drvdata(a), a, b); \
+		hid_hw_request(a, b, c); \
+	} while (0)
+
+void picolcd_debug_raw_event(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report,
+		u8 *raw_data, int size);
+
+void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset);
+
+void picolcd_exit_devfs(struct picolcd_data *data);
+#else
+static inline void picolcd_debug_out_report(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report)
+{
+}
+static inline void picolcd_debug_raw_event(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report,
+		u8 *raw_data, int size)
+{
+}
+static inline void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset)
+{
+}
+static inline void picolcd_exit_devfs(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
+
+
+#ifdef CONFIG_HID_PICOLCD_FB
+int picolcd_fb_reset(struct picolcd_data *data, int clear);
+
+int picolcd_init_framebuffer(struct picolcd_data *data);
+
+void picolcd_exit_framebuffer(struct picolcd_data *data);
+
+void picolcd_fb_refresh(struct picolcd_data *data);
+#define picolcd_fbinfo(d) ((d)->fb_info)
+#else
+static inline int picolcd_fb_reset(struct picolcd_data *data, int clear)
+{
+	return 0;
+}
+static inline int picolcd_init_framebuffer(struct picolcd_data *data)
+{
+	return 0;
+}
+static inline void picolcd_exit_framebuffer(struct picolcd_data *data)
+{
+}
+static inline void picolcd_fb_refresh(struct picolcd_data *data)
+{
+}
+#define picolcd_fbinfo(d) NULL
+#endif /* CONFIG_HID_PICOLCD_FB */
+
+
+#ifdef CONFIG_HID_PICOLCD_BACKLIGHT
+int picolcd_init_backlight(struct picolcd_data *data,
+		struct hid_report *report);
+
+void picolcd_exit_backlight(struct picolcd_data *data);
+
+int picolcd_resume_backlight(struct picolcd_data *data);
+
+void picolcd_suspend_backlight(struct picolcd_data *data);
+#else
+static inline int picolcd_init_backlight(struct picolcd_data *data,
+		struct hid_report *report)
+{
+	return 0;
+}
+static inline void picolcd_exit_backlight(struct picolcd_data *data)
+{
+}
+static inline int picolcd_resume_backlight(struct picolcd_data *data)
+{
+	return 0;
+}
+static inline void picolcd_suspend_backlight(struct picolcd_data *data)
+{
+}
+
+#endif /* CONFIG_HID_PICOLCD_BACKLIGHT */
+
+
+#ifdef CONFIG_HID_PICOLCD_LCD
+int picolcd_init_lcd(struct picolcd_data *data,
+		struct hid_report *report);
+
+void picolcd_exit_lcd(struct picolcd_data *data);
+
+int picolcd_resume_lcd(struct picolcd_data *data);
+#else
+static inline int picolcd_init_lcd(struct picolcd_data *data,
+		struct hid_report *report)
+{
+	return 0;
+}
+static inline void picolcd_exit_lcd(struct picolcd_data *data)
+{
+}
+static inline int picolcd_resume_lcd(struct picolcd_data *data)
+{
+	return 0;
+}
+#endif /* CONFIG_HID_PICOLCD_LCD */
+
+
+#ifdef CONFIG_HID_PICOLCD_LEDS
+int picolcd_init_leds(struct picolcd_data *data,
+		struct hid_report *report);
+
+void picolcd_exit_leds(struct picolcd_data *data);
+
+void picolcd_leds_set(struct picolcd_data *data);
+#else
+static inline int picolcd_init_leds(struct picolcd_data *data,
+		struct hid_report *report)
+{
+	return 0;
+}
+static inline void picolcd_exit_leds(struct picolcd_data *data)
+{
+}
+static inline void picolcd_leds_set(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_HID_PICOLCD_LEDS */
+
+
+#ifdef CONFIG_HID_PICOLCD_CIR
+int picolcd_raw_cir(struct picolcd_data *data,
+		struct hid_report *report, u8 *raw_data, int size);
+
+int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report);
+
+void picolcd_exit_cir(struct picolcd_data *data);
+#else
+static inline int picolcd_raw_cir(struct picolcd_data *data,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	return 1;
+}
+static inline int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
+{
+	return 0;
+}
+static inline void picolcd_exit_cir(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_HID_PICOLCD_CIR */
+
+int picolcd_reset(struct hid_device *hdev);
+struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
+			int report_id, const u8 *raw_data, int size);
diff --git a/drivers/hid/hid-picolcd_backlight.c b/drivers/hid/hid-picolcd_backlight.c
new file mode 100644
index 0000000..808807a
--- /dev/null
+++ b/drivers/hid/hid-picolcd_backlight.c
@@ -0,0 +1,119 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+
+#include <linux/fb.h>
+#include <linux/backlight.h>
+
+#include "hid-picolcd.h"
+
+static int picolcd_get_brightness(struct backlight_device *bdev)
+{
+	struct picolcd_data *data = bl_get_data(bdev);
+	return data->lcd_brightness;
+}
+
+static int picolcd_set_brightness(struct backlight_device *bdev)
+{
+	struct picolcd_data *data = bl_get_data(bdev);
+	struct hid_report *report = picolcd_out_report(REPORT_BRIGHTNESS, data->hdev);
+	unsigned long flags;
+
+	if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
+		return -ENODEV;
+
+	data->lcd_brightness = bdev->props.brightness & 0x0ff;
+	data->lcd_power      = bdev->props.power;
+	spin_lock_irqsave(&data->lock, flags);
+	hid_set_field(report->field[0], 0, data->lcd_power == FB_BLANK_UNBLANK ? data->lcd_brightness : 0);
+	if (!(data->status & PICOLCD_FAILED))
+		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+	return 0;
+}
+
+static int picolcd_check_bl_fb(struct backlight_device *bdev, struct fb_info *fb)
+{
+	return fb && fb == picolcd_fbinfo((struct picolcd_data *)bl_get_data(bdev));
+}
+
+static const struct backlight_ops picolcd_blops = {
+	.update_status  = picolcd_set_brightness,
+	.get_brightness = picolcd_get_brightness,
+	.check_fb       = picolcd_check_bl_fb,
+};
+
+int picolcd_init_backlight(struct picolcd_data *data, struct hid_report *report)
+{
+	struct device *dev = &data->hdev->dev;
+	struct backlight_device *bdev;
+	struct backlight_properties props;
+	if (!report)
+		return -ENODEV;
+	if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
+			report->field[0]->report_size != 8) {
+		dev_err(dev, "unsupported BRIGHTNESS report");
+		return -EINVAL;
+	}
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = 0xff;
+	bdev = backlight_device_register(dev_name(dev), dev, data,
+			&picolcd_blops, &props);
+	if (IS_ERR(bdev)) {
+		dev_err(dev, "failed to register backlight\n");
+		return PTR_ERR(bdev);
+	}
+	bdev->props.brightness     = 0xff;
+	data->lcd_brightness       = 0xff;
+	data->backlight            = bdev;
+	picolcd_set_brightness(bdev);
+	return 0;
+}
+
+void picolcd_exit_backlight(struct picolcd_data *data)
+{
+	struct backlight_device *bdev = data->backlight;
+
+	data->backlight = NULL;
+	backlight_device_unregister(bdev);
+}
+
+int picolcd_resume_backlight(struct picolcd_data *data)
+{
+	if (!data->backlight)
+		return 0;
+	return picolcd_set_brightness(data->backlight);
+}
+
+#ifdef CONFIG_PM
+void picolcd_suspend_backlight(struct picolcd_data *data)
+{
+	int bl_power = data->lcd_power;
+	if (!data->backlight)
+		return;
+
+	data->backlight->props.power = FB_BLANK_POWERDOWN;
+	picolcd_set_brightness(data->backlight);
+	data->lcd_power = data->backlight->props.power = bl_power;
+}
+#endif /* CONFIG_PM */
+
diff --git a/drivers/hid/hid-picolcd_cir.c b/drivers/hid/hid-picolcd_cir.c
new file mode 100644
index 0000000..32747b7
--- /dev/null
+++ b/drivers/hid/hid-picolcd_cir.c
@@ -0,0 +1,149 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+#include <linux/input.h>
+#include "hid-ids.h"
+
+#include <linux/fb.h>
+#include <linux/vmalloc.h>
+#include <linux/backlight.h>
+#include <linux/lcd.h>
+
+#include <linux/leds.h>
+
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <media/rc-core.h>
+
+#include "hid-picolcd.h"
+
+
+int picolcd_raw_cir(struct picolcd_data *data,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	unsigned long flags;
+	int i, w, sz;
+	DEFINE_IR_RAW_EVENT(rawir);
+
+	/* ignore if rc_dev is NULL or status is shunned */
+	spin_lock_irqsave(&data->lock, flags);
+	if (!data->rc_dev || (data->status & PICOLCD_CIR_SHUN)) {
+		spin_unlock_irqrestore(&data->lock, flags);
+		return 1;
+	}
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	/* PicoLCD USB packets contain 16-bit intervals in network order,
+	 * with value negated for pulse. Intervals are in microseconds.
+	 *
+	 * Note: some userspace LIRC code for PicoLCD says negated values
+	 * for space - is it a matter of IR chip? (pulse for my TSOP2236)
+	 *
+	 * In addition, the first interval seems to be around 15000 + base
+	 * interval for non-first report of IR data - thus the quirk below
+	 * to get RC_CODE to understand Sony and JVC remotes I have at hand
+	 */
+	sz = size > 0 ? min((int)raw_data[0], size-1) : 0;
+	for (i = 0; i+1 < sz; i += 2) {
+		init_ir_raw_event(&rawir);
+		w = (raw_data[i] << 8) | (raw_data[i+1]);
+		rawir.pulse = !!(w & 0x8000);
+		rawir.duration = US_TO_NS(rawir.pulse ? (65536 - w) : w);
+		/* Quirk!! - see above */
+		if (i == 0 && rawir.duration > 15000000)
+			rawir.duration -= 15000000;
+		ir_raw_event_store(data->rc_dev, &rawir);
+	}
+	ir_raw_event_handle(data->rc_dev);
+
+	return 1;
+}
+
+static int picolcd_cir_open(struct rc_dev *dev)
+{
+	struct picolcd_data *data = dev->priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&data->lock, flags);
+	data->status &= ~PICOLCD_CIR_SHUN;
+	spin_unlock_irqrestore(&data->lock, flags);
+	return 0;
+}
+
+static void picolcd_cir_close(struct rc_dev *dev)
+{
+	struct picolcd_data *data = dev->priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&data->lock, flags);
+	data->status |= PICOLCD_CIR_SHUN;
+	spin_unlock_irqrestore(&data->lock, flags);
+}
+
+/* initialize CIR input device */
+int picolcd_init_cir(struct picolcd_data *data, struct hid_report *report)
+{
+	struct rc_dev *rdev;
+	int ret = 0;
+
+	rdev = rc_allocate_device(RC_DRIVER_IR_RAW);
+	if (!rdev)
+		return -ENOMEM;
+
+	rdev->priv             = data;
+	rdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
+	rdev->open             = picolcd_cir_open;
+	rdev->close            = picolcd_cir_close;
+	rdev->device_name      = data->hdev->name;
+	rdev->input_phys       = data->hdev->phys;
+	rdev->input_id.bustype = data->hdev->bus;
+	rdev->input_id.vendor  = data->hdev->vendor;
+	rdev->input_id.product = data->hdev->product;
+	rdev->input_id.version = data->hdev->version;
+	rdev->dev.parent       = &data->hdev->dev;
+	rdev->driver_name      = PICOLCD_NAME;
+	rdev->map_name         = RC_MAP_RC6_MCE;
+	rdev->timeout          = MS_TO_NS(100);
+	rdev->rx_resolution    = US_TO_NS(1);
+
+	ret = rc_register_device(rdev);
+	if (ret)
+		goto err;
+	data->rc_dev = rdev;
+	return 0;
+
+err:
+	rc_free_device(rdev);
+	return ret;
+}
+
+void picolcd_exit_cir(struct picolcd_data *data)
+{
+	struct rc_dev *rdev = data->rc_dev;
+
+	data->rc_dev = NULL;
+	rc_unregister_device(rdev);
+}
+
diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c
new file mode 100644
index 0000000..c1b29a9
--- /dev/null
+++ b/drivers/hid/hid-picolcd_core.c
@@ -0,0 +1,682 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+#include <linux/input.h>
+#include "hid-ids.h"
+
+#include <linux/fb.h>
+#include <linux/vmalloc.h>
+
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+
+#include "hid-picolcd.h"
+
+
+/* Input device
+ *
+ * The PicoLCD has an IR receiver header, a built-in keypad with 5 keys
+ * and header for 4x4 key matrix. The built-in keys are part of the matrix.
+ */
+static const unsigned short def_keymap[PICOLCD_KEYS] = {
+	KEY_RESERVED,	/* none */
+	KEY_BACK,	/* col 4 + row 1 */
+	KEY_HOMEPAGE,	/* col 3 + row 1 */
+	KEY_RESERVED,	/* col 2 + row 1 */
+	KEY_RESERVED,	/* col 1 + row 1 */
+	KEY_SCROLLUP,	/* col 4 + row 2 */
+	KEY_OK,		/* col 3 + row 2 */
+	KEY_SCROLLDOWN,	/* col 2 + row 2 */
+	KEY_RESERVED,	/* col 1 + row 2 */
+	KEY_RESERVED,	/* col 4 + row 3 */
+	KEY_RESERVED,	/* col 3 + row 3 */
+	KEY_RESERVED,	/* col 2 + row 3 */
+	KEY_RESERVED,	/* col 1 + row 3 */
+	KEY_RESERVED,	/* col 4 + row 4 */
+	KEY_RESERVED,	/* col 3 + row 4 */
+	KEY_RESERVED,	/* col 2 + row 4 */
+	KEY_RESERVED,	/* col 1 + row 4 */
+};
+
+
+/* Find a given report */
+struct hid_report *picolcd_report(int id, struct hid_device *hdev, int dir)
+{
+	struct list_head *feature_report_list = &hdev->report_enum[dir].report_list;
+	struct hid_report *report = NULL;
+
+	list_for_each_entry(report, feature_report_list, list) {
+		if (report->id == id)
+			return report;
+	}
+	hid_warn(hdev, "No report with id 0x%x found\n", id);
+	return NULL;
+}
+
+/* Submit a report and wait for a reply from device - if device fades away
+ * or does not respond in time, return NULL */
+struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
+		int report_id, const u8 *raw_data, int size)
+{
+	struct picolcd_data *data = hid_get_drvdata(hdev);
+	struct picolcd_pending *work;
+	struct hid_report *report = picolcd_out_report(report_id, hdev);
+	unsigned long flags;
+	int i, j, k;
+
+	if (!report || !data)
+		return NULL;
+	if (data->status & PICOLCD_FAILED)
+		return NULL;
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return NULL;
+
+	init_completion(&work->ready);
+	work->out_report = report;
+	work->in_report  = NULL;
+	work->raw_size   = 0;
+
+	mutex_lock(&data->mutex);
+	spin_lock_irqsave(&data->lock, flags);
+	for (i = k = 0; i < report->maxfield; i++)
+		for (j = 0; j < report->field[i]->report_count; j++) {
+			hid_set_field(report->field[i], j, k < size ? raw_data[k] : 0);
+			k++;
+		}
+	if (data->status & PICOLCD_FAILED) {
+		kfree(work);
+		work = NULL;
+	} else {
+		data->pending = work;
+		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+		spin_unlock_irqrestore(&data->lock, flags);
+		wait_for_completion_interruptible_timeout(&work->ready, HZ*2);
+		spin_lock_irqsave(&data->lock, flags);
+		data->pending = NULL;
+	}
+	spin_unlock_irqrestore(&data->lock, flags);
+	mutex_unlock(&data->mutex);
+	return work;
+}
+
+/*
+ * input class device
+ */
+static int picolcd_raw_keypad(struct picolcd_data *data,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	/*
+	 * Keypad event
+	 * First and second data bytes list currently pressed keys,
+	 * 0x00 means no key and at most 2 keys may be pressed at same time
+	 */
+	int i, j;
+
+	/* determine newly pressed keys */
+	for (i = 0; i < size; i++) {
+		unsigned int key_code;
+		if (raw_data[i] == 0)
+			continue;
+		for (j = 0; j < sizeof(data->pressed_keys); j++)
+			if (data->pressed_keys[j] == raw_data[i])
+				goto key_already_down;
+		for (j = 0; j < sizeof(data->pressed_keys); j++)
+			if (data->pressed_keys[j] == 0) {
+				data->pressed_keys[j] = raw_data[i];
+				break;
+			}
+		input_event(data->input_keys, EV_MSC, MSC_SCAN, raw_data[i]);
+		if (raw_data[i] < PICOLCD_KEYS)
+			key_code = data->keycode[raw_data[i]];
+		else
+			key_code = KEY_UNKNOWN;
+		if (key_code != KEY_UNKNOWN) {
+			dbg_hid(PICOLCD_NAME " got key press for %u:%d",
+					raw_data[i], key_code);
+			input_report_key(data->input_keys, key_code, 1);
+		}
+		input_sync(data->input_keys);
+key_already_down:
+		continue;
+	}
+
+	/* determine newly released keys */
+	for (j = 0; j < sizeof(data->pressed_keys); j++) {
+		unsigned int key_code;
+		if (data->pressed_keys[j] == 0)
+			continue;
+		for (i = 0; i < size; i++)
+			if (data->pressed_keys[j] == raw_data[i])
+				goto key_still_down;
+		input_event(data->input_keys, EV_MSC, MSC_SCAN, data->pressed_keys[j]);
+		if (data->pressed_keys[j] < PICOLCD_KEYS)
+			key_code = data->keycode[data->pressed_keys[j]];
+		else
+			key_code = KEY_UNKNOWN;
+		if (key_code != KEY_UNKNOWN) {
+			dbg_hid(PICOLCD_NAME " got key release for %u:%d",
+					data->pressed_keys[j], key_code);
+			input_report_key(data->input_keys, key_code, 0);
+		}
+		input_sync(data->input_keys);
+		data->pressed_keys[j] = 0;
+key_still_down:
+		continue;
+	}
+	return 1;
+}
+
+static int picolcd_check_version(struct hid_device *hdev)
+{
+	struct picolcd_data *data = hid_get_drvdata(hdev);
+	struct picolcd_pending *verinfo;
+	int ret = 0;
+
+	if (!data)
+		return -ENODEV;
+
+	verinfo = picolcd_send_and_wait(hdev, REPORT_VERSION, NULL, 0);
+	if (!verinfo) {
+		hid_err(hdev, "no version response from PicoLCD\n");
+		return -ENODEV;
+	}
+
+	if (verinfo->raw_size == 2) {
+		data->version[0] = verinfo->raw_data[1];
+		data->version[1] = verinfo->raw_data[0];
+		if (data->status & PICOLCD_BOOTLOADER) {
+			hid_info(hdev, "PicoLCD, bootloader version %d.%d\n",
+				 verinfo->raw_data[1], verinfo->raw_data[0]);
+		} else {
+			hid_info(hdev, "PicoLCD, firmware version %d.%d\n",
+				 verinfo->raw_data[1], verinfo->raw_data[0]);
+		}
+	} else {
+		hid_err(hdev, "confused, got unexpected version response from PicoLCD\n");
+		ret = -EINVAL;
+	}
+	kfree(verinfo);
+	return ret;
+}
+
+/*
+ * Reset our device and wait for answer to VERSION request
+ */
+int picolcd_reset(struct hid_device *hdev)
+{
+	struct picolcd_data *data = hid_get_drvdata(hdev);
+	struct hid_report *report = picolcd_out_report(REPORT_RESET, hdev);
+	unsigned long flags;
+	int error;
+
+	if (!data || !report || report->maxfield != 1)
+		return -ENODEV;
+
+	spin_lock_irqsave(&data->lock, flags);
+	if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
+		data->status |= PICOLCD_BOOTLOADER;
+
+	/* perform the reset */
+	hid_set_field(report->field[0], 0, 1);
+	if (data->status & PICOLCD_FAILED) {
+		spin_unlock_irqrestore(&data->lock, flags);
+		return -ENODEV;
+	}
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	error = picolcd_check_version(hdev);
+	if (error)
+		return error;
+
+	picolcd_resume_lcd(data);
+	picolcd_resume_backlight(data);
+	picolcd_fb_refresh(data);
+	picolcd_leds_set(data);
+	return 0;
+}
+
+/*
+ * The "operation_mode" sysfs attribute
+ */
+static ssize_t picolcd_operation_mode_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
+	else
+		return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
+}
+
+static ssize_t picolcd_operation_mode_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+	struct hid_report *report = NULL;
+	size_t cnt = count;
+	int timeout = data->opmode_delay;
+	unsigned long flags;
+
+	if (cnt >= 3 && strncmp("lcd", buf, 3) == 0) {
+		if (data->status & PICOLCD_BOOTLOADER)
+			report = picolcd_out_report(REPORT_EXIT_FLASHER, data->hdev);
+		buf += 3;
+		cnt -= 3;
+	} else if (cnt >= 10 && strncmp("bootloader", buf, 10) == 0) {
+		if (!(data->status & PICOLCD_BOOTLOADER))
+			report = picolcd_out_report(REPORT_EXIT_KEYBOARD, data->hdev);
+		buf += 10;
+		cnt -= 10;
+	}
+	if (!report || report->maxfield != 1)
+		return -EINVAL;
+
+	while (cnt > 0 && (buf[cnt-1] == '\n' || buf[cnt-1] == '\r'))
+		cnt--;
+	if (cnt != 0)
+		return -EINVAL;
+
+	spin_lock_irqsave(&data->lock, flags);
+	hid_set_field(report->field[0], 0, timeout & 0xff);
+	hid_set_field(report->field[0], 1, (timeout >> 8) & 0xff);
+	hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+	return count;
+}
+
+static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
+		picolcd_operation_mode_store);
+
+/*
+ * The "operation_mode_delay" sysfs attribute
+ */
+static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+
+	return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
+}
+
+static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+	unsigned u;
+	if (sscanf(buf, "%u", &u) != 1)
+		return -EINVAL;
+	if (u > 30000)
+		return -EINVAL;
+	else
+		data->opmode_delay = u;
+	return count;
+}
+
+static DEVICE_ATTR(operation_mode_delay, 0644, picolcd_operation_mode_delay_show,
+		picolcd_operation_mode_delay_store);
+
+/*
+ * Handle raw report as sent by device
+ */
+static int picolcd_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	struct picolcd_data *data = hid_get_drvdata(hdev);
+	unsigned long flags;
+	int ret = 0;
+
+	if (!data)
+		return 1;
+
+	if (size > 64) {
+		hid_warn(hdev, "invalid size value (%d) for picolcd raw event (%d)\n",
+				size, report->id);
+		return 0;
+	}
+
+	if (report->id == REPORT_KEY_STATE) {
+		if (data->input_keys)
+			ret = picolcd_raw_keypad(data, report, raw_data+1, size-1);
+	} else if (report->id == REPORT_IR_DATA) {
+		ret = picolcd_raw_cir(data, report, raw_data+1, size-1);
+	} else {
+		spin_lock_irqsave(&data->lock, flags);
+		/*
+		 * We let the caller of picolcd_send_and_wait() check if the
+		 * report we got is one of the expected ones or not.
+		 */
+		if (data->pending) {
+			memcpy(data->pending->raw_data, raw_data+1, size-1);
+			data->pending->raw_size  = size-1;
+			data->pending->in_report = report;
+			complete(&data->pending->ready);
+		}
+		spin_unlock_irqrestore(&data->lock, flags);
+	}
+
+	picolcd_debug_raw_event(data, hdev, report, raw_data, size);
+	return 1;
+}
+
+#ifdef CONFIG_PM
+static int picolcd_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	if (PMSG_IS_AUTO(message))
+		return 0;
+
+	picolcd_suspend_backlight(hid_get_drvdata(hdev));
+	dbg_hid(PICOLCD_NAME " device ready for suspend\n");
+	return 0;
+}
+
+static int picolcd_resume(struct hid_device *hdev)
+{
+	int ret;
+	ret = picolcd_resume_backlight(hid_get_drvdata(hdev));
+	if (ret)
+		dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret);
+	return 0;
+}
+
+static int picolcd_reset_resume(struct hid_device *hdev)
+{
+	int ret;
+	ret = picolcd_reset(hdev);
+	if (ret)
+		dbg_hid(PICOLCD_NAME " resetting our device failed: %d\n", ret);
+	ret = picolcd_fb_reset(hid_get_drvdata(hdev), 0);
+	if (ret)
+		dbg_hid(PICOLCD_NAME " restoring framebuffer content failed: %d\n", ret);
+	ret = picolcd_resume_lcd(hid_get_drvdata(hdev));
+	if (ret)
+		dbg_hid(PICOLCD_NAME " restoring lcd failed: %d\n", ret);
+	ret = picolcd_resume_backlight(hid_get_drvdata(hdev));
+	if (ret)
+		dbg_hid(PICOLCD_NAME " restoring backlight failed: %d\n", ret);
+	picolcd_leds_set(hid_get_drvdata(hdev));
+	return 0;
+}
+#endif
+
+/* initialize keypad input device */
+static int picolcd_init_keys(struct picolcd_data *data,
+		struct hid_report *report)
+{
+	struct hid_device *hdev = data->hdev;
+	struct input_dev *idev;
+	int error, i;
+
+	if (!report)
+		return -ENODEV;
+	if (report->maxfield != 1 || report->field[0]->report_count != 2 ||
+			report->field[0]->report_size != 8) {
+		hid_err(hdev, "unsupported KEY_STATE report\n");
+		return -EINVAL;
+	}
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		hid_err(hdev, "failed to allocate input device\n");
+		return -ENOMEM;
+	}
+	input_set_drvdata(idev, hdev);
+	memcpy(data->keycode, def_keymap, sizeof(def_keymap));
+	idev->name = hdev->name;
+	idev->phys = hdev->phys;
+	idev->uniq = hdev->uniq;
+	idev->id.bustype = hdev->bus;
+	idev->id.vendor  = hdev->vendor;
+	idev->id.product = hdev->product;
+	idev->id.version = hdev->version;
+	idev->dev.parent = &hdev->dev;
+	idev->keycode     = &data->keycode;
+	idev->keycodemax  = PICOLCD_KEYS;
+	idev->keycodesize = sizeof(data->keycode[0]);
+	input_set_capability(idev, EV_MSC, MSC_SCAN);
+	set_bit(EV_REP, idev->evbit);
+	for (i = 0; i < PICOLCD_KEYS; i++)
+		input_set_capability(idev, EV_KEY, data->keycode[i]);
+	error = input_register_device(idev);
+	if (error) {
+		hid_err(hdev, "error registering the input device\n");
+		input_free_device(idev);
+		return error;
+	}
+	data->input_keys = idev;
+	return 0;
+}
+
+static void picolcd_exit_keys(struct picolcd_data *data)
+{
+	struct input_dev *idev = data->input_keys;
+
+	data->input_keys = NULL;
+	if (idev)
+		input_unregister_device(idev);
+}
+
+static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
+{
+	int error;
+
+	/* Setup keypad input device */
+	error = picolcd_init_keys(data, picolcd_in_report(REPORT_KEY_STATE, hdev));
+	if (error)
+		goto err;
+
+	/* Setup CIR input device */
+	error = picolcd_init_cir(data, picolcd_in_report(REPORT_IR_DATA, hdev));
+	if (error)
+		goto err;
+
+	/* Set up the framebuffer device */
+	error = picolcd_init_framebuffer(data);
+	if (error)
+		goto err;
+
+	/* Setup lcd class device */
+	error = picolcd_init_lcd(data, picolcd_out_report(REPORT_CONTRAST, hdev));
+	if (error)
+		goto err;
+
+	/* Setup backlight class device */
+	error = picolcd_init_backlight(data, picolcd_out_report(REPORT_BRIGHTNESS, hdev));
+	if (error)
+		goto err;
+
+	/* Setup the LED class devices */
+	error = picolcd_init_leds(data, picolcd_out_report(REPORT_LED_STATE, hdev));
+	if (error)
+		goto err;
+
+	picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
+			picolcd_out_report(REPORT_EE_WRITE, hdev),
+			picolcd_out_report(REPORT_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
+			picolcd_out_report(REPORT_RESET, hdev));
+	return 0;
+err:
+	picolcd_exit_leds(data);
+	picolcd_exit_backlight(data);
+	picolcd_exit_lcd(data);
+	picolcd_exit_framebuffer(data);
+	picolcd_exit_cir(data);
+	picolcd_exit_keys(data);
+	return error;
+}
+
+static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
+{
+	picolcd_init_devfs(data, NULL, NULL,
+			picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
+	return 0;
+}
+
+static int picolcd_probe(struct hid_device *hdev,
+		     const struct hid_device_id *id)
+{
+	struct picolcd_data *data;
+	int error = -ENOMEM;
+
+	dbg_hid(PICOLCD_NAME " hardware probe...\n");
+
+	/*
+	 * Let's allocate the picolcd data structure, set some reasonable
+	 * defaults, and associate it with the device
+	 */
+	data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL);
+	if (data == NULL) {
+		hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n");
+		error = -ENOMEM;
+		goto err_no_cleanup;
+	}
+
+	spin_lock_init(&data->lock);
+	mutex_init(&data->mutex);
+	data->hdev = hdev;
+	data->opmode_delay = 5000;
+	if (hdev->product == USB_DEVICE_ID_PICOLCD_BOOTLOADER)
+		data->status |= PICOLCD_BOOTLOADER;
+	hid_set_drvdata(hdev, data);
+
+	/* Parse the device reports and start it up */
+	error = hid_parse(hdev);
+	if (error) {
+		hid_err(hdev, "device report parse failed\n");
+		goto err_cleanup_data;
+	}
+
+	error = hid_hw_start(hdev, 0);
+	if (error) {
+		hid_err(hdev, "hardware start failed\n");
+		goto err_cleanup_data;
+	}
+
+	error = hid_hw_open(hdev);
+	if (error) {
+		hid_err(hdev, "failed to open input interrupt pipe for key and IR events\n");
+		goto err_cleanup_hid_hw;
+	}
+
+	error = device_create_file(&hdev->dev, &dev_attr_operation_mode_delay);
+	if (error) {
+		hid_err(hdev, "failed to create sysfs attributes\n");
+		goto err_cleanup_hid_ll;
+	}
+
+	error = device_create_file(&hdev->dev, &dev_attr_operation_mode);
+	if (error) {
+		hid_err(hdev, "failed to create sysfs attributes\n");
+		goto err_cleanup_sysfs1;
+	}
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		error = picolcd_probe_bootloader(hdev, data);
+	else
+		error = picolcd_probe_lcd(hdev, data);
+	if (error)
+		goto err_cleanup_sysfs2;
+
+	dbg_hid(PICOLCD_NAME " activated and initialized\n");
+	return 0;
+
+err_cleanup_sysfs2:
+	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
+err_cleanup_sysfs1:
+	device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay);
+err_cleanup_hid_ll:
+	hid_hw_close(hdev);
+err_cleanup_hid_hw:
+	hid_hw_stop(hdev);
+err_cleanup_data:
+	kfree(data);
+err_no_cleanup:
+	hid_set_drvdata(hdev, NULL);
+
+	return error;
+}
+
+static void picolcd_remove(struct hid_device *hdev)
+{
+	struct picolcd_data *data = hid_get_drvdata(hdev);
+	unsigned long flags;
+
+	dbg_hid(PICOLCD_NAME " hardware remove...\n");
+	spin_lock_irqsave(&data->lock, flags);
+	data->status |= PICOLCD_FAILED;
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	picolcd_exit_devfs(data);
+	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
+	device_remove_file(&hdev->dev, &dev_attr_operation_mode_delay);
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+
+	/* Shortcut potential pending reply that will never arrive */
+	spin_lock_irqsave(&data->lock, flags);
+	if (data->pending)
+		complete(&data->pending->ready);
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	/* Cleanup LED */
+	picolcd_exit_leds(data);
+	/* Clean up the framebuffer */
+	picolcd_exit_backlight(data);
+	picolcd_exit_lcd(data);
+	picolcd_exit_framebuffer(data);
+	/* Cleanup input */
+	picolcd_exit_cir(data);
+	picolcd_exit_keys(data);
+
+	hid_set_drvdata(hdev, NULL);
+	mutex_destroy(&data->mutex);
+	/* Finally, clean up the picolcd data itself */
+	kfree(data);
+}
+
+static const struct hid_device_id picolcd_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, picolcd_devices);
+
+static struct hid_driver picolcd_driver = {
+	.name =          "hid-picolcd",
+	.id_table =      picolcd_devices,
+	.probe =         picolcd_probe,
+	.remove =        picolcd_remove,
+	.raw_event =     picolcd_raw_event,
+#ifdef CONFIG_PM
+	.suspend =       picolcd_suspend,
+	.resume =        picolcd_resume,
+	.reset_resume =  picolcd_reset_resume,
+#endif
+};
+module_hid_driver(picolcd_driver);
+
+MODULE_DESCRIPTION("Minibox graphics PicoLCD Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-picolcd_debugfs.c b/drivers/hid/hid-picolcd_debugfs.c
new file mode 100644
index 0000000..3e0feb4
--- /dev/null
+++ b/drivers/hid/hid-picolcd_debugfs.c
@@ -0,0 +1,895 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+
+#include <linux/fb.h>
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#include "hid-picolcd.h"
+
+
+static int picolcd_debug_reset_show(struct seq_file *f, void *p)
+{
+	if (picolcd_fbinfo((struct picolcd_data *)f->private))
+		seq_printf(f, "all fb\n");
+	else
+		seq_printf(f, "all\n");
+	return 0;
+}
+
+static int picolcd_debug_reset_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, picolcd_debug_reset_show, inode->i_private);
+}
+
+static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
+		size_t count, loff_t *ppos)
+{
+	struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
+	char buf[32];
+	size_t cnt = min(count, sizeof(buf)-1);
+	if (copy_from_user(buf, user_buf, cnt))
+		return -EFAULT;
+
+	while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
+		cnt--;
+	buf[cnt] = '\0';
+	if (strcmp(buf, "all") == 0) {
+		picolcd_reset(data->hdev);
+		picolcd_fb_reset(data, 1);
+	} else if (strcmp(buf, "fb") == 0) {
+		picolcd_fb_reset(data, 1);
+	} else {
+		return -EINVAL;
+	}
+	return count;
+}
+
+static const struct file_operations picolcd_debug_reset_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_reset_open,
+	.read     = seq_read,
+	.llseek   = seq_lseek,
+	.write    = picolcd_debug_reset_write,
+	.release  = single_release,
+};
+
+/*
+ * The "eeprom" file
+ */
+static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	ssize_t ret = -EIO;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return 0;
+
+	/* prepare buffer with info about what we want to read (addr & len) */
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) & 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
+			sizeof(raw_data));
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* successful read :) */
+		ret = resp->raw_data[2];
+		if (ret > s)
+			ret = s;
+		if (copy_to_user(u, resp->raw_data+3, ret))
+			ret = -EFAULT;
+		else
+			*off += ret;
+	} /* anything else is some kind of IO error */
+
+	kfree(resp);
+	return ret;
+}
+
+static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	ssize_t ret = -EIO;
+	u8 raw_data[23];
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return -ENOSPC;
+
+	memset(raw_data, 0, sizeof(raw_data));
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) & 0xff;
+	raw_data[2] = min_t(size_t, 20, s);
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+
+	if (copy_from_user(raw_data+3, u, min((u8)20, raw_data[2])))
+		return -EFAULT;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
+			sizeof(raw_data));
+
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* check if written data matches */
+		if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
+			*off += raw_data[2];
+			ret = raw_data[2];
+		}
+	}
+	kfree(resp);
+	return ret;
+}
+
+/*
+ * Notes:
+ * - read/write happens in chunks of at most 20 bytes, it's up to userspace
+ *   to loop in order to get more data.
+ * - on write errors on otherwise correct write request the bytes
+ *   that should have been written are in undefined state.
+ */
+static const struct file_operations picolcd_debug_eeprom_fops = {
+	.owner    = THIS_MODULE,
+	.open     = simple_open,
+	.read     = picolcd_debug_eeprom_read,
+	.write    = picolcd_debug_eeprom_write,
+	.llseek   = generic_file_llseek,
+};
+
+/*
+ * The "flash" file
+ */
+/* record a flash address to buf (bounds check to be done by caller) */
+static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
+{
+	buf[0] = off & 0xff;
+	buf[1] = (off >> 8) & 0xff;
+	if (data->addr_sz == 3)
+		buf[2] = (off >> 16) & 0xff;
+	return data->addr_sz == 2 ? 2 : 3;
+}
+
+/* read a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
+		char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[4];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_READ_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
+				goto skip;
+			if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
+				err = -EFAULT;
+				goto skip;
+			}
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			return ret > 0 ? ret : err;
+	}
+	return ret;
+}
+
+static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x05fff)
+		return 0;
+	if (*off + s > 0x05fff)
+		s = 0x06000 - *off;
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
+	else
+		return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
+}
+
+/* erase block aligned to 64bytes boundary */
+static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
+		loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	int len_off;
+	ssize_t ret = -EIO;
+
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+	resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
+	if (!resp || !resp->in_report)
+		goto skip;
+	if (resp->in_report->id == REPORT_MEMORY ||
+		resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
+		if (memcmp(raw_data, resp->raw_data, len_off) != 0)
+			goto skip;
+		ret = 0;
+	}
+skip:
+	kfree(resp);
+	return ret;
+}
+
+/* write a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
+		const char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[36];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
+			err = -EFAULT;
+			break;
+		}
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
+				len_off+1+raw_data[len_off]);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
+				goto skip;
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			break;
+	}
+	return ret > 0 ? ret : err;
+}
+
+static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	ssize_t err, ret = 0;
+	int report_erase, report_write;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x5fff)
+		return -ENOSPC;
+	if (s & 0x3f)
+		return -EINVAL;
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	if (data->status & PICOLCD_BOOTLOADER) {
+		report_erase = REPORT_BL_ERASE_MEMORY;
+		report_write = REPORT_BL_WRITE_MEMORY;
+	} else {
+		report_erase = REPORT_ERASE_MEMORY;
+		report_write = REPORT_WRITE_MEMORY;
+	}
+	mutex_lock(&data->mutex_flash);
+	while (s > 0) {
+		err = _picolcd_flash_erase64(data, report_erase, off);
+		if (err)
+			break;
+		err = _picolcd_flash_write(data, report_write, u, 64, off);
+		if (err < 0)
+			break;
+		ret += err;
+		*off += err;
+		s -= err;
+		if (err != 64)
+			break;
+	}
+	mutex_unlock(&data->mutex_flash);
+	return ret > 0 ? ret : err;
+}
+
+/*
+ * Notes:
+ * - concurrent writing is prevented by mutex and all writes must be
+ *   n*64 bytes and 64-byte aligned, each write being preceded by an
+ *   ERASE which erases a 64byte block.
+ *   If less than requested was written or an error is returned for an
+ *   otherwise correct write request the next 64-byte block which should
+ *   have been written is in undefined state (mostly: original, erased,
+ *   (half-)written with write error)
+ * - reading can happen without special restriction
+ */
+static const struct file_operations picolcd_debug_flash_fops = {
+	.owner    = THIS_MODULE,
+	.open     = simple_open,
+	.read     = picolcd_debug_flash_read,
+	.write    = picolcd_debug_flash_write,
+	.llseek   = generic_file_llseek,
+};
+
+
+/*
+ * Helper code for HID report level dumping/debugging
+ */
+static const char * const error_codes[] = {
+	"success", "parameter missing", "data_missing", "block readonly",
+	"block not erasable", "block too big", "section overflow",
+	"invalid command length", "invalid data length",
+};
+
+static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data,
+		const size_t data_len)
+{
+	int i, j;
+	for (i = j = 0; i < data_len && j + 4 < dst_sz; i++) {
+		dst[j++] = hex_asc[(data[i] >> 4) & 0x0f];
+		dst[j++] = hex_asc[data[i] & 0x0f];
+		dst[j++] = ' ';
+	}
+	dst[j]   = '\0';
+	if (j > 0)
+		dst[j-1] = '\n';
+	if (i < data_len && j > 2)
+		dst[j-2] = dst[j-3] = '.';
+}
+
+void picolcd_debug_out_report(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report)
+{
+	u8 *raw_data;
+	int raw_size = (report->size >> 3) + 1;
+	char *buff;
+#define BUFF_SZ 256
+
+	/* Avoid unnecessary overhead if debugfs is disabled */
+	if (list_empty(&hdev->debug_list))
+		return;
+
+	buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
+	if (!buff)
+		return;
+
+	raw_data = hid_alloc_report_buf(report, GFP_ATOMIC);
+	if (!raw_data) {
+		kfree(buff);
+		return;
+	}
+
+	snprintf(buff, BUFF_SZ, "\nout report %d (size %d) =  ",
+			report->id, raw_size);
+	hid_debug_event(hdev, buff);
+	raw_data[0] = report->id;
+	hid_output_report(report, raw_data);
+	dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size);
+	hid_debug_event(hdev, buff);
+
+	switch (report->id) {
+	case REPORT_LED_STATE:
+		/* 1 data byte with GPO state */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_LED_STATE", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tGPO state: 0x%02x\n", raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_BRIGHTNESS:
+		/* 1 data byte with brightness */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_BRIGHTNESS", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tBrightness: 0x%02x\n", raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_CONTRAST:
+		/* 1 data byte with contrast */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_CONTRAST", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tContrast: 0x%02x\n", raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_RESET:
+		/* 2 data bytes with reset duration in ms */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_RESET", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tDuration: 0x%02x%02x (%dms)\n",
+				raw_data[2], raw_data[1], raw_data[2] << 8 | raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_LCD_CMD:
+		/* 63 data bytes with LCD commands */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_LCD_CMD", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		/* TODO: format decoding */
+		break;
+	case REPORT_LCD_DATA:
+		/* 63 data bytes with LCD data */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_LCD_CMD", report->id, raw_size-1);
+		/* TODO: format decoding */
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_LCD_CMD_DATA:
+		/* 63 data bytes with LCD commands and data */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_LCD_CMD", report->id, raw_size-1);
+		/* TODO: format decoding */
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_EE_READ:
+		/* 3 data bytes with read area description */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_EE_READ", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_EE_WRITE:
+		/* 3+1..20 data bytes with write area description */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_EE_WRITE", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+		hid_debug_event(hdev, buff);
+		if (raw_data[3] == 0) {
+			snprintf(buff, BUFF_SZ, "\tNo data\n");
+		} else if (raw_data[3] + 4 <= raw_size) {
+			snprintf(buff, BUFF_SZ, "\tData: ");
+			hid_debug_event(hdev, buff);
+			dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
+		} else {
+			snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+		}
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_ERASE_MEMORY:
+	case REPORT_BL_ERASE_MEMORY:
+		/* 3 data bytes with pointer inside erase block */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_ERASE_MEMORY", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		switch (data->addr_sz) {
+		case 2:
+			snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x\n",
+					raw_data[2], raw_data[1]);
+			break;
+		case 3:
+			snprintf(buff, BUFF_SZ, "\tAddress inside 64 byte block: 0x%02x%02x%02x\n",
+					raw_data[3], raw_data[2], raw_data[1]);
+			break;
+		default:
+			snprintf(buff, BUFF_SZ, "\tNot supported\n");
+		}
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_READ_MEMORY:
+	case REPORT_BL_READ_MEMORY:
+		/* 4 data bytes with read area description */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_READ_MEMORY", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		switch (data->addr_sz) {
+		case 2:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+					raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+			break;
+		case 3:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
+					raw_data[3], raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
+			break;
+		default:
+			snprintf(buff, BUFF_SZ, "\tNot supported\n");
+		}
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_WRITE_MEMORY:
+	case REPORT_BL_WRITE_MEMORY:
+		/* 4+1..32 data bytes with write adrea description */
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_WRITE_MEMORY", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		switch (data->addr_sz) {
+		case 2:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+					raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+			hid_debug_event(hdev, buff);
+			if (raw_data[3] == 0) {
+				snprintf(buff, BUFF_SZ, "\tNo data\n");
+			} else if (raw_data[3] + 4 <= raw_size) {
+				snprintf(buff, BUFF_SZ, "\tData: ");
+				hid_debug_event(hdev, buff);
+				dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
+			} else {
+				snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+			}
+			break;
+		case 3:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
+					raw_data[3], raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
+			hid_debug_event(hdev, buff);
+			if (raw_data[4] == 0) {
+				snprintf(buff, BUFF_SZ, "\tNo data\n");
+			} else if (raw_data[4] + 5 <= raw_size) {
+				snprintf(buff, BUFF_SZ, "\tData: ");
+				hid_debug_event(hdev, buff);
+				dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
+			} else {
+				snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+			}
+			break;
+		default:
+			snprintf(buff, BUFF_SZ, "\tNot supported\n");
+		}
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_SPLASH_RESTART:
+		/* TODO */
+		break;
+	case REPORT_EXIT_KEYBOARD:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_EXIT_KEYBOARD", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
+				raw_data[1] | (raw_data[2] << 8),
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_VERSION:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_VERSION", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_DEVID:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_DEVID", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_SPLASH_SIZE:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_SPLASH_SIZE", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_HOOK_VERSION:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_HOOK_VERSION", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_EXIT_FLASHER:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"REPORT_VERSION", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tRestart delay: %dms (0x%02x%02x)\n",
+				raw_data[1] | (raw_data[2] << 8),
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	default:
+		snprintf(buff, BUFF_SZ, "out report %s (%d, size=%d)\n",
+			"<unknown>", report->id, raw_size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	}
+	wake_up_interruptible(&hdev->debug_wait);
+	kfree(raw_data);
+	kfree(buff);
+}
+
+void picolcd_debug_raw_event(struct picolcd_data *data,
+		struct hid_device *hdev, struct hid_report *report,
+		u8 *raw_data, int size)
+{
+	char *buff;
+
+#define BUFF_SZ 256
+	/* Avoid unnecessary overhead if debugfs is disabled */
+	if (list_empty(&hdev->debug_list))
+		return;
+
+	buff = kmalloc(BUFF_SZ, GFP_ATOMIC);
+	if (!buff)
+		return;
+
+	switch (report->id) {
+	case REPORT_ERROR_CODE:
+		/* 2 data bytes with affected report and error code */
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_ERROR_CODE", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		if (raw_data[2] < ARRAY_SIZE(error_codes))
+			snprintf(buff, BUFF_SZ, "\tError code 0x%02x (%s) in reply to report 0x%02x\n",
+					raw_data[2], error_codes[raw_data[2]], raw_data[1]);
+		else
+			snprintf(buff, BUFF_SZ, "\tError code 0x%02x in reply to report 0x%02x\n",
+					raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_KEY_STATE:
+		/* 2 data bytes with key state */
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_KEY_STATE", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		if (raw_data[1] == 0)
+			snprintf(buff, BUFF_SZ, "\tNo key pressed\n");
+		else if (raw_data[2] == 0)
+			snprintf(buff, BUFF_SZ, "\tOne key pressed: 0x%02x (%d)\n",
+					raw_data[1], raw_data[1]);
+		else
+			snprintf(buff, BUFF_SZ, "\tTwo keys pressed: 0x%02x (%d), 0x%02x (%d)\n",
+					raw_data[1], raw_data[1], raw_data[2], raw_data[2]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_IR_DATA:
+		/* Up to 20 byes of IR scancode data */
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_IR_DATA", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		if (raw_data[1] == 0) {
+			snprintf(buff, BUFF_SZ, "\tUnexpectedly 0 data length\n");
+			hid_debug_event(hdev, buff);
+		} else if (raw_data[1] + 1 <= size) {
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n\tIR Data: ",
+					raw_data[1]);
+			hid_debug_event(hdev, buff);
+			dump_buff_as_hex(buff, BUFF_SZ, raw_data+2, raw_data[1]);
+			hid_debug_event(hdev, buff);
+		} else {
+			snprintf(buff, BUFF_SZ, "\tOverflowing data length: %d\n",
+					raw_data[1]-1);
+			hid_debug_event(hdev, buff);
+		}
+		break;
+	case REPORT_EE_DATA:
+		/* Data buffer in response to REPORT_EE_READ or REPORT_EE_WRITE */
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_EE_DATA", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+		hid_debug_event(hdev, buff);
+		if (raw_data[3] == 0) {
+			snprintf(buff, BUFF_SZ, "\tNo data\n");
+			hid_debug_event(hdev, buff);
+		} else if (raw_data[3] + 4 <= size) {
+			snprintf(buff, BUFF_SZ, "\tData: ");
+			hid_debug_event(hdev, buff);
+			dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
+			hid_debug_event(hdev, buff);
+		} else {
+			snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+			hid_debug_event(hdev, buff);
+		}
+		break;
+	case REPORT_MEMORY:
+		/* Data buffer in response to REPORT_READ_MEMORY or REPORT_WRITE_MEMORY */
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_MEMORY", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		switch (data->addr_sz) {
+		case 2:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x\n",
+					raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[3]);
+			hid_debug_event(hdev, buff);
+			if (raw_data[3] == 0) {
+				snprintf(buff, BUFF_SZ, "\tNo data\n");
+			} else if (raw_data[3] + 4 <= size) {
+				snprintf(buff, BUFF_SZ, "\tData: ");
+				hid_debug_event(hdev, buff);
+				dump_buff_as_hex(buff, BUFF_SZ, raw_data+4, raw_data[3]);
+			} else {
+				snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+			}
+			break;
+		case 3:
+			snprintf(buff, BUFF_SZ, "\tData address: 0x%02x%02x%02x\n",
+					raw_data[3], raw_data[2], raw_data[1]);
+			hid_debug_event(hdev, buff);
+			snprintf(buff, BUFF_SZ, "\tData length: %d\n", raw_data[4]);
+			hid_debug_event(hdev, buff);
+			if (raw_data[4] == 0) {
+				snprintf(buff, BUFF_SZ, "\tNo data\n");
+			} else if (raw_data[4] + 5 <= size) {
+				snprintf(buff, BUFF_SZ, "\tData: ");
+				hid_debug_event(hdev, buff);
+				dump_buff_as_hex(buff, BUFF_SZ, raw_data+5, raw_data[4]);
+			} else {
+				snprintf(buff, BUFF_SZ, "\tData overflowed\n");
+			}
+			break;
+		default:
+			snprintf(buff, BUFF_SZ, "\tNot supported\n");
+		}
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_VERSION:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_VERSION", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
+				raw_data[2], raw_data[1]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_BL_ERASE_MEMORY:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_BL_ERASE_MEMORY", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		/* TODO */
+		break;
+	case REPORT_BL_READ_MEMORY:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_BL_READ_MEMORY", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		/* TODO */
+		break;
+	case REPORT_BL_WRITE_MEMORY:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_BL_WRITE_MEMORY", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		/* TODO */
+		break;
+	case REPORT_DEVID:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_DEVID", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tSerial: 0x%02x%02x%02x%02x\n",
+				raw_data[1], raw_data[2], raw_data[3], raw_data[4]);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tType: 0x%02x\n",
+				raw_data[5]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_SPLASH_SIZE:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_SPLASH_SIZE", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tTotal splash space: %d\n",
+				(raw_data[2] << 8) | raw_data[1]);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tUsed splash space: %d\n",
+				(raw_data[4] << 8) | raw_data[3]);
+		hid_debug_event(hdev, buff);
+		break;
+	case REPORT_HOOK_VERSION:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"REPORT_HOOK_VERSION", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		snprintf(buff, BUFF_SZ, "\tFirmware version: %d.%d\n",
+				raw_data[1], raw_data[2]);
+		hid_debug_event(hdev, buff);
+		break;
+	default:
+		snprintf(buff, BUFF_SZ, "report %s (%d, size=%d)\n",
+			"<unknown>", report->id, size-1);
+		hid_debug_event(hdev, buff);
+		break;
+	}
+	wake_up_interruptible(&hdev->debug_wait);
+	kfree(buff);
+}
+
+void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset)
+{
+	struct hid_device *hdev = data->hdev;
+
+	mutex_init(&data->mutex_flash);
+
+	/* reset */
+	if (reset)
+		data->debug_reset = debugfs_create_file("reset", 0600,
+				hdev->debug_dir, data, &picolcd_debug_reset_fops);
+
+	/* eeprom */
+	if (eeprom_r || eeprom_w)
+		data->debug_eeprom = debugfs_create_file("eeprom",
+			(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
+
+	/* flash */
+	if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
+		data->addr_sz = flash_r->field[0]->report_count - 1;
+	else
+		data->addr_sz = -1;
+	if (data->addr_sz == 2 || data->addr_sz == 3) {
+		data->debug_flash = debugfs_create_file("flash",
+			(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_flash_fops);
+	} else if (flash_r || flash_w)
+		hid_warn(hdev, "Unexpected FLASH access reports, please submit rdesc for review\n");
+}
+
+void picolcd_exit_devfs(struct picolcd_data *data)
+{
+	struct dentry *dent;
+
+	dent = data->debug_reset;
+	data->debug_reset = NULL;
+	debugfs_remove(dent);
+	dent = data->debug_eeprom;
+	data->debug_eeprom = NULL;
+	debugfs_remove(dent);
+	dent = data->debug_flash;
+	data->debug_flash = NULL;
+	debugfs_remove(dent);
+	mutex_destroy(&data->mutex_flash);
+}
+
diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c
new file mode 100644
index 0000000..864a084
--- /dev/null
+++ b/drivers/hid/hid-picolcd_fb.c
@@ -0,0 +1,618 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+#include <linux/vmalloc.h>
+
+#include <linux/fb.h>
+#include <linux/module.h>
+
+#include "hid-picolcd.h"
+
+/* Framebuffer
+ *
+ * The PicoLCD use a Topway LCD module of 256x64 pixel
+ * This display area is tiled over 4 controllers with 8 tiles
+ * each. Each tile has 8x64 pixel, each data byte representing
+ * a 1-bit wide vertical line of the tile.
+ *
+ * The display can be updated at a tile granularity.
+ *
+ *       Chip 1           Chip 2           Chip 3           Chip 4
+ * +----------------+----------------+----------------+----------------+
+ * |     Tile 1     |     Tile 1     |     Tile 1     |     Tile 1     |
+ * +----------------+----------------+----------------+----------------+
+ * |     Tile 2     |     Tile 2     |     Tile 2     |     Tile 2     |
+ * +----------------+----------------+----------------+----------------+
+ *                                  ...
+ * +----------------+----------------+----------------+----------------+
+ * |     Tile 8     |     Tile 8     |     Tile 8     |     Tile 8     |
+ * +----------------+----------------+----------------+----------------+
+ */
+#define PICOLCDFB_NAME "picolcdfb"
+#define PICOLCDFB_WIDTH (256)
+#define PICOLCDFB_HEIGHT (64)
+#define PICOLCDFB_SIZE (PICOLCDFB_WIDTH * PICOLCDFB_HEIGHT / 8)
+
+#define PICOLCDFB_UPDATE_RATE_LIMIT   10
+#define PICOLCDFB_UPDATE_RATE_DEFAULT  2
+
+/* Framebuffer visual structures */
+static const struct fb_fix_screeninfo picolcdfb_fix = {
+	.id          = PICOLCDFB_NAME,
+	.type        = FB_TYPE_PACKED_PIXELS,
+	.visual      = FB_VISUAL_MONO01,
+	.xpanstep    = 0,
+	.ypanstep    = 0,
+	.ywrapstep   = 0,
+	.line_length = PICOLCDFB_WIDTH / 8,
+	.accel       = FB_ACCEL_NONE,
+};
+
+static const struct fb_var_screeninfo picolcdfb_var = {
+	.xres           = PICOLCDFB_WIDTH,
+	.yres           = PICOLCDFB_HEIGHT,
+	.xres_virtual   = PICOLCDFB_WIDTH,
+	.yres_virtual   = PICOLCDFB_HEIGHT,
+	.width          = 103,
+	.height         = 26,
+	.bits_per_pixel = 1,
+	.grayscale      = 1,
+	.red            = {
+		.offset = 0,
+		.length = 1,
+		.msb_right = 0,
+	},
+	.green          = {
+		.offset = 0,
+		.length = 1,
+		.msb_right = 0,
+	},
+	.blue           = {
+		.offset = 0,
+		.length = 1,
+		.msb_right = 0,
+	},
+	.transp         = {
+		.offset = 0,
+		.length = 0,
+		.msb_right = 0,
+	},
+};
+
+/* Send a given tile to PicoLCD */
+static int picolcd_fb_send_tile(struct picolcd_data *data, u8 *vbitmap,
+		int chip, int tile)
+{
+	struct hid_report *report1, *report2;
+	unsigned long flags;
+	u8 *tdata;
+	int i;
+
+	report1 = picolcd_out_report(REPORT_LCD_CMD_DATA, data->hdev);
+	if (!report1 || report1->maxfield != 1)
+		return -ENODEV;
+	report2 = picolcd_out_report(REPORT_LCD_DATA, data->hdev);
+	if (!report2 || report2->maxfield != 1)
+		return -ENODEV;
+
+	spin_lock_irqsave(&data->lock, flags);
+	if ((data->status & PICOLCD_FAILED)) {
+		spin_unlock_irqrestore(&data->lock, flags);
+		return -ENODEV;
+	}
+	hid_set_field(report1->field[0],  0, chip << 2);
+	hid_set_field(report1->field[0],  1, 0x02);
+	hid_set_field(report1->field[0],  2, 0x00);
+	hid_set_field(report1->field[0],  3, 0x00);
+	hid_set_field(report1->field[0],  4, 0xb8 | tile);
+	hid_set_field(report1->field[0],  5, 0x00);
+	hid_set_field(report1->field[0],  6, 0x00);
+	hid_set_field(report1->field[0],  7, 0x40);
+	hid_set_field(report1->field[0],  8, 0x00);
+	hid_set_field(report1->field[0],  9, 0x00);
+	hid_set_field(report1->field[0], 10,   32);
+
+	hid_set_field(report2->field[0],  0, (chip << 2) | 0x01);
+	hid_set_field(report2->field[0],  1, 0x00);
+	hid_set_field(report2->field[0],  2, 0x00);
+	hid_set_field(report2->field[0],  3,   32);
+
+	tdata = vbitmap + (tile * 4 + chip) * 64;
+	for (i = 0; i < 64; i++)
+		if (i < 32)
+			hid_set_field(report1->field[0], 11 + i, tdata[i]);
+		else
+			hid_set_field(report2->field[0], 4 + i - 32, tdata[i]);
+
+	hid_hw_request(data->hdev, report1, HID_REQ_SET_REPORT);
+	hid_hw_request(data->hdev, report2, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+	return 0;
+}
+
+/* Translate a single tile*/
+static int picolcd_fb_update_tile(u8 *vbitmap, const u8 *bitmap, int bpp,
+		int chip, int tile)
+{
+	int i, b, changed = 0;
+	u8 tdata[64];
+	u8 *vdata = vbitmap + (tile * 4 + chip) * 64;
+
+	if (bpp == 1) {
+		for (b = 7; b >= 0; b--) {
+			const u8 *bdata = bitmap + tile * 256 + chip * 8 + b * 32;
+			for (i = 0; i < 64; i++) {
+				tdata[i] <<= 1;
+				tdata[i] |= (bdata[i/8] >> (i % 8)) & 0x01;
+			}
+		}
+	} else if (bpp == 8) {
+		for (b = 7; b >= 0; b--) {
+			const u8 *bdata = bitmap + (tile * 256 + chip * 8 + b * 32) * 8;
+			for (i = 0; i < 64; i++) {
+				tdata[i] <<= 1;
+				tdata[i] |= (bdata[i] & 0x80) ? 0x01 : 0x00;
+			}
+		}
+	} else {
+		/* Oops, we should never get here! */
+		WARN_ON(1);
+		return 0;
+	}
+
+	for (i = 0; i < 64; i++)
+		if (tdata[i] != vdata[i]) {
+			changed = 1;
+			vdata[i] = tdata[i];
+		}
+	return changed;
+}
+
+void picolcd_fb_refresh(struct picolcd_data *data)
+{
+	if (data->fb_info)
+		schedule_delayed_work(&data->fb_info->deferred_work, 0);
+}
+
+/* Reconfigure LCD display */
+int picolcd_fb_reset(struct picolcd_data *data, int clear)
+{
+	struct hid_report *report = picolcd_out_report(REPORT_LCD_CMD, data->hdev);
+	struct picolcd_fb_data *fbdata = data->fb_info->par;
+	int i, j;
+	unsigned long flags;
+	static const u8 mapcmd[8] = { 0x00, 0x02, 0x00, 0x64, 0x3f, 0x00, 0x64, 0xc0 };
+
+	if (!report || report->maxfield != 1)
+		return -ENODEV;
+
+	spin_lock_irqsave(&data->lock, flags);
+	for (i = 0; i < 4; i++) {
+		for (j = 0; j < report->field[0]->maxusage; j++)
+			if (j == 0)
+				hid_set_field(report->field[0], j, i << 2);
+			else if (j < sizeof(mapcmd))
+				hid_set_field(report->field[0], j, mapcmd[j]);
+			else
+				hid_set_field(report->field[0], j, 0);
+		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+	}
+	spin_unlock_irqrestore(&data->lock, flags);
+
+	if (clear) {
+		memset(fbdata->vbitmap, 0, PICOLCDFB_SIZE);
+		memset(fbdata->bitmap, 0, PICOLCDFB_SIZE*fbdata->bpp);
+	}
+	fbdata->force = 1;
+
+	/* schedule first output of framebuffer */
+	if (fbdata->ready)
+		schedule_delayed_work(&data->fb_info->deferred_work, 0);
+	else
+		fbdata->ready = 1;
+
+	return 0;
+}
+
+/* Update fb_vbitmap from the screen_base and send changed tiles to device */
+static void picolcd_fb_update(struct fb_info *info)
+{
+	int chip, tile, n;
+	unsigned long flags;
+	struct picolcd_fb_data *fbdata = info->par;
+	struct picolcd_data *data;
+
+	mutex_lock(&info->lock);
+
+	spin_lock_irqsave(&fbdata->lock, flags);
+	if (!fbdata->ready && fbdata->picolcd)
+		picolcd_fb_reset(fbdata->picolcd, 0);
+	spin_unlock_irqrestore(&fbdata->lock, flags);
+
+	/*
+	 * Translate the framebuffer into the format needed by the PicoLCD.
+	 * See display layout above.
+	 * Do this one tile after the other and push those tiles that changed.
+	 *
+	 * Wait for our IO to complete as otherwise we might flood the queue!
+	 */
+	n = 0;
+	for (chip = 0; chip < 4; chip++)
+		for (tile = 0; tile < 8; tile++) {
+			if (!fbdata->force && !picolcd_fb_update_tile(
+					fbdata->vbitmap, fbdata->bitmap,
+					fbdata->bpp, chip, tile))
+				continue;
+			n += 2;
+			if (n >= HID_OUTPUT_FIFO_SIZE / 2) {
+				spin_lock_irqsave(&fbdata->lock, flags);
+				data = fbdata->picolcd;
+				spin_unlock_irqrestore(&fbdata->lock, flags);
+				mutex_unlock(&info->lock);
+				if (!data)
+					return;
+				hid_hw_wait(data->hdev);
+				mutex_lock(&info->lock);
+				n = 0;
+			}
+			spin_lock_irqsave(&fbdata->lock, flags);
+			data = fbdata->picolcd;
+			spin_unlock_irqrestore(&fbdata->lock, flags);
+			if (!data || picolcd_fb_send_tile(data,
+					fbdata->vbitmap, chip, tile))
+				goto out;
+		}
+	fbdata->force = false;
+	if (n) {
+		spin_lock_irqsave(&fbdata->lock, flags);
+		data = fbdata->picolcd;
+		spin_unlock_irqrestore(&fbdata->lock, flags);
+		mutex_unlock(&info->lock);
+		if (data)
+			hid_hw_wait(data->hdev);
+		return;
+	}
+out:
+	mutex_unlock(&info->lock);
+}
+
+/* Stub to call the system default and update the image on the picoLCD */
+static void picolcd_fb_fillrect(struct fb_info *info,
+		const struct fb_fillrect *rect)
+{
+	if (!info->par)
+		return;
+	sys_fillrect(info, rect);
+
+	schedule_delayed_work(&info->deferred_work, 0);
+}
+
+/* Stub to call the system default and update the image on the picoLCD */
+static void picolcd_fb_copyarea(struct fb_info *info,
+		const struct fb_copyarea *area)
+{
+	if (!info->par)
+		return;
+	sys_copyarea(info, area);
+
+	schedule_delayed_work(&info->deferred_work, 0);
+}
+
+/* Stub to call the system default and update the image on the picoLCD */
+static void picolcd_fb_imageblit(struct fb_info *info, const struct fb_image *image)
+{
+	if (!info->par)
+		return;
+	sys_imageblit(info, image);
+
+	schedule_delayed_work(&info->deferred_work, 0);
+}
+
+/*
+ * this is the slow path from userspace. they can seek and write to
+ * the fb. it's inefficient to do anything less than a full screen draw
+ */
+static ssize_t picolcd_fb_write(struct fb_info *info, const char __user *buf,
+		size_t count, loff_t *ppos)
+{
+	ssize_t ret;
+	if (!info->par)
+		return -ENODEV;
+	ret = fb_sys_write(info, buf, count, ppos);
+	if (ret >= 0)
+		schedule_delayed_work(&info->deferred_work, 0);
+	return ret;
+}
+
+static int picolcd_fb_blank(int blank, struct fb_info *info)
+{
+	/* We let fb notification do this for us via lcd/backlight device */
+	return 0;
+}
+
+static void picolcd_fb_destroy(struct fb_info *info)
+{
+	struct picolcd_fb_data *fbdata = info->par;
+
+	/* make sure no work is deferred */
+	fb_deferred_io_cleanup(info);
+
+	/* No thridparty should ever unregister our framebuffer! */
+	WARN_ON(fbdata->picolcd != NULL);
+
+	vfree((u8 *)info->fix.smem_start);
+	framebuffer_release(info);
+}
+
+static int picolcd_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	__u32 bpp      = var->bits_per_pixel;
+	__u32 activate = var->activate;
+
+	/* only allow 1/8 bit depth (8-bit is grayscale) */
+	*var = picolcdfb_var;
+	var->activate = activate;
+	if (bpp >= 8) {
+		var->bits_per_pixel = 8;
+		var->red.length     = 8;
+		var->green.length   = 8;
+		var->blue.length    = 8;
+	} else {
+		var->bits_per_pixel = 1;
+		var->red.length     = 1;
+		var->green.length   = 1;
+		var->blue.length    = 1;
+	}
+	return 0;
+}
+
+static int picolcd_set_par(struct fb_info *info)
+{
+	struct picolcd_fb_data *fbdata = info->par;
+	u8 *tmp_fb, *o_fb;
+	if (info->var.bits_per_pixel == fbdata->bpp)
+		return 0;
+	/* switch between 1/8 bit depths */
+	if (info->var.bits_per_pixel != 1 && info->var.bits_per_pixel != 8)
+		return -EINVAL;
+
+	o_fb   = fbdata->bitmap;
+	tmp_fb = kmalloc_array(PICOLCDFB_SIZE, info->var.bits_per_pixel,
+			       GFP_KERNEL);
+	if (!tmp_fb)
+		return -ENOMEM;
+
+	/* translate FB content to new bits-per-pixel */
+	if (info->var.bits_per_pixel == 1) {
+		int i, b;
+		for (i = 0; i < PICOLCDFB_SIZE; i++) {
+			u8 p = 0;
+			for (b = 0; b < 8; b++) {
+				p <<= 1;
+				p |= o_fb[i*8+b] ? 0x01 : 0x00;
+			}
+			tmp_fb[i] = p;
+		}
+		memcpy(o_fb, tmp_fb, PICOLCDFB_SIZE);
+		info->fix.visual = FB_VISUAL_MONO01;
+		info->fix.line_length = PICOLCDFB_WIDTH / 8;
+	} else {
+		int i;
+		memcpy(tmp_fb, o_fb, PICOLCDFB_SIZE);
+		for (i = 0; i < PICOLCDFB_SIZE * 8; i++)
+			o_fb[i] = tmp_fb[i/8] & (0x01 << (7 - i % 8)) ? 0xff : 0x00;
+		info->fix.visual = FB_VISUAL_DIRECTCOLOR;
+		info->fix.line_length = PICOLCDFB_WIDTH;
+	}
+
+	kfree(tmp_fb);
+	fbdata->bpp = info->var.bits_per_pixel;
+	return 0;
+}
+
+/* Note this can't be const because of struct fb_info definition */
+static struct fb_ops picolcdfb_ops = {
+	.owner        = THIS_MODULE,
+	.fb_destroy   = picolcd_fb_destroy,
+	.fb_read      = fb_sys_read,
+	.fb_write     = picolcd_fb_write,
+	.fb_blank     = picolcd_fb_blank,
+	.fb_fillrect  = picolcd_fb_fillrect,
+	.fb_copyarea  = picolcd_fb_copyarea,
+	.fb_imageblit = picolcd_fb_imageblit,
+	.fb_check_var = picolcd_fb_check_var,
+	.fb_set_par   = picolcd_set_par,
+};
+
+
+/* Callback from deferred IO workqueue */
+static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist)
+{
+	picolcd_fb_update(info);
+}
+
+static const struct fb_deferred_io picolcd_fb_defio = {
+	.delay = HZ / PICOLCDFB_UPDATE_RATE_DEFAULT,
+	.deferred_io = picolcd_fb_deferred_io,
+};
+
+
+/*
+ * The "fb_update_rate" sysfs attribute
+ */
+static ssize_t picolcd_fb_update_rate_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+	struct picolcd_fb_data *fbdata = data->fb_info->par;
+	unsigned i, fb_update_rate = fbdata->update_rate;
+	size_t ret = 0;
+
+	for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
+		if (ret >= PAGE_SIZE)
+			break;
+		else if (i == fb_update_rate)
+			ret += snprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
+		else
+			ret += snprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
+	if (ret > 0)
+		buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
+	return ret;
+}
+
+static ssize_t picolcd_fb_update_rate_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct picolcd_data *data = dev_get_drvdata(dev);
+	struct picolcd_fb_data *fbdata = data->fb_info->par;
+	int i;
+	unsigned u;
+
+	if (count < 1 || count > 10)
+		return -EINVAL;
+
+	i = sscanf(buf, "%u", &u);
+	if (i != 1)
+		return -EINVAL;
+
+	if (u > PICOLCDFB_UPDATE_RATE_LIMIT)
+		return -ERANGE;
+	else if (u == 0)
+		u = PICOLCDFB_UPDATE_RATE_DEFAULT;
+
+	fbdata->update_rate = u;
+	data->fb_info->fbdefio->delay = HZ / fbdata->update_rate;
+	return count;
+}
+
+static DEVICE_ATTR(fb_update_rate, 0664, picolcd_fb_update_rate_show,
+		picolcd_fb_update_rate_store);
+
+/* initialize Framebuffer device */
+int picolcd_init_framebuffer(struct picolcd_data *data)
+{
+	struct device *dev = &data->hdev->dev;
+	struct fb_info *info = NULL;
+	struct picolcd_fb_data *fbdata = NULL;
+	int i, error = -ENOMEM;
+	u32 *palette;
+
+	/* The extra memory is:
+	 * - 256*u32 for pseudo_palette
+	 * - struct fb_deferred_io
+	 */
+	info = framebuffer_alloc(256 * sizeof(u32) +
+			sizeof(struct fb_deferred_io) +
+			sizeof(struct picolcd_fb_data) +
+			PICOLCDFB_SIZE, dev);
+	if (info == NULL) {
+		dev_err(dev, "failed to allocate a framebuffer\n");
+		goto err_nomem;
+	}
+
+	info->fbdefio = info->par;
+	*info->fbdefio = picolcd_fb_defio;
+	info->par += sizeof(struct fb_deferred_io);
+	palette = info->par;
+	info->par += 256 * sizeof(u32);
+	for (i = 0; i < 256; i++)
+		palette[i] = i > 0 && i < 16 ? 0xff : 0;
+	info->pseudo_palette = palette;
+	info->fbops = &picolcdfb_ops;
+	info->var = picolcdfb_var;
+	info->fix = picolcdfb_fix;
+	info->fix.smem_len   = PICOLCDFB_SIZE*8;
+	info->flags = FBINFO_FLAG_DEFAULT;
+
+	fbdata = info->par;
+	spin_lock_init(&fbdata->lock);
+	fbdata->picolcd = data;
+	fbdata->update_rate = PICOLCDFB_UPDATE_RATE_DEFAULT;
+	fbdata->bpp     = picolcdfb_var.bits_per_pixel;
+	fbdata->force   = 1;
+	fbdata->vbitmap = info->par + sizeof(struct picolcd_fb_data);
+	fbdata->bitmap  = vmalloc(PICOLCDFB_SIZE*8);
+	if (fbdata->bitmap == NULL) {
+		dev_err(dev, "can't get a free page for framebuffer\n");
+		goto err_nomem;
+	}
+	info->screen_base = (char __force __iomem *)fbdata->bitmap;
+	info->fix.smem_start = (unsigned long)fbdata->bitmap;
+	memset(fbdata->vbitmap, 0xff, PICOLCDFB_SIZE);
+	data->fb_info = info;
+
+	error = picolcd_fb_reset(data, 1);
+	if (error) {
+		dev_err(dev, "failed to configure display\n");
+		goto err_cleanup;
+	}
+
+	error = device_create_file(dev, &dev_attr_fb_update_rate);
+	if (error) {
+		dev_err(dev, "failed to create sysfs attributes\n");
+		goto err_cleanup;
+	}
+
+	fb_deferred_io_init(info);
+	error = register_framebuffer(info);
+	if (error) {
+		dev_err(dev, "failed to register framebuffer\n");
+		goto err_sysfs;
+	}
+	return 0;
+
+err_sysfs:
+	device_remove_file(dev, &dev_attr_fb_update_rate);
+	fb_deferred_io_cleanup(info);
+err_cleanup:
+	data->fb_info    = NULL;
+
+err_nomem:
+	if (fbdata)
+		vfree(fbdata->bitmap);
+	framebuffer_release(info);
+	return error;
+}
+
+void picolcd_exit_framebuffer(struct picolcd_data *data)
+{
+	struct fb_info *info = data->fb_info;
+	struct picolcd_fb_data *fbdata;
+	unsigned long flags;
+
+	if (!info)
+		return;
+
+	device_remove_file(&data->hdev->dev, &dev_attr_fb_update_rate);
+	fbdata = info->par;
+
+	/* disconnect framebuffer from HID dev */
+	spin_lock_irqsave(&fbdata->lock, flags);
+	fbdata->picolcd = NULL;
+	spin_unlock_irqrestore(&fbdata->lock, flags);
+
+	/* make sure there is no running update - thus that fbdata->picolcd
+	 * once obtained under lock is guaranteed not to get free() under
+	 * the feet of the deferred work */
+	flush_delayed_work(&info->deferred_work);
+
+	data->fb_info = NULL;
+	unregister_framebuffer(info);
+}
diff --git a/drivers/hid/hid-picolcd_lcd.c b/drivers/hid/hid-picolcd_lcd.c
new file mode 100644
index 0000000..22dcbe1
--- /dev/null
+++ b/drivers/hid/hid-picolcd_lcd.c
@@ -0,0 +1,104 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+
+#include <linux/fb.h>
+#include <linux/lcd.h>
+
+#include "hid-picolcd.h"
+
+/*
+ * lcd class device
+ */
+static int picolcd_get_contrast(struct lcd_device *ldev)
+{
+	struct picolcd_data *data = lcd_get_data(ldev);
+	return data->lcd_contrast;
+}
+
+static int picolcd_set_contrast(struct lcd_device *ldev, int contrast)
+{
+	struct picolcd_data *data = lcd_get_data(ldev);
+	struct hid_report *report = picolcd_out_report(REPORT_CONTRAST, data->hdev);
+	unsigned long flags;
+
+	if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
+		return -ENODEV;
+
+	data->lcd_contrast = contrast & 0x0ff;
+	spin_lock_irqsave(&data->lock, flags);
+	hid_set_field(report->field[0], 0, data->lcd_contrast);
+	if (!(data->status & PICOLCD_FAILED))
+		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+	return 0;
+}
+
+static int picolcd_check_lcd_fb(struct lcd_device *ldev, struct fb_info *fb)
+{
+	return fb && fb == picolcd_fbinfo((struct picolcd_data *)lcd_get_data(ldev));
+}
+
+static struct lcd_ops picolcd_lcdops = {
+	.get_contrast   = picolcd_get_contrast,
+	.set_contrast   = picolcd_set_contrast,
+	.check_fb       = picolcd_check_lcd_fb,
+};
+
+int picolcd_init_lcd(struct picolcd_data *data, struct hid_report *report)
+{
+	struct device *dev = &data->hdev->dev;
+	struct lcd_device *ldev;
+
+	if (!report)
+		return -ENODEV;
+	if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
+			report->field[0]->report_size != 8) {
+		dev_err(dev, "unsupported CONTRAST report");
+		return -EINVAL;
+	}
+
+	ldev = lcd_device_register(dev_name(dev), dev, data, &picolcd_lcdops);
+	if (IS_ERR(ldev)) {
+		dev_err(dev, "failed to register LCD\n");
+		return PTR_ERR(ldev);
+	}
+	ldev->props.max_contrast = 0x0ff;
+	data->lcd_contrast = 0xe5;
+	data->lcd = ldev;
+	picolcd_set_contrast(ldev, 0xe5);
+	return 0;
+}
+
+void picolcd_exit_lcd(struct picolcd_data *data)
+{
+	struct lcd_device *ldev = data->lcd;
+
+	data->lcd = NULL;
+	lcd_device_unregister(ldev);
+}
+
+int picolcd_resume_lcd(struct picolcd_data *data)
+{
+	if (!data->lcd)
+		return 0;
+	return picolcd_set_contrast(data->lcd, data->lcd_contrast);
+}
+
diff --git a/drivers/hid/hid-picolcd_leds.c b/drivers/hid/hid-picolcd_leds.c
new file mode 100644
index 0000000..a802b4f
--- /dev/null
+++ b/drivers/hid/hid-picolcd_leds.c
@@ -0,0 +1,173 @@
+/***************************************************************************
+ *   Copyright (C) 2010-2012 by Bruno Prémont <bonbons@linux-vserver.org>  *
+ *                                                                         *
+ *   Based on Logitech G13 driver (v0.4)                                   *
+ *     Copyright (C) 2009 by Rick L. Vinyard, Jr. <rvinyard@cs.nmsu.edu>   *
+ *                                                                         *
+ *   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, version 2 of the License.               *
+ *                                                                         *
+ *   This driver is distributed in the hope that it will be useful, but    *
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
+ *   General Public License for more details.                              *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this software. If not see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+
+#include <linux/hid.h>
+#include <linux/hid-debug.h>
+#include <linux/input.h>
+#include "hid-ids.h"
+
+#include <linux/fb.h>
+#include <linux/vmalloc.h>
+#include <linux/backlight.h>
+#include <linux/lcd.h>
+
+#include <linux/leds.h>
+
+#include <linux/seq_file.h>
+#include <linux/debugfs.h>
+
+#include <linux/completion.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+
+#include "hid-picolcd.h"
+
+
+void picolcd_leds_set(struct picolcd_data *data)
+{
+	struct hid_report *report;
+	unsigned long flags;
+
+	if (!data->led[0])
+		return;
+	report = picolcd_out_report(REPORT_LED_STATE, data->hdev);
+	if (!report || report->maxfield != 1 || report->field[0]->report_count != 1)
+		return;
+
+	spin_lock_irqsave(&data->lock, flags);
+	hid_set_field(report->field[0], 0, data->led_state);
+	if (!(data->status & PICOLCD_FAILED))
+		hid_hw_request(data->hdev, report, HID_REQ_SET_REPORT);
+	spin_unlock_irqrestore(&data->lock, flags);
+}
+
+static void picolcd_led_set_brightness(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct picolcd_data *data;
+	int i, state = 0;
+
+	dev  = led_cdev->dev->parent;
+	hdev = to_hid_device(dev);
+	data = hid_get_drvdata(hdev);
+	if (!data)
+		return;
+	for (i = 0; i < 8; i++) {
+		if (led_cdev != data->led[i])
+			continue;
+		state = (data->led_state >> i) & 1;
+		if (value == LED_OFF && state) {
+			data->led_state &= ~(1 << i);
+			picolcd_leds_set(data);
+		} else if (value != LED_OFF && !state) {
+			data->led_state |= 1 << i;
+			picolcd_leds_set(data);
+		}
+		break;
+	}
+}
+
+static enum led_brightness picolcd_led_get_brightness(struct led_classdev *led_cdev)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct picolcd_data *data;
+	int i, value = 0;
+
+	dev  = led_cdev->dev->parent;
+	hdev = to_hid_device(dev);
+	data = hid_get_drvdata(hdev);
+	for (i = 0; i < 8; i++)
+		if (led_cdev == data->led[i]) {
+			value = (data->led_state >> i) & 1;
+			break;
+		}
+	return value ? LED_FULL : LED_OFF;
+}
+
+int picolcd_init_leds(struct picolcd_data *data, struct hid_report *report)
+{
+	struct device *dev = &data->hdev->dev;
+	struct led_classdev *led;
+	size_t name_sz = strlen(dev_name(dev)) + 8;
+	char *name;
+	int i, ret = 0;
+
+	if (!report)
+		return -ENODEV;
+	if (report->maxfield != 1 || report->field[0]->report_count != 1 ||
+			report->field[0]->report_size != 8) {
+		dev_err(dev, "unsupported LED_STATE report");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < 8; i++) {
+		led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+		if (!led) {
+			dev_err(dev, "can't allocate memory for LED %d\n", i);
+			ret = -ENOMEM;
+			goto err;
+		}
+		name = (void *)(&led[1]);
+		snprintf(name, name_sz, "%s::GPO%d", dev_name(dev), i);
+		led->name = name;
+		led->brightness = 0;
+		led->max_brightness = 1;
+		led->brightness_get = picolcd_led_get_brightness;
+		led->brightness_set = picolcd_led_set_brightness;
+
+		data->led[i] = led;
+		ret = led_classdev_register(dev, data->led[i]);
+		if (ret) {
+			data->led[i] = NULL;
+			kfree(led);
+			dev_err(dev, "can't register LED %d\n", i);
+			goto err;
+		}
+	}
+	return 0;
+err:
+	for (i = 0; i < 8; i++)
+		if (data->led[i]) {
+			led = data->led[i];
+			data->led[i] = NULL;
+			led_classdev_unregister(led);
+			kfree(led);
+		}
+	return ret;
+}
+
+void picolcd_exit_leds(struct picolcd_data *data)
+{
+	struct led_classdev *led;
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		led = data->led[i];
+		data->led[i] = NULL;
+		if (!led)
+			continue;
+		led_classdev_unregister(led);
+		kfree(led);
+	}
+}
+
+
diff --git a/drivers/hid/hid-pl.c b/drivers/hid/hid-pl.c
new file mode 100644
index 0000000..2dcd7d9
--- /dev/null
+++ b/drivers/hid/hid-pl.c
@@ -0,0 +1,234 @@
+/*
+ *  Force feedback support for PantherLord/GreenAsia based devices
+ *
+ *  The devices are distributed under various names and the same USB device ID
+ *  can be used in both adapters and actual game controllers.
+ *
+ *  0810:0001 "Twin USB Joystick"
+ *   - tested with PantherLord USB/PS2 2in1 Adapter
+ *   - contains two reports, one for each port (HID_QUIRK_MULTI_INPUT)
+ *
+ *  0e8f:0003 "GreenAsia Inc.    USB Joystick     "
+ *   - tested with König Gaming gamepad
+ *
+ *  0e8f:0003 "GASIA USB Gamepad"
+ *   - another version of the König gamepad
+ *
+ *  0f30:0111 "Saitek Color Rumble Pad"
+ *
+ *  Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* #define DEBUG */
+
+#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/hid.h>
+
+#include "hid-ids.h"
+
+#ifdef CONFIG_PANTHERLORD_FF
+
+struct plff_device {
+	struct hid_report *report;
+	s32 maxval;
+	s32 *strong;
+	s32 *weak;
+};
+
+static int hid_plff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct plff_device *plff = data;
+	int left, right;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	debug("called with 0x%04x 0x%04x", left, right);
+
+	left = left * plff->maxval / 0xffff;
+	right = right * plff->maxval / 0xffff;
+
+	*plff->strong = left;
+	*plff->weak = right;
+	debug("running with 0x%02x 0x%02x", left, right);
+	hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int plff_init(struct hid_device *hid)
+{
+	struct plff_device *plff;
+	struct hid_report *report;
+	struct hid_input *hidinput;
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct list_head *report_ptr = report_list;
+	struct input_dev *dev;
+	int error;
+	s32 maxval;
+	s32 *strong;
+	s32 *weak;
+
+	/* The device contains one output report per physical device, all
+	   containing 1 field, which contains 4 ff00.0002 usages and 4 16bit
+	   absolute values.
+
+	   The input reports also contain a field which contains
+	   8 ff00.0001 usages and 8 boolean values. Their meaning is
+	   currently unknown.
+	   
+	   A version of the 0e8f:0003 exists that has all the values in
+	   separate fields and misses the extra input field, thus resembling
+	   Zeroplus (hid-zpff) devices.
+	*/
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+
+		report_ptr = report_ptr->next;
+
+		if (report_ptr == report_list) {
+			hid_err(hid, "required output report is missing\n");
+			return -ENODEV;
+		}
+
+		report = list_entry(report_ptr, struct hid_report, list);
+		if (report->maxfield < 1) {
+			hid_err(hid, "no fields in the report\n");
+			return -ENODEV;
+		}
+
+		maxval = 0x7f;
+		if (report->field[0]->report_count >= 4) {
+			report->field[0]->value[0] = 0x00;
+			report->field[0]->value[1] = 0x00;
+			strong = &report->field[0]->value[2];
+			weak = &report->field[0]->value[3];
+			debug("detected single-field device");
+		} else if (report->field[0]->maxusage == 1 &&
+			   report->field[0]->usage[0].hid ==
+				(HID_UP_LED | 0x43) &&
+			   report->maxfield >= 4 &&
+			   report->field[0]->report_count >= 1 &&
+			   report->field[1]->report_count >= 1 &&
+			   report->field[2]->report_count >= 1 &&
+			   report->field[3]->report_count >= 1) {
+			report->field[0]->value[0] = 0x00;
+			report->field[1]->value[0] = 0x00;
+			strong = &report->field[2]->value[0];
+			weak = &report->field[3]->value[0];
+			if (hid->vendor == USB_VENDOR_ID_JESS2)
+				maxval = 0xff;
+			debug("detected 4-field device");
+		} else {
+			hid_err(hid, "not enough fields or values\n");
+			return -ENODEV;
+		}
+
+		plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
+		if (!plff)
+			return -ENOMEM;
+
+		dev = hidinput->input;
+
+		set_bit(FF_RUMBLE, dev->ffbit);
+
+		error = input_ff_create_memless(dev, plff, hid_plff_play);
+		if (error) {
+			kfree(plff);
+			return error;
+		}
+
+		plff->report = report;
+		plff->strong = strong;
+		plff->weak = weak;
+		plff->maxval = maxval;
+
+		*strong = 0x00;
+		*weak = 0x00;
+		hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
+	}
+
+	hid_info(hid, "Force feedback for PantherLord/GreenAsia devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+}
+#else
+static inline int plff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	if (id->driver_data)
+		hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	plff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id pl_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR),
+		.driver_data = 1 }, /* Twin USB Joystick */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR),
+		.driver_data = 1 }, /* Twin USB Joystick */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003), },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD), },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, pl_devices);
+
+static struct hid_driver pl_driver = {
+	.name = "pantherlord",
+	.id_table = pl_devices,
+	.probe = pl_probe,
+};
+module_hid_driver(pl_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c
new file mode 100644
index 0000000..584b10d
--- /dev/null
+++ b/drivers/hid/hid-plantronics.c
@@ -0,0 +1,175 @@
+/*
+ *  Plantronics USB HID Driver
+ *
+ *  Copyright (c) 2014 JD Cole <jd.cole@plantronics.com>
+ *  Copyright (c) 2015-2018 Terry Junge <terry.junge@plantronics.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include "hid-ids.h"
+
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#define PLT_HID_1_0_PAGE	0xffa00000
+#define PLT_HID_2_0_PAGE	0xffa20000
+
+#define PLT_BASIC_TELEPHONY	0x0003
+#define PLT_BASIC_EXCEPTION	0x0005
+
+#define PLT_VOL_UP		0x00b1
+#define PLT_VOL_DOWN		0x00b2
+
+#define PLT1_VOL_UP		(PLT_HID_1_0_PAGE | PLT_VOL_UP)
+#define PLT1_VOL_DOWN		(PLT_HID_1_0_PAGE | PLT_VOL_DOWN)
+#define PLT2_VOL_UP		(PLT_HID_2_0_PAGE | PLT_VOL_UP)
+#define PLT2_VOL_DOWN		(PLT_HID_2_0_PAGE | PLT_VOL_DOWN)
+
+#define PLT_DA60		0xda60
+#define PLT_BT300_MIN		0x0413
+#define PLT_BT300_MAX		0x0418
+
+
+#define PLT_ALLOW_CONSUMER (field->application == HID_CP_CONSUMERCONTROL && \
+			    (usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER)
+
+static int plantronics_input_mapping(struct hid_device *hdev,
+				     struct hid_input *hi,
+				     struct hid_field *field,
+				     struct hid_usage *usage,
+				     unsigned long **bit, int *max)
+{
+	unsigned short mapped_key;
+	unsigned long plt_type = (unsigned long)hid_get_drvdata(hdev);
+
+	/* special case for PTT products */
+	if (field->application == HID_GD_JOYSTICK)
+		goto defaulted;
+
+	/* handle volume up/down mapping */
+	/* non-standard types or multi-HID interfaces - plt_type is PID */
+	if (!(plt_type & HID_USAGE_PAGE)) {
+		switch (plt_type) {
+		case PLT_DA60:
+			if (PLT_ALLOW_CONSUMER)
+				goto defaulted;
+			goto ignored;
+		default:
+			if (PLT_ALLOW_CONSUMER)
+				goto defaulted;
+		}
+	}
+	/* handle standard types - plt_type is 0xffa0uuuu or 0xffa2uuuu */
+	/* 'basic telephony compliant' - allow default consumer page map */
+	else if ((plt_type & HID_USAGE) >= PLT_BASIC_TELEPHONY &&
+		 (plt_type & HID_USAGE) != PLT_BASIC_EXCEPTION) {
+		if (PLT_ALLOW_CONSUMER)
+			goto defaulted;
+	}
+	/* not 'basic telephony' - apply legacy mapping */
+	/* only map if the field is in the device's primary vendor page */
+	else if (!((field->application ^ plt_type) & HID_USAGE_PAGE)) {
+		switch (usage->hid) {
+		case PLT1_VOL_UP:
+		case PLT2_VOL_UP:
+			mapped_key = KEY_VOLUMEUP;
+			goto mapped;
+		case PLT1_VOL_DOWN:
+		case PLT2_VOL_DOWN:
+			mapped_key = KEY_VOLUMEDOWN;
+			goto mapped;
+		}
+	}
+
+/*
+ * Future mapping of call control or other usages,
+ * if and when keys are defined would go here
+ * otherwise, ignore everything else that was not mapped
+ */
+
+ignored:
+	return -1;
+
+defaulted:
+	hid_dbg(hdev, "usage: %08x (appl: %08x) - defaulted\n",
+		usage->hid, field->application);
+	return 0;
+
+mapped:
+	hid_map_usage_clear(hi, usage, bit, max, EV_KEY, mapped_key);
+	hid_dbg(hdev, "usage: %08x (appl: %08x) - mapped to key %d\n",
+		usage->hid, field->application, mapped_key);
+	return 1;
+}
+
+static unsigned long plantronics_device_type(struct hid_device *hdev)
+{
+	unsigned i, col_page;
+	unsigned long plt_type = hdev->product;
+
+	/* multi-HID interfaces? - plt_type is PID */
+	if (plt_type >= PLT_BT300_MIN && plt_type <= PLT_BT300_MAX)
+		goto exit;
+
+	/* determine primary vendor page */
+	for (i = 0; i < hdev->maxcollection; i++) {
+		col_page = hdev->collection[i].usage & HID_USAGE_PAGE;
+		if (col_page == PLT_HID_2_0_PAGE) {
+			plt_type = hdev->collection[i].usage;
+			break;
+		}
+		if (col_page == PLT_HID_1_0_PAGE)
+			plt_type = hdev->collection[i].usage;
+	}
+
+exit:
+	hid_dbg(hdev, "plt_type decoded as: %08lx\n", plt_type);
+	return plt_type;
+}
+
+static int plantronics_probe(struct hid_device *hdev,
+			     const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	hid_set_drvdata(hdev, (void *)plantronics_device_type(hdev));
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
+		HID_CONNECT_HIDINPUT_FORCE | HID_CONNECT_HIDDEV_FORCE);
+	if (ret)
+		hid_err(hdev, "hw start failed\n");
+
+err:
+	return ret;
+}
+
+static const struct hid_device_id plantronics_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, plantronics_devices);
+
+static struct hid_driver plantronics_driver = {
+	.name = "plantronics",
+	.id_table = plantronics_devices,
+	.input_mapping = plantronics_input_mapping,
+	.probe = plantronics_probe,
+};
+module_hid_driver(plantronics_driver);
+
+MODULE_AUTHOR("JD Cole <jd.cole@plantronics.com>");
+MODULE_AUTHOR("Terry Junge <terry.junge@plantronics.com>");
+MODULE_DESCRIPTION("Plantronics USB HID Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-primax.c b/drivers/hid/hid-primax.c
new file mode 100644
index 0000000..3a1c3c4
--- /dev/null
+++ b/drivers/hid/hid-primax.c
@@ -0,0 +1,81 @@
+/*
+ * HID driver for primax and similar keyboards with in-band modifiers
+ *
+ * Copyright 2011 Google Inc. All Rights Reserved
+ *
+ * Author:
+ *	Terry Lambert <tlambert@google.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static int px_raw_event(struct hid_device *hid, struct hid_report *report,
+	 u8 *data, int size)
+{
+	int idx = size;
+
+	switch (report->id) {
+	case 0:		/* keyboard input */
+		/*
+		 * Convert in-band modifier key values into out of band
+		 * modifier bits and pull the key strokes from the report.
+		 * Thus a report data set which looked like:
+		 *
+		 * [00][00][E0][30][00][00][00][00]
+		 * (no modifier bits + "Left Shift" key + "1" key)
+		 *
+		 * Would be converted to:
+		 *
+		 * [01][00][00][30][00][00][00][00]
+		 * (Left Shift modifier bit + "1" key)
+		 *
+		 * As long as it's in the size range, the upper level
+		 * drivers don't particularly care if there are in-band
+		 * 0-valued keys, so they don't stop parsing.
+		 */
+		while (--idx > 1) {
+			if (data[idx] < 0xE0 || data[idx] > 0xE7)
+				continue;
+			data[0] |= (1 << (data[idx] - 0xE0));
+			data[idx] = 0;
+		}
+		hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
+		return 1;
+
+	default:	/* unknown report */
+		/* Unknown report type; pass upstream */
+		hid_info(hid, "unknown report type %d\n", report->id);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id px_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, px_devices);
+
+static struct hid_driver px_driver = {
+	.name = "primax",
+	.id_table = px_devices,
+	.raw_event = px_raw_event,
+};
+module_hid_driver(px_driver);
+
+MODULE_AUTHOR("Terry Lambert <tlambert@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-prodikeys.c b/drivers/hid/hid-prodikeys.c
new file mode 100644
index 0000000..87eda34
--- /dev/null
+++ b/drivers/hid/hid-prodikeys.c
@@ -0,0 +1,890 @@
+/*
+ *  HID driver for the Prodikeys PC-MIDI Keyboard
+ *  providing midi & extra multimedia keys functionality
+ *
+ *  Copyright (c) 2009 Don Prince <dhprince.devel@yahoo.co.uk>
+ *
+ *  Controls for Octave Shift Up/Down, Channel, and
+ *  Sustain Duration available via sysfs.
+ *
+ */
+
+/*
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/hid.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include "hid-ids.h"
+
+
+#define pk_debug(format, arg...) \
+	pr_debug("hid-prodikeys: " format "\n" , ## arg)
+#define pk_error(format, arg...) \
+	pr_err("hid-prodikeys: " format "\n" , ## arg)
+
+struct pcmidi_snd;
+
+struct pk_device {
+	unsigned long		quirks;
+
+	struct hid_device	*hdev;
+	struct pcmidi_snd	*pm; /* pcmidi device context */
+};
+
+struct pcmidi_sustain {
+	unsigned long		in_use;
+	struct pcmidi_snd	*pm;
+	struct timer_list	timer;
+	unsigned char		status;
+	unsigned char		note;
+	unsigned char		velocity;
+};
+
+#define PCMIDI_SUSTAINED_MAX	32
+struct pcmidi_snd {
+	struct pk_device		*pk;
+	unsigned short			ifnum;
+	struct hid_report		*pcmidi_report6;
+	struct input_dev		*input_ep82;
+	unsigned short			midi_mode;
+	unsigned short			midi_sustain_mode;
+	unsigned short			midi_sustain;
+	unsigned short			midi_channel;
+	short				midi_octave;
+	struct pcmidi_sustain		sustained_notes[PCMIDI_SUSTAINED_MAX];
+	unsigned short			fn_state;
+	unsigned short			last_key[24];
+	spinlock_t			rawmidi_in_lock;
+	struct snd_card			*card;
+	struct snd_rawmidi		*rwmidi;
+	struct snd_rawmidi_substream	*in_substream;
+	struct snd_rawmidi_substream	*out_substream;
+	unsigned long			in_triggered;
+	unsigned long			out_active;
+};
+
+#define PK_QUIRK_NOGET	0x00010000
+#define PCMIDI_MIDDLE_C 60
+#define PCMIDI_CHANNEL_MIN 0
+#define PCMIDI_CHANNEL_MAX 15
+#define PCMIDI_OCTAVE_MIN (-2)
+#define PCMIDI_OCTAVE_MAX 2
+#define PCMIDI_SUSTAIN_MIN 0
+#define PCMIDI_SUSTAIN_MAX 5000
+
+static const char shortname[] = "PC-MIDI";
+static const char longname[] = "Prodikeys PC-MIDI Keyboard";
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+module_param_array(id, charp, NULL, 0444);
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the PC-MIDI virtual audio driver");
+MODULE_PARM_DESC(id, "ID string for the PC-MIDI virtual audio driver");
+MODULE_PARM_DESC(enable, "Enable for the PC-MIDI virtual audio driver");
+
+
+/* Output routine for the sysfs channel file */
+static ssize_t show_channel(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	dbg_hid("pcmidi sysfs read channel=%u\n", pk->pm->midi_channel);
+
+	return sprintf(buf, "%u (min:%u, max:%u)\n", pk->pm->midi_channel,
+		PCMIDI_CHANNEL_MIN, PCMIDI_CHANNEL_MAX);
+}
+
+/* Input routine for the sysfs channel file */
+static ssize_t store_channel(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	unsigned channel = 0;
+
+	if (sscanf(buf, "%u", &channel) > 0 && channel <= PCMIDI_CHANNEL_MAX) {
+		dbg_hid("pcmidi sysfs write channel=%u\n", channel);
+		pk->pm->midi_channel = channel;
+		return strlen(buf);
+	}
+	return -EINVAL;
+}
+
+static DEVICE_ATTR(channel, S_IRUGO | S_IWUSR | S_IWGRP , show_channel,
+		store_channel);
+
+static struct device_attribute *sysfs_device_attr_channel = {
+		&dev_attr_channel,
+		};
+
+/* Output routine for the sysfs sustain file */
+static ssize_t show_sustain(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	dbg_hid("pcmidi sysfs read sustain=%u\n", pk->pm->midi_sustain);
+
+	return sprintf(buf, "%u (off:%u, max:%u (ms))\n", pk->pm->midi_sustain,
+		PCMIDI_SUSTAIN_MIN, PCMIDI_SUSTAIN_MAX);
+}
+
+/* Input routine for the sysfs sustain file */
+static ssize_t store_sustain(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	unsigned sustain = 0;
+
+	if (sscanf(buf, "%u", &sustain) > 0 && sustain <= PCMIDI_SUSTAIN_MAX) {
+		dbg_hid("pcmidi sysfs write sustain=%u\n", sustain);
+		pk->pm->midi_sustain = sustain;
+		pk->pm->midi_sustain_mode =
+			(0 == sustain || !pk->pm->midi_mode) ? 0 : 1;
+		return strlen(buf);
+	}
+	return -EINVAL;
+}
+
+static DEVICE_ATTR(sustain, S_IRUGO | S_IWUSR | S_IWGRP, show_sustain,
+		store_sustain);
+
+static struct device_attribute *sysfs_device_attr_sustain = {
+		&dev_attr_sustain,
+		};
+
+/* Output routine for the sysfs octave file */
+static ssize_t show_octave(struct device *dev,
+	struct device_attribute *attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	dbg_hid("pcmidi sysfs read octave=%d\n", pk->pm->midi_octave);
+
+	return sprintf(buf, "%d (min:%d, max:%d)\n", pk->pm->midi_octave,
+		PCMIDI_OCTAVE_MIN, PCMIDI_OCTAVE_MAX);
+}
+
+/* Input routine for the sysfs octave file */
+static ssize_t store_octave(struct device *dev,
+	struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct pk_device *pk = hid_get_drvdata(hdev);
+
+	int octave = 0;
+
+	if (sscanf(buf, "%d", &octave) > 0 &&
+		octave >= PCMIDI_OCTAVE_MIN && octave <= PCMIDI_OCTAVE_MAX) {
+		dbg_hid("pcmidi sysfs write octave=%d\n", octave);
+		pk->pm->midi_octave = octave;
+		return strlen(buf);
+	}
+	return -EINVAL;
+}
+
+static DEVICE_ATTR(octave, S_IRUGO | S_IWUSR | S_IWGRP, show_octave,
+		store_octave);
+
+static struct device_attribute *sysfs_device_attr_octave = {
+		&dev_attr_octave,
+		};
+
+
+static void pcmidi_send_note(struct pcmidi_snd *pm,
+	unsigned char status, unsigned char note, unsigned char velocity)
+{
+	unsigned long flags;
+	unsigned char buffer[3];
+
+	buffer[0] = status;
+	buffer[1] = note;
+	buffer[2] = velocity;
+
+	spin_lock_irqsave(&pm->rawmidi_in_lock, flags);
+
+	if (!pm->in_substream)
+		goto drop_note;
+	if (!test_bit(pm->in_substream->number, &pm->in_triggered))
+		goto drop_note;
+
+	snd_rawmidi_receive(pm->in_substream, buffer, 3);
+
+drop_note:
+	spin_unlock_irqrestore(&pm->rawmidi_in_lock, flags);
+
+	return;
+}
+
+static void pcmidi_sustained_note_release(struct timer_list *t)
+{
+	struct pcmidi_sustain *pms = from_timer(pms, t, timer);
+
+	pcmidi_send_note(pms->pm, pms->status, pms->note, pms->velocity);
+	pms->in_use = 0;
+}
+
+static void init_sustain_timers(struct pcmidi_snd *pm)
+{
+	struct pcmidi_sustain *pms;
+	unsigned i;
+
+	for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
+		pms = &pm->sustained_notes[i];
+		pms->in_use = 0;
+		pms->pm = pm;
+		timer_setup(&pms->timer, pcmidi_sustained_note_release, 0);
+	}
+}
+
+static void stop_sustain_timers(struct pcmidi_snd *pm)
+{
+	struct pcmidi_sustain *pms;
+	unsigned i;
+
+	for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
+		pms = &pm->sustained_notes[i];
+		pms->in_use = 1;
+		del_timer_sync(&pms->timer);
+	}
+}
+
+static int pcmidi_get_output_report(struct pcmidi_snd *pm)
+{
+	struct hid_device *hdev = pm->pk->hdev;
+	struct hid_report *report;
+
+	list_for_each_entry(report,
+		&hdev->report_enum[HID_OUTPUT_REPORT].report_list, list) {
+		if (!(6 == report->id))
+			continue;
+
+		if (report->maxfield < 1) {
+			hid_err(hdev, "output report is empty\n");
+			break;
+		}
+		if (report->field[0]->report_count != 2) {
+			hid_err(hdev, "field count too low\n");
+			break;
+		}
+		pm->pcmidi_report6 = report;
+		return 0;
+	}
+	/* should never get here */
+	return -ENODEV;
+}
+
+static void pcmidi_submit_output_report(struct pcmidi_snd *pm, int state)
+{
+	struct hid_device *hdev = pm->pk->hdev;
+	struct hid_report *report = pm->pcmidi_report6;
+	report->field[0]->value[0] = 0x01;
+	report->field[0]->value[1] = state;
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+}
+
+static int pcmidi_handle_report1(struct pcmidi_snd *pm, u8 *data)
+{
+	u32 bit_mask;
+
+	bit_mask = data[1];
+	bit_mask = (bit_mask << 8) | data[2];
+	bit_mask = (bit_mask << 8) | data[3];
+
+	dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
+
+	/*KEY_MAIL or octave down*/
+	if (pm->midi_mode && bit_mask == 0x004000) {
+		/* octave down */
+		pm->midi_octave--;
+		if (pm->midi_octave < -2)
+			pm->midi_octave = -2;
+		dbg_hid("pcmidi mode: %d octave: %d\n",
+			pm->midi_mode, pm->midi_octave);
+		return 1;
+	}
+	/*KEY_WWW or sustain*/
+	else if (pm->midi_mode && bit_mask == 0x000004) {
+		/* sustain on/off*/
+		pm->midi_sustain_mode ^= 0x1;
+		return 1;
+	}
+
+	return 0; /* continue key processing */
+}
+
+static int pcmidi_handle_report3(struct pcmidi_snd *pm, u8 *data, int size)
+{
+	struct pcmidi_sustain *pms;
+	unsigned i, j;
+	unsigned char status, note, velocity;
+
+	unsigned num_notes = (size-1)/2;
+	for (j = 0; j < num_notes; j++)	{
+		note = data[j*2+1];
+		velocity = data[j*2+2];
+
+		if (note < 0x81) { /* note on */
+			status = 128 + 16 + pm->midi_channel; /* 1001nnnn */
+			note = note - 0x54 + PCMIDI_MIDDLE_C +
+				(pm->midi_octave * 12);
+			if (0 == velocity)
+				velocity = 1; /* force note on */
+		} else { /* note off */
+			status = 128 + pm->midi_channel; /* 1000nnnn */
+			note = note - 0x94 + PCMIDI_MIDDLE_C +
+				(pm->midi_octave*12);
+
+			if (pm->midi_sustain_mode) {
+				for (i = 0; i < PCMIDI_SUSTAINED_MAX; i++) {
+					pms = &pm->sustained_notes[i];
+					if (!pms->in_use) {
+						pms->status = status;
+						pms->note = note;
+						pms->velocity = velocity;
+						pms->in_use = 1;
+
+						mod_timer(&pms->timer,
+							jiffies +
+					msecs_to_jiffies(pm->midi_sustain));
+						return 1;
+					}
+				}
+			}
+		}
+		pcmidi_send_note(pm, status, note, velocity);
+	}
+
+	return 1;
+}
+
+static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
+{
+	unsigned	key;
+	u32		bit_mask;
+	u32		bit_index;
+
+	bit_mask = data[1];
+	bit_mask = (bit_mask << 8) | data[2];
+	bit_mask = (bit_mask << 8) | data[3];
+
+	/* break keys */
+	for (bit_index = 0; bit_index < 24; bit_index++) {
+		if (!((0x01 << bit_index) & bit_mask)) {
+			input_event(pm->input_ep82, EV_KEY,
+				pm->last_key[bit_index], 0);
+			pm->last_key[bit_index] = 0;
+		}
+	}
+
+	/* make keys */
+	for (bit_index = 0; bit_index < 24; bit_index++) {
+		key = 0;
+		switch ((0x01 << bit_index) & bit_mask) {
+		case 0x000010: /* Fn lock*/
+			pm->fn_state ^= 0x000010;
+			if (pm->fn_state)
+				pcmidi_submit_output_report(pm, 0xc5);
+			else
+				pcmidi_submit_output_report(pm, 0xc6);
+			continue;
+		case 0x020000: /* midi launcher..send a key (qwerty) or not? */
+			pcmidi_submit_output_report(pm, 0xc1);
+			pm->midi_mode ^= 0x01;
+
+			dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
+			continue;
+		case 0x100000: /* KEY_MESSENGER or octave up */
+			dbg_hid("pcmidi mode: %d\n", pm->midi_mode);
+			if (pm->midi_mode) {
+				pm->midi_octave++;
+				if (pm->midi_octave > 2)
+					pm->midi_octave = 2;
+				dbg_hid("pcmidi mode: %d octave: %d\n",
+					pm->midi_mode, pm->midi_octave);
+				continue;
+			} else
+				key = KEY_MESSENGER;
+			break;
+		case 0x400000:
+			key = KEY_CALENDAR;
+			break;
+		case 0x080000:
+			key = KEY_ADDRESSBOOK;
+			break;
+		case 0x040000:
+			key = KEY_DOCUMENTS;
+			break;
+		case 0x800000:
+			key = KEY_WORDPROCESSOR;
+			break;
+		case 0x200000:
+			key = KEY_SPREADSHEET;
+			break;
+		case 0x010000:
+			key = KEY_COFFEE;
+			break;
+		case 0x000100:
+			key = KEY_HELP;
+			break;
+		case 0x000200:
+			key = KEY_SEND;
+			break;
+		case 0x000400:
+			key = KEY_REPLY;
+			break;
+		case 0x000800:
+			key = KEY_FORWARDMAIL;
+			break;
+		case 0x001000:
+			key = KEY_NEW;
+			break;
+		case 0x002000:
+			key = KEY_OPEN;
+			break;
+		case 0x004000:
+			key = KEY_CLOSE;
+			break;
+		case 0x008000:
+			key = KEY_SAVE;
+			break;
+		case 0x000001:
+			key = KEY_UNDO;
+			break;
+		case 0x000002:
+			key = KEY_REDO;
+			break;
+		case 0x000004:
+			key = KEY_SPELLCHECK;
+			break;
+		case 0x000008:
+			key = KEY_PRINT;
+			break;
+		}
+		if (key) {
+			input_event(pm->input_ep82, EV_KEY, key, 1);
+			pm->last_key[bit_index] = key;
+		}
+	}
+
+	return 1;
+}
+
+static int pcmidi_handle_report(
+	struct pcmidi_snd *pm, unsigned report_id, u8 *data, int size)
+{
+	int ret = 0;
+
+	switch (report_id) {
+	case 0x01: /* midi keys (qwerty)*/
+		ret = pcmidi_handle_report1(pm, data);
+		break;
+	case 0x03: /* midi keyboard (musical)*/
+		ret = pcmidi_handle_report3(pm, data, size);
+		break;
+	case 0x04: /* multimedia/midi keys (qwerty)*/
+		ret = pcmidi_handle_report4(pm, data);
+		break;
+	}
+	return ret;
+}
+
+static void pcmidi_setup_extra_keys(
+	struct pcmidi_snd *pm, struct input_dev *input)
+{
+	/* reassigned functionality for N/A keys
+		MY PICTURES =>	KEY_WORDPROCESSOR
+		MY MUSIC=>	KEY_SPREADSHEET
+	*/
+	unsigned int keys[] = {
+		KEY_FN,
+		KEY_MESSENGER, KEY_CALENDAR,
+		KEY_ADDRESSBOOK, KEY_DOCUMENTS,
+		KEY_WORDPROCESSOR,
+		KEY_SPREADSHEET,
+		KEY_COFFEE,
+		KEY_HELP, KEY_SEND,
+		KEY_REPLY, KEY_FORWARDMAIL,
+		KEY_NEW, KEY_OPEN,
+		KEY_CLOSE, KEY_SAVE,
+		KEY_UNDO, KEY_REDO,
+		KEY_SPELLCHECK,	KEY_PRINT,
+		0
+	};
+
+	unsigned int *pkeys = &keys[0];
+	unsigned short i;
+
+	if (pm->ifnum != 1)  /* only set up ONCE for interace 1 */
+		return;
+
+	pm->input_ep82 = input;
+
+	for (i = 0; i < 24; i++)
+		pm->last_key[i] = 0;
+
+	while (*pkeys != 0) {
+		set_bit(*pkeys, pm->input_ep82->keybit);
+		++pkeys;
+	}
+}
+
+static int pcmidi_set_operational(struct pcmidi_snd *pm)
+{
+	if (pm->ifnum != 1)
+		return 0; /* only set up ONCE for interace 1 */
+
+	pcmidi_get_output_report(pm);
+	pcmidi_submit_output_report(pm, 0xc1);
+	return 0;
+}
+
+static int pcmidi_snd_free(struct snd_device *dev)
+{
+	return 0;
+}
+
+static int pcmidi_in_open(struct snd_rawmidi_substream *substream)
+{
+	struct pcmidi_snd *pm = substream->rmidi->private_data;
+
+	dbg_hid("pcmidi in open\n");
+	pm->in_substream = substream;
+	return 0;
+}
+
+static int pcmidi_in_close(struct snd_rawmidi_substream *substream)
+{
+	dbg_hid("pcmidi in close\n");
+	return 0;
+}
+
+static void pcmidi_in_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct pcmidi_snd *pm = substream->rmidi->private_data;
+
+	dbg_hid("pcmidi in trigger %d\n", up);
+
+	pm->in_triggered = up;
+}
+
+static const struct snd_rawmidi_ops pcmidi_in_ops = {
+	.open = pcmidi_in_open,
+	.close = pcmidi_in_close,
+	.trigger = pcmidi_in_trigger
+};
+
+static int pcmidi_snd_initialise(struct pcmidi_snd *pm)
+{
+	static int dev;
+	struct snd_card *card;
+	struct snd_rawmidi *rwmidi;
+	int err;
+
+	static struct snd_device_ops ops = {
+		.dev_free = pcmidi_snd_free,
+	};
+
+	if (pm->ifnum != 1)
+		return 0; /* only set up midi device ONCE for interace 1 */
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	/* Setup sound card */
+
+	err = snd_card_new(&pm->pk->hdev->dev, index[dev], id[dev],
+			   THIS_MODULE, 0, &card);
+	if (err < 0) {
+		pk_error("failed to create pc-midi sound card\n");
+		err = -ENOMEM;
+		goto fail;
+	}
+	pm->card = card;
+
+	/* Setup sound device */
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pm, &ops);
+	if (err < 0) {
+		pk_error("failed to create pc-midi sound device: error %d\n",
+			err);
+		goto fail;
+	}
+
+	strncpy(card->driver, shortname, sizeof(card->driver));
+	strncpy(card->shortname, shortname, sizeof(card->shortname));
+	strncpy(card->longname, longname, sizeof(card->longname));
+
+	/* Set up rawmidi */
+	err = snd_rawmidi_new(card, card->shortname, 0,
+			      0, 1, &rwmidi);
+	if (err < 0) {
+		pk_error("failed to create pc-midi rawmidi device: error %d\n",
+			err);
+		goto fail;
+	}
+	pm->rwmidi = rwmidi;
+	strncpy(rwmidi->name, card->shortname, sizeof(rwmidi->name));
+	rwmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT;
+	rwmidi->private_data = pm;
+
+	snd_rawmidi_set_ops(rwmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+		&pcmidi_in_ops);
+
+	/* create sysfs variables */
+	err = device_create_file(&pm->pk->hdev->dev,
+				 sysfs_device_attr_channel);
+	if (err < 0) {
+		pk_error("failed to create sysfs attribute channel: error %d\n",
+			err);
+		goto fail;
+	}
+
+	err = device_create_file(&pm->pk->hdev->dev,
+				sysfs_device_attr_sustain);
+	if (err < 0) {
+		pk_error("failed to create sysfs attribute sustain: error %d\n",
+			err);
+		goto fail_attr_sustain;
+	}
+
+	err = device_create_file(&pm->pk->hdev->dev,
+			 sysfs_device_attr_octave);
+	if (err < 0) {
+		pk_error("failed to create sysfs attribute octave: error %d\n",
+			err);
+		goto fail_attr_octave;
+	}
+
+	spin_lock_init(&pm->rawmidi_in_lock);
+
+	init_sustain_timers(pm);
+	pcmidi_set_operational(pm);
+
+	/* register it */
+	err = snd_card_register(card);
+	if (err < 0) {
+		pk_error("failed to register pc-midi sound card: error %d\n",
+			 err);
+		goto fail_register;
+	}
+
+	dbg_hid("pcmidi_snd_initialise finished ok\n");
+	return 0;
+
+fail_register:
+	stop_sustain_timers(pm);
+	device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_octave);
+fail_attr_octave:
+	device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_sustain);
+fail_attr_sustain:
+	device_remove_file(&pm->pk->hdev->dev, sysfs_device_attr_channel);
+fail:
+	if (pm->card) {
+		snd_card_free(pm->card);
+		pm->card = NULL;
+	}
+	return err;
+}
+
+static int pcmidi_snd_terminate(struct pcmidi_snd *pm)
+{
+	if (pm->card) {
+		stop_sustain_timers(pm);
+
+		device_remove_file(&pm->pk->hdev->dev,
+			sysfs_device_attr_channel);
+		device_remove_file(&pm->pk->hdev->dev,
+			sysfs_device_attr_sustain);
+		device_remove_file(&pm->pk->hdev->dev,
+			sysfs_device_attr_octave);
+
+		snd_card_disconnect(pm->card);
+		snd_card_free_when_closed(pm->card);
+	}
+
+	return 0;
+}
+
+/*
+ * PC-MIDI report descriptor for report id is wrong.
+ */
+static __u8 *pk_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize == 178 &&
+	      rdesc[111] == 0x06 && rdesc[112] == 0x00 &&
+	      rdesc[113] == 0xff) {
+		hid_info(hdev,
+			 "fixing up pc-midi keyboard report descriptor\n");
+
+		rdesc[144] = 0x18; /* report 4: was 0x10 report count */
+	}
+	return rdesc;
+}
+
+static int pk_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct pk_device *pk = hid_get_drvdata(hdev);
+	struct pcmidi_snd *pm;
+
+	pm = pk->pm;
+
+	if (HID_UP_MSVENDOR == (usage->hid & HID_USAGE_PAGE) &&
+		1 == pm->ifnum) {
+		pcmidi_setup_extra_keys(pm, hi->input);
+		return 0;
+	}
+
+	return 0;
+}
+
+
+static int pk_raw_event(struct hid_device *hdev, struct hid_report *report,
+	u8 *data, int size)
+{
+	struct pk_device *pk = hid_get_drvdata(hdev);
+	int ret = 0;
+
+	if (1 == pk->pm->ifnum) {
+		if (report->id == data[0])
+			switch (report->id) {
+			case 0x01: /* midi keys (qwerty)*/
+			case 0x03: /* midi keyboard (musical)*/
+			case 0x04: /* extra/midi keys (qwerty)*/
+				ret = pcmidi_handle_report(pk->pm,
+						report->id, data, size);
+				break;
+			}
+	}
+
+	return ret;
+}
+
+static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
+	unsigned long quirks = id->driver_data;
+	struct pk_device *pk;
+	struct pcmidi_snd *pm = NULL;
+
+	pk = kzalloc(sizeof(*pk), GFP_KERNEL);
+	if (pk == NULL) {
+		hid_err(hdev, "can't alloc descriptor\n");
+		return -ENOMEM;
+	}
+
+	pk->hdev = hdev;
+
+	pm = kzalloc(sizeof(*pm), GFP_KERNEL);
+	if (pm == NULL) {
+		hid_err(hdev, "can't alloc descriptor\n");
+		ret = -ENOMEM;
+		goto err_free_pk;
+	}
+
+	pm->pk = pk;
+	pk->pm = pm;
+	pm->ifnum = ifnum;
+
+	hid_set_drvdata(hdev, pk);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid parse failed\n");
+		goto err_free;
+	}
+
+	if (quirks & PK_QUIRK_NOGET) { /* hid_parse cleared all the quirks */
+		hdev->quirks |= HID_QUIRK_NOGET;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	ret = pcmidi_snd_initialise(pm);
+	if (ret < 0)
+		goto err_stop;
+
+	return 0;
+err_stop:
+	hid_hw_stop(hdev);
+err_free:
+	kfree(pm);
+err_free_pk:
+	kfree(pk);
+
+	return ret;
+}
+
+static void pk_remove(struct hid_device *hdev)
+{
+	struct pk_device *pk = hid_get_drvdata(hdev);
+	struct pcmidi_snd *pm;
+
+	pm = pk->pm;
+	if (pm) {
+		pcmidi_snd_terminate(pm);
+		kfree(pm);
+	}
+
+	hid_hw_stop(hdev);
+
+	kfree(pk);
+}
+
+static const struct hid_device_id pk_devices[] = {
+	{HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS,
+		USB_DEVICE_ID_PRODIKEYS_PCMIDI),
+	    .driver_data = PK_QUIRK_NOGET},
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, pk_devices);
+
+static struct hid_driver pk_driver = {
+	.name = "prodikeys",
+	.id_table = pk_devices,
+	.report_fixup = pk_report_fixup,
+	.input_mapping = pk_input_mapping,
+	.raw_event = pk_raw_event,
+	.probe = pk_probe,
+	.remove = pk_remove,
+};
+module_hid_driver(pk_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
new file mode 100644
index 0000000..77316f0
--- /dev/null
+++ b/drivers/hid/hid-quirks.c
@@ -0,0 +1,1290 @@
+/*
+ *  HID quirks support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2007 Paul Walmsley
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include "hid-ids.h"
+
+/*
+ * Alphabetically sorted by vendor then product.
+ */
+
+static const struct hid_device_id hid_quirks[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016), HID_QUIRK_FULLSPEED_INTERVAL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIREN, USB_DEVICE_ID_AIREN_SLIMPLUS), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI_09E8, USB_DEVICE_ID_AKAI_09E8_MIDIMIX), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AKAI, USB_DEVICE_ID_AKAI_MPKMINI2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ALPS, USB_DEVICE_ID_IBM_GAMEPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AMI, USB_DEVICE_ID_AMI_VIRT_KEYBOARD_AND_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_2PORTKVM), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVMC), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_4PORTKVM), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS124U), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS1758), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHIC, USB_DEVICE_ID_CHIC_GAMEPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_3AXIS_5BUTTON_STICK), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_AXIS_295), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_COMBATSTICK), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FIGHTERSTICK), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_ECLIPSE_YOKE), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_FLIGHT_SIM_YOKE), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_PEDALS), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CH, USB_DEVICE_ID_CH_PRO_THROTTLE), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K65RGB_RAPIDFIRE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70RGB_RAPIDFIRE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K70R), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K95RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_M65RGB), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_GLAIVE_RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_STRAFE), HID_QUIRK_NO_INIT_REPORTS | HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_CREATIVE_SB_OMNI_SURROUND_51), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELL, USB_DEVICE_ID_DELL_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DMI, USB_DEVICE_ID_DMI_ENC), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_2NES2SNES), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRACAL_RAPHNET, USB_DEVICE_ID_RAPHNET_4NES4SNES), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER), HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH_2968), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETURBOTOUCH, USB_DEVICE_ID_ETURBOTOUCH), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FORMOSA, USB_DEVICE_ID_FORMOSA_IR_RECEIVER), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FREESCALE, USB_DEVICE_ID_FREESCALE_MX28), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTABA, USB_DEVICE_ID_LED_DISPLAY), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, USB_DEVICE_ID_GREENASIA_DUAL_USB_JOYPAD), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_DRIVING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FIGHTING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HAPP, USB_DEVICE_ID_UGCI_FLYING), HID_QUIRK_BADPAD | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0B4A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HP, USB_PRODUCT_ID_HP_PIXART_OEM_USB_OPTICAL_MOUSE_094A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IDEACOM, USB_DEVICE_ID_IDEACOM_IDC6680), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_INNOMEDIA, USB_DEVICE_ID_INNEX_GENESIS_ATARI), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE_ID2), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C01A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MCS, USB_DEVICE_ID_MCS_GAMEPADBLOCK), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PIXART_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE_PRO_2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TOUCH_COVER_2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_TYPE_COVER_2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MOJO, USB_DEVICE_ID_RETRO_ADAPTER), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MULTIPLE_1781, USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NATSU, USB_DEVICE_ID_NATSU_GAMEPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEC, USB_DEVICE_ID_NEC_USB_GAME_PAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXIO, USB_DEVICE_ID_NEXIO_MULTITOUCH_PTI0750), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NEXTWINDOW, USB_DEVICE_ID_NEXTWINDOW_TOUCHSCREEN), HID_QUIRK_MULTI_INPUT},
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NOVATEK, USB_DEVICE_ID_NOVATEK_MOUSE), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_DUOSENSE), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANTHERLORD, USB_DEVICE_ID_PANTHERLORD_TWIN_USB_JOYSTICK), HID_QUIRK_MULTI_INPUT | HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1610), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_1640), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PI_ENGINEERING, USB_DEVICE_ID_PI_ENGINEERING_VEC_USB_FOOTPEDAL), HID_QUIRK_HIDINPUT_FORCE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN1), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22), HID_QUIRK_ALWAYS_POLL },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRODIGE, USB_DEVICE_ID_PRODIGE_CORDLESS), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3001), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3003), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH_3008), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_REALTEK, USB_DEVICE_ID_REALTEK_READER), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPAD), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RETROUSB, USB_DEVICE_ID_RETROUSB_SNES_RETROPORT), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RUMBLEPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS9200_TOUCH), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS_TS), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SUN, USB_DEVICE_ID_RARITAN_KVM_DONGLE), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_1), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYMBOL, USB_DEVICE_ID_SYMBOL_SCANNER_2), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS1), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_LTS2), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103), HID_QUIRK_NO_INIT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPMAX, USB_DEVICE_ID_TOPMAX_COBRAPAD), HID_QUIRK_BADPAD },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOUCHPACK, USB_DEVICE_ID_TOUCHPACK_RTS), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8882), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TPV, USB_DEVICE_ID_TPV_OPTICAL_TOUCHSCREEN_8883), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_TURBOX_KEYBOARD), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_KNA5), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWA60), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET), HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
+
+	{ 0 }
+};
+
+/*
+ * A list of devices for which there is a specialized driver on HID bus.
+ *
+ * Please note that for multitouch devices (driven by hid-multitouch driver),
+ * there is a proper autodetection and autoloading in place (based on presence
+ * of HID_DG_CONTACTID), so those devices don't need to be added to this list,
+ * as we are doing the right thing in hid_scan_usage().
+ *
+ * Autodetection for (USB) HID sensor hubs exists too. If a collection of type
+ * physical is found inside a usage page of type sensor, hid-sensor-hub will be
+ * used as a driver. See hid_scan_report().
+ */
+static const struct hid_device_id hid_have_special_driver[] = {
+#if IS_ENABLED(CONFIG_HID_A4TECH)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_RP_649) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ACCUTOUCH)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_ACCUTOUCH_2216) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ACRUX)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0x0802) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACRUX, 0xf705) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ALPS)
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
+#endif
+#if IS_ENABLED(CONFIG_HID_APPLE)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_MINI_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_REVB_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
+#endif
+#if IS_ENABLED(CONFIG_HID_APPLEIR)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL5) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ASUS)
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_KEYBOARD) },
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_I2C_TOUCHPAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_KEYBOARD3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_ASUS_MD_5112) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TURBOX, USB_DEVICE_ID_ASUS_MD_5110) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T100CHI_KEYBOARD) },
+#endif
+#if IS_ENABLED(CONFIG_HID_AUREAL)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AUREAL, USB_DEVICE_ID_AUREAL_W01RN) },
+#endif
+#if IS_ENABLED(CONFIG_HID_BELKIN)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
+#endif
+#if IS_ENABLED(CONFIG_HID_BETOP_FF)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185BFM, 0x2208) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185PC, 0x5506) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2PC, 0x1850) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BETOP_2185V2BFM, 0x5500) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CHERRY)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHERRY, USB_DEVICE_ID_CHERRY_CYMOTION_SOLAR) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CHICONY)
+	{ 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_ASUS_AK1D) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_ACER_SWITCH12) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CMEDIA)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM6533) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CORSAIR)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_K90) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_GLAIVE_RGB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, USB_DEVICE_ID_CORSAIR_SCIMITAR_PRO_RGB) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CP2112)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_CP2112) },
+#endif
+#if IS_ENABLED(CONFIG_HID_CYPRESS)
+	{ 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) },
+#endif
+#if IS_ENABLED(CONFIG_HID_DRAGONRISE)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0006) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, 0x0011) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ELAN)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2_10_COVER) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ELECOM)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ELO)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0030) },
+#endif
+#if IS_ENABLED(CONFIG_HID_EMS_FF)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II) },
+#endif
+#if IS_ENABLED(CONFIG_HID_EZKEY)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_EZKEY, USB_DEVICE_ID_BTC_8193) },
+#endif
+#if IS_ENABLED(CONFIG_HID_GEMBIRD)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GEMBIRD, USB_DEVICE_ID_GEMBIRD_JPD_DUALFORCE2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_GFRM)
+	{ HID_BLUETOOTH_DEVICE(0x58, 0x2000) },
+	{ HID_BLUETOOTH_DEVICE(0x471, 0x2210) },
+#endif
+#if IS_ENABLED(CONFIG_HID_GREENASIA)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0012) },
+#endif
+#if IS_ENABLED(CONFIG_HID_GT683R)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MSI, USB_DEVICE_ID_MSI_GT683R_LED_PANEL) },
+#endif
+#if IS_ENABLED(CONFIG_HID_GYRATION)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE) },
+	{ 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) },
+#endif
+#if IS_ENABLED(CONFIG_HID_HOLTEK)
+	{ 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_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A04A) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A067) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A0C2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ITE)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ITE, USB_DEVICE_ID_ITE8595) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ICADE)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ION, USB_DEVICE_ID_ICADE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_JABRA)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, HID_ANY_ID) },
+#endif
+#if IS_ENABLED(CONFIG_HID_KENSINGTON)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_KEYTOUCH)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) },
+#endif
+#if IS_ENABLED(CONFIG_HID_KYE)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_GENIUS_GILA_GAMING_MOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_GENIUS_MANTICORE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_GENIUS_GX_IMPERATOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_I405X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_PENSKETCH_M912) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LCPOWER)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LED)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_WN) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DREAM_CHEEKY, USB_DEVICE_ID_DREAM_CHEEKY_FA) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_LUXAFOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RISO_KAGAKU, USB_DEVICE_ID_RI_KA_WEBMAIL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THINGM, USB_DEVICE_ID_BLINK1) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LENOVO)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LOGITECH)
+	{ 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) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RECEIVER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_DESKTOP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_EDGE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_DINOVO_MINI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_ELITE_KBD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_EXTREME_3D) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DUAL_ACTION) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD_CORD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G29_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_F3D) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WINGMAN_FFG) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FORCE3D_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOMO_WHEEL2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_VIBRATION_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFP_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DFGT_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G25_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G27_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_WII_WHEEL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_RUMBLEPAD2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACETRAVELLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_SPACENAVIGATOR) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LOGITECH_HIDPP)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_T651) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL) },
+#endif
+#if IS_ENABLED(CONFIG_HID_LOGITECH_DJ)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_UNIFYING_RECEIVER_2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_MAGICMOUSE)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICTRACKPAD) },
+#endif
+#if IS_ENABLED(CONFIG_HID_MAYFLASH)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_DOLPHINBAR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_GAMECUBE2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_MICROSOFT)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_MOUSE_4500) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_COMFORT_KEYBOARD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE4K_JP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_NE7K) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_LK6K) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_USB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3K) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_WIRELESS_OPTICAL_DESKTOP_3_0) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_OFFICE_KB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_7K) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_600) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_DIGITAL_MEDIA_3KV1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PRESENTER_8K_BT) },
+#endif
+#if IS_ENABLED(CONFIG_HID_MONTEREY)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MONTEREY, USB_DEVICE_ID_GENIUS_KB29E) },
+#endif
+#if IS_ENABLED(CONFIG_HID_MULTITOUCH)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LG, USB_DEVICE_ID_LG_MELFAS_MT) },
+#endif
+#if IS_ENABLED(CONFIG_HID_WIIMOTE)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_NTI)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) },
+#endif
+#if IS_ENABLED(CONFIG_HID_NTRIG)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_4) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_5) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_6) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_7) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_8) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_9) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_10) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_11) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_12) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_13) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_14) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_15) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_16) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_17) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NTRIG, USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ORTEK)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_PKB1700) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_WKB2000) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ORTEK, USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PANTHERLORD)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PSX_ADAPTOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GAMERON, USB_DEVICE_ID_GAMERON_DUAL_PCS_ADAPTOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GREENASIA, 0x0003) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JESS2, USB_DEVICE_ID_JESS2_COLOR_RUMBLE_PAD) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PENMOUNT)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PENMOUNT, USB_DEVICE_ID_PENMOUNT_6000) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PETALYNX)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PETALYNX, USB_DEVICE_ID_PETALYNX_MAXTER_REMOTE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PICOLCD)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICOLCD_BOOTLOADER) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PLANTRONICS)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PRIMAX)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_KEYBOARD) },
+#endif
+#if IS_ENABLED(CONFIG_HID_PRODIKEYS)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CREATIVELABS, USB_DEVICE_ID_PRODIKEYS_PCMIDI) },
+#endif
+#if IS_ENABLED(CONFIG_HID_RETRODE)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_RETRODE2) },
+#endif
+#if IS_ENABLED(CONFIG_HID_RMI)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_COVER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RAZER, USB_DEVICE_ID_RAZER_BLADE_14) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_REZEL) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ROCCAT)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKUFX) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEXTD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_LUA) },
+	{ 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_RYOS_MK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_SAVU) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SAITEK)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT9) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SAMSUNG)
+	{ 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) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SMARTJOYPLUS)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_DUAL_BOX_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SONY)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SPEEDLINK)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_STEELSERIES)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+#endif
+#if IS_ENABLED(CONFIG_HID_SUNPLUS)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
+#endif
+#if IS_ENABLED(CONFIG_HID_THRUSTMASTER)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb323) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb324) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb605) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb651) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb653) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb654) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a) },
+#endif
+#if IS_ENABLED(CONFIG_HID_TIVO)
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
+#endif
+#if IS_ENABLED(CONFIG_HID_TOPSEED)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) },
+#endif
+#if IS_ENABLED(CONFIG_HID_TWINHAN)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_UCLOGIC)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
+#endif
+#if IS_ENABLED(CONFIG_HID_UDRAW_PS3)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW) },
+#endif
+#if IS_ENABLED(CONFIG_HID_WALTOP)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_Q_PAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_PID_0038) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP, USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET) },
+#endif
+#if IS_ENABLED(CONFIG_HID_XINMO)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_THT_2P_ARCADE) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ZEROPLUS)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
+#endif
+#if IS_ENABLED(CONFIG_HID_ZYDACRON)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
+#endif
+	{ }
+};
+
+/* a list of devices that shouldn't be handled by HID core at all */
+static const struct hid_device_id hid_ignore_list[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_FLAIR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ACECAD, USB_DEVICE_ID_ACECAD_302) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ADS_TECH, USB_DEVICE_ID_ADS_TECH_RADIO_SI470X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_01) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_10) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_20) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_21) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_22) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_23) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIPTEK, USB_DEVICE_ID_AIPTEK_24) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AIRCABLE, USB_DEVICE_ID_AIRCABLE1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ALCOR, USB_DEVICE_ID_ALCOR_USBRS232) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_LCM)},
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_LCM2)},
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AVERMEDIA, USB_DEVICE_ID_AVER_FM_MR800) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_AXENTIA, USB_DEVICE_ID_AXENTIA_FM_RADIO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM109) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_HIDCOM) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CYPRESS, USB_DEVICE_ID_CYPRESS_ULTRAMOUSE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DEALEXTREAME, USB_DEVICE_ID_DEALEXTREAME_RADIO_SI4701) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EARTHMATE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_DELORME, USB_DEVICE_ID_DELORME_EM_LT20) },
+	{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, 0x0400) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ESSENTIAL_REALITY, USB_DEVICE_ID_ESSENTIAL_REALITY_P5) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC5UH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ETT, USB_DEVICE_ID_TC4UM) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH, 0x0001) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH, 0x0002) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH, 0x0004) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_SUPER_Q2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_GOGOPEN) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GOTOP, USB_DEVICE_ID_PENPOWER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GRETAGMACBETH, USB_DEVICE_ID_GRETAGMACBETH_HUEY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_POWERMATE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_SOUNDKNOB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GRIFFIN, USB_DEVICE_ID_RADIOSHARK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_90) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_100) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_101) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_103) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_104) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_105) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_106) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_107) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_108) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_200) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_201) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_202) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_203) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_204) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_205) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_206) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_207) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_300) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_301) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_302) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_303) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_304) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_305) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_306) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_307) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_308) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_309) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_400) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_401) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_402) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_403) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_404) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_405) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_500) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_501) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_502) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_503) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_504) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1000) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1001) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1002) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1003) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1004) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1005) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1006) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_GTCO, USB_DEVICE_ID_GTCO_1007) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_IMATION, USB_DEVICE_ID_DISC_STAKKA) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_JABRA, USB_DEVICE_ID_JABRA_GN9350E) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KBGEAR, USB_DEVICE_ID_KBGEAR_JAMSTUDIO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KWORLD, USB_DEVICE_ID_KWORLD_RADIO_FM700) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_GPEN_560) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_KYE, 0x0058) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYVOLTAGE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYCURRENT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTIME) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYPH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERANALYSERCASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETESTCASSY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIC) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOTOR) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_ABSESP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_AUTODATABUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MCT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HYBRID) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HEATCONTROL) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_BEATPAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1024LS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MCC, USB_DEVICE_ID_MCC_PMD1208LS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT1) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICKIT2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICK16F1454) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_PICK16F1454_V2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR, USB_DEVICE_ID_N_S_HARMONY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 20) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 30) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 100) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 108) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 118) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 200) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 300) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 400) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ONTRAK, USB_DEVICE_ID_ONTRAK_ADU100 + 500) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANJIT, 0x0001) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANJIT, 0x0002) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANJIT, 0x0003) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PANJIT, 0x0004) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PETZL, USB_DEVICE_ID_PETZL_HEADLAMP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PHILIPS, USB_DEVICE_ID_PHILIPS_IEEE802154_DONGLE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_POWERCOM, USB_DEVICE_ID_POWERCOM_UPS) },
+#if IS_ENABLED(CONFIG_MOUSE_SYNAPTICS_USB)
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_INT_TP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_CPAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_STICK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_WP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_COMP_TP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_WTP) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DPAD) },
+#endif
+	{ HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) },
+	{ }
+};
+
+/**
+ * hid_mouse_ignore_list - mouse devices which should not be handled by the hid layer
+ *
+ * There are composite devices for which we want to ignore only a certain
+ * interface. This is a list of devices for which only the mouse interface will
+ * be ignored. This allows a dedicated driver to take care of the interface.
+ */
+static const struct hid_device_id hid_mouse_ignore_list[] = {
+	/* appletouch driver */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER3_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER4_HF_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING2_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING3_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING5A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING6A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING7A_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING8_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) },
+	{ }
+};
+
+bool hid_ignore(struct hid_device *hdev)
+{
+	if (hdev->quirks & HID_QUIRK_NO_IGNORE)
+		return false;
+	if (hdev->quirks & HID_QUIRK_IGNORE)
+		return true;
+
+	switch (hdev->vendor) {
+	case USB_VENDOR_ID_CODEMERCS:
+		/* ignore all Code Mercenaries IOWarrior devices */
+		if (hdev->product >= USB_DEVICE_ID_CODEMERCS_IOW_FIRST &&
+		    hdev->product <= USB_DEVICE_ID_CODEMERCS_IOW_LAST)
+			return true;
+		break;
+	case USB_VENDOR_ID_LOGITECH:
+		if (hdev->product >= USB_DEVICE_ID_LOGITECH_HARMONY_FIRST &&
+		    hdev->product <= USB_DEVICE_ID_LOGITECH_HARMONY_LAST)
+			return true;
+		/*
+		 * The Keene FM transmitter USB device has the same USB ID as
+		 * the Logitech AudioHub Speaker, but it should ignore the hid.
+		 * Check if the name is that of the Keene device.
+		 * For reference: the name of the AudioHub is
+		 * "HOLTEK  AudioHub Speaker".
+		 */
+		if (hdev->product == USB_DEVICE_ID_LOGITECH_AUDIOHUB &&
+		    !strcmp(hdev->name, "HOLTEK  B-LINK USB Audio  "))
+			return true;
+		break;
+	case USB_VENDOR_ID_SOUNDGRAPH:
+		if (hdev->product >= USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST &&
+		    hdev->product <= USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST)
+			return true;
+		break;
+	case USB_VENDOR_ID_HANWANG:
+		if (hdev->product >= USB_DEVICE_ID_HANWANG_TABLET_FIRST &&
+		    hdev->product <= USB_DEVICE_ID_HANWANG_TABLET_LAST)
+			return true;
+		break;
+	case USB_VENDOR_ID_JESS:
+		if (hdev->product == USB_DEVICE_ID_JESS_YUREX &&
+		    hdev->type == HID_TYPE_USBNONE)
+			return true;
+		break;
+	case USB_VENDOR_ID_VELLEMAN:
+		/* These are not HID devices.  They are handled by comedi. */
+		if ((hdev->product >= USB_DEVICE_ID_VELLEMAN_K8055_FIRST &&
+		     hdev->product <= USB_DEVICE_ID_VELLEMAN_K8055_LAST) ||
+		    (hdev->product >= USB_DEVICE_ID_VELLEMAN_K8061_FIRST &&
+		     hdev->product <= USB_DEVICE_ID_VELLEMAN_K8061_LAST))
+			return true;
+		break;
+	case USB_VENDOR_ID_ATMEL_V_USB:
+		/* Masterkit MA901 usb radio based on Atmel tiny85 chip and
+		 * it has the same USB ID as many Atmel V-USB devices. This
+		 * usb radio is handled by radio-ma901.c driver so we want
+		 * ignore the hid. Check the name, bus, product and ignore
+		 * if we have MA901 usb radio.
+		 */
+		if (hdev->product == USB_DEVICE_ID_ATMEL_V_USB &&
+		    hdev->bus == BUS_USB &&
+		    strncmp(hdev->name, "www.masterkit.ru MA901", 22) == 0)
+			return true;
+		break;
+	case USB_VENDOR_ID_ELAN:
+		/*
+		 * Many Elan devices have a product id of 0x0401 and are handled
+		 * by the elan_i2c input driver. But the ACPI HID ELAN0800 dev
+		 * is not (and cannot be) handled by that driver ->
+		 * Ignore all 0x0401 devs except for the ELAN0800 dev.
+		 */
+		if (hdev->product == 0x0401 &&
+		    strncmp(hdev->name, "ELAN0800", 8) != 0)
+			return true;
+		break;
+	}
+
+	if (hdev->type == HID_TYPE_USBMOUSE &&
+	    hid_match_id(hdev, hid_mouse_ignore_list))
+		return true;
+
+	return !!hid_match_id(hdev, hid_ignore_list);
+}
+EXPORT_SYMBOL_GPL(hid_ignore);
+
+/* Dynamic HID quirks list - specified at runtime */
+struct quirks_list_struct {
+	struct hid_device_id hid_bl_item;
+	struct list_head node;
+};
+
+static LIST_HEAD(dquirks_list);
+static DEFINE_MUTEX(dquirks_lock);
+
+/* Runtime ("dynamic") quirks manipulation functions */
+
+/**
+ * hid_exists_dquirk: find any dynamic quirks for a HID device
+ * @hdev: the HID device to match
+ *
+ * Description:
+ *         Scans dquirks_list for a matching dynamic quirk and returns
+ *         the pointer to the relevant struct hid_device_id if found.
+ *         Must be called with a read lock held on dquirks_lock.
+ *
+ * Returns: NULL if no quirk found, struct hid_device_id * if found.
+ */
+static struct hid_device_id *hid_exists_dquirk(const struct hid_device *hdev)
+{
+	struct quirks_list_struct *q;
+	struct hid_device_id *bl_entry = NULL;
+
+	list_for_each_entry(q, &dquirks_list, node) {
+		if (hid_match_one_id(hdev, &q->hid_bl_item)) {
+			bl_entry = &q->hid_bl_item;
+			break;
+		}
+	}
+
+	if (bl_entry != NULL)
+		dbg_hid("Found dynamic quirk 0x%lx for HID device 0x%hx:0x%hx\n",
+			bl_entry->driver_data, bl_entry->vendor,
+			bl_entry->product);
+
+	return bl_entry;
+}
+
+
+/**
+ * hid_modify_dquirk: add/replace a HID quirk
+ * @id: the HID device to match
+ * @quirks: the unsigned long quirks value to add/replace
+ *
+ * Description:
+ *         If an dynamic quirk exists in memory for this device, replace its
+ *         quirks value with what was provided.  Otherwise, add the quirk
+ *         to the dynamic quirks list.
+ *
+ * Returns: 0 OK, -error on failure.
+ */
+static int hid_modify_dquirk(const struct hid_device_id *id,
+			     const unsigned long quirks)
+{
+	struct hid_device *hdev;
+	struct quirks_list_struct *q_new, *q;
+	int list_edited = 0;
+	int ret = 0;
+
+	hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
+	if (!hdev)
+		return -ENOMEM;
+
+	q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
+	if (!q_new) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	hdev->bus = q_new->hid_bl_item.bus = id->bus;
+	hdev->group = q_new->hid_bl_item.group = id->group;
+	hdev->vendor = q_new->hid_bl_item.vendor = id->vendor;
+	hdev->product = q_new->hid_bl_item.product = id->product;
+	q_new->hid_bl_item.driver_data = quirks;
+
+	mutex_lock(&dquirks_lock);
+
+	list_for_each_entry(q, &dquirks_list, node) {
+
+		if (hid_match_one_id(hdev, &q->hid_bl_item)) {
+
+			list_replace(&q->node, &q_new->node);
+			kfree(q);
+			list_edited = 1;
+			break;
+
+		}
+
+	}
+
+	if (!list_edited)
+		list_add_tail(&q_new->node, &dquirks_list);
+
+	mutex_unlock(&dquirks_lock);
+
+ out:
+	kfree(hdev);
+	return ret;
+}
+
+/**
+ * hid_remove_all_dquirks: remove all runtime HID quirks from memory
+ * @bus: bus to match against. Use HID_BUS_ANY if all need to be removed.
+ *
+ * Description:
+ *         Free all memory associated with dynamic quirks - called before
+ *         module unload.
+ *
+ */
+static void hid_remove_all_dquirks(__u16 bus)
+{
+	struct quirks_list_struct *q, *temp;
+
+	mutex_lock(&dquirks_lock);
+	list_for_each_entry_safe(q, temp, &dquirks_list, node) {
+		if (bus == HID_BUS_ANY || bus == q->hid_bl_item.bus) {
+			list_del(&q->node);
+			kfree(q);
+		}
+	}
+	mutex_unlock(&dquirks_lock);
+
+}
+
+/**
+ * hid_quirks_init: apply HID quirks specified at module load time
+ */
+int hid_quirks_init(char **quirks_param, __u16 bus, int count)
+{
+	struct hid_device_id id = { 0 };
+	int n = 0, m;
+	unsigned short int vendor, product;
+	u32 quirks;
+
+	id.bus = bus;
+
+	for (; n < count && quirks_param[n]; n++) {
+
+		m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
+				&vendor, &product, &quirks);
+
+		id.vendor = (__u16)vendor;
+		id.product = (__u16)product;
+
+		if (m != 3 ||
+		    hid_modify_dquirk(&id, quirks) != 0) {
+			pr_warn("Could not parse HID quirk module param %s\n",
+				quirks_param[n]);
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hid_quirks_init);
+
+/**
+ * hid_quirks_exit: release memory associated with dynamic_quirks
+ * @bus: a bus to match against
+ *
+ * Description:
+ *     Release all memory associated with dynamic quirks for a given bus.
+ *     Called upon module unload.
+ *     Use HID_BUS_ANY to remove all dynamic quirks.
+ *
+ * Returns: nothing
+ */
+void hid_quirks_exit(__u16 bus)
+{
+	hid_remove_all_dquirks(bus);
+}
+EXPORT_SYMBOL_GPL(hid_quirks_exit);
+
+/**
+ * hid_gets_squirk: return any static quirks for a HID device
+ * @hdev: the HID device to match
+ *
+ * Description:
+ *     Given a HID device, return a pointer to the quirked hid_device_id entry
+ *     associated with that device.
+ *
+ * Returns: the quirks.
+ */
+static unsigned long hid_gets_squirk(const struct hid_device *hdev)
+{
+	const struct hid_device_id *bl_entry;
+	unsigned long quirks = 0;
+
+	if (hid_match_id(hdev, hid_ignore_list))
+		quirks |= HID_QUIRK_IGNORE;
+
+	if (hid_match_id(hdev, hid_have_special_driver))
+		quirks |= HID_QUIRK_HAVE_SPECIAL_DRIVER;
+
+	bl_entry = hid_match_id(hdev, hid_quirks);
+	if (bl_entry != NULL)
+		quirks |= bl_entry->driver_data;
+
+	if (quirks)
+		dbg_hid("Found squirk 0x%lx for HID device 0x%hx:0x%hx\n",
+			quirks, hdev->vendor, hdev->product);
+	return quirks;
+}
+
+/**
+ * hid_lookup_quirk: return any quirks associated with a HID device
+ * @hdev: the HID device to look for
+ *
+ * Description:
+ *     Given a HID device, return any quirks associated with that device.
+ *
+ * Returns: an unsigned long quirks value.
+ */
+unsigned long hid_lookup_quirk(const struct hid_device *hdev)
+{
+	unsigned long quirks = 0;
+	const struct hid_device_id *quirk_entry = NULL;
+
+	/* NCR devices must not be queried for reports */
+	if (hdev->bus == BUS_USB &&
+	    hdev->vendor == USB_VENDOR_ID_NCR &&
+	    hdev->product >= USB_DEVICE_ID_NCR_FIRST &&
+	    hdev->product <= USB_DEVICE_ID_NCR_LAST)
+		return HID_QUIRK_NO_INIT_REPORTS;
+
+	/* These devices must be ignored if version (bcdDevice) is too old */
+	if (hdev->bus == BUS_USB && hdev->vendor == USB_VENDOR_ID_JABRA) {
+		switch (hdev->product) {
+		case USB_DEVICE_ID_JABRA_SPEAK_410:
+			if (hdev->version < 0x0111)
+				return HID_QUIRK_IGNORE;
+			break;
+		case USB_DEVICE_ID_JABRA_SPEAK_510:
+			if (hdev->version < 0x0214)
+				return HID_QUIRK_IGNORE;
+			break;
+		}
+	}
+
+	mutex_lock(&dquirks_lock);
+	quirk_entry = hid_exists_dquirk(hdev);
+	if (quirk_entry)
+		quirks = quirk_entry->driver_data;
+	else
+		quirks = hid_gets_squirk(hdev);
+	mutex_unlock(&dquirks_lock);
+
+	return quirks;
+}
+EXPORT_SYMBOL_GPL(hid_lookup_quirk);
diff --git a/drivers/hid/hid-redragon.c b/drivers/hid/hid-redragon.c
new file mode 100644
index 0000000..73c9d4c
--- /dev/null
+++ b/drivers/hid/hid-redragon.c
@@ -0,0 +1,62 @@
+/*
+ *  HID driver for Redragon keyboards
+ *
+ *  Copyright (c) 2017 Robert Munteanu
+ *  SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+
+/*
+ * The Redragon Asura keyboard sends an incorrect HID descriptor.
+ * At byte 100 it contains
+ *
+ *   0x81, 0x00
+ *
+ * which is Input (Data, Arr, Abs), but it should be
+ *
+ *   0x81, 0x02
+ *
+ * which is Input (Data, Var, Abs), which is consistent with the way
+ * key codes are generated.
+ */
+
+static __u8 *redragon_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+	unsigned int *rsize)
+{
+	if (*rsize >= 102 && rdesc[100] == 0x81 && rdesc[101] == 0x00) {
+		dev_info(&hdev->dev, "Fixing Redragon ASURA report descriptor.\n");
+		rdesc[101] = 0x02;
+	}
+
+	return rdesc;
+}
+
+static const struct hid_device_id redragon_devices[] = {
+	{HID_USB_DEVICE(USB_VENDOR_ID_JESS, USB_DEVICE_ID_REDRAGON_ASURA)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, redragon_devices);
+
+static struct hid_driver redragon_driver = {
+	.name = "redragon",
+	.id_table = redragon_devices,
+	.report_fixup = redragon_report_fixup
+};
+
+module_hid_driver(redragon_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-retrode.c b/drivers/hid/hid-retrode.c
new file mode 100644
index 0000000..30cc7eb
--- /dev/null
+++ b/drivers/hid/hid-retrode.c
@@ -0,0 +1,100 @@
+/*
+ *  HID driver for Retrode 2 controller adapter and plug-in extensions
+ *
+ *  Copyright (c) 2017 Bastien Nocera <hadess@hadess.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/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+#define CONTROLLER_NAME_BASE "Retrode"
+
+static int retrode_input_configured(struct hid_device *hdev,
+					struct hid_input *hi)
+{
+	struct hid_field *field = hi->report->field[0];
+	const char *suffix;
+	int number = 0;
+	char *name;
+
+	switch (field->report->id) {
+	case 0:
+		suffix = "SNES Mouse";
+		break;
+	case 1:
+	case 2:
+		suffix = "SNES / N64";
+		number = field->report->id;
+		break;
+	case 3:
+	case 4:
+		suffix = "Mega Drive";
+		number = field->report->id - 2;
+		break;
+	default:
+		hid_err(hdev, "Got unhandled report id %d\n", field->report->id);
+		suffix = "Unknown";
+	}
+
+	if (number)
+		name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+				"%s %s #%d", CONTROLLER_NAME_BASE,
+				suffix, number);
+	else
+		name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+				"%s %s", CONTROLLER_NAME_BASE, suffix);
+
+	if (!name)
+		return -ENOMEM;
+
+	hi->input->name = name;
+
+	return 0;
+}
+
+static int retrode_probe(struct hid_device *hdev,
+			const struct hid_device_id *id)
+{
+
+	int ret;
+
+	/* Has no effect on the mouse device */
+	hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+	ret = hid_parse(hdev);
+	if (ret)
+		return ret;
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct hid_device_id retrode_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_FUTURE_TECHNOLOGY, USB_DEVICE_ID_RETRODE2) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, retrode_devices);
+
+static struct hid_driver retrode_driver = {
+	.name             = "hid-retrode",
+	.id_table         = retrode_devices,
+	.input_configured = retrode_input_configured,
+	.probe            = retrode_probe,
+};
+
+module_hid_driver(retrode_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
new file mode 100644
index 0000000..9e33165
--- /dev/null
+++ b/drivers/hid/hid-rmi.c
@@ -0,0 +1,780 @@
+/*
+ *  Copyright (c) 2013 Andrew Duggan <aduggan@synaptics.com>
+ *  Copyright (c) 2013 Synaptics Incorporated
+ *  Copyright (c) 2014 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ *  Copyright (c) 2014 Red Hat, Inc
+ *
+ * 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/kernel.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/rmi.h>
+#include "hid-ids.h"
+
+#define RMI_MOUSE_REPORT_ID		0x01 /* Mouse emulation Report */
+#define RMI_WRITE_REPORT_ID		0x09 /* Output Report */
+#define RMI_READ_ADDR_REPORT_ID		0x0a /* Output Report */
+#define RMI_READ_DATA_REPORT_ID		0x0b /* Input Report */
+#define RMI_ATTN_REPORT_ID		0x0c /* Input Report */
+#define RMI_SET_RMI_MODE_REPORT_ID	0x0f /* Feature Report */
+
+/* flags */
+#define RMI_READ_REQUEST_PENDING	0
+#define RMI_READ_DATA_PENDING		1
+#define RMI_STARTED			2
+
+/* device flags */
+#define RMI_DEVICE			BIT(0)
+#define RMI_DEVICE_HAS_PHYS_BUTTONS	BIT(1)
+
+/*
+ * retrieve the ctrl registers
+ * the ctrl register has a size of 20 but a fw bug split it into 16 + 4,
+ * and there is no way to know if the first 20 bytes are here or not.
+ * We use only the first 12 bytes, so get only them.
+ */
+#define RMI_F11_CTRL_REG_COUNT		12
+
+enum rmi_mode_type {
+	RMI_MODE_OFF			= 0,
+	RMI_MODE_ATTN_REPORTS		= 1,
+	RMI_MODE_NO_PACKED_ATTN_REPORTS	= 2,
+};
+
+/**
+ * struct rmi_data - stores information for hid communication
+ *
+ * @page_mutex: Locks current page to avoid changing pages in unexpected ways.
+ * @page: Keeps track of the current virtual page
+ * @xport: transport device to be registered with the RMI4 core.
+ *
+ * @wait: Used for waiting for read data
+ *
+ * @writeReport: output buffer when writing RMI registers
+ * @readReport: input buffer when reading RMI registers
+ *
+ * @input_report_size: size of an input report (advertised by HID)
+ * @output_report_size: size of an output report (advertised by HID)
+ *
+ * @flags: flags for the current device (started, reading, etc...)
+ *
+ * @reset_work: worker which will be called in case of a mouse report
+ * @hdev: pointer to the struct hid_device
+ *
+ * @device_flags: flags which describe the device
+ *
+ * @domain: the IRQ domain allocated for this RMI4 device
+ * @rmi_irq: the irq that will be used to generate events to rmi-core
+ */
+struct rmi_data {
+	struct mutex page_mutex;
+	int page;
+	struct rmi_transport_dev xport;
+
+	wait_queue_head_t wait;
+
+	u8 *writeReport;
+	u8 *readReport;
+
+	u32 input_report_size;
+	u32 output_report_size;
+
+	unsigned long flags;
+
+	struct work_struct reset_work;
+	struct hid_device *hdev;
+
+	unsigned long device_flags;
+
+	struct irq_domain *domain;
+	int rmi_irq;
+};
+
+#define RMI_PAGE(addr) (((addr) >> 8) & 0xff)
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len);
+
+/**
+ * rmi_set_page - Set RMI page
+ * @hdev: The pointer to the hid_device struct
+ * @page: The new page address.
+ *
+ * RMI devices have 16-bit addressing, but some of the physical
+ * implementations (like SMBus) only have 8-bit addressing. So RMI implements
+ * a page address at 0xff of every page so we can reliable page addresses
+ * every 256 registers.
+ *
+ * The page_mutex lock must be held when this function is entered.
+ *
+ * Returns zero on success, non-zero on failure.
+ */
+static int rmi_set_page(struct hid_device *hdev, u8 page)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+	int retval;
+
+	data->writeReport[0] = RMI_WRITE_REPORT_ID;
+	data->writeReport[1] = 1;
+	data->writeReport[2] = 0xFF;
+	data->writeReport[4] = page;
+
+	retval = rmi_write_report(hdev, data->writeReport,
+			data->output_report_size);
+	if (retval != data->output_report_size) {
+		dev_err(&hdev->dev,
+			"%s: set page failed: %d.", __func__, retval);
+		return retval;
+	}
+
+	data->page = page;
+	return 0;
+}
+
+static int rmi_set_mode(struct hid_device *hdev, u8 mode)
+{
+	int ret;
+	const u8 txbuf[2] = {RMI_SET_RMI_MODE_REPORT_ID, mode};
+	u8 *buf;
+
+	buf = kmemdup(txbuf, sizeof(txbuf), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, RMI_SET_RMI_MODE_REPORT_ID, buf,
+			sizeof(txbuf), HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	kfree(buf);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "unable to set rmi mode to %d (%d)\n", mode,
+			ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rmi_write_report(struct hid_device *hdev, u8 *report, int len)
+{
+	int ret;
+
+	ret = hid_hw_output_report(hdev, (void *)report, len);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to write hid report (%d)\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int rmi_hid_read_block(struct rmi_transport_dev *xport, u16 addr,
+		void *buf, size_t len)
+{
+	struct rmi_data *data = container_of(xport, struct rmi_data, xport);
+	struct hid_device *hdev = data->hdev;
+	int ret;
+	int bytes_read;
+	int bytes_needed;
+	int retries;
+	int read_input_count;
+
+	mutex_lock(&data->page_mutex);
+
+	if (RMI_PAGE(addr) != data->page) {
+		ret = rmi_set_page(hdev, RMI_PAGE(addr));
+		if (ret < 0)
+			goto exit;
+	}
+
+	for (retries = 5; retries > 0; retries--) {
+		data->writeReport[0] = RMI_READ_ADDR_REPORT_ID;
+		data->writeReport[1] = 0; /* old 1 byte read count */
+		data->writeReport[2] = addr & 0xFF;
+		data->writeReport[3] = (addr >> 8) & 0xFF;
+		data->writeReport[4] = len  & 0xFF;
+		data->writeReport[5] = (len >> 8) & 0xFF;
+
+		set_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+
+		ret = rmi_write_report(hdev, data->writeReport,
+						data->output_report_size);
+		if (ret != data->output_report_size) {
+			clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+			dev_err(&hdev->dev,
+				"failed to write request output report (%d)\n",
+				ret);
+			goto exit;
+		}
+
+		bytes_read = 0;
+		bytes_needed = len;
+		while (bytes_read < len) {
+			if (!wait_event_timeout(data->wait,
+				test_bit(RMI_READ_DATA_PENDING, &data->flags),
+					msecs_to_jiffies(1000))) {
+				hid_warn(hdev, "%s: timeout elapsed\n",
+					 __func__);
+				ret = -EAGAIN;
+				break;
+			}
+
+			read_input_count = data->readReport[1];
+			memcpy(buf + bytes_read, &data->readReport[2],
+				read_input_count < bytes_needed ?
+					read_input_count : bytes_needed);
+
+			bytes_read += read_input_count;
+			bytes_needed -= read_input_count;
+			clear_bit(RMI_READ_DATA_PENDING, &data->flags);
+		}
+
+		if (ret >= 0) {
+			ret = 0;
+			break;
+		}
+	}
+
+exit:
+	clear_bit(RMI_READ_REQUEST_PENDING, &data->flags);
+	mutex_unlock(&data->page_mutex);
+	return ret;
+}
+
+static int rmi_hid_write_block(struct rmi_transport_dev *xport, u16 addr,
+		const void *buf, size_t len)
+{
+	struct rmi_data *data = container_of(xport, struct rmi_data, xport);
+	struct hid_device *hdev = data->hdev;
+	int ret;
+
+	mutex_lock(&data->page_mutex);
+
+	if (RMI_PAGE(addr) != data->page) {
+		ret = rmi_set_page(hdev, RMI_PAGE(addr));
+		if (ret < 0)
+			goto exit;
+	}
+
+	data->writeReport[0] = RMI_WRITE_REPORT_ID;
+	data->writeReport[1] = len;
+	data->writeReport[2] = addr & 0xFF;
+	data->writeReport[3] = (addr >> 8) & 0xFF;
+	memcpy(&data->writeReport[4], buf, len);
+
+	ret = rmi_write_report(hdev, data->writeReport,
+					data->output_report_size);
+	if (ret < 0) {
+		dev_err(&hdev->dev,
+			"failed to write request output report (%d)\n",
+			ret);
+		goto exit;
+	}
+	ret = 0;
+
+exit:
+	mutex_unlock(&data->page_mutex);
+	return ret;
+}
+
+static int rmi_reset_attn_mode(struct hid_device *hdev)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+	struct rmi_device *rmi_dev = data->xport.rmi_dev;
+	int ret;
+
+	ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+	if (ret)
+		return ret;
+
+	if (test_bit(RMI_STARTED, &data->flags))
+		ret = rmi_dev->driver->reset_handler(rmi_dev);
+
+	return ret;
+}
+
+static void rmi_reset_work(struct work_struct *work)
+{
+	struct rmi_data *hdata = container_of(work, struct rmi_data,
+						reset_work);
+
+	/* switch the device to RMI if we receive a generic mouse report */
+	rmi_reset_attn_mode(hdata->hdev);
+}
+
+static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct rmi_data *hdata = hid_get_drvdata(hdev);
+	struct rmi_device *rmi_dev = hdata->xport.rmi_dev;
+	unsigned long flags;
+
+	if (!(test_bit(RMI_STARTED, &hdata->flags)))
+		return 0;
+
+	local_irq_save(flags);
+
+	rmi_set_attn_data(rmi_dev, data[1], &data[2], size - 2);
+
+	generic_handle_irq(hdata->rmi_irq);
+
+	local_irq_restore(flags);
+
+	return 1;
+}
+
+static int rmi_read_data_event(struct hid_device *hdev, u8 *data, int size)
+{
+	struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+	if (!test_bit(RMI_READ_REQUEST_PENDING, &hdata->flags)) {
+		hid_dbg(hdev, "no read request pending\n");
+		return 0;
+	}
+
+	memcpy(hdata->readReport, data, size < hdata->input_report_size ?
+			size : hdata->input_report_size);
+	set_bit(RMI_READ_DATA_PENDING, &hdata->flags);
+	wake_up(&hdata->wait);
+
+	return 1;
+}
+
+static int rmi_check_sanity(struct hid_device *hdev, u8 *data, int size)
+{
+	int valid_size = size;
+	/*
+	 * On the Dell XPS 13 9333, the bus sometimes get confused and fills
+	 * the report with a sentinel value "ff". Synaptics told us that such
+	 * behavior does not comes from the touchpad itself, so we filter out
+	 * such reports here.
+	 */
+
+	while ((data[valid_size - 1] == 0xff) && valid_size > 0)
+		valid_size--;
+
+	return valid_size;
+}
+
+static int rmi_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+	if (!(hdata->device_flags & RMI_DEVICE))
+		return 0;
+
+	size = rmi_check_sanity(hdev, data, size);
+	if (size < 2)
+		return 0;
+
+	switch (data[0]) {
+	case RMI_READ_DATA_REPORT_ID:
+		return rmi_read_data_event(hdev, data, size);
+	case RMI_ATTN_REPORT_ID:
+		return rmi_input_event(hdev, data, size);
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static int rmi_event(struct hid_device *hdev, struct hid_field *field,
+			struct hid_usage *usage, __s32 value)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+
+	if ((data->device_flags & RMI_DEVICE) &&
+	    (field->application == HID_GD_POINTER ||
+	    field->application == HID_GD_MOUSE)) {
+		if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) {
+			if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON)
+				return 0;
+
+			if ((usage->hid == HID_GD_X || usage->hid == HID_GD_Y)
+			    && !value)
+				return 1;
+		}
+
+		schedule_work(&data->reset_work);
+		return 1;
+	}
+
+	return 0;
+}
+
+static void rmi_report(struct hid_device *hid, struct hid_report *report)
+{
+	struct hid_field *field = report->field[0];
+
+	if (!(hid->claimed & HID_CLAIMED_INPUT))
+		return;
+
+	switch (report->id) {
+	case RMI_READ_DATA_REPORT_ID:
+		/* fall-through */
+	case RMI_ATTN_REPORT_ID:
+		return;
+	}
+
+	if (field && field->hidinput && field->hidinput->input)
+		input_sync(field->hidinput->input);
+}
+
+#ifdef CONFIG_PM
+static int rmi_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+	struct rmi_device *rmi_dev = data->xport.rmi_dev;
+	int ret;
+
+	if (!(data->device_flags & RMI_DEVICE))
+		return 0;
+
+	ret = rmi_driver_suspend(rmi_dev, false);
+	if (ret) {
+		hid_warn(hdev, "Failed to suspend device: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rmi_post_resume(struct hid_device *hdev)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+	struct rmi_device *rmi_dev = data->xport.rmi_dev;
+	int ret;
+
+	if (!(data->device_flags & RMI_DEVICE))
+		return 0;
+
+	/* Make sure the HID device is ready to receive events */
+	ret = hid_hw_open(hdev);
+	if (ret)
+		return ret;
+
+	ret = rmi_reset_attn_mode(hdev);
+	if (ret)
+		goto out;
+
+	ret = rmi_driver_resume(rmi_dev, false);
+	if (ret) {
+		hid_warn(hdev, "Failed to resume device: %d\n", ret);
+		goto out;
+	}
+
+out:
+	hid_hw_close(hdev);
+	return ret;
+}
+#endif /* CONFIG_PM */
+
+static int rmi_hid_reset(struct rmi_transport_dev *xport, u16 reset_addr)
+{
+	struct rmi_data *data = container_of(xport, struct rmi_data, xport);
+	struct hid_device *hdev = data->hdev;
+
+	return rmi_reset_attn_mode(hdev);
+}
+
+static int rmi_input_configured(struct hid_device *hdev, struct hid_input *hi)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+	struct input_dev *input = hi->input;
+	int ret = 0;
+
+	if (!(data->device_flags & RMI_DEVICE))
+		return 0;
+
+	data->xport.input = input;
+
+	hid_dbg(hdev, "Opening low level driver\n");
+	ret = hid_hw_open(hdev);
+	if (ret)
+		return ret;
+
+	/* Allow incoming hid reports */
+	hid_device_io_start(hdev);
+
+	ret = rmi_set_mode(hdev, RMI_MODE_ATTN_REPORTS);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to set rmi mode\n");
+		goto exit;
+	}
+
+	ret = rmi_set_page(hdev, 0);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to set page select to 0.\n");
+		goto exit;
+	}
+
+	ret = rmi_register_transport_device(&data->xport);
+	if (ret < 0) {
+		dev_err(&hdev->dev, "failed to register transport driver\n");
+		goto exit;
+	}
+
+	set_bit(RMI_STARTED, &data->flags);
+
+exit:
+	hid_device_io_stop(hdev);
+	hid_hw_close(hdev);
+	return ret;
+}
+
+static int rmi_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	struct rmi_data *data = hid_get_drvdata(hdev);
+
+	/*
+	 * we want to make HID ignore the advertised HID collection
+	 * for RMI deivces
+	 */
+	if (data->device_flags & RMI_DEVICE) {
+		if ((data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS) &&
+		    ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON))
+			return 0;
+
+		return -1;
+	}
+
+	return 0;
+}
+
+static int rmi_check_valid_report_id(struct hid_device *hdev, unsigned type,
+		unsigned id, struct hid_report **report)
+{
+	int i;
+
+	*report = hdev->report_enum[type].report_id_hash[id];
+	if (*report) {
+		for (i = 0; i < (*report)->maxfield; i++) {
+			unsigned app = (*report)->field[i]->application;
+			if ((app & HID_USAGE_PAGE) >= HID_UP_MSVENDOR)
+				return 1;
+		}
+	}
+
+	return 0;
+}
+
+static struct rmi_device_platform_data rmi_hid_pdata = {
+	.sensor_pdata = {
+		.sensor_type = rmi_sensor_touchpad,
+		.axis_align.flip_y = true,
+		.dribble = RMI_REG_STATE_ON,
+		.palm_detect = RMI_REG_STATE_OFF,
+	},
+};
+
+static const struct rmi_transport_ops hid_rmi_ops = {
+	.write_block	= rmi_hid_write_block,
+	.read_block	= rmi_hid_read_block,
+	.reset		= rmi_hid_reset,
+};
+
+static void rmi_irq_teardown(void *data)
+{
+	struct rmi_data *hdata = data;
+	struct irq_domain *domain = hdata->domain;
+
+	if (!domain)
+		return;
+
+	irq_dispose_mapping(irq_find_mapping(domain, 0));
+
+	irq_domain_remove(domain);
+	hdata->domain = NULL;
+	hdata->rmi_irq = 0;
+}
+
+static int rmi_irq_map(struct irq_domain *h, unsigned int virq,
+		       irq_hw_number_t hw_irq_num)
+{
+	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops rmi_irq_ops = {
+	.map = rmi_irq_map,
+};
+
+static int rmi_setup_irq_domain(struct hid_device *hdev)
+{
+	struct rmi_data *hdata = hid_get_drvdata(hdev);
+	int ret;
+
+	hdata->domain = irq_domain_create_linear(hdev->dev.fwnode, 1,
+						 &rmi_irq_ops, hdata);
+	if (!hdata->domain)
+		return -ENOMEM;
+
+	ret = devm_add_action_or_reset(&hdev->dev, &rmi_irq_teardown, hdata);
+	if (ret)
+		return ret;
+
+	hdata->rmi_irq = irq_create_mapping(hdata->domain, 0);
+	if (hdata->rmi_irq <= 0) {
+		hid_err(hdev, "Can't allocate an IRQ\n");
+		return hdata->rmi_irq < 0 ? hdata->rmi_irq : -ENXIO;
+	}
+
+	return 0;
+}
+
+static int rmi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct rmi_data *data = NULL;
+	int ret;
+	size_t alloc_size;
+	struct hid_report *input_report;
+	struct hid_report *output_report;
+	struct hid_report *feature_report;
+
+	data = devm_kzalloc(&hdev->dev, sizeof(struct rmi_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	INIT_WORK(&data->reset_work, rmi_reset_work);
+	data->hdev = hdev;
+
+	hid_set_drvdata(hdev, data);
+
+	hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+	hdev->quirks |= HID_QUIRK_NO_INPUT_SYNC;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	if (id->driver_data)
+		data->device_flags = id->driver_data;
+
+	/*
+	 * Check for the RMI specific report ids. If they are misisng
+	 * simply return and let the events be processed by hid-input
+	 */
+	if (!rmi_check_valid_report_id(hdev, HID_FEATURE_REPORT,
+	    RMI_SET_RMI_MODE_REPORT_ID, &feature_report)) {
+		hid_dbg(hdev, "device does not have set mode feature report\n");
+		goto start;
+	}
+
+	if (!rmi_check_valid_report_id(hdev, HID_INPUT_REPORT,
+	    RMI_ATTN_REPORT_ID, &input_report)) {
+		hid_dbg(hdev, "device does not have attention input report\n");
+		goto start;
+	}
+
+	data->input_report_size = hid_report_len(input_report);
+
+	if (!rmi_check_valid_report_id(hdev, HID_OUTPUT_REPORT,
+	    RMI_WRITE_REPORT_ID, &output_report)) {
+		hid_dbg(hdev,
+			"device does not have rmi write output report\n");
+		goto start;
+	}
+
+	data->output_report_size = hid_report_len(output_report);
+
+	data->device_flags |= RMI_DEVICE;
+	alloc_size = data->output_report_size + data->input_report_size;
+
+	data->writeReport = devm_kzalloc(&hdev->dev, alloc_size, GFP_KERNEL);
+	if (!data->writeReport) {
+		hid_err(hdev, "failed to allocate buffer for HID reports\n");
+		return -ENOMEM;
+	}
+
+	data->readReport = data->writeReport + data->output_report_size;
+
+	init_waitqueue_head(&data->wait);
+
+	mutex_init(&data->page_mutex);
+
+	ret = rmi_setup_irq_domain(hdev);
+	if (ret) {
+		hid_err(hdev, "failed to allocate IRQ domain\n");
+		return ret;
+	}
+
+	if (data->device_flags & RMI_DEVICE_HAS_PHYS_BUTTONS)
+		rmi_hid_pdata.f30_data.disable = true;
+
+	data->xport.dev = hdev->dev.parent;
+	data->xport.pdata = rmi_hid_pdata;
+	data->xport.pdata.irq = data->rmi_irq;
+	data->xport.proto_name = "hid";
+	data->xport.ops = &hid_rmi_ops;
+
+start:
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void rmi_remove(struct hid_device *hdev)
+{
+	struct rmi_data *hdata = hid_get_drvdata(hdev);
+
+	if (hdata->device_flags & RMI_DEVICE) {
+		clear_bit(RMI_STARTED, &hdata->flags);
+		cancel_work_sync(&hdata->reset_work);
+		rmi_unregister_transport_device(&hdata->xport);
+	}
+
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id rmi_id[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_RAZER, USB_DEVICE_ID_RAZER_BLADE_14),
+		.driver_data = RMI_DEVICE_HAS_PHYS_BUTTONS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_COVER) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_REZEL) },
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_RMI, HID_ANY_ID, HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, rmi_id);
+
+static struct hid_driver rmi_driver = {
+	.name = "hid-rmi",
+	.id_table		= rmi_id,
+	.probe			= rmi_probe,
+	.remove			= rmi_remove,
+	.event			= rmi_event,
+	.raw_event		= rmi_raw_event,
+	.report			= rmi_report,
+	.input_mapping		= rmi_input_mapping,
+	.input_configured	= rmi_input_configured,
+#ifdef CONFIG_PM
+	.suspend		= rmi_suspend,
+	.resume			= rmi_post_resume,
+	.reset_resume		= rmi_post_resume,
+#endif
+};
+
+module_hid_driver(rmi_driver);
+
+MODULE_AUTHOR("Andrew Duggan <aduggan@synaptics.com>");
+MODULE_DESCRIPTION("RMI HID driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c
new file mode 100644
index 0000000..329c5d1
--- /dev/null
+++ b/drivers/hid/hid-roccat-arvo.c
@@ -0,0 +1,458 @@
+/*
+ * Roccat Arvo driver for Linux
+ *
+ * Copyright (c) 2011 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 Arvo is a gamer keyboard with 5 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-arvo.h"
+
+static struct class *arvo_class;
+
+static ssize_t arvo_sysfs_show_mode_key(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev =
+			interface_to_usbdev(to_usb_interface(dev->parent->parent));
+	struct arvo_mode_key temp_buf;
+	int retval;
+
+	mutex_lock(&arvo->arvo_lock);
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_MODE_KEY,
+			&temp_buf, sizeof(struct arvo_mode_key));
+	mutex_unlock(&arvo->arvo_lock);
+	if (retval)
+		return retval;
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.state);
+}
+
+static ssize_t arvo_sysfs_set_mode_key(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev =
+			interface_to_usbdev(to_usb_interface(dev->parent->parent));
+	struct arvo_mode_key temp_buf;
+	unsigned long state;
+	int retval;
+
+	retval = kstrtoul(buf, 10, &state);
+	if (retval)
+		return retval;
+
+	temp_buf.command = ARVO_COMMAND_MODE_KEY;
+	temp_buf.state = state;
+
+	mutex_lock(&arvo->arvo_lock);
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_MODE_KEY,
+			&temp_buf, sizeof(struct arvo_mode_key));
+	mutex_unlock(&arvo->arvo_lock);
+	if (retval)
+		return retval;
+
+	return size;
+}
+static DEVICE_ATTR(mode_key, 0660,
+		   arvo_sysfs_show_mode_key, arvo_sysfs_set_mode_key);
+
+static ssize_t arvo_sysfs_show_key_mask(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev =
+			interface_to_usbdev(to_usb_interface(dev->parent->parent));
+	struct arvo_key_mask temp_buf;
+	int retval;
+
+	mutex_lock(&arvo->arvo_lock);
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_KEY_MASK,
+			&temp_buf, sizeof(struct arvo_key_mask));
+	mutex_unlock(&arvo->arvo_lock);
+	if (retval)
+		return retval;
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.key_mask);
+}
+
+static ssize_t arvo_sysfs_set_key_mask(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev =
+			interface_to_usbdev(to_usb_interface(dev->parent->parent));
+	struct arvo_key_mask temp_buf;
+	unsigned long key_mask;
+	int retval;
+
+	retval = kstrtoul(buf, 10, &key_mask);
+	if (retval)
+		return retval;
+
+	temp_buf.command = ARVO_COMMAND_KEY_MASK;
+	temp_buf.key_mask = key_mask;
+
+	mutex_lock(&arvo->arvo_lock);
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_KEY_MASK,
+			&temp_buf, sizeof(struct arvo_key_mask));
+	mutex_unlock(&arvo->arvo_lock);
+	if (retval)
+		return retval;
+
+	return size;
+}
+static DEVICE_ATTR(key_mask, 0660,
+		   arvo_sysfs_show_key_mask, arvo_sysfs_set_key_mask);
+
+/* retval is 1-5 on success, < 0 on error */
+static int arvo_get_actual_profile(struct usb_device *usb_dev)
+{
+	struct arvo_actual_profile temp_buf;
+	int retval;
+
+	retval = roccat_common2_receive(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
+			&temp_buf, sizeof(struct arvo_actual_profile));
+
+	if (retval)
+		return retval;
+
+	return temp_buf.actual_profile;
+}
+
+static ssize_t arvo_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", arvo->actual_profile);
+}
+
+static ssize_t arvo_sysfs_set_actual_profile(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct arvo_device *arvo =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev =
+			interface_to_usbdev(to_usb_interface(dev->parent->parent));
+	struct arvo_actual_profile temp_buf;
+	unsigned long profile;
+	int retval;
+
+	retval = kstrtoul(buf, 10, &profile);
+	if (retval)
+		return retval;
+
+	if (profile < 1 || profile > 5)
+		return -EINVAL;
+
+	temp_buf.command = ARVO_COMMAND_ACTUAL_PROFILE;
+	temp_buf.actual_profile = profile;
+
+	mutex_lock(&arvo->arvo_lock);
+	retval = roccat_common2_send(usb_dev, ARVO_COMMAND_ACTUAL_PROFILE,
+			&temp_buf, sizeof(struct arvo_actual_profile));
+	if (!retval) {
+		arvo->actual_profile = profile;
+		retval = size;
+	}
+	mutex_unlock(&arvo->arvo_lock);
+	return retval;
+}
+static DEVICE_ATTR(actual_profile, 0660,
+		   arvo_sysfs_show_actual_profile,
+		   arvo_sysfs_set_actual_profile);
+
+static ssize_t arvo_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 = kobj_to_dev(kobj)->parent->parent;
+	struct arvo_device *arvo = 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(&arvo->arvo_lock);
+	retval = roccat_common2_send(usb_dev, command, buf, real_size);
+	mutex_unlock(&arvo->arvo_lock);
+
+	return (retval ? retval : real_size);
+}
+
+static ssize_t arvo_sysfs_read(struct file *fp,
+		struct kobject *kobj, void *buf, loff_t off,
+		size_t count, size_t real_size, uint command)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct arvo_device *arvo = 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(&arvo->arvo_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&arvo->arvo_lock);
+
+	return (retval ? retval : real_size);
+}
+
+static ssize_t arvo_sysfs_write_button(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	return arvo_sysfs_write(fp, kobj, buf, off, count,
+			sizeof(struct arvo_button), ARVO_COMMAND_BUTTON);
+}
+static BIN_ATTR(button, 0220, NULL, arvo_sysfs_write_button,
+		sizeof(struct arvo_button));
+
+static ssize_t arvo_sysfs_read_info(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	return arvo_sysfs_read(fp, kobj, buf, off, count,
+			sizeof(struct arvo_info), ARVO_COMMAND_INFO);
+}
+static BIN_ATTR(info, 0440, arvo_sysfs_read_info, NULL,
+		sizeof(struct arvo_info));
+
+static struct attribute *arvo_attrs[] = {
+	&dev_attr_mode_key.attr,
+	&dev_attr_key_mask.attr,
+	&dev_attr_actual_profile.attr,
+	NULL,
+};
+
+static struct bin_attribute *arvo_bin_attributes[] = {
+	&bin_attr_button,
+	&bin_attr_info,
+	NULL,
+};
+
+static const struct attribute_group arvo_group = {
+	.attrs = arvo_attrs,
+	.bin_attrs = arvo_bin_attributes,
+};
+
+static const struct attribute_group *arvo_groups[] = {
+	&arvo_group,
+	NULL,
+};
+
+static int arvo_init_arvo_device_struct(struct usb_device *usb_dev,
+		struct arvo_device *arvo)
+{
+	int retval;
+
+	mutex_init(&arvo->arvo_lock);
+
+	retval = arvo_get_actual_profile(usb_dev);
+	if (retval < 0)
+		return retval;
+	arvo->actual_profile = retval;
+
+	return 0;
+}
+
+static int arvo_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 arvo_device *arvo;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_KEYBOARD) {
+		hid_set_drvdata(hdev, NULL);
+		return 0;
+	}
+
+	arvo = kzalloc(sizeof(*arvo), GFP_KERNEL);
+	if (!arvo) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, arvo);
+
+	retval = arvo_init_arvo_device_struct(usb_dev, arvo);
+	if (retval) {
+		hid_err(hdev, "couldn't init struct arvo_device\n");
+		goto exit_free;
+	}
+
+	retval = roccat_connect(arvo_class, hdev,
+			sizeof(struct arvo_roccat_report));
+	if (retval < 0) {
+		hid_err(hdev, "couldn't init char dev\n");
+	} else {
+		arvo->chrdev_minor = retval;
+		arvo->roccat_claimed = 1;
+	}
+
+	return 0;
+exit_free:
+	kfree(arvo);
+	return retval;
+}
+
+static void arvo_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct arvo_device *arvo;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_KEYBOARD)
+		return;
+
+	arvo = hid_get_drvdata(hdev);
+	if (arvo->roccat_claimed)
+		roccat_disconnect(arvo->chrdev_minor);
+	kfree(arvo);
+}
+
+static int arvo_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 = arvo_init_specials(hdev);
+	if (retval) {
+		hid_err(hdev, "couldn't install keyboard\n");
+		goto exit_stop;
+	}
+
+	return 0;
+
+exit_stop:
+	hid_hw_stop(hdev);
+exit:
+	return retval;
+}
+
+static void arvo_remove(struct hid_device *hdev)
+{
+	arvo_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void arvo_report_to_chrdev(struct arvo_device const *arvo,
+		u8 const *data)
+{
+	struct arvo_special_report const *special_report;
+	struct arvo_roccat_report roccat_report;
+
+	special_report = (struct arvo_special_report const *)data;
+
+	roccat_report.profile = arvo->actual_profile;
+	roccat_report.button = special_report->event &
+			ARVO_SPECIAL_REPORT_EVENT_MASK_BUTTON;
+	if ((special_report->event & ARVO_SPECIAL_REPORT_EVENT_MASK_ACTION) ==
+			ARVO_SPECIAL_REPORT_EVENT_ACTION_PRESS)
+		roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_PRESS;
+	else
+		roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_RELEASE;
+
+	roccat_report_event(arvo->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+}
+
+static int arvo_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct arvo_device *arvo = hid_get_drvdata(hdev);
+
+	if (size != 3)
+		return 0;
+
+	if (arvo && arvo->roccat_claimed)
+		arvo_report_to_chrdev(arvo, data);
+
+	return 0;
+}
+
+static const struct hid_device_id arvo_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, arvo_devices);
+
+static struct hid_driver arvo_driver = {
+	.name = "arvo",
+	.id_table = arvo_devices,
+	.probe = arvo_probe,
+	.remove = arvo_remove,
+	.raw_event = arvo_raw_event
+};
+
+static int __init arvo_init(void)
+{
+	int retval;
+
+	arvo_class = class_create(THIS_MODULE, "arvo");
+	if (IS_ERR(arvo_class))
+		return PTR_ERR(arvo_class);
+	arvo_class->dev_groups = arvo_groups;
+
+	retval = hid_register_driver(&arvo_driver);
+	if (retval)
+		class_destroy(arvo_class);
+	return retval;
+}
+
+static void __exit arvo_exit(void)
+{
+	hid_unregister_driver(&arvo_driver);
+	class_destroy(arvo_class);
+}
+
+module_init(arvo_init);
+module_exit(arvo_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Arvo driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-arvo.h b/drivers/hid/hid-roccat-arvo.h
new file mode 100644
index 0000000..ce8415e
--- /dev/null
+++ b/drivers/hid/hid-roccat-arvo.h
@@ -0,0 +1,85 @@
+#ifndef __HID_ROCCAT_ARVO_H
+#define __HID_ROCCAT_ARVO_H
+
+/*
+ * Copyright (c) 2011 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>
+
+struct arvo_mode_key { /* 2 bytes */
+	uint8_t command; /* ARVO_COMMAND_MODE_KEY */
+	uint8_t state;
+} __packed;
+
+struct arvo_button {
+	uint8_t unknown[24];
+} __packed;
+
+struct arvo_info {
+	uint8_t unknown[8];
+} __packed;
+
+struct arvo_key_mask { /* 2 bytes */
+	uint8_t command; /* ARVO_COMMAND_KEY_MASK */
+	uint8_t key_mask;
+} __packed;
+
+/* selected profile is persistent */
+struct arvo_actual_profile { /* 2 bytes */
+	uint8_t command; /* ARVO_COMMAND_ACTUAL_PROFILE */
+	uint8_t actual_profile;
+} __packed;
+
+enum arvo_commands {
+	ARVO_COMMAND_MODE_KEY = 0x3,
+	ARVO_COMMAND_BUTTON = 0x4,
+	ARVO_COMMAND_INFO = 0x5,
+	ARVO_COMMAND_KEY_MASK = 0x6,
+	ARVO_COMMAND_ACTUAL_PROFILE = 0x7,
+};
+
+struct arvo_special_report {
+	uint8_t unknown1; /* always 0x01 */
+	uint8_t event;
+	uint8_t unknown2; /* always 0x70 */
+} __packed;
+
+enum arvo_special_report_events {
+	ARVO_SPECIAL_REPORT_EVENT_ACTION_PRESS = 0x10,
+	ARVO_SPECIAL_REPORT_EVENT_ACTION_RELEASE = 0x0,
+};
+
+enum arvo_special_report_event_masks {
+	ARVO_SPECIAL_REPORT_EVENT_MASK_ACTION = 0xf0,
+	ARVO_SPECIAL_REPORT_EVENT_MASK_BUTTON = 0x0f,
+};
+
+struct arvo_roccat_report {
+	uint8_t profile;
+	uint8_t button;
+	uint8_t action;
+} __packed;
+
+enum arvo_roccat_report_action {
+	ARVO_ROCCAT_REPORT_ACTION_RELEASE = 0,
+	ARVO_ROCCAT_REPORT_ACTION_PRESS = 1,
+};
+
+struct arvo_device {
+	int roccat_claimed;
+	int chrdev_minor;
+
+	struct mutex arvo_lock;
+
+	int actual_profile;
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-common.c b/drivers/hid/hid-roccat-common.c
new file mode 100644
index 0000000..8155ac5
--- /dev/null
+++ b/drivers/hid/hid-roccat-common.c
@@ -0,0 +1,178 @@
+/*
+ * Roccat common functions for device specific drivers
+ *
+ * Copyright (c) 2011 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/hid.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include "hid-roccat-common.h"
+
+static inline uint16_t roccat_common2_feature_report(uint8_t report_id)
+{
+	return 0x300 | report_id;
+}
+
+int roccat_common2_receive(struct usb_device *usb_dev, uint report_id,
+		void *data, uint size)
+{
+	char *buf;
+	int len;
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	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_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_common2_receive);
+
+int roccat_common2_send(struct usb_device *usb_dev, uint report_id,
+		void const *data, uint size)
+{
+	char *buf;
+	int len;
+
+	buf = kmemdup(data, size, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	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_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_common2_send);
+
+enum roccat_common2_control_states {
+	ROCCAT_COMMON_CONTROL_STATUS_CRITICAL = 0,
+	ROCCAT_COMMON_CONTROL_STATUS_OK = 1,
+	ROCCAT_COMMON_CONTROL_STATUS_INVALID = 2,
+	ROCCAT_COMMON_CONTROL_STATUS_BUSY = 3,
+	ROCCAT_COMMON_CONTROL_STATUS_CRITICAL_NEW = 4,
+};
+
+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_BUSY:
+			msleep(500);
+			continue;
+		case ROCCAT_COMMON_CONTROL_STATUS_INVALID:
+		case ROCCAT_COMMON_CONTROL_STATUS_CRITICAL:
+		case ROCCAT_COMMON_CONTROL_STATUS_CRITICAL_NEW:
+			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);
+
+int roccat_common2_device_init_struct(struct usb_device *usb_dev,
+		struct roccat_common2_device *dev)
+{
+	mutex_init(&dev->lock);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(roccat_common2_device_init_struct);
+
+ssize_t roccat_common2_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 = kobj_to_dev(kobj)->parent->parent;
+	struct roccat_common2_device *roccat_dev = 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(&roccat_dev->lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&roccat_dev->lock);
+
+	return retval ? retval : real_size;
+}
+EXPORT_SYMBOL_GPL(roccat_common2_sysfs_read);
+
+ssize_t roccat_common2_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 = kobj_to_dev(kobj)->parent->parent;
+	struct roccat_common2_device *roccat_dev = 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(&roccat_dev->lock);
+	retval = roccat_common2_send_with_status(usb_dev, command, buf, real_size);
+	mutex_unlock(&roccat_dev->lock);
+
+	return retval ? retval : real_size;
+}
+EXPORT_SYMBOL_GPL(roccat_common2_sysfs_write);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat common driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h
new file mode 100644
index 0000000..eaa56eb
--- /dev/null
+++ b/drivers/hid/hid-roccat-common.h
@@ -0,0 +1,97 @@
+#ifndef __HID_ROCCAT_COMMON_H
+#define __HID_ROCCAT_COMMON_H
+
+/*
+ * Copyright (c) 2011 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/usb.h>
+#include <linux/types.h>
+
+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_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);
+
+struct roccat_common2_device {
+	int roccat_claimed;
+	int chrdev_minor;
+	struct mutex lock;
+};
+
+int roccat_common2_device_init_struct(struct usb_device *usb_dev,
+		struct roccat_common2_device *dev);
+ssize_t roccat_common2_sysfs_read(struct file *fp, struct kobject *kobj,
+		char *buf, loff_t off, size_t count,
+		size_t real_size, uint command);
+ssize_t roccat_common2_sysfs_write(struct file *fp, struct kobject *kobj,
+		void const *buf, loff_t off, size_t count,
+		size_t real_size, uint command);
+
+#define ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE) \
+static ssize_t roccat_common2_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return roccat_common2_sysfs_write(fp, kobj, buf, off, count, \
+			SIZE, COMMAND); \
+}
+
+#define ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE) \
+static ssize_t roccat_common2_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return roccat_common2_sysfs_read(fp, kobj, buf, off, count, \
+			SIZE, COMMAND); \
+}
+
+#define ROCCAT_COMMON2_SYSFS_RW(thingy, COMMAND, SIZE) \
+ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE) \
+ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE)
+
+#define ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(thingy, COMMAND, SIZE) \
+ROCCAT_COMMON2_SYSFS_RW(thingy, COMMAND, SIZE); \
+static struct bin_attribute bin_attr_ ## thingy = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = SIZE, \
+	.read = roccat_common2_sysfs_read_ ## thingy, \
+	.write = roccat_common2_sysfs_write_ ## thingy \
+}
+
+#define ROCCAT_COMMON2_BIN_ATTRIBUTE_R(thingy, COMMAND, SIZE) \
+ROCCAT_COMMON2_SYSFS_R(thingy, COMMAND, SIZE); \
+static struct bin_attribute bin_attr_ ## thingy = { \
+	.attr = { .name = #thingy, .mode = 0440 }, \
+	.size = SIZE, \
+	.read = roccat_common2_sysfs_read_ ## thingy, \
+}
+
+#define ROCCAT_COMMON2_BIN_ATTRIBUTE_W(thingy, COMMAND, SIZE) \
+ROCCAT_COMMON2_SYSFS_W(thingy, COMMAND, SIZE); \
+static struct bin_attribute bin_attr_ ## thingy = { \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = SIZE, \
+	.write = roccat_common2_sysfs_write_ ## thingy \
+}
+
+#endif
diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c
new file mode 100644
index 0000000..02db537
--- /dev/null
+++ b/drivers/hid/hid-roccat-isku.c
@@ -0,0 +1,460 @@
+/*
+ * Roccat Isku driver for Linux
+ *
+ * Copyright (c) 2011 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 Isku is a gamer keyboard 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-isku.h"
+
+static struct class *isku_class;
+
+static void isku_profile_activated(struct isku_device *isku, uint new_profile)
+{
+	isku->actual_profile = new_profile;
+}
+
+static int isku_receive(struct usb_device *usb_dev, uint command,
+		void *buf, uint size)
+{
+	return roccat_common2_receive(usb_dev, command, buf, size);
+}
+
+static int isku_get_actual_profile(struct usb_device *usb_dev)
+{
+	struct isku_actual_profile buf;
+	int retval;
+
+	retval = isku_receive(usb_dev, ISKU_COMMAND_ACTUAL_PROFILE,
+			&buf, sizeof(struct isku_actual_profile));
+	return retval ? retval : buf.actual_profile;
+}
+
+static int isku_set_actual_profile(struct usb_device *usb_dev, int new_profile)
+{
+	struct isku_actual_profile buf;
+
+	buf.command = ISKU_COMMAND_ACTUAL_PROFILE;
+	buf.size = sizeof(struct isku_actual_profile);
+	buf.actual_profile = new_profile;
+	return roccat_common2_send_with_status(usb_dev,
+			ISKU_COMMAND_ACTUAL_PROFILE, &buf,
+			sizeof(struct isku_actual_profile));
+}
+
+static ssize_t isku_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct isku_device *isku =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile);
+}
+
+static ssize_t isku_sysfs_set_actual_profile(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct isku_device *isku;
+	struct usb_device *usb_dev;
+	unsigned long profile;
+	int retval;
+	struct isku_roccat_report roccat_report;
+
+	dev = dev->parent->parent;
+	isku = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	retval = kstrtoul(buf, 10, &profile);
+	if (retval)
+		return retval;
+
+	if (profile > 4)
+		return -EINVAL;
+
+	mutex_lock(&isku->isku_lock);
+
+	retval = isku_set_actual_profile(usb_dev, profile);
+	if (retval) {
+		mutex_unlock(&isku->isku_lock);
+		return retval;
+	}
+
+	isku_profile_activated(isku, profile);
+
+	roccat_report.event = ISKU_REPORT_BUTTON_EVENT_PROFILE;
+	roccat_report.data1 = profile + 1;
+	roccat_report.data2 = 0;
+	roccat_report.profile = profile + 1;
+	roccat_report_event(isku->chrdev_minor, (uint8_t const *)&roccat_report);
+
+	mutex_unlock(&isku->isku_lock);
+
+	return size;
+}
+static DEVICE_ATTR(actual_profile, 0660, isku_sysfs_show_actual_profile,
+		   isku_sysfs_set_actual_profile);
+
+static struct attribute *isku_attrs[] = {
+	&dev_attr_actual_profile.attr,
+	NULL,
+};
+
+static ssize_t isku_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 = kobj_to_dev(kobj)->parent->parent;
+	struct isku_device *isku = 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(&isku->isku_lock);
+	retval = isku_receive(usb_dev, command, buf, count);
+	mutex_unlock(&isku->isku_lock);
+
+	return retval ? retval : count;
+}
+
+static ssize_t isku_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 = kobj_to_dev(kobj)->parent->parent;
+	struct isku_device *isku = 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(&isku->isku_lock);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			(void *)buf, count);
+	mutex_unlock(&isku->isku_lock);
+
+	return retval ? retval : count;
+}
+
+#define ISKU_SYSFS_W(thingy, THINGY) \
+static ssize_t isku_sysfs_write_ ## thingy(struct file *fp, struct kobject *kobj, \
+		struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return isku_sysfs_write(fp, kobj, buf, off, count, \
+			ISKU_SIZE_ ## THINGY, ISKU_COMMAND_ ## THINGY); \
+}
+
+#define ISKU_SYSFS_R(thingy, THINGY) \
+static ssize_t isku_sysfs_read_ ## thingy(struct file *fp, struct kobject *kobj, \
+		struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return isku_sysfs_read(fp, kobj, buf, off, count, \
+			ISKU_SIZE_ ## THINGY, ISKU_COMMAND_ ## THINGY); \
+}
+
+#define ISKU_SYSFS_RW(thingy, THINGY) \
+ISKU_SYSFS_R(thingy, THINGY) \
+ISKU_SYSFS_W(thingy, THINGY)
+
+#define ISKU_BIN_ATTR_RW(thingy, THINGY) \
+ISKU_SYSFS_RW(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = ISKU_SIZE_ ## THINGY, \
+	.read = isku_sysfs_read_ ## thingy, \
+	.write = isku_sysfs_write_ ## thingy \
+}
+
+#define ISKU_BIN_ATTR_R(thingy, THINGY) \
+ISKU_SYSFS_R(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0440 }, \
+	.size = ISKU_SIZE_ ## THINGY, \
+	.read = isku_sysfs_read_ ## thingy, \
+}
+
+#define ISKU_BIN_ATTR_W(thingy, THINGY) \
+ISKU_SYSFS_W(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = ISKU_SIZE_ ## THINGY, \
+	.write = isku_sysfs_write_ ## thingy \
+}
+
+ISKU_BIN_ATTR_RW(macro, MACRO);
+ISKU_BIN_ATTR_RW(keys_function, KEYS_FUNCTION);
+ISKU_BIN_ATTR_RW(keys_easyzone, KEYS_EASYZONE);
+ISKU_BIN_ATTR_RW(keys_media, KEYS_MEDIA);
+ISKU_BIN_ATTR_RW(keys_thumbster, KEYS_THUMBSTER);
+ISKU_BIN_ATTR_RW(keys_macro, KEYS_MACRO);
+ISKU_BIN_ATTR_RW(keys_capslock, KEYS_CAPSLOCK);
+ISKU_BIN_ATTR_RW(light, LIGHT);
+ISKU_BIN_ATTR_RW(key_mask, KEY_MASK);
+ISKU_BIN_ATTR_RW(last_set, LAST_SET);
+ISKU_BIN_ATTR_W(talk, TALK);
+ISKU_BIN_ATTR_W(talkfx, TALKFX);
+ISKU_BIN_ATTR_W(control, CONTROL);
+ISKU_BIN_ATTR_W(reset, RESET);
+ISKU_BIN_ATTR_R(info, INFO);
+
+static struct bin_attribute *isku_bin_attributes[] = {
+	&bin_attr_macro,
+	&bin_attr_keys_function,
+	&bin_attr_keys_easyzone,
+	&bin_attr_keys_media,
+	&bin_attr_keys_thumbster,
+	&bin_attr_keys_macro,
+	&bin_attr_keys_capslock,
+	&bin_attr_light,
+	&bin_attr_key_mask,
+	&bin_attr_last_set,
+	&bin_attr_talk,
+	&bin_attr_talkfx,
+	&bin_attr_control,
+	&bin_attr_reset,
+	&bin_attr_info,
+	NULL,
+};
+
+static const struct attribute_group isku_group = {
+	.attrs = isku_attrs,
+	.bin_attrs = isku_bin_attributes,
+};
+
+static const struct attribute_group *isku_groups[] = {
+	&isku_group,
+	NULL,
+};
+
+static int isku_init_isku_device_struct(struct usb_device *usb_dev,
+		struct isku_device *isku)
+{
+	int retval;
+
+	mutex_init(&isku->isku_lock);
+
+	retval = isku_get_actual_profile(usb_dev);
+	if (retval < 0)
+		return retval;
+	isku_profile_activated(isku, retval);
+
+	return 0;
+}
+
+static int isku_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 isku_device *isku;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= ISKU_USB_INTERFACE_PROTOCOL) {
+		hid_set_drvdata(hdev, NULL);
+		return 0;
+	}
+
+	isku = kzalloc(sizeof(*isku), GFP_KERNEL);
+	if (!isku) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, isku);
+
+	retval = isku_init_isku_device_struct(usb_dev, isku);
+	if (retval) {
+		hid_err(hdev, "couldn't init struct isku_device\n");
+		goto exit_free;
+	}
+
+	retval = roccat_connect(isku_class, hdev,
+			sizeof(struct isku_roccat_report));
+	if (retval < 0) {
+		hid_err(hdev, "couldn't init char dev\n");
+	} else {
+		isku->chrdev_minor = retval;
+		isku->roccat_claimed = 1;
+	}
+
+	return 0;
+exit_free:
+	kfree(isku);
+	return retval;
+}
+
+static void isku_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct isku_device *isku;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= ISKU_USB_INTERFACE_PROTOCOL)
+		return;
+
+	isku = hid_get_drvdata(hdev);
+	if (isku->roccat_claimed)
+		roccat_disconnect(isku->chrdev_minor);
+	kfree(isku);
+}
+
+static int isku_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 = isku_init_specials(hdev);
+	if (retval) {
+		hid_err(hdev, "couldn't install keyboard\n");
+		goto exit_stop;
+	}
+
+	return 0;
+
+exit_stop:
+	hid_hw_stop(hdev);
+exit:
+	return retval;
+}
+
+static void isku_remove(struct hid_device *hdev)
+{
+	isku_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void isku_keep_values_up_to_date(struct isku_device *isku,
+		u8 const *data)
+{
+	struct isku_report_button const *button_report;
+
+	switch (data[0]) {
+	case ISKU_REPORT_NUMBER_BUTTON:
+		button_report = (struct isku_report_button const *)data;
+		switch (button_report->event) {
+		case ISKU_REPORT_BUTTON_EVENT_PROFILE:
+			isku_profile_activated(isku, button_report->data1 - 1);
+			break;
+		}
+		break;
+	}
+}
+
+static void isku_report_to_chrdev(struct isku_device const *isku,
+		u8 const *data)
+{
+	struct isku_roccat_report roccat_report;
+	struct isku_report_button const *button_report;
+
+	if (data[0] != ISKU_REPORT_NUMBER_BUTTON)
+		return;
+
+	button_report = (struct isku_report_button const *)data;
+
+	roccat_report.event = button_report->event;
+	roccat_report.data1 = button_report->data1;
+	roccat_report.data2 = button_report->data2;
+	roccat_report.profile = isku->actual_profile + 1;
+	roccat_report_event(isku->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+}
+
+static int isku_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 isku_device *isku = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= ISKU_USB_INTERFACE_PROTOCOL)
+		return 0;
+
+	if (isku == NULL)
+		return 0;
+
+	isku_keep_values_up_to_date(isku, data);
+
+	if (isku->roccat_claimed)
+		isku_report_to_chrdev(isku, data);
+
+	return 0;
+}
+
+static const struct hid_device_id isku_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKU) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ISKUFX) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, isku_devices);
+
+static struct hid_driver isku_driver = {
+		.name = "isku",
+		.id_table = isku_devices,
+		.probe = isku_probe,
+		.remove = isku_remove,
+		.raw_event = isku_raw_event
+};
+
+static int __init isku_init(void)
+{
+	int retval;
+	isku_class = class_create(THIS_MODULE, "isku");
+	if (IS_ERR(isku_class))
+		return PTR_ERR(isku_class);
+	isku_class->dev_groups = isku_groups;
+
+	retval = hid_register_driver(&isku_driver);
+	if (retval)
+		class_destroy(isku_class);
+	return retval;
+}
+
+static void __exit isku_exit(void)
+{
+	hid_unregister_driver(&isku_driver);
+	class_destroy(isku_class);
+}
+
+module_init(isku_init);
+module_exit(isku_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Isku/FX driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-isku.h b/drivers/hid/hid-roccat-isku.h
new file mode 100644
index 0000000..5305686
--- /dev/null
+++ b/drivers/hid/hid-roccat-isku.h
@@ -0,0 +1,100 @@
+#ifndef __HID_ROCCAT_ISKU_H
+#define __HID_ROCCAT_ISKU_H
+
+/*
+ * Copyright (c) 2011 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 {
+	ISKU_SIZE_CONTROL = 0x03,
+	ISKU_SIZE_INFO = 0x06,
+	ISKU_SIZE_KEY_MASK = 0x06,
+	ISKU_SIZE_KEYS_FUNCTION = 0x29,
+	ISKU_SIZE_KEYS_EASYZONE = 0x41,
+	ISKU_SIZE_KEYS_MEDIA = 0x1d,
+	ISKU_SIZE_KEYS_THUMBSTER = 0x17,
+	ISKU_SIZE_KEYS_MACRO = 0x23,
+	ISKU_SIZE_KEYS_CAPSLOCK = 0x06,
+	ISKU_SIZE_LAST_SET = 0x14,
+	ISKU_SIZE_LIGHT = 0x10,
+	ISKU_SIZE_MACRO = 0x823,
+	ISKU_SIZE_RESET = 0x03,
+	ISKU_SIZE_TALK = 0x10,
+	ISKU_SIZE_TALKFX = 0x10,
+};
+
+enum {
+	ISKU_PROFILE_NUM = 5,
+	ISKU_USB_INTERFACE_PROTOCOL = 0,
+};
+
+struct isku_actual_profile {
+	uint8_t command; /* ISKU_COMMAND_ACTUAL_PROFILE */
+	uint8_t size; /* always 3 */
+	uint8_t actual_profile;
+} __packed;
+
+enum isku_commands {
+	ISKU_COMMAND_CONTROL = 0x4,
+	ISKU_COMMAND_ACTUAL_PROFILE = 0x5,
+	ISKU_COMMAND_KEY_MASK = 0x7,
+	ISKU_COMMAND_KEYS_FUNCTION = 0x8,
+	ISKU_COMMAND_KEYS_EASYZONE = 0x9,
+	ISKU_COMMAND_KEYS_MEDIA = 0xa,
+	ISKU_COMMAND_KEYS_THUMBSTER = 0xb,
+	ISKU_COMMAND_KEYS_MACRO = 0xd,
+	ISKU_COMMAND_MACRO = 0xe,
+	ISKU_COMMAND_INFO = 0xf,
+	ISKU_COMMAND_LIGHT = 0x10,
+	ISKU_COMMAND_RESET = 0x11,
+	ISKU_COMMAND_KEYS_CAPSLOCK = 0x13,
+	ISKU_COMMAND_LAST_SET = 0x14,
+	ISKU_COMMAND_15 = 0x15,
+	ISKU_COMMAND_TALK = 0x16,
+	ISKU_COMMAND_TALKFX = 0x17,
+	ISKU_COMMAND_FIRMWARE_WRITE = 0x1b,
+	ISKU_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c,
+};
+
+struct isku_report_button {
+	uint8_t number; /* ISKU_REPORT_NUMBER_BUTTON */
+	uint8_t zero;
+	uint8_t event;
+	uint8_t data1;
+	uint8_t data2;
+};
+
+enum isku_report_numbers {
+	ISKU_REPORT_NUMBER_BUTTON = 3,
+};
+
+enum isku_report_button_events {
+	ISKU_REPORT_BUTTON_EVENT_PROFILE = 0x2,
+};
+
+struct isku_roccat_report {
+	uint8_t event;
+	uint8_t data1;
+	uint8_t data2;
+	uint8_t profile;
+} __packed;
+
+struct isku_device {
+	int roccat_claimed;
+	int chrdev_minor;
+
+	struct mutex isku_lock;
+
+	int actual_profile;
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c
new file mode 100644
index 0000000..bf4675a
--- /dev/null
+++ b/drivers/hid/hid-roccat-kone.c
@@ -0,0 +1,905 @@
+/*
+ * Roccat Kone driver for Linux
+ *
+ * Copyright (c) 2010 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 Kone is a gamer mouse which consists of a mouse part and a keyboard
+ * part. The keyboard part enables the mouse to execute stored macros with mixed
+ * key- and button-events.
+ *
+ * TODO implement on-the-fly polling-rate change
+ *      The windows driver has the ability to change the polling rate of the
+ *      device on the press of a mousebutton.
+ *      Is it possible to remove and reinstall the urb in raw-event- or any
+ *      other handler, or to defer this action to be executed somewhere else?
+ *
+ * TODO is it possible to overwrite group for sysfs attributes via udev?
+ */
+
+#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-kone.h"
+
+static uint profile_numbers[5] = {0, 1, 2, 3, 4};
+
+static void kone_profile_activated(struct kone_device *kone, uint new_profile)
+{
+	kone->actual_profile = new_profile;
+	kone->actual_dpi = kone->profiles[new_profile - 1].startup_dpi;
+}
+
+static void kone_profile_report(struct kone_device *kone, uint new_profile)
+{
+	struct kone_roccat_report roccat_report;
+
+	roccat_report.event = kone_mouse_event_switch_profile;
+	roccat_report.value = new_profile;
+	roccat_report.key = 0;
+	roccat_report_event(kone->chrdev_minor, (uint8_t *)&roccat_report);
+}
+
+static int kone_receive(struct usb_device *usb_dev, uint usb_command,
+		void *data, uint size)
+{
+	char *buf;
+	int len;
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			HID_REQ_GET_REPORT,
+			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT);
+
+	memcpy(data, buf, size);
+	kfree(buf);
+	return ((len < 0) ? len : ((len != size) ? -EIO : 0));
+}
+
+static int kone_send(struct usb_device *usb_dev, uint usb_command,
+		void const *data, uint size)
+{
+	char *buf;
+	int len;
+
+	buf = kmemdup(data, size, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			HID_REQ_SET_REPORT,
+			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+			usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT);
+
+	kfree(buf);
+	return ((len < 0) ? len : ((len != size) ? -EIO : 0));
+}
+
+/* kone_class is used for creating sysfs attributes via roccat char device */
+static struct class *kone_class;
+
+static void kone_set_settings_checksum(struct kone_settings *settings)
+{
+	uint16_t checksum = 0;
+	unsigned char *address = (unsigned char *)settings;
+	int i;
+
+	for (i = 0; i < sizeof(struct kone_settings) - 2; ++i, ++address)
+		checksum += *address;
+	settings->checksum = cpu_to_le16(checksum);
+}
+
+/*
+ * Checks success after writing data to mouse
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_check_write(struct usb_device *usb_dev)
+{
+	int retval;
+	uint8_t data;
+
+	do {
+		/*
+		 * Mouse needs 50 msecs until it says ok, but there are
+		 * 30 more msecs needed for next write to work.
+		 */
+		msleep(80);
+
+		retval = kone_receive(usb_dev,
+				kone_command_confirm_write, &data, 1);
+		if (retval)
+			return retval;
+
+		/*
+		 * value of 3 seems to mean something like
+		 * "not finished yet, but it looks good"
+		 * So check again after a moment.
+		 */
+	} while (data == 3);
+
+	if (data == 1) /* everything alright */
+		return 0;
+
+	/* unknown answer */
+	dev_err(&usb_dev->dev, "got retval %d when checking write\n", data);
+	return -EIO;
+}
+
+/*
+ * Reads settings from mouse and stores it in @buf
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_get_settings(struct usb_device *usb_dev,
+		struct kone_settings *buf)
+{
+	return kone_receive(usb_dev, kone_command_settings, buf,
+			sizeof(struct kone_settings));
+}
+
+/*
+ * Writes settings from @buf to mouse
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_set_settings(struct usb_device *usb_dev,
+		struct kone_settings const *settings)
+{
+	int retval;
+
+	retval = kone_send(usb_dev, kone_command_settings,
+			settings, sizeof(struct kone_settings));
+	if (retval)
+		return retval;
+	return kone_check_write(usb_dev);
+}
+
+/*
+ * Reads profile data from mouse and stores it in @buf
+ * @number: profile number to read
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_get_profile(struct usb_device *usb_dev,
+		struct kone_profile *buf, int number)
+{
+	int len;
+
+	if (number < 1 || number > 5)
+		return -EINVAL;
+
+	len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+			USB_REQ_CLEAR_FEATURE,
+			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+			kone_command_profile, number, buf,
+			sizeof(struct kone_profile), USB_CTRL_SET_TIMEOUT);
+
+	if (len != sizeof(struct kone_profile))
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Writes profile data to mouse.
+ * @number: profile number to write
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_set_profile(struct usb_device *usb_dev,
+		struct kone_profile const *profile, int number)
+{
+	int len;
+
+	if (number < 1 || number > 5)
+		return -EINVAL;
+
+	len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0),
+			USB_REQ_SET_CONFIGURATION,
+			USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+			kone_command_profile, number, (void *)profile,
+			sizeof(struct kone_profile),
+			USB_CTRL_SET_TIMEOUT);
+
+	if (len != sizeof(struct kone_profile))
+		return len;
+
+	if (kone_check_write(usb_dev))
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Reads value of "fast-clip-weight" and stores it in @result
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_get_weight(struct usb_device *usb_dev, int *result)
+{
+	int retval;
+	uint8_t data;
+
+	retval = kone_receive(usb_dev, kone_command_weight, &data, 1);
+
+	if (retval)
+		return retval;
+
+	*result = (int)data;
+	return 0;
+}
+
+/*
+ * Reads firmware_version of mouse and stores it in @result
+ * On success returns 0
+ * On failure returns errno
+ */
+static int kone_get_firmware_version(struct usb_device *usb_dev, int *result)
+{
+	int retval;
+	uint16_t data;
+
+	retval = kone_receive(usb_dev, kone_command_firmware_version,
+			&data, 2);
+	if (retval)
+		return retval;
+
+	*result = le16_to_cpu(data);
+	return 0;
+}
+
+static ssize_t kone_sysfs_read_settings(struct file *fp, struct kobject *kobj,
+		struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count) {
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
+
+	if (off >= sizeof(struct kone_settings))
+		return 0;
+
+	if (off + count > sizeof(struct kone_settings))
+		count = sizeof(struct kone_settings) - off;
+
+	mutex_lock(&kone->kone_lock);
+	memcpy(buf, ((char const *)&kone->settings) + off, count);
+	mutex_unlock(&kone->kone_lock);
+
+	return count;
+}
+
+/*
+ * Writing settings automatically activates startup_profile.
+ * This function keeps values in kone_device up to date and assumes that in
+ * case of error the old data is still valid
+ */
+static ssize_t kone_sysfs_write_settings(struct file *fp, struct kobject *kobj,
+		struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count) {
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	int retval = 0, difference, old_profile;
+
+	/* I need to get my data in one piece */
+	if (off != 0 || count != sizeof(struct kone_settings))
+		return -EINVAL;
+
+	mutex_lock(&kone->kone_lock);
+	difference = memcmp(buf, &kone->settings, sizeof(struct kone_settings));
+	if (difference) {
+		retval = kone_set_settings(usb_dev,
+				(struct kone_settings const *)buf);
+		if (retval) {
+			mutex_unlock(&kone->kone_lock);
+			return retval;
+		}
+
+		old_profile = kone->settings.startup_profile;
+		memcpy(&kone->settings, buf, sizeof(struct kone_settings));
+
+		kone_profile_activated(kone, kone->settings.startup_profile);
+
+		if (kone->settings.startup_profile != old_profile)
+			kone_profile_report(kone, kone->settings.startup_profile);
+	}
+	mutex_unlock(&kone->kone_lock);
+
+	return sizeof(struct kone_settings);
+}
+static BIN_ATTR(settings, 0660, kone_sysfs_read_settings,
+		kone_sysfs_write_settings, sizeof(struct kone_settings));
+
+static ssize_t kone_sysfs_read_profilex(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr,
+		char *buf, loff_t off, size_t count) {
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
+
+	if (off >= sizeof(struct kone_profile))
+		return 0;
+
+	if (off + count > sizeof(struct kone_profile))
+		count = sizeof(struct kone_profile) - off;
+
+	mutex_lock(&kone->kone_lock);
+	memcpy(buf, ((char const *)&kone->profiles[*(uint *)(attr->private)]) + off, count);
+	mutex_unlock(&kone->kone_lock);
+
+	return count;
+}
+
+/* Writes data only if different to stored data */
+static ssize_t kone_sysfs_write_profilex(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr,
+		char *buf, loff_t off, size_t count) {
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct kone_device *kone = hid_get_drvdata(dev_get_drvdata(dev));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	struct kone_profile *profile;
+	int retval = 0, difference;
+
+	/* I need to get my data in one piece */
+	if (off != 0 || count != sizeof(struct kone_profile))
+		return -EINVAL;
+
+	profile = &kone->profiles[*(uint *)(attr->private)];
+
+	mutex_lock(&kone->kone_lock);
+	difference = memcmp(buf, profile, sizeof(struct kone_profile));
+	if (difference) {
+		retval = kone_set_profile(usb_dev,
+				(struct kone_profile const *)buf,
+				*(uint *)(attr->private) + 1);
+		if (!retval)
+			memcpy(profile, buf, sizeof(struct kone_profile));
+	}
+	mutex_unlock(&kone->kone_lock);
+
+	if (retval)
+		return retval;
+
+	return sizeof(struct kone_profile);
+}
+#define PROFILE_ATTR(number)					\
+static struct bin_attribute bin_attr_profile##number = {	\
+	.attr = { .name = "profile" #number, .mode = 0660 },	\
+	.size = sizeof(struct kone_profile),			\
+	.read = kone_sysfs_read_profilex,			\
+	.write = kone_sysfs_write_profilex,			\
+	.private = &profile_numbers[number-1],			\
+}
+PROFILE_ATTR(1);
+PROFILE_ATTR(2);
+PROFILE_ATTR(3);
+PROFILE_ATTR(4);
+PROFILE_ATTR(5);
+
+static ssize_t kone_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
+}
+static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
+
+static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
+}
+static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
+
+/* weight is read each time, since we don't get informed when it's changed */
+static ssize_t kone_sysfs_show_weight(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone;
+	struct usb_device *usb_dev;
+	int weight = 0;
+	int retval;
+
+	dev = dev->parent->parent;
+	kone = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	mutex_lock(&kone->kone_lock);
+	retval = kone_get_weight(usb_dev, &weight);
+	mutex_unlock(&kone->kone_lock);
+
+	if (retval)
+		return retval;
+	return snprintf(buf, PAGE_SIZE, "%d\n", weight);
+}
+static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
+
+static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
+}
+static DEVICE_ATTR(firmware_version, 0440, kone_sysfs_show_firmware_version,
+		   NULL);
+
+static ssize_t kone_sysfs_show_tcu(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
+}
+
+static int kone_tcu_command(struct usb_device *usb_dev, int number)
+{
+	unsigned char value;
+
+	value = number;
+	return kone_send(usb_dev, kone_command_calibrate, &value, 1);
+}
+
+/*
+ * Calibrating the tcu is the only action that changes settings data inside the
+ * mouse, so this data needs to be reread
+ */
+static ssize_t kone_sysfs_set_tcu(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct kone_device *kone;
+	struct usb_device *usb_dev;
+	int retval;
+	unsigned long state;
+
+	dev = dev->parent->parent;
+	kone = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	retval = kstrtoul(buf, 10, &state);
+	if (retval)
+		return retval;
+
+	if (state != 0 && state != 1)
+		return -EINVAL;
+
+	mutex_lock(&kone->kone_lock);
+
+	if (state == 1) { /* state activate */
+		retval = kone_tcu_command(usb_dev, 1);
+		if (retval)
+			goto exit_unlock;
+		retval = kone_tcu_command(usb_dev, 2);
+		if (retval)
+			goto exit_unlock;
+		ssleep(5); /* tcu needs this time for calibration */
+		retval = kone_tcu_command(usb_dev, 3);
+		if (retval)
+			goto exit_unlock;
+		retval = kone_tcu_command(usb_dev, 0);
+		if (retval)
+			goto exit_unlock;
+		retval = kone_tcu_command(usb_dev, 4);
+		if (retval)
+			goto exit_unlock;
+		/*
+		 * Kone needs this time to settle things.
+		 * Reading settings too early will result in invalid data.
+		 * Roccat's driver waits 1 sec, maybe this time could be
+		 * shortened.
+		 */
+		ssleep(1);
+	}
+
+	/* calibration changes values in settings, so reread */
+	retval = kone_get_settings(usb_dev, &kone->settings);
+	if (retval)
+		goto exit_no_settings;
+
+	/* only write settings back if activation state is different */
+	if (kone->settings.tcu != state) {
+		kone->settings.tcu = state;
+		kone_set_settings_checksum(&kone->settings);
+
+		retval = kone_set_settings(usb_dev, &kone->settings);
+		if (retval) {
+			dev_err(&usb_dev->dev, "couldn't set tcu state\n");
+			/*
+			 * try to reread valid settings into buffer overwriting
+			 * first error code
+			 */
+			retval = kone_get_settings(usb_dev, &kone->settings);
+			if (retval)
+				goto exit_no_settings;
+			goto exit_unlock;
+		}
+		/* calibration resets profile */
+		kone_profile_activated(kone, kone->settings.startup_profile);
+	}
+
+	retval = size;
+exit_no_settings:
+	dev_err(&usb_dev->dev, "couldn't read settings\n");
+exit_unlock:
+	mutex_unlock(&kone->kone_lock);
+	return retval;
+}
+static DEVICE_ATTR(tcu, 0660, kone_sysfs_show_tcu, kone_sysfs_set_tcu);
+
+static ssize_t kone_sysfs_show_startup_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kone_device *kone =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile);
+}
+
+static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct kone_device *kone;
+	struct usb_device *usb_dev;
+	int retval;
+	unsigned long new_startup_profile;
+
+	dev = dev->parent->parent;
+	kone = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	retval = kstrtoul(buf, 10, &new_startup_profile);
+	if (retval)
+		return retval;
+
+	if (new_startup_profile  < 1 || new_startup_profile > 5)
+		return -EINVAL;
+
+	mutex_lock(&kone->kone_lock);
+
+	kone->settings.startup_profile = new_startup_profile;
+	kone_set_settings_checksum(&kone->settings);
+
+	retval = kone_set_settings(usb_dev, &kone->settings);
+	if (retval) {
+		mutex_unlock(&kone->kone_lock);
+		return retval;
+	}
+
+	/* changing the startup profile immediately activates this profile */
+	kone_profile_activated(kone, new_startup_profile);
+	kone_profile_report(kone, new_startup_profile);
+
+	mutex_unlock(&kone->kone_lock);
+	return size;
+}
+static DEVICE_ATTR(startup_profile, 0660, kone_sysfs_show_startup_profile,
+		   kone_sysfs_set_startup_profile);
+
+static struct attribute *kone_attrs[] = {
+	/*
+	 * Read actual dpi settings.
+	 * Returns raw value for further processing. Refer to enum
+	 * kone_polling_rates to get real value.
+	 */
+	&dev_attr_actual_dpi.attr,
+	&dev_attr_actual_profile.attr,
+
+	/*
+	 * The mouse can be equipped with one of four supplied weights from 5
+	 * to 20 grams which are recognized and its value can be read out.
+	 * This returns the raw value reported by the mouse for easy evaluation
+	 * by software. Refer to enum kone_weights to get corresponding real
+	 * weight.
+	 */
+	&dev_attr_weight.attr,
+
+	/*
+	 * Prints firmware version stored in mouse as integer.
+	 * The raw value reported by the mouse is returned for easy evaluation,
+	 * to get the real version number the decimal point has to be shifted 2
+	 * positions to the left. E.g. a value of 138 means 1.38.
+	 */
+	&dev_attr_firmware_version.attr,
+
+	/*
+	 * Prints state of Tracking Control Unit as number where 0 = off and
+	 * 1 = on. Writing 0 deactivates tcu and writing 1 calibrates and
+	 * activates the tcu
+	 */
+	&dev_attr_tcu.attr,
+
+	/* Prints and takes the number of the profile the mouse starts with */
+	&dev_attr_startup_profile.attr,
+	NULL,
+};
+
+static struct bin_attribute *kone_bin_attributes[] = {
+	&bin_attr_settings,
+	&bin_attr_profile1,
+	&bin_attr_profile2,
+	&bin_attr_profile3,
+	&bin_attr_profile4,
+	&bin_attr_profile5,
+	NULL,
+};
+
+static const struct attribute_group kone_group = {
+	.attrs = kone_attrs,
+	.bin_attrs = kone_bin_attributes,
+};
+
+static const struct attribute_group *kone_groups[] = {
+	&kone_group,
+	NULL,
+};
+
+static int kone_init_kone_device_struct(struct usb_device *usb_dev,
+		struct kone_device *kone)
+{
+	uint i;
+	int retval;
+
+	mutex_init(&kone->kone_lock);
+
+	for (i = 0; i < 5; ++i) {
+		retval = kone_get_profile(usb_dev, &kone->profiles[i], i + 1);
+		if (retval)
+			return retval;
+	}
+
+	retval = kone_get_settings(usb_dev, &kone->settings);
+	if (retval)
+		return retval;
+
+	retval = kone_get_firmware_version(usb_dev, &kone->firmware_version);
+	if (retval)
+		return retval;
+
+	kone_profile_activated(kone, kone->settings.startup_profile);
+
+	return 0;
+}
+
+/*
+ * Since IGNORE_MOUSE quirk moved to hid-apple, there is no way to bind only to
+ * mousepart if usb_hid is compiled into the kernel and kone is compiled as
+ * module.
+ * Secial behaviour is bound only to mousepart since only mouseevents contain
+ * additional notifications.
+ */
+static int kone_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 kone_device *kone;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+
+		kone = kzalloc(sizeof(*kone), GFP_KERNEL);
+		if (!kone)
+			return -ENOMEM;
+		hid_set_drvdata(hdev, kone);
+
+		retval = kone_init_kone_device_struct(usb_dev, kone);
+		if (retval) {
+			hid_err(hdev, "couldn't init struct kone_device\n");
+			goto exit_free;
+		}
+
+		retval = roccat_connect(kone_class, hdev,
+				sizeof(struct kone_roccat_report));
+		if (retval < 0) {
+			hid_err(hdev, "couldn't init char dev\n");
+			/* be tolerant about not getting chrdev */
+		} else {
+			kone->roccat_claimed = 1;
+			kone->chrdev_minor = retval;
+		}
+	} else {
+		hid_set_drvdata(hdev, NULL);
+	}
+
+	return 0;
+exit_free:
+	kfree(kone);
+	return retval;
+}
+
+static void kone_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct kone_device *kone;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+		kone = hid_get_drvdata(hdev);
+		if (kone->roccat_claimed)
+			roccat_disconnect(kone->chrdev_minor);
+		kfree(hid_get_drvdata(hdev));
+	}
+}
+
+static int kone_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 = kone_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 kone_remove(struct hid_device *hdev)
+{
+	kone_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+/* handle special events and keep actual profile and dpi values up to date */
+static void kone_keep_values_up_to_date(struct kone_device *kone,
+		struct kone_mouse_event const *event)
+{
+	switch (event->event) {
+	case kone_mouse_event_switch_profile:
+		kone->actual_dpi = kone->profiles[event->value - 1].
+				startup_dpi;
+	case kone_mouse_event_osd_profile:
+		kone->actual_profile = event->value;
+		break;
+	case kone_mouse_event_switch_dpi:
+	case kone_mouse_event_osd_dpi:
+		kone->actual_dpi = event->value;
+		break;
+	}
+}
+
+static void kone_report_to_chrdev(struct kone_device const *kone,
+		struct kone_mouse_event const *event)
+{
+	struct kone_roccat_report roccat_report;
+
+	switch (event->event) {
+	case kone_mouse_event_switch_profile:
+	case kone_mouse_event_switch_dpi:
+	case kone_mouse_event_osd_profile:
+	case kone_mouse_event_osd_dpi:
+		roccat_report.event = event->event;
+		roccat_report.value = event->value;
+		roccat_report.key = 0;
+		roccat_report_event(kone->chrdev_minor,
+				(uint8_t *)&roccat_report);
+		break;
+	case kone_mouse_event_call_overlong_macro:
+	case kone_mouse_event_multimedia:
+		if (event->value == kone_keystroke_action_press) {
+			roccat_report.event = event->event;
+			roccat_report.value = kone->actual_profile;
+			roccat_report.key = event->macro_key;
+			roccat_report_event(kone->chrdev_minor,
+					(uint8_t *)&roccat_report);
+		}
+		break;
+	}
+
+}
+
+/*
+ * Is called for keyboard- and mousepart.
+ * Only mousepart gets informations about special events in its extended event
+ * structure.
+ */
+static int kone_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *data, int size)
+{
+	struct kone_device *kone = hid_get_drvdata(hdev);
+	struct kone_mouse_event *event = (struct kone_mouse_event *)data;
+
+	/* keyboard events are always processed by default handler */
+	if (size != sizeof(struct kone_mouse_event))
+		return 0;
+
+	if (kone == NULL)
+		return 0;
+
+	/*
+	 * Firmware 1.38 introduced new behaviour for tilt and special buttons.
+	 * Pressed button is reported in each movement event.
+	 * Workaround sends only one event per press.
+	 */
+	if (memcmp(&kone->last_mouse_event.tilt, &event->tilt, 5))
+		memcpy(&kone->last_mouse_event, event,
+				sizeof(struct kone_mouse_event));
+	else
+		memset(&event->tilt, 0, 5);
+
+	kone_keep_values_up_to_date(kone, event);
+
+	if (kone->roccat_claimed)
+		kone_report_to_chrdev(kone, event);
+
+	return 0; /* always do further processing */
+}
+
+static const struct hid_device_id kone_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, kone_devices);
+
+static struct hid_driver kone_driver = {
+		.name = "kone",
+		.id_table = kone_devices,
+		.probe = kone_probe,
+		.remove = kone_remove,
+		.raw_event = kone_raw_event
+};
+
+static int __init kone_init(void)
+{
+	int retval;
+
+	/* class name has to be same as driver name */
+	kone_class = class_create(THIS_MODULE, "kone");
+	if (IS_ERR(kone_class))
+		return PTR_ERR(kone_class);
+	kone_class->dev_groups = kone_groups;
+
+	retval = hid_register_driver(&kone_driver);
+	if (retval)
+		class_destroy(kone_class);
+	return retval;
+}
+
+static void __exit kone_exit(void)
+{
+	hid_unregister_driver(&kone_driver);
+	class_destroy(kone_class);
+}
+
+module_init(kone_init);
+module_exit(kone_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Kone driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-kone.h b/drivers/hid/hid-roccat-kone.h
new file mode 100644
index 0000000..52c6167
--- /dev/null
+++ b/drivers/hid/hid-roccat-kone.h
@@ -0,0 +1,227 @@
+#ifndef __HID_ROCCAT_KONE_H
+#define __HID_ROCCAT_KONE_H
+
+/*
+ * Copyright (c) 2010 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>
+
+struct kone_keystroke {
+	uint8_t key;
+	uint8_t action;
+	uint16_t period; /* in milliseconds */
+} __attribute__ ((__packed__));
+
+enum kone_keystroke_buttons {
+	kone_keystroke_button_1 = 0xf0, /* left mouse button */
+	kone_keystroke_button_2 = 0xf1, /* right mouse button */
+	kone_keystroke_button_3 = 0xf2, /* wheel */
+	kone_keystroke_button_9 = 0xf3, /* side button up */
+	kone_keystroke_button_8 = 0xf4 /* side button down */
+};
+
+enum kone_keystroke_actions {
+	kone_keystroke_action_press = 0,
+	kone_keystroke_action_release = 1
+};
+
+struct kone_button_info {
+	uint8_t number; /* range 1-8 */
+	uint8_t type;
+	uint8_t macro_type; /* 0 = short, 1 = overlong */
+	uint8_t macro_set_name[16]; /* can be max 15 chars long */
+	uint8_t macro_name[16]; /* can be max 15 chars long */
+	uint8_t count;
+	struct kone_keystroke keystrokes[20];
+} __attribute__ ((__packed__));
+
+enum kone_button_info_types {
+	/* valid button types until firmware 1.32 */
+	kone_button_info_type_button_1 = 0x1, /* click (left mouse button) */
+	kone_button_info_type_button_2 = 0x2, /* menu (right mouse button)*/
+	kone_button_info_type_button_3 = 0x3, /* scroll (wheel) */
+	kone_button_info_type_double_click = 0x4,
+	kone_button_info_type_key = 0x5,
+	kone_button_info_type_macro = 0x6,
+	kone_button_info_type_off = 0x7,
+	/* TODO clarify function and rename */
+	kone_button_info_type_osd_xy_prescaling = 0x8,
+	kone_button_info_type_osd_dpi = 0x9,
+	kone_button_info_type_osd_profile = 0xa,
+	kone_button_info_type_button_9 = 0xb, /* ie forward */
+	kone_button_info_type_button_8 = 0xc, /* ie backward */
+	kone_button_info_type_dpi_up = 0xd, /* internal */
+	kone_button_info_type_dpi_down = 0xe, /* internal */
+	kone_button_info_type_button_7 = 0xf, /* tilt left */
+	kone_button_info_type_button_6 = 0x10, /* tilt right */
+	kone_button_info_type_profile_up = 0x11, /* internal */
+	kone_button_info_type_profile_down = 0x12, /* internal */
+	/* additional valid button types since firmware 1.38 */
+	kone_button_info_type_multimedia_open_player = 0x20,
+	kone_button_info_type_multimedia_next_track = 0x21,
+	kone_button_info_type_multimedia_prev_track = 0x22,
+	kone_button_info_type_multimedia_play_pause = 0x23,
+	kone_button_info_type_multimedia_stop = 0x24,
+	kone_button_info_type_multimedia_mute = 0x25,
+	kone_button_info_type_multimedia_volume_up = 0x26,
+	kone_button_info_type_multimedia_volume_down = 0x27
+};
+
+enum kone_button_info_numbers {
+	kone_button_top = 1,
+	kone_button_wheel_tilt_left = 2,
+	kone_button_wheel_tilt_right = 3,
+	kone_button_forward = 4,
+	kone_button_backward = 5,
+	kone_button_middle = 6,
+	kone_button_plus = 7,
+	kone_button_minus = 8,
+};
+
+struct kone_light_info {
+	uint8_t number; /* number of light 1-5 */
+	uint8_t mod;   /* 1 = on, 2 = off */
+	uint8_t red;   /* range 0x00-0xff */
+	uint8_t green; /* range 0x00-0xff */
+	uint8_t blue;  /* range 0x00-0xff */
+} __attribute__ ((__packed__));
+
+struct kone_profile {
+	uint16_t size; /* always 975 */
+	uint16_t unused; /* always 0 */
+
+	/*
+	 * range 1-5
+	 * This number does not need to correspond with location where profile
+	 * saved
+	 */
+	uint8_t profile; /* range 1-5 */
+
+	uint16_t main_sensitivity; /* range 100-1000 */
+	uint8_t xy_sensitivity_enabled; /* 1 = on, 2 = off */
+	uint16_t x_sensitivity; /* range 100-1000 */
+	uint16_t y_sensitivity; /* range 100-1000 */
+	uint8_t dpi_rate; /* bit 1 = 800, ... */
+	uint8_t startup_dpi; /* range 1-6 */
+	uint8_t polling_rate; /* 1 = 125Hz, 2 = 500Hz, 3 = 1000Hz */
+	/* kone has no dcu
+	 * value is always 2 in firmwares <= 1.32 and
+	 * 1 in firmwares > 1.32
+	 */
+	uint8_t dcu_flag;
+	uint8_t light_effect_1; /* range 1-3 */
+	uint8_t light_effect_2; /* range 1-5 */
+	uint8_t light_effect_3; /* range 1-4 */
+	uint8_t light_effect_speed; /* range 0-255 */
+
+	struct kone_light_info light_infos[5];
+	/* offset is kone_button_info_numbers - 1 */
+	struct kone_button_info button_infos[8];
+
+	uint16_t checksum; /* \brief holds checksum of struct */
+} __attribute__ ((__packed__));
+
+enum kone_polling_rates {
+	kone_polling_rate_125 = 1,
+	kone_polling_rate_500 = 2,
+	kone_polling_rate_1000 = 3
+};
+
+struct kone_settings {
+	uint16_t size; /* always 36 */
+	uint8_t  startup_profile; /* 1-5 */
+	uint8_t	 unknown1;
+	uint8_t  tcu; /* 0 = off, 1 = on */
+	uint8_t  unknown2[23];
+	uint8_t  calibration_data[4];
+	uint8_t  unknown3[2];
+	uint16_t checksum;
+} __attribute__ ((__packed__));
+
+/*
+ * 12 byte mouse event read by interrupt_read
+ */
+struct kone_mouse_event {
+	uint8_t report_number; /* always 1 */
+	uint8_t button;
+	uint16_t x;
+	uint16_t y;
+	uint8_t wheel; /* up = 1, down = -1 */
+	uint8_t tilt; /* right = 1, left = -1 */
+	uint8_t unknown;
+	uint8_t event;
+	uint8_t value; /* press = 0, release = 1 */
+	uint8_t macro_key; /* 0 to 8 */
+} __attribute__ ((__packed__));
+
+enum kone_mouse_events {
+	/* osd events are thought to be display on screen */
+	kone_mouse_event_osd_dpi = 0xa0,
+	kone_mouse_event_osd_profile = 0xb0,
+	/* TODO clarify meaning and occurence of kone_mouse_event_calibration */
+	kone_mouse_event_calibration = 0xc0,
+	kone_mouse_event_call_overlong_macro = 0xe0,
+	kone_mouse_event_multimedia = 0xe1,
+	/* switch events notify if user changed values with mousebutton click */
+	kone_mouse_event_switch_dpi = 0xf0,
+	kone_mouse_event_switch_profile = 0xf1
+};
+
+enum kone_commands {
+	kone_command_profile = 0x5a,
+	kone_command_settings = 0x15a,
+	kone_command_firmware_version = 0x25a,
+	kone_command_weight = 0x45a,
+	kone_command_calibrate = 0x55a,
+	kone_command_confirm_write = 0x65a,
+	kone_command_firmware = 0xe5a
+};
+
+struct kone_roccat_report {
+	uint8_t event;
+	uint8_t value; /* holds dpi or profile value */
+	uint8_t key; /* macro key on overlong macro execution */
+} __attribute__ ((__packed__));
+
+struct kone_device {
+	/*
+	 * Storing actual values when we get informed about changes since there
+	 * is no way of getting this information from the device on demand
+	 */
+	int actual_profile, actual_dpi;
+	/* Used for neutralizing abnormal button behaviour */
+	struct kone_mouse_event last_mouse_event;
+
+	/*
+	 * It's unlikely that multiple sysfs attributes are accessed at a time,
+	 * so only one mutex is used to secure hardware access and profiles and
+	 * settings of this struct.
+	 */
+	struct mutex kone_lock;
+
+	/*
+	 * Storing the data here reduces IO and ensures that data is available
+	 * when its needed (E.g. interrupt handler).
+	 */
+	struct kone_profile profiles[5];
+	struct kone_settings settings;
+
+	/*
+	 * firmware doesn't change unless firmware update is implemented,
+	 * so it's read only once
+	 */
+	int firmware_version;
+
+	int roccat_claimed;
+	int chrdev_minor;
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c
new file mode 100644
index 0000000..09e8fc7
--- /dev/null
+++ b/drivers/hid/hid-roccat-koneplus.c
@@ -0,0 +1,574 @@
+/*
+ * Roccat Kone[+] driver for Linux
+ *
+ * Copyright (c) 2010 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 Kone[+] is an updated/improved version of the Kone with more memory
+ * and functionality and without the non-standard behaviours the Kone had.
+ * KoneXTD has same capabilities but updated sensor.
+ */
+
+#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-koneplus.h"
+
+static uint profile_numbers[5] = {0, 1, 2, 3, 4};
+
+static struct class *koneplus_class;
+
+static void koneplus_profile_activated(struct koneplus_device *koneplus,
+		uint new_profile)
+{
+	koneplus->actual_profile = new_profile;
+}
+
+static int koneplus_send_control(struct usb_device *usb_dev, uint value,
+		enum koneplus_control_requests request)
+{
+	struct roccat_common2_control control;
+
+	if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
+			request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
+			value > 4)
+		return -EINVAL;
+
+	control.command = ROCCAT_COMMON_COMMAND_CONTROL;
+	control.value = value;
+	control.request = request;
+
+	return roccat_common2_send_with_status(usb_dev,
+			ROCCAT_COMMON_COMMAND_CONTROL,
+			&control, sizeof(struct roccat_common2_control));
+}
+
+
+/* retval is 0-4 on success, < 0 on error */
+static int koneplus_get_actual_profile(struct usb_device *usb_dev)
+{
+	struct koneplus_actual_profile buf;
+	int retval;
+
+	retval = roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_ACTUAL_PROFILE,
+			&buf, KONEPLUS_SIZE_ACTUAL_PROFILE);
+
+	return retval ? retval : buf.actual_profile;
+}
+
+static int koneplus_set_actual_profile(struct usb_device *usb_dev,
+		int new_profile)
+{
+	struct koneplus_actual_profile buf;
+
+	buf.command = KONEPLUS_COMMAND_ACTUAL_PROFILE;
+	buf.size = KONEPLUS_SIZE_ACTUAL_PROFILE;
+	buf.actual_profile = new_profile;
+
+	return roccat_common2_send_with_status(usb_dev,
+			KONEPLUS_COMMAND_ACTUAL_PROFILE,
+			&buf, KONEPLUS_SIZE_ACTUAL_PROFILE);
+}
+
+static ssize_t koneplus_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 = kobj_to_dev(kobj)->parent->parent;
+	struct koneplus_device *koneplus = 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(&koneplus->koneplus_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&koneplus->koneplus_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+static ssize_t koneplus_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 = kobj_to_dev(kobj)->parent->parent;
+	struct koneplus_device *koneplus = 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(&koneplus->koneplus_lock);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			buf, real_size);
+	mutex_unlock(&koneplus->koneplus_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+#define KONEPLUS_SYSFS_W(thingy, THINGY) \
+static ssize_t koneplus_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return koneplus_sysfs_write(fp, kobj, buf, off, count, \
+			KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
+}
+
+#define KONEPLUS_SYSFS_R(thingy, THINGY) \
+static ssize_t koneplus_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return koneplus_sysfs_read(fp, kobj, buf, off, count, \
+			KONEPLUS_SIZE_ ## THINGY, KONEPLUS_COMMAND_ ## THINGY); \
+}
+
+#define KONEPLUS_SYSFS_RW(thingy, THINGY) \
+KONEPLUS_SYSFS_W(thingy, THINGY) \
+KONEPLUS_SYSFS_R(thingy, THINGY)
+
+#define KONEPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
+KONEPLUS_SYSFS_RW(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = KONEPLUS_SIZE_ ## THINGY, \
+	.read = koneplus_sysfs_read_ ## thingy, \
+	.write = koneplus_sysfs_write_ ## thingy \
+}
+
+#define KONEPLUS_BIN_ATTRIBUTE_R(thingy, THINGY) \
+KONEPLUS_SYSFS_R(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0440 }, \
+	.size = KONEPLUS_SIZE_ ## THINGY, \
+	.read = koneplus_sysfs_read_ ## thingy, \
+}
+
+#define KONEPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
+KONEPLUS_SYSFS_W(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = KONEPLUS_SIZE_ ## THINGY, \
+	.write = koneplus_sysfs_write_ ## thingy \
+}
+KONEPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
+KONEPLUS_BIN_ATTRIBUTE_W(talk, TALK);
+KONEPLUS_BIN_ATTRIBUTE_W(macro, MACRO);
+KONEPLUS_BIN_ATTRIBUTE_R(tcu_image, TCU_IMAGE);
+KONEPLUS_BIN_ATTRIBUTE_RW(info, INFO);
+KONEPLUS_BIN_ATTRIBUTE_RW(sensor, SENSOR);
+KONEPLUS_BIN_ATTRIBUTE_RW(tcu, TCU);
+KONEPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
+KONEPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
+
+static ssize_t koneplus_sysfs_read_profilex_settings(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = koneplus_send_control(usb_dev, *(uint *)(attr->private),
+			KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
+	if (retval)
+		return retval;
+
+	return koneplus_sysfs_read(fp, kobj, buf, off, count,
+			KONEPLUS_SIZE_PROFILE_SETTINGS,
+			KONEPLUS_COMMAND_PROFILE_SETTINGS);
+}
+
+static ssize_t koneplus_sysfs_read_profilex_buttons(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = koneplus_send_control(usb_dev, *(uint *)(attr->private),
+			KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
+	if (retval)
+		return retval;
+
+	return koneplus_sysfs_read(fp, kobj, buf, off, count,
+			KONEPLUS_SIZE_PROFILE_BUTTONS,
+			KONEPLUS_COMMAND_PROFILE_BUTTONS);
+}
+
+#define PROFILE_ATTR(number)						\
+static struct bin_attribute bin_attr_profile##number##_settings = {	\
+	.attr = { .name = "profile" #number "_settings", .mode = 0440 },	\
+	.size = KONEPLUS_SIZE_PROFILE_SETTINGS,				\
+	.read = koneplus_sysfs_read_profilex_settings,			\
+	.private = &profile_numbers[number-1],				\
+};									\
+static struct bin_attribute bin_attr_profile##number##_buttons = {	\
+	.attr = { .name = "profile" #number "_buttons", .mode = 0440 },	\
+	.size = KONEPLUS_SIZE_PROFILE_BUTTONS,				\
+	.read = koneplus_sysfs_read_profilex_buttons,			\
+	.private = &profile_numbers[number-1],				\
+};
+PROFILE_ATTR(1);
+PROFILE_ATTR(2);
+PROFILE_ATTR(3);
+PROFILE_ATTR(4);
+PROFILE_ATTR(5);
+
+static ssize_t koneplus_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct koneplus_device *koneplus =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", koneplus->actual_profile);
+}
+
+static ssize_t koneplus_sysfs_set_actual_profile(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct koneplus_device *koneplus;
+	struct usb_device *usb_dev;
+	unsigned long profile;
+	int retval;
+	struct koneplus_roccat_report roccat_report;
+
+	dev = dev->parent->parent;
+	koneplus = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	retval = kstrtoul(buf, 10, &profile);
+	if (retval)
+		return retval;
+
+	if (profile > 4)
+		return -EINVAL;
+
+	mutex_lock(&koneplus->koneplus_lock);
+
+	retval = koneplus_set_actual_profile(usb_dev, profile);
+	if (retval) {
+		mutex_unlock(&koneplus->koneplus_lock);
+		return retval;
+	}
+
+	koneplus_profile_activated(koneplus, profile);
+
+	roccat_report.type = KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE;
+	roccat_report.data1 = profile + 1;
+	roccat_report.data2 = 0;
+	roccat_report.profile = profile + 1;
+	roccat_report_event(koneplus->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+
+	mutex_unlock(&koneplus->koneplus_lock);
+
+	return size;
+}
+static DEVICE_ATTR(actual_profile, 0660,
+		   koneplus_sysfs_show_actual_profile,
+		   koneplus_sysfs_set_actual_profile);
+static DEVICE_ATTR(startup_profile, 0660,
+		   koneplus_sysfs_show_actual_profile,
+		   koneplus_sysfs_set_actual_profile);
+
+static ssize_t koneplus_sysfs_show_firmware_version(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct koneplus_device *koneplus;
+	struct usb_device *usb_dev;
+	struct koneplus_info info;
+
+	dev = dev->parent->parent;
+	koneplus = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	mutex_lock(&koneplus->koneplus_lock);
+	roccat_common2_receive(usb_dev, KONEPLUS_COMMAND_INFO,
+			&info, KONEPLUS_SIZE_INFO);
+	mutex_unlock(&koneplus->koneplus_lock);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+}
+static DEVICE_ATTR(firmware_version, 0440,
+		   koneplus_sysfs_show_firmware_version, NULL);
+
+static struct attribute *koneplus_attrs[] = {
+	&dev_attr_actual_profile.attr,
+	&dev_attr_startup_profile.attr,
+	&dev_attr_firmware_version.attr,
+	NULL,
+};
+
+static struct bin_attribute *koneplus_bin_attributes[] = {
+	&bin_attr_control,
+	&bin_attr_talk,
+	&bin_attr_macro,
+	&bin_attr_tcu_image,
+	&bin_attr_info,
+	&bin_attr_sensor,
+	&bin_attr_tcu,
+	&bin_attr_profile_settings,
+	&bin_attr_profile_buttons,
+	&bin_attr_profile1_settings,
+	&bin_attr_profile2_settings,
+	&bin_attr_profile3_settings,
+	&bin_attr_profile4_settings,
+	&bin_attr_profile5_settings,
+	&bin_attr_profile1_buttons,
+	&bin_attr_profile2_buttons,
+	&bin_attr_profile3_buttons,
+	&bin_attr_profile4_buttons,
+	&bin_attr_profile5_buttons,
+	NULL,
+};
+
+static const struct attribute_group koneplus_group = {
+	.attrs = koneplus_attrs,
+	.bin_attrs = koneplus_bin_attributes,
+};
+
+static const struct attribute_group *koneplus_groups[] = {
+	&koneplus_group,
+	NULL,
+};
+
+static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev,
+		struct koneplus_device *koneplus)
+{
+	int retval;
+
+	mutex_init(&koneplus->koneplus_lock);
+
+	retval = koneplus_get_actual_profile(usb_dev);
+	if (retval < 0)
+		return retval;
+	koneplus_profile_activated(koneplus, retval);
+
+	return 0;
+}
+
+static int koneplus_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 koneplus_device *koneplus;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+
+		koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL);
+		if (!koneplus) {
+			hid_err(hdev, "can't alloc device descriptor\n");
+			return -ENOMEM;
+		}
+		hid_set_drvdata(hdev, koneplus);
+
+		retval = koneplus_init_koneplus_device_struct(usb_dev, koneplus);
+		if (retval) {
+			hid_err(hdev, "couldn't init struct koneplus_device\n");
+			goto exit_free;
+		}
+
+		retval = roccat_connect(koneplus_class, hdev,
+				sizeof(struct koneplus_roccat_report));
+		if (retval < 0) {
+			hid_err(hdev, "couldn't init char dev\n");
+		} else {
+			koneplus->chrdev_minor = retval;
+			koneplus->roccat_claimed = 1;
+		}
+	} else {
+		hid_set_drvdata(hdev, NULL);
+	}
+
+	return 0;
+exit_free:
+	kfree(koneplus);
+	return retval;
+}
+
+static void koneplus_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct koneplus_device *koneplus;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+		koneplus = hid_get_drvdata(hdev);
+		if (koneplus->roccat_claimed)
+			roccat_disconnect(koneplus->chrdev_minor);
+		kfree(koneplus);
+	}
+}
+
+static int koneplus_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 = koneplus_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 koneplus_remove(struct hid_device *hdev)
+{
+	koneplus_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void koneplus_keep_values_up_to_date(struct koneplus_device *koneplus,
+		u8 const *data)
+{
+	struct koneplus_mouse_report_button const *button_report;
+
+	switch (data[0]) {
+	case KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON:
+		button_report = (struct koneplus_mouse_report_button const *)data;
+		switch (button_report->type) {
+		case KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE:
+			koneplus_profile_activated(koneplus, button_report->data1 - 1);
+			break;
+		}
+		break;
+	}
+}
+
+static void koneplus_report_to_chrdev(struct koneplus_device const *koneplus,
+		u8 const *data)
+{
+	struct koneplus_roccat_report roccat_report;
+	struct koneplus_mouse_report_button const *button_report;
+
+	if (data[0] != KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON)
+		return;
+
+	button_report = (struct koneplus_mouse_report_button const *)data;
+
+	if ((button_report->type == KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH ||
+			button_report->type == KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER) &&
+			button_report->data2 != KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS)
+		return;
+
+	roccat_report.type = button_report->type;
+	roccat_report.data1 = button_report->data1;
+	roccat_report.data2 = button_report->data2;
+	roccat_report.profile = koneplus->actual_profile + 1;
+	roccat_report_event(koneplus->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+}
+
+static int koneplus_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 koneplus_device *koneplus = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return 0;
+
+	if (koneplus == NULL)
+		return 0;
+
+	koneplus_keep_values_up_to_date(koneplus, data);
+
+	if (koneplus->roccat_claimed)
+		koneplus_report_to_chrdev(koneplus, data);
+
+	return 0;
+}
+
+static const struct hid_device_id koneplus_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEXTD) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, koneplus_devices);
+
+static struct hid_driver koneplus_driver = {
+		.name = "koneplus",
+		.id_table = koneplus_devices,
+		.probe = koneplus_probe,
+		.remove = koneplus_remove,
+		.raw_event = koneplus_raw_event
+};
+
+static int __init koneplus_init(void)
+{
+	int retval;
+
+	/* class name has to be same as driver name */
+	koneplus_class = class_create(THIS_MODULE, "koneplus");
+	if (IS_ERR(koneplus_class))
+		return PTR_ERR(koneplus_class);
+	koneplus_class->dev_groups = koneplus_groups;
+
+	retval = hid_register_driver(&koneplus_driver);
+	if (retval)
+		class_destroy(koneplus_class);
+	return retval;
+}
+
+static void __exit koneplus_exit(void)
+{
+	hid_unregister_driver(&koneplus_driver);
+	class_destroy(koneplus_class);
+}
+
+module_init(koneplus_init);
+module_exit(koneplus_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Kone[+]/XTD driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-koneplus.h b/drivers/hid/hid-roccat-koneplus.h
new file mode 100644
index 0000000..af7f57e
--- /dev/null
+++ b/drivers/hid/hid-roccat-koneplus.h
@@ -0,0 +1,125 @@
+#ifndef __HID_ROCCAT_KONEPLUS_H
+#define __HID_ROCCAT_KONEPLUS_H
+
+/*
+ * Copyright (c) 2010 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 {
+	KONEPLUS_SIZE_ACTUAL_PROFILE = 0x03,
+	KONEPLUS_SIZE_CONTROL = 0x03,
+	KONEPLUS_SIZE_FIRMWARE_WRITE = 0x0402,
+	KONEPLUS_SIZE_INFO = 0x06,
+	KONEPLUS_SIZE_MACRO = 0x0822,
+	KONEPLUS_SIZE_PROFILE_SETTINGS = 0x2b,
+	KONEPLUS_SIZE_PROFILE_BUTTONS = 0x4d,
+	KONEPLUS_SIZE_SENSOR = 0x06,
+	KONEPLUS_SIZE_TALK = 0x10,
+	KONEPLUS_SIZE_TCU = 0x04,
+	KONEPLUS_SIZE_TCU_IMAGE = 0x0404,
+};
+
+enum koneplus_control_requests {
+	KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x80,
+	KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x90,
+};
+
+struct koneplus_actual_profile {
+	uint8_t command; /* KONEPLUS_COMMAND_ACTUAL_PROFILE */
+	uint8_t size; /* always 3 */
+	uint8_t actual_profile; /* Range 0-4! */
+} __attribute__ ((__packed__));
+
+struct koneplus_info {
+	uint8_t command; /* KONEPLUS_COMMAND_INFO */
+	uint8_t size; /* always 6 */
+	uint8_t firmware_version;
+	uint8_t unknown[3];
+} __attribute__ ((__packed__));
+
+enum koneplus_commands {
+	KONEPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
+	KONEPLUS_COMMAND_CONTROL = 0x4,
+	KONEPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
+	KONEPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
+	KONEPLUS_COMMAND_MACRO = 0x8,
+	KONEPLUS_COMMAND_INFO = 0x9,
+	KONEPLUS_COMMAND_TCU = 0xc,
+	KONEPLUS_COMMAND_TCU_IMAGE = 0xc,
+	KONEPLUS_COMMAND_E = 0xe,
+	KONEPLUS_COMMAND_SENSOR = 0xf,
+	KONEPLUS_COMMAND_TALK = 0x10,
+	KONEPLUS_COMMAND_FIRMWARE_WRITE = 0x1b,
+	KONEPLUS_COMMAND_FIRMWARE_WRITE_CONTROL = 0x1c,
+};
+
+enum koneplus_mouse_report_numbers {
+	KONEPLUS_MOUSE_REPORT_NUMBER_HID = 1,
+	KONEPLUS_MOUSE_REPORT_NUMBER_AUDIO = 2,
+	KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON = 3,
+};
+
+struct koneplus_mouse_report_button {
+	uint8_t report_number; /* always KONEPLUS_MOUSE_REPORT_NUMBER_BUTTON */
+	uint8_t zero1;
+	uint8_t type;
+	uint8_t data1;
+	uint8_t data2;
+	uint8_t zero2;
+	uint8_t unknown[2];
+} __attribute__ ((__packed__));
+
+enum koneplus_mouse_report_button_types {
+	/* data1 = new profile range 1-5 */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE = 0x20,
+
+	/* data1 = button number range 1-24; data2 = action */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
+
+	/* data1 = button number range 1-24; data2 = action */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
+
+	/* data1 = setting number range 1-5 */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
+
+	/* data1 and data2 = range 0x1-0xb */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
+
+	/* data1 = 22 = next track...
+	 * data2 = action
+	 */
+	KONEPLUS_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
+	KONEPLUS_MOUSE_REPORT_TALK = 0xff,
+};
+
+enum koneplus_mouse_report_button_action {
+	KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS = 0,
+	KONEPLUS_MOUSE_REPORT_BUTTON_ACTION_RELEASE = 1,
+};
+
+struct koneplus_roccat_report {
+	uint8_t type;
+	uint8_t data1;
+	uint8_t data2;
+	uint8_t profile;
+} __attribute__ ((__packed__));
+
+struct koneplus_device {
+	int actual_profile;
+
+	int roccat_claimed;
+	int chrdev_minor;
+
+	struct mutex koneplus_lock;
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-konepure.c b/drivers/hid/hid-roccat-konepure.c
new file mode 100644
index 0000000..07de2f9
--- /dev/null
+++ b/drivers/hid/hid-roccat-konepure.c
@@ -0,0 +1,232 @@
+/*
+ * Roccat KonePure 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 KonePure is a smaller version of KoneXTD with less buttons and lights.
+ */
+
+#include <linux/types.h>
+#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"
+
+enum {
+	KONEPURE_MOUSE_REPORT_NUMBER_BUTTON = 3,
+};
+
+struct konepure_mouse_report_button {
+	uint8_t report_number; /* always KONEPURE_MOUSE_REPORT_NUMBER_BUTTON */
+	uint8_t zero;
+	uint8_t type;
+	uint8_t data1;
+	uint8_t data2;
+	uint8_t zero2;
+	uint8_t unknown[2];
+} __packed;
+
+static struct class *konepure_class;
+
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(actual_profile, 0x05, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_settings, 0x06, 0x1f);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile_buttons, 0x07, 0x3b);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(macro, 0x08, 0x0822);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x09, 0x06);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(tcu, 0x0c, 0x04);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_R(tcu_image, 0x0c, 0x0404);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0x0f, 0x06);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(talk, 0x10, 0x10);
+
+static struct bin_attribute *konepure_bin_attrs[] = {
+	&bin_attr_actual_profile,
+	&bin_attr_control,
+	&bin_attr_info,
+	&bin_attr_talk,
+	&bin_attr_macro,
+	&bin_attr_sensor,
+	&bin_attr_tcu,
+	&bin_attr_tcu_image,
+	&bin_attr_profile_settings,
+	&bin_attr_profile_buttons,
+	NULL,
+};
+
+static const struct attribute_group konepure_group = {
+	.bin_attrs = konepure_bin_attrs,
+};
+
+static const struct attribute_group *konepure_groups[] = {
+	&konepure_group,
+	NULL,
+};
+
+static int konepure_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 roccat_common2_device *konepure;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE) {
+		hid_set_drvdata(hdev, NULL);
+		return 0;
+	}
+
+	konepure = kzalloc(sizeof(*konepure), GFP_KERNEL);
+	if (!konepure) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, konepure);
+
+	retval = roccat_common2_device_init_struct(usb_dev, konepure);
+	if (retval) {
+		hid_err(hdev, "couldn't init KonePure device\n");
+		goto exit_free;
+	}
+
+	retval = roccat_connect(konepure_class, hdev,
+			sizeof(struct konepure_mouse_report_button));
+	if (retval < 0) {
+		hid_err(hdev, "couldn't init char dev\n");
+	} else {
+		konepure->chrdev_minor = retval;
+		konepure->roccat_claimed = 1;
+	}
+
+	return 0;
+exit_free:
+	kfree(konepure);
+	return retval;
+}
+
+static void konepure_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct roccat_common2_device *konepure;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return;
+
+	konepure = hid_get_drvdata(hdev);
+	if (konepure->roccat_claimed)
+		roccat_disconnect(konepure->chrdev_minor);
+	kfree(konepure);
+}
+
+static int konepure_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 = konepure_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 konepure_remove(struct hid_device *hdev)
+{
+	konepure_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static int konepure_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 roccat_common2_device *konepure = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return 0;
+
+	if (data[0] != KONEPURE_MOUSE_REPORT_NUMBER_BUTTON)
+		return 0;
+
+	if (konepure != NULL && konepure->roccat_claimed)
+		roccat_report_event(konepure->chrdev_minor, data);
+
+	return 0;
+}
+
+static const struct hid_device_id konepure_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPURE_OPTICAL) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, konepure_devices);
+
+static struct hid_driver konepure_driver = {
+		.name = "konepure",
+		.id_table = konepure_devices,
+		.probe = konepure_probe,
+		.remove = konepure_remove,
+		.raw_event = konepure_raw_event
+};
+
+static int __init konepure_init(void)
+{
+	int retval;
+
+	konepure_class = class_create(THIS_MODULE, "konepure");
+	if (IS_ERR(konepure_class))
+		return PTR_ERR(konepure_class);
+	konepure_class->dev_groups = konepure_groups;
+
+	retval = hid_register_driver(&konepure_driver);
+	if (retval)
+		class_destroy(konepure_class);
+	return retval;
+}
+
+static void __exit konepure_exit(void)
+{
+	hid_unregister_driver(&konepure_driver);
+	class_destroy(konepure_class);
+}
+
+module_init(konepure_init);
+module_exit(konepure_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat KonePure/Optical driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c
new file mode 100644
index 0000000..317c9c2
--- /dev/null
+++ b/drivers/hid/hid-roccat-kovaplus.c
@@ -0,0 +1,663 @@
+/*
+ * Roccat Kova[+] driver for Linux
+ *
+ * Copyright (c) 2011 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 Kova[+] is a bigger version of the Pyra with two more side buttons.
+ */
+
+#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-kovaplus.h"
+
+static uint profile_numbers[5] = {0, 1, 2, 3, 4};
+
+static struct class *kovaplus_class;
+
+static uint kovaplus_convert_event_cpi(uint value)
+{
+	return (value == 7 ? 4 : (value == 4 ? 3 : value));
+}
+
+static void kovaplus_profile_activated(struct kovaplus_device *kovaplus,
+		uint new_profile_index)
+{
+	if (new_profile_index >= ARRAY_SIZE(kovaplus->profile_settings))
+		return;
+	kovaplus->actual_profile = new_profile_index;
+	kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level;
+	kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x;
+	kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y;
+}
+
+static int kovaplus_send_control(struct usb_device *usb_dev, uint value,
+		enum kovaplus_control_requests request)
+{
+	int retval;
+	struct roccat_common2_control control;
+
+	if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS ||
+			request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) &&
+			value > 4)
+		return -EINVAL;
+
+	control.command = ROCCAT_COMMON_COMMAND_CONTROL;
+	control.value = value;
+	control.request = request;
+
+	retval = roccat_common2_send(usb_dev, ROCCAT_COMMON_COMMAND_CONTROL,
+			&control, sizeof(struct roccat_common2_control));
+
+	return retval;
+}
+
+static int kovaplus_select_profile(struct usb_device *usb_dev, uint number,
+		enum kovaplus_control_requests request)
+{
+	return kovaplus_send_control(usb_dev, number, request);
+}
+
+static int kovaplus_get_profile_settings(struct usb_device *usb_dev,
+		struct kovaplus_profile_settings *buf, uint number)
+{
+	int retval;
+
+	retval = kovaplus_select_profile(usb_dev, number,
+			KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
+	if (retval)
+		return retval;
+
+	return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_SETTINGS,
+			buf, KOVAPLUS_SIZE_PROFILE_SETTINGS);
+}
+
+static int kovaplus_get_profile_buttons(struct usb_device *usb_dev,
+		struct kovaplus_profile_buttons *buf, int number)
+{
+	int retval;
+
+	retval = kovaplus_select_profile(usb_dev, number,
+			KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
+	if (retval)
+		return retval;
+
+	return roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_PROFILE_BUTTONS,
+			buf, KOVAPLUS_SIZE_PROFILE_BUTTONS);
+}
+
+/* retval is 0-4 on success, < 0 on error */
+static int kovaplus_get_actual_profile(struct usb_device *usb_dev)
+{
+	struct kovaplus_actual_profile buf;
+	int retval;
+
+	retval = roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_ACTUAL_PROFILE,
+			&buf, sizeof(struct kovaplus_actual_profile));
+
+	return retval ? retval : buf.actual_profile;
+}
+
+static int kovaplus_set_actual_profile(struct usb_device *usb_dev,
+		int new_profile)
+{
+	struct kovaplus_actual_profile buf;
+
+	buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE;
+	buf.size = sizeof(struct kovaplus_actual_profile);
+	buf.actual_profile = new_profile;
+
+	return roccat_common2_send_with_status(usb_dev,
+			KOVAPLUS_COMMAND_ACTUAL_PROFILE,
+			&buf, sizeof(struct kovaplus_actual_profile));
+}
+
+static ssize_t kovaplus_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 = kobj_to_dev(kobj)->parent->parent;
+	struct kovaplus_device *kovaplus = 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(&kovaplus->kovaplus_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&kovaplus->kovaplus_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+static ssize_t kovaplus_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 = kobj_to_dev(kobj)->parent->parent;
+	struct kovaplus_device *kovaplus = 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(&kovaplus->kovaplus_lock);
+	retval = roccat_common2_send_with_status(usb_dev, command,
+			buf, real_size);
+	mutex_unlock(&kovaplus->kovaplus_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+#define KOVAPLUS_SYSFS_W(thingy, THINGY) \
+static ssize_t kovaplus_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return kovaplus_sysfs_write(fp, kobj, buf, off, count, \
+			KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
+}
+
+#define KOVAPLUS_SYSFS_R(thingy, THINGY) \
+static ssize_t kovaplus_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return kovaplus_sysfs_read(fp, kobj, buf, off, count, \
+			KOVAPLUS_SIZE_ ## THINGY, KOVAPLUS_COMMAND_ ## THINGY); \
+}
+
+#define KOVAPLUS_SYSFS_RW(thingy, THINGY) \
+KOVAPLUS_SYSFS_W(thingy, THINGY) \
+KOVAPLUS_SYSFS_R(thingy, THINGY)
+
+#define KOVAPLUS_BIN_ATTRIBUTE_RW(thingy, THINGY) \
+KOVAPLUS_SYSFS_RW(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = KOVAPLUS_SIZE_ ## THINGY, \
+	.read = kovaplus_sysfs_read_ ## thingy, \
+	.write = kovaplus_sysfs_write_ ## thingy \
+}
+
+#define KOVAPLUS_BIN_ATTRIBUTE_W(thingy, THINGY) \
+KOVAPLUS_SYSFS_W(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = KOVAPLUS_SIZE_ ## THINGY, \
+	.write = kovaplus_sysfs_write_ ## thingy \
+}
+KOVAPLUS_BIN_ATTRIBUTE_W(control, CONTROL);
+KOVAPLUS_BIN_ATTRIBUTE_RW(info, INFO);
+KOVAPLUS_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
+KOVAPLUS_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
+
+static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private),
+			KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS);
+	if (retval)
+		return retval;
+
+	return kovaplus_sysfs_read(fp, kobj, buf, off, count,
+			KOVAPLUS_SIZE_PROFILE_SETTINGS,
+			KOVAPLUS_COMMAND_PROFILE_SETTINGS);
+}
+
+static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = kovaplus_select_profile(usb_dev, *(uint *)(attr->private),
+			KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS);
+	if (retval)
+		return retval;
+
+	return kovaplus_sysfs_read(fp, kobj, buf, off, count,
+			KOVAPLUS_SIZE_PROFILE_BUTTONS,
+			KOVAPLUS_COMMAND_PROFILE_BUTTONS);
+}
+
+#define PROFILE_ATTR(number)						\
+static struct bin_attribute bin_attr_profile##number##_settings = {	\
+	.attr = { .name = "profile" #number "_settings", .mode = 0440 },	\
+	.size = KOVAPLUS_SIZE_PROFILE_SETTINGS,				\
+	.read = kovaplus_sysfs_read_profilex_settings,			\
+	.private = &profile_numbers[number-1],				\
+};									\
+static struct bin_attribute bin_attr_profile##number##_buttons = {	\
+	.attr = { .name = "profile" #number "_buttons", .mode = 0440 },	\
+	.size = KOVAPLUS_SIZE_PROFILE_BUTTONS,				\
+	.read = kovaplus_sysfs_read_profilex_buttons,			\
+	.private = &profile_numbers[number-1],				\
+};
+PROFILE_ATTR(1);
+PROFILE_ATTR(2);
+PROFILE_ATTR(3);
+PROFILE_ATTR(4);
+PROFILE_ATTR(5);
+
+static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kovaplus_device *kovaplus =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);
+}
+
+static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
+		struct device_attribute *attr, char const *buf, size_t size)
+{
+	struct kovaplus_device *kovaplus;
+	struct usb_device *usb_dev;
+	unsigned long profile;
+	int retval;
+	struct kovaplus_roccat_report roccat_report;
+
+	dev = dev->parent->parent;
+	kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	retval = kstrtoul(buf, 10, &profile);
+	if (retval)
+		return retval;
+
+	if (profile >= 5)
+		return -EINVAL;
+
+	mutex_lock(&kovaplus->kovaplus_lock);
+	retval = kovaplus_set_actual_profile(usb_dev, profile);
+	if (retval) {
+		mutex_unlock(&kovaplus->kovaplus_lock);
+		return retval;
+	}
+
+	kovaplus_profile_activated(kovaplus, profile);
+
+	roccat_report.type = KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1;
+	roccat_report.profile = profile + 1;
+	roccat_report.button = 0;
+	roccat_report.data1 = profile + 1;
+	roccat_report.data2 = 0;
+	roccat_report_event(kovaplus->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+
+	mutex_unlock(&kovaplus->kovaplus_lock);
+
+	return size;
+}
+static DEVICE_ATTR(actual_profile, 0660,
+		   kovaplus_sysfs_show_actual_profile,
+		   kovaplus_sysfs_set_actual_profile);
+
+static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kovaplus_device *kovaplus =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);
+}
+static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);
+
+static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kovaplus_device *kovaplus =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);
+}
+static DEVICE_ATTR(actual_sensitivity_x, 0440,
+		   kovaplus_sysfs_show_actual_sensitivity_x, NULL);
+
+static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kovaplus_device *kovaplus =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);
+}
+static DEVICE_ATTR(actual_sensitivity_y, 0440,
+		   kovaplus_sysfs_show_actual_sensitivity_y, NULL);
+
+static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct kovaplus_device *kovaplus;
+	struct usb_device *usb_dev;
+	struct kovaplus_info info;
+
+	dev = dev->parent->parent;
+	kovaplus = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	mutex_lock(&kovaplus->kovaplus_lock);
+	roccat_common2_receive(usb_dev, KOVAPLUS_COMMAND_INFO,
+			&info, KOVAPLUS_SIZE_INFO);
+	mutex_unlock(&kovaplus->kovaplus_lock);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+}
+static DEVICE_ATTR(firmware_version, 0440,
+		   kovaplus_sysfs_show_firmware_version, NULL);
+
+static struct attribute *kovaplus_attrs[] = {
+	&dev_attr_actual_cpi.attr,
+	&dev_attr_firmware_version.attr,
+	&dev_attr_actual_profile.attr,
+	&dev_attr_actual_sensitivity_x.attr,
+	&dev_attr_actual_sensitivity_y.attr,
+	NULL,
+};
+
+static struct bin_attribute *kovaplus_bin_attributes[] = {
+	&bin_attr_control,
+	&bin_attr_info,
+	&bin_attr_profile_settings,
+	&bin_attr_profile_buttons,
+	&bin_attr_profile1_settings,
+	&bin_attr_profile2_settings,
+	&bin_attr_profile3_settings,
+	&bin_attr_profile4_settings,
+	&bin_attr_profile5_settings,
+	&bin_attr_profile1_buttons,
+	&bin_attr_profile2_buttons,
+	&bin_attr_profile3_buttons,
+	&bin_attr_profile4_buttons,
+	&bin_attr_profile5_buttons,
+	NULL,
+};
+
+static const struct attribute_group kovaplus_group = {
+	.attrs = kovaplus_attrs,
+	.bin_attrs = kovaplus_bin_attributes,
+};
+
+static const struct attribute_group *kovaplus_groups[] = {
+	&kovaplus_group,
+	NULL,
+};
+
+static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev,
+		struct kovaplus_device *kovaplus)
+{
+	int retval, i;
+	static uint wait = 70; /* device will freeze with just 60 */
+
+	mutex_init(&kovaplus->kovaplus_lock);
+
+	for (i = 0; i < 5; ++i) {
+		msleep(wait);
+		retval = kovaplus_get_profile_settings(usb_dev,
+				&kovaplus->profile_settings[i], i);
+		if (retval)
+			return retval;
+
+		msleep(wait);
+		retval = kovaplus_get_profile_buttons(usb_dev,
+				&kovaplus->profile_buttons[i], i);
+		if (retval)
+			return retval;
+	}
+
+	msleep(wait);
+	retval = kovaplus_get_actual_profile(usb_dev);
+	if (retval < 0)
+		return retval;
+	kovaplus_profile_activated(kovaplus, retval);
+
+	return 0;
+}
+
+static int kovaplus_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 kovaplus_device *kovaplus;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+
+		kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL);
+		if (!kovaplus) {
+			hid_err(hdev, "can't alloc device descriptor\n");
+			return -ENOMEM;
+		}
+		hid_set_drvdata(hdev, kovaplus);
+
+		retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus);
+		if (retval) {
+			hid_err(hdev, "couldn't init struct kovaplus_device\n");
+			goto exit_free;
+		}
+
+		retval = roccat_connect(kovaplus_class, hdev,
+				sizeof(struct kovaplus_roccat_report));
+		if (retval < 0) {
+			hid_err(hdev, "couldn't init char dev\n");
+		} else {
+			kovaplus->chrdev_minor = retval;
+			kovaplus->roccat_claimed = 1;
+		}
+
+	} else {
+		hid_set_drvdata(hdev, NULL);
+	}
+
+	return 0;
+exit_free:
+	kfree(kovaplus);
+	return retval;
+}
+
+static void kovaplus_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct kovaplus_device *kovaplus;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+		kovaplus = hid_get_drvdata(hdev);
+		if (kovaplus->roccat_claimed)
+			roccat_disconnect(kovaplus->chrdev_minor);
+		kfree(kovaplus);
+	}
+}
+
+static int kovaplus_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 = kovaplus_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 kovaplus_remove(struct hid_device *hdev)
+{
+	kovaplus_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus,
+		u8 const *data)
+{
+	struct kovaplus_mouse_report_button const *button_report;
+
+	if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)
+		return;
+
+	button_report = (struct kovaplus_mouse_report_button const *)data;
+
+	switch (button_report->type) {
+	case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1:
+		kovaplus_profile_activated(kovaplus, button_report->data1 - 1);
+		break;
+	case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI:
+		kovaplus->actual_cpi = kovaplus_convert_event_cpi(button_report->data1);
+		break;
+	case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY:
+		kovaplus->actual_x_sensitivity = button_report->data1;
+		kovaplus->actual_y_sensitivity = button_report->data2;
+		break;
+	default:
+		break;
+	}
+}
+
+static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus,
+		u8 const *data)
+{
+	struct kovaplus_roccat_report roccat_report;
+	struct kovaplus_mouse_report_button const *button_report;
+
+	if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON)
+		return;
+
+	button_report = (struct kovaplus_mouse_report_button const *)data;
+
+	if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2)
+		return;
+
+	roccat_report.type = button_report->type;
+	roccat_report.profile = kovaplus->actual_profile + 1;
+
+	if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO ||
+			roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT ||
+			roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH ||
+			roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER)
+		roccat_report.button = button_report->data1;
+	else
+		roccat_report.button = 0;
+
+	if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI)
+		roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1);
+	else
+		roccat_report.data1 = button_report->data1;
+
+	roccat_report.data2 = button_report->data2;
+
+	roccat_report_event(kovaplus->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+}
+
+static int kovaplus_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 kovaplus_device *kovaplus = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return 0;
+
+	if (kovaplus == NULL)
+		return 0;
+
+	kovaplus_keep_values_up_to_date(kovaplus, data);
+
+	if (kovaplus->roccat_claimed)
+		kovaplus_report_to_chrdev(kovaplus, data);
+
+	return 0;
+}
+
+static const struct hid_device_id kovaplus_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, kovaplus_devices);
+
+static struct hid_driver kovaplus_driver = {
+		.name = "kovaplus",
+		.id_table = kovaplus_devices,
+		.probe = kovaplus_probe,
+		.remove = kovaplus_remove,
+		.raw_event = kovaplus_raw_event
+};
+
+static int __init kovaplus_init(void)
+{
+	int retval;
+
+	kovaplus_class = class_create(THIS_MODULE, "kovaplus");
+	if (IS_ERR(kovaplus_class))
+		return PTR_ERR(kovaplus_class);
+	kovaplus_class->dev_groups = kovaplus_groups;
+
+	retval = hid_register_driver(&kovaplus_driver);
+	if (retval)
+		class_destroy(kovaplus_class);
+	return retval;
+}
+
+static void __exit kovaplus_exit(void)
+{
+	hid_unregister_driver(&kovaplus_driver);
+	class_destroy(kovaplus_class);
+}
+
+module_init(kovaplus_init);
+module_exit(kovaplus_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Kova[+] driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-kovaplus.h b/drivers/hid/hid-roccat-kovaplus.h
new file mode 100644
index 0000000..fbb7a16
--- /dev/null
+++ b/drivers/hid/hid-roccat-kovaplus.h
@@ -0,0 +1,133 @@
+#ifndef __HID_ROCCAT_KOVAPLUS_H
+#define __HID_ROCCAT_KOVAPLUS_H
+
+/*
+ * Copyright (c) 2010 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 {
+	KOVAPLUS_SIZE_CONTROL = 0x03,
+	KOVAPLUS_SIZE_INFO = 0x06,
+	KOVAPLUS_SIZE_PROFILE_SETTINGS = 0x10,
+	KOVAPLUS_SIZE_PROFILE_BUTTONS = 0x17,
+};
+
+enum kovaplus_control_requests {
+	/* 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,
+};
+
+struct kovaplus_actual_profile {
+	uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */
+	uint8_t size; /* always 3 */
+	uint8_t actual_profile; /* Range 0-4! */
+} __packed;
+
+struct kovaplus_profile_settings {
+	uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_SETTINGS */
+	uint8_t size; /* 16 */
+	uint8_t profile_index; /* range 0-4 */
+	uint8_t unknown1;
+	uint8_t sensitivity_x; /* range 1-10 */
+	uint8_t sensitivity_y; /* range 1-10 */
+	uint8_t cpi_levels_enabled;
+	uint8_t cpi_startup_level; /* range 1-4 */
+	uint8_t data[8];
+} __packed;
+
+struct kovaplus_profile_buttons {
+	uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_BUTTONS */
+	uint8_t size; /* 23 */
+	uint8_t profile_index; /* range 0-4 */
+	uint8_t data[20];
+} __packed;
+
+struct kovaplus_info {
+	uint8_t command; /* KOVAPLUS_COMMAND_INFO */
+	uint8_t size; /* 6 */
+	uint8_t firmware_version;
+	uint8_t unknown[3];
+} __packed;
+
+enum kovaplus_commands {
+	KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5,
+	KOVAPLUS_COMMAND_CONTROL = 0x4,
+	KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6,
+	KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7,
+	KOVAPLUS_COMMAND_INFO = 0x9,
+	KOVAPLUS_COMMAND_A = 0xa,
+};
+
+enum kovaplus_mouse_report_numbers {
+	KOVAPLUS_MOUSE_REPORT_NUMBER_MOUSE = 1,
+	KOVAPLUS_MOUSE_REPORT_NUMBER_AUDIO = 2,
+	KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON = 3,
+	KOVAPLUS_MOUSE_REPORT_NUMBER_KBD = 4,
+};
+
+struct kovaplus_mouse_report_button {
+	uint8_t report_number; /* KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON */
+	uint8_t unknown1;
+	uint8_t type;
+	uint8_t data1;
+	uint8_t data2;
+} __packed;
+
+enum kovaplus_mouse_report_button_types {
+	/* data1 = profile_number range 1-5; no release event */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1 = 0x20,
+	/* data1 = profile_number range 1-5; no release event */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2 = 0x30,
+	/* data1 = button_number range 1-18; data2 = action */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO = 0x40,
+	/* data1 = button_number range 1-18; data2 = action */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT = 0x50,
+	/* data1 = button_number range 1-18; data2 = action */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
+	/* data1 = button_number range 1-18; data2 = action */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80,
+	/* data1 = 1 = 400, 2 = 800, 4 = 1600, 7 = 3200; no release event */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0,
+	/* data1 + data2 = sense range 1-10; no release event */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0,
+	/* data1 = type as in profile_buttons; data2 = action */
+	KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
+};
+
+enum kovaplus_mouse_report_button_actions {
+	KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS = 0,
+	KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_RELEASE = 1,
+};
+
+struct kovaplus_roccat_report {
+	uint8_t type;
+	uint8_t profile;
+	uint8_t button;
+	uint8_t data1;
+	uint8_t data2;
+} __packed;
+
+struct kovaplus_device {
+	int actual_profile;
+	int actual_cpi;
+	int actual_x_sensitivity;
+	int actual_y_sensitivity;
+	int roccat_claimed;
+	int chrdev_minor;
+	struct mutex kovaplus_lock;
+	struct kovaplus_profile_settings profile_settings[5];
+	struct kovaplus_profile_buttons profile_buttons[5];
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-lua.c b/drivers/hid/hid-roccat-lua.c
new file mode 100644
index 0000000..ac1a731
--- /dev/null
+++ b/drivers/hid/hid-roccat-lua.c
@@ -0,0 +1,215 @@
+/*
+ * Roccat Lua 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 Lua is a gamer mouse which cpi, button and light settings can be
+ * configured.
+ */
+
+#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-lua.h"
+
+static ssize_t lua_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 = kobj_to_dev(kobj);
+	struct lua_device *lua = 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(&lua->lua_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&lua->lua_lock);
+
+	return retval ? retval : real_size;
+}
+
+static ssize_t lua_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 = kobj_to_dev(kobj);
+	struct lua_device *lua = 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(&lua->lua_lock);
+	retval = roccat_common2_send(usb_dev, command, buf, real_size);
+	mutex_unlock(&lua->lua_lock);
+
+	return retval ? retval : real_size;
+}
+
+#define LUA_SYSFS_W(thingy, THINGY) \
+static ssize_t lua_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, \
+		char *buf, loff_t off, size_t count) \
+{ \
+	return lua_sysfs_write(fp, kobj, buf, off, count, \
+			LUA_SIZE_ ## THINGY, LUA_COMMAND_ ## THINGY); \
+}
+
+#define LUA_SYSFS_R(thingy, THINGY) \
+static ssize_t lua_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, \
+		char *buf, loff_t off, size_t count) \
+{ \
+	return lua_sysfs_read(fp, kobj, buf, off, count, \
+			LUA_SIZE_ ## THINGY, LUA_COMMAND_ ## THINGY); \
+}
+
+#define LUA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
+LUA_SYSFS_W(thingy, THINGY) \
+LUA_SYSFS_R(thingy, THINGY) \
+static struct bin_attribute lua_ ## thingy ## _attr = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = LUA_SIZE_ ## THINGY, \
+	.read = lua_sysfs_read_ ## thingy, \
+	.write = lua_sysfs_write_ ## thingy \
+};
+
+LUA_BIN_ATTRIBUTE_RW(control, CONTROL)
+
+static int lua_create_sysfs_attributes(struct usb_interface *intf)
+{
+	return sysfs_create_bin_file(&intf->dev.kobj, &lua_control_attr);
+}
+
+static void lua_remove_sysfs_attributes(struct usb_interface *intf)
+{
+	sysfs_remove_bin_file(&intf->dev.kobj, &lua_control_attr);
+}
+
+static int lua_init_lua_device_struct(struct usb_device *usb_dev,
+		struct lua_device *lua)
+{
+	mutex_init(&lua->lua_lock);
+
+	return 0;
+}
+
+static int lua_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 lua_device *lua;
+	int retval;
+
+	lua = kzalloc(sizeof(*lua), GFP_KERNEL);
+	if (!lua) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, lua);
+
+	retval = lua_init_lua_device_struct(usb_dev, lua);
+	if (retval) {
+		hid_err(hdev, "couldn't init struct lua_device\n");
+		goto exit;
+	}
+
+	retval = lua_create_sysfs_attributes(intf);
+	if (retval) {
+		hid_err(hdev, "cannot create sysfs files\n");
+		goto exit;
+	}
+
+	return 0;
+exit:
+	kfree(lua);
+	return retval;
+}
+
+static void lua_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct lua_device *lua;
+
+	lua_remove_sysfs_attributes(intf);
+
+	lua = hid_get_drvdata(hdev);
+	kfree(lua);
+}
+
+static int lua_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 = lua_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 lua_remove(struct hid_device *hdev)
+{
+	lua_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id lua_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_LUA) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, lua_devices);
+
+static struct hid_driver lua_driver = {
+		.name = "lua",
+		.id_table = lua_devices,
+		.probe = lua_probe,
+		.remove = lua_remove
+};
+module_hid_driver(lua_driver);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Lua driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-lua.h b/drivers/hid/hid-roccat-lua.h
new file mode 100644
index 0000000..547d77a
--- /dev/null
+++ b/drivers/hid/hid-roccat-lua.h
@@ -0,0 +1,29 @@
+#ifndef __HID_ROCCAT_LUA_H
+#define __HID_ROCCAT_LUA_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 {
+	LUA_SIZE_CONTROL = 8,
+};
+
+enum lua_commands {
+	LUA_COMMAND_CONTROL = 3,
+};
+
+struct lua_device {
+	struct mutex lua_lock;
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c
new file mode 100644
index 0000000..b30aa7b
--- /dev/null
+++ b/drivers/hid/hid-roccat-pyra.c
@@ -0,0 +1,610 @@
+/*
+ * Roccat Pyra driver for Linux
+ *
+ * Copyright (c) 2010 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 Pyra is a mobile gamer mouse which comes in wired and wireless
+ * variant. Wireless variant is not tested.
+ * Userland tools can be found at http://sourceforge.net/projects/roccat
+ */
+
+#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-pyra.h"
+
+static uint profile_numbers[5] = {0, 1, 2, 3, 4};
+
+/* pyra_class is used for creating sysfs attributes via roccat char device */
+static struct class *pyra_class;
+
+static void profile_activated(struct pyra_device *pyra,
+		unsigned int new_profile)
+{
+	if (new_profile >= ARRAY_SIZE(pyra->profile_settings))
+		return;
+	pyra->actual_profile = new_profile;
+	pyra->actual_cpi = pyra->profile_settings[pyra->actual_profile].y_cpi;
+}
+
+static int pyra_send_control(struct usb_device *usb_dev, int value,
+		enum pyra_control_requests request)
+{
+	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 = ROCCAT_COMMON_COMMAND_CONTROL;
+	control.value = value;
+	control.request = request;
+
+	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,
+		struct pyra_profile_settings *buf, int number)
+{
+	int retval;
+	retval = pyra_send_control(usb_dev, number,
+			PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
+	if (retval)
+		return retval;
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_PROFILE_SETTINGS,
+			buf, PYRA_SIZE_PROFILE_SETTINGS);
+}
+
+static int pyra_get_settings(struct usb_device *usb_dev,
+		struct pyra_settings *buf)
+{
+	return roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
+			buf, PYRA_SIZE_SETTINGS);
+}
+
+static int pyra_set_settings(struct usb_device *usb_dev,
+		struct pyra_settings const *settings)
+{
+	return roccat_common2_send_with_status(usb_dev,
+			PYRA_COMMAND_SETTINGS, settings,
+			PYRA_SIZE_SETTINGS);
+}
+
+static ssize_t pyra_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 = kobj_to_dev(kobj)->parent->parent;
+	struct pyra_device *pyra = 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(&pyra->pyra_lock);
+	retval = roccat_common2_receive(usb_dev, command, buf, real_size);
+	mutex_unlock(&pyra->pyra_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+static ssize_t pyra_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 = kobj_to_dev(kobj)->parent->parent;
+	struct pyra_device *pyra = 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(&pyra->pyra_lock);
+	retval = roccat_common2_send_with_status(usb_dev, command, (void *)buf, real_size);
+	mutex_unlock(&pyra->pyra_lock);
+
+	if (retval)
+		return retval;
+
+	return real_size;
+}
+
+#define PYRA_SYSFS_W(thingy, THINGY) \
+static ssize_t pyra_sysfs_write_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return pyra_sysfs_write(fp, kobj, buf, off, count, \
+			PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
+}
+
+#define PYRA_SYSFS_R(thingy, THINGY) \
+static ssize_t pyra_sysfs_read_ ## thingy(struct file *fp, \
+		struct kobject *kobj, struct bin_attribute *attr, char *buf, \
+		loff_t off, size_t count) \
+{ \
+	return pyra_sysfs_read(fp, kobj, buf, off, count, \
+			PYRA_SIZE_ ## THINGY, PYRA_COMMAND_ ## THINGY); \
+}
+
+#define PYRA_SYSFS_RW(thingy, THINGY) \
+PYRA_SYSFS_W(thingy, THINGY) \
+PYRA_SYSFS_R(thingy, THINGY)
+
+#define PYRA_BIN_ATTRIBUTE_RW(thingy, THINGY) \
+PYRA_SYSFS_RW(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0660 }, \
+	.size = PYRA_SIZE_ ## THINGY, \
+	.read = pyra_sysfs_read_ ## thingy, \
+	.write = pyra_sysfs_write_ ## thingy \
+}
+
+#define PYRA_BIN_ATTRIBUTE_R(thingy, THINGY) \
+PYRA_SYSFS_R(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0440 }, \
+	.size = PYRA_SIZE_ ## THINGY, \
+	.read = pyra_sysfs_read_ ## thingy, \
+}
+
+#define PYRA_BIN_ATTRIBUTE_W(thingy, THINGY) \
+PYRA_SYSFS_W(thingy, THINGY); \
+static struct bin_attribute bin_attr_##thingy = { \
+	.attr = { .name = #thingy, .mode = 0220 }, \
+	.size = PYRA_SIZE_ ## THINGY, \
+	.write = pyra_sysfs_write_ ## thingy \
+}
+
+PYRA_BIN_ATTRIBUTE_W(control, CONTROL);
+PYRA_BIN_ATTRIBUTE_RW(info, INFO);
+PYRA_BIN_ATTRIBUTE_RW(profile_settings, PROFILE_SETTINGS);
+PYRA_BIN_ATTRIBUTE_RW(profile_buttons, PROFILE_BUTTONS);
+
+static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = pyra_send_control(usb_dev, *(uint *)(attr->private),
+			PYRA_CONTROL_REQUEST_PROFILE_SETTINGS);
+	if (retval)
+		return retval;
+
+	return pyra_sysfs_read(fp, kobj, buf, off, count,
+			PYRA_SIZE_PROFILE_SETTINGS,
+			PYRA_COMMAND_PROFILE_SETTINGS);
+}
+
+static ssize_t pyra_sysfs_read_profilex_buttons(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	ssize_t retval;
+
+	retval = pyra_send_control(usb_dev, *(uint *)(attr->private),
+			PYRA_CONTROL_REQUEST_PROFILE_BUTTONS);
+	if (retval)
+		return retval;
+
+	return pyra_sysfs_read(fp, kobj, buf, off, count,
+			PYRA_SIZE_PROFILE_BUTTONS,
+			PYRA_COMMAND_PROFILE_BUTTONS);
+}
+
+#define PROFILE_ATTR(number)						\
+static struct bin_attribute bin_attr_profile##number##_settings = {	\
+	.attr = { .name = "profile" #number "_settings", .mode = 0440 },	\
+	.size = PYRA_SIZE_PROFILE_SETTINGS,				\
+	.read = pyra_sysfs_read_profilex_settings,			\
+	.private = &profile_numbers[number-1],				\
+};									\
+static struct bin_attribute bin_attr_profile##number##_buttons = {	\
+	.attr = { .name = "profile" #number "_buttons", .mode = 0440 },	\
+	.size = PYRA_SIZE_PROFILE_BUTTONS,				\
+	.read = pyra_sysfs_read_profilex_buttons,			\
+	.private = &profile_numbers[number-1],				\
+};
+PROFILE_ATTR(1);
+PROFILE_ATTR(2);
+PROFILE_ATTR(3);
+PROFILE_ATTR(4);
+PROFILE_ATTR(5);
+
+static ssize_t pyra_sysfs_write_settings(struct file *fp,
+		struct kobject *kobj, struct bin_attribute *attr, char *buf,
+		loff_t off, size_t count)
+{
+	struct device *dev = kobj_to_dev(kobj)->parent->parent;
+	struct pyra_device *pyra = hid_get_drvdata(dev_get_drvdata(dev));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	int retval = 0;
+	struct pyra_roccat_report roccat_report;
+	struct pyra_settings const *settings;
+
+	if (off != 0 || count != PYRA_SIZE_SETTINGS)
+		return -EINVAL;
+
+	settings = (struct pyra_settings const *)buf;
+	if (settings->startup_profile >= ARRAY_SIZE(pyra->profile_settings))
+		return -EINVAL;
+
+	mutex_lock(&pyra->pyra_lock);
+
+	retval = pyra_set_settings(usb_dev, settings);
+	if (retval) {
+		mutex_unlock(&pyra->pyra_lock);
+		return retval;
+	}
+
+	profile_activated(pyra, settings->startup_profile);
+
+	roccat_report.type = PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2;
+	roccat_report.value = settings->startup_profile + 1;
+	roccat_report.key = 0;
+	roccat_report_event(pyra->chrdev_minor,
+			(uint8_t const *)&roccat_report);
+
+	mutex_unlock(&pyra->pyra_lock);
+	return PYRA_SIZE_SETTINGS;
+}
+
+PYRA_SYSFS_R(settings, SETTINGS);
+static struct bin_attribute bin_attr_settings =
+	__BIN_ATTR(settings, (S_IWUSR | S_IRUGO),
+		   pyra_sysfs_read_settings, pyra_sysfs_write_settings,
+		   PYRA_SIZE_SETTINGS);
+
+static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct pyra_device *pyra =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi);
+}
+static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL);
+
+static ssize_t pyra_sysfs_show_actual_profile(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct pyra_device *pyra =
+			hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
+	struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev));
+	struct pyra_settings settings;
+
+	mutex_lock(&pyra->pyra_lock);
+	roccat_common2_receive(usb_dev, PYRA_COMMAND_SETTINGS,
+			&settings, PYRA_SIZE_SETTINGS);
+	mutex_unlock(&pyra->pyra_lock);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", settings.startup_profile);
+}
+static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
+static DEVICE_ATTR(startup_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
+
+static ssize_t pyra_sysfs_show_firmware_version(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct pyra_device *pyra;
+	struct usb_device *usb_dev;
+	struct pyra_info info;
+
+	dev = dev->parent->parent;
+	pyra = hid_get_drvdata(dev_get_drvdata(dev));
+	usb_dev = interface_to_usbdev(to_usb_interface(dev));
+
+	mutex_lock(&pyra->pyra_lock);
+	roccat_common2_receive(usb_dev, PYRA_COMMAND_INFO,
+			&info, PYRA_SIZE_INFO);
+	mutex_unlock(&pyra->pyra_lock);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
+}
+static DEVICE_ATTR(firmware_version, 0440, pyra_sysfs_show_firmware_version,
+		   NULL);
+
+static struct attribute *pyra_attrs[] = {
+	&dev_attr_actual_cpi.attr,
+	&dev_attr_actual_profile.attr,
+	&dev_attr_firmware_version.attr,
+	&dev_attr_startup_profile.attr,
+	NULL,
+};
+
+static struct bin_attribute *pyra_bin_attributes[] = {
+	&bin_attr_control,
+	&bin_attr_info,
+	&bin_attr_profile_settings,
+	&bin_attr_profile_buttons,
+	&bin_attr_settings,
+	&bin_attr_profile1_settings,
+	&bin_attr_profile2_settings,
+	&bin_attr_profile3_settings,
+	&bin_attr_profile4_settings,
+	&bin_attr_profile5_settings,
+	&bin_attr_profile1_buttons,
+	&bin_attr_profile2_buttons,
+	&bin_attr_profile3_buttons,
+	&bin_attr_profile4_buttons,
+	&bin_attr_profile5_buttons,
+	NULL,
+};
+
+static const struct attribute_group pyra_group = {
+	.attrs = pyra_attrs,
+	.bin_attrs = pyra_bin_attributes,
+};
+
+static const struct attribute_group *pyra_groups[] = {
+	&pyra_group,
+	NULL,
+};
+
+static int pyra_init_pyra_device_struct(struct usb_device *usb_dev,
+		struct pyra_device *pyra)
+{
+	struct pyra_settings settings;
+	int retval, i;
+
+	mutex_init(&pyra->pyra_lock);
+
+	retval = pyra_get_settings(usb_dev, &settings);
+	if (retval)
+		return retval;
+
+	for (i = 0; i < 5; ++i) {
+		retval = pyra_get_profile_settings(usb_dev,
+				&pyra->profile_settings[i], i);
+		if (retval)
+			return retval;
+	}
+
+	profile_activated(pyra, settings.startup_profile);
+
+	return 0;
+}
+
+static int pyra_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 pyra_device *pyra;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+
+		pyra = kzalloc(sizeof(*pyra), GFP_KERNEL);
+		if (!pyra) {
+			hid_err(hdev, "can't alloc device descriptor\n");
+			return -ENOMEM;
+		}
+		hid_set_drvdata(hdev, pyra);
+
+		retval = pyra_init_pyra_device_struct(usb_dev, pyra);
+		if (retval) {
+			hid_err(hdev, "couldn't init struct pyra_device\n");
+			goto exit_free;
+		}
+
+		retval = roccat_connect(pyra_class, hdev,
+				sizeof(struct pyra_roccat_report));
+		if (retval < 0) {
+			hid_err(hdev, "couldn't init char dev\n");
+		} else {
+			pyra->chrdev_minor = retval;
+			pyra->roccat_claimed = 1;
+		}
+	} else {
+		hid_set_drvdata(hdev, NULL);
+	}
+
+	return 0;
+exit_free:
+	kfree(pyra);
+	return retval;
+}
+
+static void pyra_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct pyra_device *pyra;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			== USB_INTERFACE_PROTOCOL_MOUSE) {
+		pyra = hid_get_drvdata(hdev);
+		if (pyra->roccat_claimed)
+			roccat_disconnect(pyra->chrdev_minor);
+		kfree(hid_get_drvdata(hdev));
+	}
+}
+
+static int pyra_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 = pyra_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 pyra_remove(struct hid_device *hdev)
+{
+	pyra_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static void pyra_keep_values_up_to_date(struct pyra_device *pyra,
+		u8 const *data)
+{
+	struct pyra_mouse_event_button const *button_event;
+
+	switch (data[0]) {
+	case PYRA_MOUSE_REPORT_NUMBER_BUTTON:
+		button_event = (struct pyra_mouse_event_button const *)data;
+		switch (button_event->type) {
+		case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2:
+			profile_activated(pyra, button_event->data1 - 1);
+			break;
+		case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI:
+			pyra->actual_cpi = button_event->data1;
+			break;
+		}
+		break;
+	}
+}
+
+static void pyra_report_to_chrdev(struct pyra_device const *pyra,
+		u8 const *data)
+{
+	struct pyra_roccat_report roccat_report;
+	struct pyra_mouse_event_button const *button_event;
+
+	if (data[0] != PYRA_MOUSE_REPORT_NUMBER_BUTTON)
+		return;
+
+	button_event = (struct pyra_mouse_event_button const *)data;
+
+	switch (button_event->type) {
+	case PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2:
+	case PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI:
+		roccat_report.type = button_event->type;
+		roccat_report.value = button_event->data1;
+		roccat_report.key = 0;
+		roccat_report_event(pyra->chrdev_minor,
+				(uint8_t const *)&roccat_report);
+		break;
+	case PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO:
+	case PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT:
+	case PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH:
+		if (button_event->data2 == PYRA_MOUSE_EVENT_BUTTON_PRESS) {
+			roccat_report.type = button_event->type;
+			roccat_report.key = button_event->data1;
+			/*
+			 * pyra reports profile numbers with range 1-5.
+			 * Keeping this behaviour.
+			 */
+			roccat_report.value = pyra->actual_profile + 1;
+			roccat_report_event(pyra->chrdev_minor,
+					(uint8_t const *)&roccat_report);
+		}
+		break;
+	}
+}
+
+static int pyra_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 pyra_device *pyra = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= USB_INTERFACE_PROTOCOL_MOUSE)
+		return 0;
+
+	if (pyra == NULL)
+		return 0;
+
+	pyra_keep_values_up_to_date(pyra, data);
+
+	if (pyra->roccat_claimed)
+		pyra_report_to_chrdev(pyra, data);
+
+	return 0;
+}
+
+static const struct hid_device_id pyra_devices[] = {
+	{ 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) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, pyra_devices);
+
+static struct hid_driver pyra_driver = {
+		.name = "pyra",
+		.id_table = pyra_devices,
+		.probe = pyra_probe,
+		.remove = pyra_remove,
+		.raw_event = pyra_raw_event
+};
+
+static int __init pyra_init(void)
+{
+	int retval;
+
+	/* class name has to be same as driver name */
+	pyra_class = class_create(THIS_MODULE, "pyra");
+	if (IS_ERR(pyra_class))
+		return PTR_ERR(pyra_class);
+	pyra_class->dev_groups = pyra_groups;
+
+	retval = hid_register_driver(&pyra_driver);
+	if (retval)
+		class_destroy(pyra_class);
+	return retval;
+}
+
+static void __exit pyra_exit(void)
+{
+	hid_unregister_driver(&pyra_driver);
+	class_destroy(pyra_class);
+}
+
+module_init(pyra_init);
+module_exit(pyra_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Pyra driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-pyra.h b/drivers/hid/hid-roccat-pyra.h
new file mode 100644
index 0000000..beedcf0
--- /dev/null
+++ b/drivers/hid/hid-roccat-pyra.h
@@ -0,0 +1,152 @@
+#ifndef __HID_ROCCAT_PYRA_H
+#define __HID_ROCCAT_PYRA_H
+
+/*
+ * Copyright (c) 2010 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 {
+	PYRA_SIZE_CONTROL = 0x03,
+	PYRA_SIZE_INFO = 0x06,
+	PYRA_SIZE_PROFILE_SETTINGS = 0x0d,
+	PYRA_SIZE_PROFILE_BUTTONS = 0x13,
+	PYRA_SIZE_SETTINGS = 0x03,
+};
+
+enum pyra_control_requests {
+	PYRA_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10,
+	PYRA_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20
+};
+
+struct pyra_settings {
+	uint8_t command; /* PYRA_COMMAND_SETTINGS */
+	uint8_t size; /* always 3 */
+	uint8_t startup_profile; /* Range 0-4! */
+} __attribute__ ((__packed__));
+
+struct pyra_profile_settings {
+	uint8_t command; /* PYRA_COMMAND_PROFILE_SETTINGS */
+	uint8_t size; /* always 0xd */
+	uint8_t number; /* Range 0-4 */
+	uint8_t xysync;
+	uint8_t x_sensitivity; /* 0x1-0xa */
+	uint8_t y_sensitivity;
+	uint8_t x_cpi; /* unused */
+	uint8_t y_cpi; /* this value is for x and y */
+	uint8_t lightswitch; /* 0 = off, 1 = on */
+	uint8_t light_effect;
+	uint8_t handedness;
+	uint16_t checksum; /* byte sum */
+} __attribute__ ((__packed__));
+
+struct pyra_info {
+	uint8_t command; /* PYRA_COMMAND_INFO */
+	uint8_t size; /* always 6 */
+	uint8_t firmware_version;
+	uint8_t unknown1; /* always 0 */
+	uint8_t unknown2; /* always 1 */
+	uint8_t unknown3; /* always 0 */
+} __attribute__ ((__packed__));
+
+enum pyra_commands {
+	PYRA_COMMAND_CONTROL = 0x4,
+	PYRA_COMMAND_SETTINGS = 0x5,
+	PYRA_COMMAND_PROFILE_SETTINGS = 0x6,
+	PYRA_COMMAND_PROFILE_BUTTONS = 0x7,
+	PYRA_COMMAND_INFO = 0x9,
+	PYRA_COMMAND_B = 0xb
+};
+
+enum pyra_mouse_report_numbers {
+	PYRA_MOUSE_REPORT_NUMBER_HID = 1,
+	PYRA_MOUSE_REPORT_NUMBER_AUDIO = 2,
+	PYRA_MOUSE_REPORT_NUMBER_BUTTON = 3,
+};
+
+struct pyra_mouse_event_button {
+	uint8_t report_number; /* always 3 */
+	uint8_t unknown; /* always 0 */
+	uint8_t type;
+	uint8_t data1;
+	uint8_t data2;
+} __attribute__ ((__packed__));
+
+struct pyra_mouse_event_audio {
+	uint8_t report_number; /* always 2 */
+	uint8_t type;
+	uint8_t unused; /* always 0 */
+} __attribute__ ((__packed__));
+
+/* hid audio controls */
+enum pyra_mouse_event_audio_types {
+	PYRA_MOUSE_EVENT_AUDIO_TYPE_MUTE = 0xe2,
+	PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_UP = 0xe9,
+	PYRA_MOUSE_EVENT_AUDIO_TYPE_VOLUME_DOWN = 0xea,
+};
+
+enum pyra_mouse_event_button_types {
+	/*
+	 * Mouse sends tilt events on report_number 1 and 3
+	 * Tilt events are sent repeatedly with 0.94s between first and second
+	 * event and 0.22s on subsequent
+	 */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_TILT = 0x10,
+
+	/*
+	 * These are sent sequentially
+	 * data1 contains new profile number in range 1-5
+	 */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_1 = 0x20,
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_PROFILE_2 = 0x30,
+
+	/*
+	 * data1 = button_number (rmp index)
+	 * data2 = pressed/released
+	 */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO = 0x40,
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT = 0x50,
+
+	/*
+	 * data1 = button_number (rmp index)
+	 */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_QUICKLAUNCH = 0x60,
+
+	/* data1 = new cpi */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_CPI = 0xb0,
+
+	/* data1 and data2 = new sensitivity */
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_SENSITIVITY = 0xc0,
+
+	PYRA_MOUSE_EVENT_BUTTON_TYPE_MULTIMEDIA = 0xf0,
+};
+
+enum {
+	PYRA_MOUSE_EVENT_BUTTON_PRESS = 0,
+	PYRA_MOUSE_EVENT_BUTTON_RELEASE = 1,
+};
+
+struct pyra_roccat_report {
+	uint8_t type;
+	uint8_t value;
+	uint8_t key;
+} __attribute__ ((__packed__));
+
+struct pyra_device {
+	int actual_profile;
+	int actual_cpi;
+	int roccat_claimed;
+	int chrdev_minor;
+	struct mutex pyra_lock;
+	struct pyra_profile_settings profile_settings[5];
+};
+
+#endif
diff --git a/drivers/hid/hid-roccat-ryos.c b/drivers/hid/hid-roccat-ryos.c
new file mode 100644
index 0000000..47cc8f3
--- /dev/null
+++ b/drivers/hid/hid-roccat-ryos.c
@@ -0,0 +1,241 @@
+/*
+ * Roccat Ryos driver for Linux
+ *
+ * Copyright (c) 2013 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>
+#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"
+
+enum {
+	RYOS_REPORT_NUMBER_SPECIAL = 3,
+	RYOS_USB_INTERFACE_PROTOCOL = 0,
+};
+
+struct ryos_report_special {
+	uint8_t number; /* RYOS_REPORT_NUMBER_SPECIAL */
+	uint8_t data[4];
+} __packed;
+
+static struct class *ryos_class;
+
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x04, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x05, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_primary, 0x06, 0x7d);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_function, 0x07, 0x5f);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_macro, 0x08, 0x23);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_thumbster, 0x09, 0x17);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_extra, 0x0a, 0x08);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(keys_easyzone, 0x0b, 0x126);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(key_mask, 0x0c, 0x06);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(light, 0x0d, 0x10);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x0e, 0x7d2);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_R(info, 0x0f, 0x08);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(reset, 0x11, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(light_control, 0x13, 0x08);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(talk, 0x16, 0x10);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(stored_lights, 0x17, 0x0566);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(custom_lights, 0x18, 0x14);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(light_macro, 0x19, 0x07d2);
+
+static struct bin_attribute *ryos_bin_attrs[] = {
+	&bin_attr_control,
+	&bin_attr_profile,
+	&bin_attr_keys_primary,
+	&bin_attr_keys_function,
+	&bin_attr_keys_macro,
+	&bin_attr_keys_thumbster,
+	&bin_attr_keys_extra,
+	&bin_attr_keys_easyzone,
+	&bin_attr_key_mask,
+	&bin_attr_light,
+	&bin_attr_macro,
+	&bin_attr_info,
+	&bin_attr_reset,
+	&bin_attr_light_control,
+	&bin_attr_talk,
+	&bin_attr_stored_lights,
+	&bin_attr_custom_lights,
+	&bin_attr_light_macro,
+	NULL,
+};
+
+static const struct attribute_group ryos_group = {
+	.bin_attrs = ryos_bin_attrs,
+};
+
+static const struct attribute_group *ryos_groups[] = {
+	&ryos_group,
+	NULL,
+};
+
+static int ryos_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 roccat_common2_device *ryos;
+	int retval;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= RYOS_USB_INTERFACE_PROTOCOL) {
+		hid_set_drvdata(hdev, NULL);
+		return 0;
+	}
+
+	ryos = kzalloc(sizeof(*ryos), GFP_KERNEL);
+	if (!ryos) {
+		hid_err(hdev, "can't alloc device descriptor\n");
+		return -ENOMEM;
+	}
+	hid_set_drvdata(hdev, ryos);
+
+	retval = roccat_common2_device_init_struct(usb_dev, ryos);
+	if (retval) {
+		hid_err(hdev, "couldn't init Ryos device\n");
+		goto exit_free;
+	}
+
+	retval = roccat_connect(ryos_class, hdev,
+			sizeof(struct ryos_report_special));
+	if (retval < 0) {
+		hid_err(hdev, "couldn't init char dev\n");
+	} else {
+		ryos->chrdev_minor = retval;
+		ryos->roccat_claimed = 1;
+	}
+
+	return 0;
+exit_free:
+	kfree(ryos);
+	return retval;
+}
+
+static void ryos_remove_specials(struct hid_device *hdev)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct roccat_common2_device *ryos;
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= RYOS_USB_INTERFACE_PROTOCOL)
+		return;
+
+	ryos = hid_get_drvdata(hdev);
+	if (ryos->roccat_claimed)
+		roccat_disconnect(ryos->chrdev_minor);
+	kfree(ryos);
+}
+
+static int ryos_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 = ryos_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 ryos_remove(struct hid_device *hdev)
+{
+	ryos_remove_specials(hdev);
+	hid_hw_stop(hdev);
+}
+
+static int ryos_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 roccat_common2_device *ryos = hid_get_drvdata(hdev);
+
+	if (intf->cur_altsetting->desc.bInterfaceProtocol
+			!= RYOS_USB_INTERFACE_PROTOCOL)
+		return 0;
+
+	if (data[0] != RYOS_REPORT_NUMBER_SPECIAL)
+		return 0;
+
+	if (ryos != NULL && ryos->roccat_claimed)
+		roccat_report_event(ryos->chrdev_minor, data);
+
+	return 0;
+}
+
+static const struct hid_device_id ryos_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_GLOW) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_RYOS_MK_PRO) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, ryos_devices);
+
+static struct hid_driver ryos_driver = {
+		.name = "ryos",
+		.id_table = ryos_devices,
+		.probe = ryos_probe,
+		.remove = ryos_remove,
+		.raw_event = ryos_raw_event
+};
+
+static int __init ryos_init(void)
+{
+	int retval;
+
+	ryos_class = class_create(THIS_MODULE, "ryos");
+	if (IS_ERR(ryos_class))
+		return PTR_ERR(ryos_class);
+	ryos_class->dev_groups = ryos_groups;
+
+	retval = hid_register_driver(&ryos_driver);
+	if (retval)
+		class_destroy(ryos_class);
+	return retval;
+}
+
+static void __exit ryos_exit(void)
+{
+	hid_unregister_driver(&ryos_driver);
+	class_destroy(ryos_class);
+}
+
+module_init(ryos_init);
+module_exit(ryos_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat Ryos MK/Glow/Pro driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-roccat-savu.c b/drivers/hid/hid-roccat-savu.c
new file mode 100644
index 0000000..6dbf6e0
--- /dev/null
+++ b/drivers/hid/hid-roccat-savu.c
@@ -0,0 +1,229 @@
+/*
+ * 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;
+
+ROCCAT_COMMON2_BIN_ATTRIBUTE_W(control, 0x4, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(profile, 0x5, 0x03);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(general, 0x6, 0x10);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(buttons, 0x7, 0x2f);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(macro, 0x8, 0x0823);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(info, 0x9, 0x08);
+ROCCAT_COMMON2_BIN_ATTRIBUTE_RW(sensor, 0xc, 0x04);
+
+static struct bin_attribute *savu_bin_attrs[] = {
+	&bin_attr_control,
+	&bin_attr_profile,
+	&bin_attr_general,
+	&bin_attr_buttons,
+	&bin_attr_macro,
+	&bin_attr_info,
+	&bin_attr_sensor,
+	NULL,
+};
+
+static const struct attribute_group savu_group = {
+	.bin_attrs = savu_bin_attrs,
+};
+
+static const struct attribute_group *savu_groups[] = {
+	&savu_group,
+	NULL,
+};
+
+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 roccat_common2_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 = roccat_common2_device_init_struct(usb_dev, savu);
+	if (retval) {
+		hid_err(hdev, "couldn't init 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 roccat_common2_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 roccat_common2_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 roccat_common2_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_groups = savu_groups;
+
+	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..d23217b
--- /dev/null
+++ b/drivers/hid/hid-roccat-savu.h
@@ -0,0 +1,55 @@
+#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>
+
+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;
+
+#endif
diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c
new file mode 100644
index 0000000..5be8de7
--- /dev/null
+++ b/drivers/hid/hid-roccat.c
@@ -0,0 +1,460 @@
+/*
+ * Roccat driver for Linux
+ *
+ * Copyright (c) 2010 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.
+ */
+
+/*
+ * Module roccat is a char device used to report special events of roccat
+ * hardware to userland. These events include requests for on-screen-display of
+ * profile or dpi settings or requests for execution of macro sequences that are
+ * not stored in device. The information in these events depends on hid device
+ * implementation and contains data that is not available in a single hid event
+ * or else hidraw could have been used.
+ * It is inspired by hidraw, but uses only one circular buffer for all readers.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/sched/signal.h>
+#include <linux/hid-roccat.h>
+#include <linux/module.h>
+
+#define ROCCAT_FIRST_MINOR 0
+#define ROCCAT_MAX_DEVICES 8
+
+/* should be a power of 2 for performance reason */
+#define ROCCAT_CBUF_SIZE 16
+
+struct roccat_report {
+	uint8_t *value;
+};
+
+struct roccat_device {
+	unsigned int minor;
+	int report_size;
+	int open;
+	int exist;
+	wait_queue_head_t wait;
+	struct device *dev;
+	struct hid_device *hid;
+	struct list_head readers;
+	/* protects modifications of readers list */
+	struct mutex readers_lock;
+
+	/*
+	 * circular_buffer has one writer and multiple readers with their own
+	 * read pointers
+	 */
+	struct roccat_report cbuf[ROCCAT_CBUF_SIZE];
+	int cbuf_end;
+	struct mutex cbuf_lock;
+};
+
+struct roccat_reader {
+	struct list_head node;
+	struct roccat_device *device;
+	int cbuf_start;
+};
+
+static int roccat_major;
+static struct cdev roccat_cdev;
+
+static struct roccat_device *devices[ROCCAT_MAX_DEVICES];
+/* protects modifications of devices array */
+static DEFINE_MUTEX(devices_lock);
+
+static ssize_t roccat_read(struct file *file, char __user *buffer,
+		size_t count, loff_t *ppos)
+{
+	struct roccat_reader *reader = file->private_data;
+	struct roccat_device *device = reader->device;
+	struct roccat_report *report;
+	ssize_t retval = 0, len;
+	DECLARE_WAITQUEUE(wait, current);
+
+	mutex_lock(&device->cbuf_lock);
+
+	/* no data? */
+	if (reader->cbuf_start == device->cbuf_end) {
+		add_wait_queue(&device->wait, &wait);
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		/* wait for data */
+		while (reader->cbuf_start == device->cbuf_end) {
+			if (file->f_flags & O_NONBLOCK) {
+				retval = -EAGAIN;
+				break;
+			}
+			if (signal_pending(current)) {
+				retval = -ERESTARTSYS;
+				break;
+			}
+			if (!device->exist) {
+				retval = -EIO;
+				break;
+			}
+
+			mutex_unlock(&device->cbuf_lock);
+			schedule();
+			mutex_lock(&device->cbuf_lock);
+			set_current_state(TASK_INTERRUPTIBLE);
+		}
+
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&device->wait, &wait);
+	}
+
+	/* here we either have data or a reason to return if retval is set */
+	if (retval)
+		goto exit_unlock;
+
+	report = &device->cbuf[reader->cbuf_start];
+	/*
+	 * If report is larger than requested amount of data, rest of report
+	 * is lost!
+	 */
+	len = device->report_size > count ? count : device->report_size;
+
+	if (copy_to_user(buffer, report->value, len)) {
+		retval = -EFAULT;
+		goto exit_unlock;
+	}
+	retval += len;
+	reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
+
+exit_unlock:
+	mutex_unlock(&device->cbuf_lock);
+	return retval;
+}
+
+static __poll_t roccat_poll(struct file *file, poll_table *wait)
+{
+	struct roccat_reader *reader = file->private_data;
+	poll_wait(file, &reader->device->wait, wait);
+	if (reader->cbuf_start != reader->device->cbuf_end)
+		return EPOLLIN | EPOLLRDNORM;
+	if (!reader->device->exist)
+		return EPOLLERR | EPOLLHUP;
+	return 0;
+}
+
+static int roccat_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+	struct roccat_reader *reader;
+	struct roccat_device *device;
+	int error = 0;
+
+	reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL);
+	if (!reader)
+		return -ENOMEM;
+
+	mutex_lock(&devices_lock);
+
+	device = devices[minor];
+
+	if (!device) {
+		pr_emerg("roccat device with minor %d doesn't exist\n", minor);
+		error = -ENODEV;
+		goto exit_err_devices;
+	}
+
+	mutex_lock(&device->readers_lock);
+
+	if (!device->open++) {
+		/* power on device on adding first reader */
+		error = hid_hw_power(device->hid, PM_HINT_FULLON);
+		if (error < 0) {
+			--device->open;
+			goto exit_err_readers;
+		}
+
+		error = hid_hw_open(device->hid);
+		if (error < 0) {
+			hid_hw_power(device->hid, PM_HINT_NORMAL);
+			--device->open;
+			goto exit_err_readers;
+		}
+	}
+
+	reader->device = device;
+	/* new reader doesn't get old events */
+	reader->cbuf_start = device->cbuf_end;
+
+	list_add_tail(&reader->node, &device->readers);
+	file->private_data = reader;
+
+exit_err_readers:
+	mutex_unlock(&device->readers_lock);
+exit_err_devices:
+	mutex_unlock(&devices_lock);
+	if (error)
+		kfree(reader);
+	return error;
+}
+
+static int roccat_release(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+	struct roccat_reader *reader = file->private_data;
+	struct roccat_device *device;
+
+	mutex_lock(&devices_lock);
+
+	device = devices[minor];
+	if (!device) {
+		mutex_unlock(&devices_lock);
+		pr_emerg("roccat device with minor %d doesn't exist\n", minor);
+		return -ENODEV;
+	}
+
+	mutex_lock(&device->readers_lock);
+	list_del(&reader->node);
+	mutex_unlock(&device->readers_lock);
+	kfree(reader);
+
+	if (!--device->open) {
+		/* removing last reader */
+		if (device->exist) {
+			hid_hw_power(device->hid, PM_HINT_NORMAL);
+			hid_hw_close(device->hid);
+		} else {
+			kfree(device);
+		}
+	}
+
+	mutex_unlock(&devices_lock);
+
+	return 0;
+}
+
+/*
+ * roccat_report_event() - output data to readers
+ * @minor: minor device number returned by roccat_connect()
+ * @data: pointer to data
+ *
+ * Return value is zero on success, a negative error code on failure.
+ *
+ * This is called from interrupt handler.
+ */
+int roccat_report_event(int minor, u8 const *data)
+{
+	struct roccat_device *device;
+	struct roccat_reader *reader;
+	struct roccat_report *report;
+	uint8_t *new_value;
+
+	device = devices[minor];
+
+	new_value = kmemdup(data, device->report_size, GFP_ATOMIC);
+	if (!new_value)
+		return -ENOMEM;
+
+	report = &device->cbuf[device->cbuf_end];
+
+	/* passing NULL is safe */
+	kfree(report->value);
+
+	report->value = new_value;
+	device->cbuf_end = (device->cbuf_end + 1) % ROCCAT_CBUF_SIZE;
+
+	list_for_each_entry(reader, &device->readers, node) {
+		/*
+		 * As we already inserted one element, the buffer can't be
+		 * empty. If start and end are equal, buffer is full and we
+		 * increase start, so that slow reader misses one event, but
+		 * gets the newer ones in the right order.
+		 */
+		if (reader->cbuf_start == device->cbuf_end)
+			reader->cbuf_start = (reader->cbuf_start + 1) % ROCCAT_CBUF_SIZE;
+	}
+
+	wake_up_interruptible(&device->wait);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(roccat_report_event);
+
+/*
+ * roccat_connect() - create a char device for special event output
+ * @class: the class thats used to create the device. Meant to hold device
+ * specific sysfs attributes.
+ * @hid: the hid device the char device should be connected to.
+ * @report_size: size of reports
+ *
+ * Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on
+ * success, a negative error code on failure.
+ */
+int roccat_connect(struct class *klass, struct hid_device *hid, int report_size)
+{
+	unsigned int minor;
+	struct roccat_device *device;
+	int temp;
+
+	device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL);
+	if (!device)
+		return -ENOMEM;
+
+	mutex_lock(&devices_lock);
+
+	for (minor = 0; minor < ROCCAT_MAX_DEVICES; ++minor) {
+		if (devices[minor])
+			continue;
+		break;
+	}
+
+	if (minor < ROCCAT_MAX_DEVICES) {
+		devices[minor] = device;
+	} else {
+		mutex_unlock(&devices_lock);
+		kfree(device);
+		return -EINVAL;
+	}
+
+	device->dev = device_create(klass, &hid->dev,
+			MKDEV(roccat_major, minor), NULL,
+			"%s%s%d", "roccat", hid->driver->name, minor);
+
+	if (IS_ERR(device->dev)) {
+		devices[minor] = NULL;
+		mutex_unlock(&devices_lock);
+		temp = PTR_ERR(device->dev);
+		kfree(device);
+		return temp;
+	}
+
+	mutex_unlock(&devices_lock);
+
+	init_waitqueue_head(&device->wait);
+	INIT_LIST_HEAD(&device->readers);
+	mutex_init(&device->readers_lock);
+	mutex_init(&device->cbuf_lock);
+	device->minor = minor;
+	device->hid = hid;
+	device->exist = 1;
+	device->cbuf_end = 0;
+	device->report_size = report_size;
+
+	return minor;
+}
+EXPORT_SYMBOL_GPL(roccat_connect);
+
+/* roccat_disconnect() - remove char device from hid device
+ * @minor: the minor device number returned by roccat_connect()
+ */
+void roccat_disconnect(int minor)
+{
+	struct roccat_device *device;
+
+	mutex_lock(&devices_lock);
+	device = devices[minor];
+	mutex_unlock(&devices_lock);
+
+	device->exist = 0; /* TODO exist maybe not needed */
+
+	device_destroy(device->dev->class, MKDEV(roccat_major, minor));
+
+	mutex_lock(&devices_lock);
+	devices[minor] = NULL;
+	mutex_unlock(&devices_lock);
+
+	if (device->open) {
+		hid_hw_close(device->hid);
+		wake_up_interruptible(&device->wait);
+	} else {
+		kfree(device);
+	}
+}
+EXPORT_SYMBOL_GPL(roccat_disconnect);
+
+static long roccat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct inode *inode = file_inode(file);
+	struct roccat_device *device;
+	unsigned int minor = iminor(inode);
+	long retval = 0;
+
+	mutex_lock(&devices_lock);
+
+	device = devices[minor];
+	if (!device) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	switch (cmd) {
+	case ROCCATIOCGREPSIZE:
+		if (put_user(device->report_size, (int __user *)arg))
+			retval = -EFAULT;
+		break;
+	default:
+		retval = -ENOTTY;
+	}
+out:
+	mutex_unlock(&devices_lock);
+	return retval;
+}
+
+static const struct file_operations roccat_ops = {
+	.owner = THIS_MODULE,
+	.read = roccat_read,
+	.poll = roccat_poll,
+	.open = roccat_open,
+	.release = roccat_release,
+	.llseek = noop_llseek,
+	.unlocked_ioctl = roccat_ioctl,
+};
+
+static int __init roccat_init(void)
+{
+	int retval;
+	dev_t dev_id;
+
+	retval = alloc_chrdev_region(&dev_id, ROCCAT_FIRST_MINOR,
+			ROCCAT_MAX_DEVICES, "roccat");
+	if (retval < 0) {
+		pr_warn("can't get major number\n");
+		goto error;
+	}
+
+	roccat_major = MAJOR(dev_id);
+
+	cdev_init(&roccat_cdev, &roccat_ops);
+	retval = cdev_add(&roccat_cdev, dev_id, ROCCAT_MAX_DEVICES);
+
+	if (retval < 0) {
+		pr_warn("cannot add cdev\n");
+		goto cleanup_alloc_chrdev_region;
+	}
+	return 0;
+
+
+ cleanup_alloc_chrdev_region:
+	unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
+ error:
+	return retval;
+}
+
+static void __exit roccat_exit(void)
+{
+	dev_t dev_id = MKDEV(roccat_major, 0);
+
+	cdev_del(&roccat_cdev);
+	unregister_chrdev_region(dev_id, ROCCAT_MAX_DEVICES);
+}
+
+module_init(roccat_init);
+module_exit(roccat_exit);
+
+MODULE_AUTHOR("Stefan Achatz");
+MODULE_DESCRIPTION("USB Roccat char device");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/hid/hid-saitek.c b/drivers/hid/hid-saitek.c
new file mode 100644
index 0000000..683861f
--- /dev/null
+++ b/drivers/hid/hid-saitek.c
@@ -0,0 +1,209 @@
+/*
+ *  HID driver for Saitek devices.
+ *
+ *  PS1000 (USB gamepad):
+ *  Fixes the HID report descriptor by removing a non-existent axis and
+ *  clearing the constant bit on the input reports for buttons and d-pad.
+ *  (This module is based on "hid-ortek".)
+ *  Copyright (c) 2012 Andreas Hübner
+ *
+ *  R.A.T.7, R.A.T.9, M.M.O.7 (USB gaming mice):
+ *  Fixes the mode button which cycles through three constantly pressed
+ *  buttons. All three press events are mapped to one button and the
+ *  missing release event is generated immediately.
+ *
+ */
+
+/*
+ * 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/kernel.h>
+
+#include "hid-ids.h"
+
+#define SAITEK_FIX_PS1000	0x0001
+#define SAITEK_RELEASE_MODE_RAT7	0x0002
+#define SAITEK_RELEASE_MODE_MMO7	0x0004
+
+struct saitek_sc {
+	unsigned long quirks;
+	int mode;
+};
+
+static int saitek_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	unsigned long quirks = id->driver_data;
+	struct saitek_sc *ssc;
+	int ret;
+
+	ssc = devm_kzalloc(&hdev->dev, sizeof(*ssc), GFP_KERNEL);
+	if (ssc == NULL) {
+		hid_err(hdev, "can't alloc saitek descriptor\n");
+		return -ENOMEM;
+	}
+
+	ssc->quirks = quirks;
+	ssc->mode = -1;
+
+	hid_set_drvdata(hdev, ssc);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static __u8 *saitek_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct saitek_sc *ssc = hid_get_drvdata(hdev);
+
+	if ((ssc->quirks & SAITEK_FIX_PS1000) && *rsize == 137 &&
+			rdesc[20] == 0x09 && rdesc[21] == 0x33 &&
+			rdesc[94] == 0x81 && rdesc[95] == 0x03 &&
+			rdesc[110] == 0x81 && rdesc[111] == 0x03) {
+
+		hid_info(hdev, "Fixing up Saitek PS1000 report descriptor\n");
+
+		/* convert spurious axis to a "noop" Logical Minimum (0) */
+		rdesc[20] = 0x15;
+		rdesc[21] = 0x00;
+
+		/* clear constant bit on buttons and d-pad */
+		rdesc[95] = 0x02;
+		rdesc[111] = 0x02;
+
+	}
+	return rdesc;
+}
+
+static int saitek_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	struct saitek_sc *ssc = hid_get_drvdata(hdev);
+
+	if (ssc->quirks & SAITEK_RELEASE_MODE_RAT7 && size == 7) {
+		/* R.A.T.7 uses bits 13, 14, 15 for the mode */
+		int mode = -1;
+		if (raw_data[1] & 0x01)
+			mode = 0;
+		else if (raw_data[1] & 0x02)
+			mode = 1;
+		else if (raw_data[1] & 0x04)
+			mode = 2;
+
+		/* clear mode bits */
+		raw_data[1] &= ~0x07;
+
+		if (mode != ssc->mode) {
+			hid_dbg(hdev, "entered mode %d\n", mode);
+			if (ssc->mode != -1) {
+				/* use bit 13 as the mode button */
+				raw_data[1] |= 0x04;
+			}
+			ssc->mode = mode;
+		}
+	} else if (ssc->quirks & SAITEK_RELEASE_MODE_MMO7 && size == 8) {
+
+		/* M.M.O.7 uses bits 8, 22, 23 for the mode */
+		int mode = -1;
+		if (raw_data[1] & 0x80)
+			mode = 0;
+		else if (raw_data[2] & 0x01)
+			mode = 1;
+		else if (raw_data[2] & 0x02)
+			mode = 2;
+
+		/* clear mode bits */
+		raw_data[1] &= ~0x80;
+		raw_data[2] &= ~0x03;
+
+		if (mode != ssc->mode) {
+			hid_dbg(hdev, "entered mode %d\n", mode);
+			if (ssc->mode != -1) {
+				/* use bit 8 as the mode button, bits 22
+				 * and 23 do not represent buttons
+				 * according to the HID report descriptor
+				 */
+				raw_data[1] |= 0x80;
+			}
+			ssc->mode = mode;
+		}
+	}
+
+	return 0;
+}
+
+static int saitek_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct saitek_sc *ssc = hid_get_drvdata(hdev);
+	struct input_dev *input = field->hidinput->input;
+
+	if (usage->type == EV_KEY && value &&
+			(((ssc->quirks & SAITEK_RELEASE_MODE_RAT7) &&
+			  usage->code - BTN_MOUSE == 10) ||
+			((ssc->quirks & SAITEK_RELEASE_MODE_MMO7) &&
+			 usage->code - BTN_MOUSE == 15))) {
+
+		input_report_key(input, usage->code, 1);
+
+		/* report missing release event */
+		input_report_key(input, usage->code, 0);
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id saitek_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_PS1000),
+		.driver_data = SAITEK_FIX_PS1000 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT5),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_OLD),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT7_CONTAGION),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_RAT9),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_RAT9),
+		.driver_data = SAITEK_RELEASE_MODE_RAT7 },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SAITEK, USB_DEVICE_ID_SAITEK_MMO7),
+		.driver_data = SAITEK_RELEASE_MODE_MMO7 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, saitek_devices);
+
+static struct hid_driver saitek_driver = {
+	.name = "saitek",
+	.id_table = saitek_devices,
+	.probe = saitek_probe,
+	.report_fixup = saitek_report_fixup,
+	.raw_event = saitek_raw_event,
+	.event = saitek_event,
+};
+module_hid_driver(saitek_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-samsung.c b/drivers/hid/hid-samsung.c
new file mode 100644
index 0000000..7cbb067
--- /dev/null
+++ b/drivers/hid/hid-samsung.c
@@ -0,0 +1,201 @@
+/*
+ *  HID driver for some samsung "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ *  Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
+ *
+ *
+ *  This driver supports several HID devices:
+ *
+ *  [0419:0001] Samsung IrDA remote controller (reports as Cypress USB Mouse).
+ *	various hid report fixups for different variants.
+ *
+ *  [0419:0600] Creative Desktop Wireless 6000 keyboard/mouse combo
+ *	several key mappings used from the consumer usage page
+ *	deviate from the USB HUT 1.12 standard.
+ *
+ */
+
+/*
+ * 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/usb.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/*
+ * There are several variants for 0419:0001:
+ *
+ * 1. 184 byte report descriptor
+ * Vendor specific report #4 has a size of 48 bit,
+ * and therefore is not accepted when inspecting the descriptors.
+ * As a workaround we reinterpret the report as:
+ *   Variable type, count 6, size 8 bit, log. maximum 255
+ * The burden to reconstruct the data is moved into user space.
+ *
+ * 2. 203 byte report descriptor
+ * Report #4 has an array field with logical range 0..18 instead of 1..15.
+ *
+ * 3. 135 byte report descriptor
+ * Report #4 has an array field with logical range 0..17 instead of 1..14.
+ *
+ * 4. 171 byte report descriptor
+ * Report #3 has an array field with logical range 0..1 instead of 1..3.
+ */
+static inline void samsung_irda_dev_trace(struct hid_device *hdev,
+		unsigned int rsize)
+{
+	hid_info(hdev, "fixing up Samsung IrDA %d byte report descriptor\n",
+		 rsize);
+}
+
+static __u8 *samsung_irda_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize == 184 && rdesc[175] == 0x25 && rdesc[176] == 0x40 &&
+			rdesc[177] == 0x75 && rdesc[178] == 0x30 &&
+			rdesc[179] == 0x95 && rdesc[180] == 0x01 &&
+			rdesc[182] == 0x40) {
+		samsung_irda_dev_trace(hdev, 184);
+		rdesc[176] = 0xff;
+		rdesc[178] = 0x08;
+		rdesc[180] = 0x06;
+		rdesc[182] = 0x42;
+	} else
+	if (*rsize == 203 && rdesc[192] == 0x15 && rdesc[193] == 0x0 &&
+			rdesc[194] == 0x25 && rdesc[195] == 0x12) {
+		samsung_irda_dev_trace(hdev, 203);
+		rdesc[193] = 0x1;
+		rdesc[195] = 0xf;
+	} else
+	if (*rsize == 135 && rdesc[124] == 0x15 && rdesc[125] == 0x0 &&
+			rdesc[126] == 0x25 && rdesc[127] == 0x11) {
+		samsung_irda_dev_trace(hdev, 135);
+		rdesc[125] = 0x1;
+		rdesc[127] = 0xe;
+	} else
+	if (*rsize == 171 && rdesc[160] == 0x15 && rdesc[161] == 0x0 &&
+			rdesc[162] == 0x25 && rdesc[163] == 0x01) {
+		samsung_irda_dev_trace(hdev, 171);
+		rdesc[161] = 0x1;
+		rdesc[163] = 0x3;
+	}
+	return rdesc;
+}
+
+#define samsung_kbd_mouse_map_key_clear(c) \
+	hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static int samsung_kbd_mouse_input_mapping(struct hid_device *hdev,
+	struct hid_input *hi, struct hid_field *field, struct hid_usage *usage,
+	unsigned long **bit, int *max)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	unsigned short ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+	if (1 != ifnum || HID_UP_CONSUMER != (usage->hid & HID_USAGE_PAGE))
+		return 0;
+
+	dbg_hid("samsung wireless keyboard/mouse input mapping event [0x%x]\n",
+		usage->hid & HID_USAGE);
+
+	switch (usage->hid & HID_USAGE) {
+	/* report 2 */
+	case 0x183: samsung_kbd_mouse_map_key_clear(KEY_MEDIA); break;
+	case 0x195: samsung_kbd_mouse_map_key_clear(KEY_EMAIL);	break;
+	case 0x196: samsung_kbd_mouse_map_key_clear(KEY_CALC); break;
+	case 0x197: samsung_kbd_mouse_map_key_clear(KEY_COMPUTER); break;
+	case 0x22b: samsung_kbd_mouse_map_key_clear(KEY_SEARCH); break;
+	case 0x22c: samsung_kbd_mouse_map_key_clear(KEY_WWW); break;
+	case 0x22d: samsung_kbd_mouse_map_key_clear(KEY_BACK); break;
+	case 0x22e: samsung_kbd_mouse_map_key_clear(KEY_FORWARD); break;
+	case 0x22f: samsung_kbd_mouse_map_key_clear(KEY_FAVORITES); break;
+	case 0x230: samsung_kbd_mouse_map_key_clear(KEY_REFRESH); break;
+	case 0x231: samsung_kbd_mouse_map_key_clear(KEY_STOP); break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static __u8 *samsung_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+	unsigned int *rsize)
+{
+	if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product)
+		rdesc = samsung_irda_report_fixup(hdev, rdesc, rsize);
+	return rdesc;
+}
+
+static int samsung_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+	struct hid_field *field, struct hid_usage *usage,
+	unsigned long **bit, int *max)
+{
+	int ret = 0;
+
+	if (USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE == hdev->product)
+		ret = samsung_kbd_mouse_input_mapping(hdev,
+			hi, field, usage, bit, max);
+
+	return ret;
+}
+
+static int samsung_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret;
+	unsigned int cmask = HID_CONNECT_DEFAULT;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	if (USB_DEVICE_ID_SAMSUNG_IR_REMOTE == hdev->product) {
+		if (hdev->rsize == 184) {
+			/* disable hidinput, force hiddev */
+			cmask = (cmask & ~HID_CONNECT_HIDINPUT) |
+				HID_CONNECT_HIDDEV_FORCE;
+		}
+	}
+
+	ret = hid_hw_start(hdev, cmask);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static const struct hid_device_id samsung_devices[] = {
+	{ 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) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, samsung_devices);
+
+static struct hid_driver samsung_driver = {
+	.name = "samsung",
+	.id_table = samsung_devices,
+	.report_fixup = samsung_report_fixup,
+	.input_mapping = samsung_input_mapping,
+	.probe = samsung_probe,
+};
+module_hid_driver(samsung_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
new file mode 100644
index 0000000..bb012bc
--- /dev/null
+++ b/drivers/hid/hid-sensor-custom.c
@@ -0,0 +1,849 @@
+/*
+ * hid-sensor-custom.c
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/kfifo.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/bsearch.h>
+#include <linux/platform_device.h>
+#include <linux/hid-sensor-hub.h>
+
+#define HID_CUSTOM_NAME_LENGTH		64
+#define HID_CUSTOM_MAX_CORE_ATTRS	10
+#define HID_CUSTOM_TOTAL_ATTRS		(HID_CUSTOM_MAX_CORE_ATTRS + 1)
+#define HID_CUSTOM_FIFO_SIZE		4096
+#define HID_CUSTOM_MAX_FEATURE_BYTES	64
+
+struct hid_sensor_custom_field {
+	int report_id;
+	char group_name[HID_CUSTOM_NAME_LENGTH];
+	struct hid_sensor_hub_attribute_info attribute;
+	struct device_attribute sd_attrs[HID_CUSTOM_MAX_CORE_ATTRS];
+	char attr_name[HID_CUSTOM_TOTAL_ATTRS][HID_CUSTOM_NAME_LENGTH];
+	struct attribute *attrs[HID_CUSTOM_TOTAL_ATTRS];
+	struct attribute_group hid_custom_attribute_group;
+};
+
+struct hid_sensor_custom {
+	struct mutex mutex;
+	struct platform_device *pdev;
+	struct hid_sensor_hub_device *hsdev;
+	struct hid_sensor_hub_callbacks callbacks;
+	int sensor_field_count;
+	struct hid_sensor_custom_field *fields;
+	int input_field_count;
+	int input_report_size;
+	int input_report_recd_size;
+	bool input_skip_sample;
+	bool enable;
+	struct hid_sensor_custom_field *power_state;
+	struct hid_sensor_custom_field *report_state;
+	struct miscdevice custom_dev;
+	struct kfifo data_fifo;
+	unsigned long misc_opened;
+	wait_queue_head_t wait;
+};
+
+/* Header for each sample to user space via dev interface */
+struct hid_sensor_sample {
+	u32 usage_id;
+	u64 timestamp;
+	u32 raw_len;
+} __packed;
+
+static struct attribute hid_custom_attrs[] = {
+	{.name = "name", .mode = S_IRUGO},
+	{.name = "units", .mode = S_IRUGO},
+	{.name = "unit-expo", .mode = S_IRUGO},
+	{.name = "minimum", .mode = S_IRUGO},
+	{.name = "maximum", .mode = S_IRUGO},
+	{.name = "size", .mode = S_IRUGO},
+	{.name = "value", .mode = S_IWUSR | S_IRUGO},
+	{.name = NULL}
+};
+
+static const struct hid_custom_usage_desc {
+	int usage_id;
+	char *desc;
+} hid_custom_usage_desc_table[] = {
+	{0x200201,	"event-sensor-state"},
+	{0x200202,	"event-sensor-event"},
+	{0x200301,	"property-friendly-name"},
+	{0x200302,	"property-persistent-unique-id"},
+	{0x200303,	"property-sensor-status"},
+	{0x200304,	"property-min-report-interval"},
+	{0x200305,	"property-sensor-manufacturer"},
+	{0x200306,	"property-sensor-model"},
+	{0x200307,	"property-sensor-serial-number"},
+	{0x200308,	"property-sensor-description"},
+	{0x200309,	"property-sensor-connection-type"},
+	{0x20030A,	"property-sensor-device-path"},
+	{0x20030B,	"property-hardware-revision"},
+	{0x20030C,	"property-firmware-version"},
+	{0x20030D,	"property-release-date"},
+	{0x20030E,	"property-report-interval"},
+	{0x20030F,	"property-change-sensitivity-absolute"},
+	{0x200310,	"property-change-sensitivity-percent-range"},
+	{0x200311,	"property-change-sensitivity-percent-relative"},
+	{0x200312,	"property-accuracy"},
+	{0x200313,	"property-resolution"},
+	{0x200314,	"property-maximum"},
+	{0x200315,	"property-minimum"},
+	{0x200316,	"property-reporting-state"},
+	{0x200317,	"property-sampling-rate"},
+	{0x200318,	"property-response-curve"},
+	{0x200319,	"property-power-state"},
+	{0x200540,	"data-field-custom"},
+	{0x200541,	"data-field-custom-usage"},
+	{0x200542,	"data-field-custom-boolean-array"},
+	{0x200543,	"data-field-custom-value"},
+	{0x200544,	"data-field-custom-value_1"},
+	{0x200545,	"data-field-custom-value_2"},
+	{0x200546,	"data-field-custom-value_3"},
+	{0x200547,	"data-field-custom-value_4"},
+	{0x200548,	"data-field-custom-value_5"},
+	{0x200549,	"data-field-custom-value_6"},
+	{0x20054A,	"data-field-custom-value_7"},
+	{0x20054B,	"data-field-custom-value_8"},
+	{0x20054C,	"data-field-custom-value_9"},
+	{0x20054D,	"data-field-custom-value_10"},
+	{0x20054E,	"data-field-custom-value_11"},
+	{0x20054F,	"data-field-custom-value_12"},
+	{0x200550,	"data-field-custom-value_13"},
+	{0x200551,	"data-field-custom-value_14"},
+	{0x200552,	"data-field-custom-value_15"},
+	{0x200553,	"data-field-custom-value_16"},
+	{0x200554,	"data-field-custom-value_17"},
+	{0x200555,	"data-field-custom-value_18"},
+	{0x200556,	"data-field-custom-value_19"},
+	{0x200557,	"data-field-custom-value_20"},
+	{0x200558,	"data-field-custom-value_21"},
+	{0x200559,	"data-field-custom-value_22"},
+	{0x20055A,	"data-field-custom-value_23"},
+	{0x20055B,	"data-field-custom-value_24"},
+	{0x20055C,	"data-field-custom-value_25"},
+	{0x20055D,	"data-field-custom-value_26"},
+	{0x20055E,	"data-field-custom-value_27"},
+	{0x20055F,	"data-field-custom-value_28"},
+};
+
+static int usage_id_cmp(const void *p1, const void *p2)
+{
+	if (*(int *)p1 < *(int *)p2)
+		return -1;
+
+	if (*(int *)p1 > *(int *)p2)
+		return 1;
+
+	return 0;
+}
+
+static ssize_t enable_sensor_show(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
+
+	return sprintf(buf, "%d\n", sensor_inst->enable);
+}
+
+static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
+				  bool state)
+{
+	int power_val = -1;
+	int report_val = -1;
+	u32 power_state_usage_id;
+	u32 report_state_usage_id;
+	int ret;
+
+	/*
+	 * It is possible that the power/report state ids are not present.
+	 * In this case this function will return success. But if the
+	 * ids are present, then it will return error if set fails.
+	 */
+	if (state) {
+		power_state_usage_id =
+			HID_USAGE_SENSOR_PROP_POWER_STATE_D0_FULL_POWER_ENUM;
+		report_state_usage_id =
+			HID_USAGE_SENSOR_PROP_REPORTING_STATE_ALL_EVENTS_ENUM;
+	} else {
+		power_state_usage_id =
+			HID_USAGE_SENSOR_PROP_POWER_STATE_D4_POWER_OFF_ENUM;
+		report_state_usage_id =
+			HID_USAGE_SENSOR_PROP_REPORTING_STATE_NO_EVENTS_ENUM;
+	}
+
+	if (sensor_inst->power_state)
+		power_val = hid_sensor_get_usage_index(sensor_inst->hsdev,
+				sensor_inst->power_state->attribute.report_id,
+				sensor_inst->power_state->attribute.index,
+				power_state_usage_id);
+	if (sensor_inst->report_state)
+		report_val = hid_sensor_get_usage_index(sensor_inst->hsdev,
+				sensor_inst->report_state->attribute.report_id,
+				sensor_inst->report_state->attribute.index,
+				report_state_usage_id);
+
+	if (power_val >= 0) {
+		power_val +=
+			sensor_inst->power_state->attribute.logical_minimum;
+		ret = sensor_hub_set_feature(sensor_inst->hsdev,
+				sensor_inst->power_state->attribute.report_id,
+				sensor_inst->power_state->attribute.index,
+				sizeof(power_val),
+				&power_val);
+		if (ret) {
+			hid_err(sensor_inst->hsdev->hdev,
+				"Set power state failed\n");
+			return ret;
+		}
+	}
+
+	if (report_val >= 0) {
+		report_val +=
+			sensor_inst->report_state->attribute.logical_minimum;
+		ret = sensor_hub_set_feature(sensor_inst->hsdev,
+				sensor_inst->report_state->attribute.report_id,
+				sensor_inst->report_state->attribute.index,
+				sizeof(report_val),
+				&report_val);
+		if (ret) {
+			hid_err(sensor_inst->hsdev->hdev,
+				"Set report state failed\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t enable_sensor_store(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
+	int value;
+	int ret = -EINVAL;
+
+	if (kstrtoint(buf, 0, &value) != 0)
+		return -EINVAL;
+
+	mutex_lock(&sensor_inst->mutex);
+	if (value && !sensor_inst->enable) {
+		ret = sensor_hub_device_open(sensor_inst->hsdev);
+		if (ret)
+			goto unlock_state;
+
+		ret = set_power_report_state(sensor_inst, true);
+		if (ret) {
+			sensor_hub_device_close(sensor_inst->hsdev);
+			goto unlock_state;
+		}
+		sensor_inst->enable = true;
+	} else if (!value && sensor_inst->enable) {
+		ret = set_power_report_state(sensor_inst, false);
+		sensor_hub_device_close(sensor_inst->hsdev);
+		sensor_inst->enable = false;
+	}
+unlock_state:
+	mutex_unlock(&sensor_inst->mutex);
+	if (ret < 0)
+		return ret;
+
+	return count;
+}
+static DEVICE_ATTR_RW(enable_sensor);
+
+static struct attribute *enable_sensor_attrs[] = {
+	&dev_attr_enable_sensor.attr,
+	NULL,
+};
+
+static const struct attribute_group enable_sensor_attr_group = {
+	.attrs = enable_sensor_attrs,
+};
+
+static ssize_t show_value(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
+	struct hid_sensor_hub_attribute_info *attribute;
+	int index, usage, field_index;
+	char name[HID_CUSTOM_NAME_LENGTH];
+	bool feature = false;
+	bool input = false;
+	int value = 0;
+
+	if (sscanf(attr->attr.name, "feature-%x-%x-%s", &index, &usage,
+		   name) == 3) {
+		feature = true;
+		field_index = index + sensor_inst->input_field_count;
+	} else if (sscanf(attr->attr.name, "input-%x-%x-%s", &index, &usage,
+		   name) == 3) {
+		input = true;
+		field_index = index;
+	} else
+		return -EINVAL;
+
+	if (!strncmp(name, "value", strlen("value"))) {
+		u32 report_id;
+		int ret;
+
+		attribute = &sensor_inst->fields[field_index].attribute;
+		report_id = attribute->report_id;
+		if (feature) {
+			u8 values[HID_CUSTOM_MAX_FEATURE_BYTES];
+			int len = 0;
+			u64 value = 0;
+			int i = 0;
+
+			ret = sensor_hub_get_feature(sensor_inst->hsdev,
+						     report_id,
+						     index,
+						     sizeof(values), values);
+			if (ret < 0)
+				return ret;
+
+			while (i < ret) {
+				if (i + attribute->size > ret) {
+					len += snprintf(&buf[len],
+							PAGE_SIZE - len,
+							"%d ", values[i]);
+					break;
+				}
+				switch (attribute->size) {
+				case 2:
+					value = (u64) *(u16 *)&values[i];
+					i += attribute->size;
+					break;
+				case 4:
+					value = (u64) *(u32 *)&values[i];
+					i += attribute->size;
+					break;
+				case 8:
+					value = *(u64 *)&values[i];
+					i += attribute->size;
+					break;
+				default:
+					value = (u64) values[i];
+					++i;
+					break;
+				}
+				len += snprintf(&buf[len], PAGE_SIZE - len,
+						"%lld ", value);
+			}
+			len += snprintf(&buf[len], PAGE_SIZE - len, "\n");
+
+			return len;
+		} else if (input)
+			value = sensor_hub_input_attr_get_raw_value(
+						sensor_inst->hsdev,
+						sensor_inst->hsdev->usage,
+						usage, report_id,
+						SENSOR_HUB_SYNC, false);
+	} else if (!strncmp(name, "units", strlen("units")))
+		value = sensor_inst->fields[field_index].attribute.units;
+	else if (!strncmp(name, "unit-expo", strlen("unit-expo")))
+		value = sensor_inst->fields[field_index].attribute.unit_expo;
+	else if (!strncmp(name, "size", strlen("size")))
+		value = sensor_inst->fields[field_index].attribute.size;
+	else if (!strncmp(name, "minimum", strlen("minimum")))
+		value = sensor_inst->fields[field_index].attribute.
+							logical_minimum;
+	else if (!strncmp(name, "maximum", strlen("maximum")))
+		value = sensor_inst->fields[field_index].attribute.
+							logical_maximum;
+	else if (!strncmp(name, "name", strlen("name"))) {
+		struct hid_custom_usage_desc *usage_desc;
+
+		usage_desc = bsearch(&usage, hid_custom_usage_desc_table,
+				     ARRAY_SIZE(hid_custom_usage_desc_table),
+				     sizeof(struct hid_custom_usage_desc),
+				     usage_id_cmp);
+		if (usage_desc)
+			return snprintf(buf, PAGE_SIZE, "%s\n",
+					usage_desc->desc);
+		else
+			return sprintf(buf, "not-specified\n");
+	 } else
+		return -EINVAL;
+
+	return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t store_value(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
+	int index, field_index, usage;
+	char name[HID_CUSTOM_NAME_LENGTH];
+	int value;
+
+	if (sscanf(attr->attr.name, "feature-%x-%x-%s", &index, &usage,
+		   name) == 3) {
+		field_index = index + sensor_inst->input_field_count;
+	} else
+		return -EINVAL;
+
+	if (!strncmp(name, "value", strlen("value"))) {
+		u32 report_id;
+		int ret;
+
+		if (kstrtoint(buf, 0, &value) != 0)
+			return -EINVAL;
+
+		report_id = sensor_inst->fields[field_index].attribute.
+								report_id;
+		ret = sensor_hub_set_feature(sensor_inst->hsdev, report_id,
+					     index, sizeof(value), &value);
+	} else
+		return -EINVAL;
+
+	return count;
+}
+
+static int hid_sensor_capture_sample(struct hid_sensor_hub_device *hsdev,
+				  unsigned usage_id, size_t raw_len,
+				  char *raw_data, void *priv)
+{
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv);
+	struct hid_sensor_sample header;
+
+	/* If any error occurs in a sample, rest of the fields are ignored */
+	if (sensor_inst->input_skip_sample) {
+		hid_err(sensor_inst->hsdev->hdev, "Skipped remaining data\n");
+		return 0;
+	}
+
+	hid_dbg(sensor_inst->hsdev->hdev, "%s received %d of %d\n", __func__,
+		(int) (sensor_inst->input_report_recd_size + raw_len),
+		sensor_inst->input_report_size);
+
+	if (!test_bit(0, &sensor_inst->misc_opened))
+		return 0;
+
+	if (!sensor_inst->input_report_recd_size) {
+		int required_size = sizeof(struct hid_sensor_sample) +
+						sensor_inst->input_report_size;
+		header.usage_id = hsdev->usage;
+		header.raw_len = sensor_inst->input_report_size;
+		header.timestamp = ktime_get_real_ns();
+		if (kfifo_avail(&sensor_inst->data_fifo) >= required_size) {
+			kfifo_in(&sensor_inst->data_fifo,
+				 (unsigned char *)&header,
+				 sizeof(header));
+		} else
+			sensor_inst->input_skip_sample = true;
+	}
+	if (kfifo_avail(&sensor_inst->data_fifo) >= raw_len)
+		kfifo_in(&sensor_inst->data_fifo, (unsigned char *)raw_data,
+			 raw_len);
+
+	sensor_inst->input_report_recd_size += raw_len;
+
+	return 0;
+}
+
+static int hid_sensor_send_event(struct hid_sensor_hub_device *hsdev,
+				 unsigned usage_id, void *priv)
+{
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(priv);
+
+	if (!test_bit(0, &sensor_inst->misc_opened))
+		return 0;
+
+	sensor_inst->input_report_recd_size = 0;
+	sensor_inst->input_skip_sample = false;
+
+	wake_up(&sensor_inst->wait);
+
+	return 0;
+}
+
+static int hid_sensor_custom_add_field(struct hid_sensor_custom *sensor_inst,
+				       int index, int report_type,
+				       struct hid_report *report,
+				       struct hid_field *field)
+{
+	struct hid_sensor_custom_field *sensor_field;
+	void *fields;
+
+	fields = krealloc(sensor_inst->fields,
+			  (sensor_inst->sensor_field_count + 1) *
+			   sizeof(struct hid_sensor_custom_field), GFP_KERNEL);
+	if (!fields) {
+		kfree(sensor_inst->fields);
+		return -ENOMEM;
+	}
+	sensor_inst->fields = fields;
+	sensor_field = &sensor_inst->fields[sensor_inst->sensor_field_count];
+	sensor_field->attribute.usage_id = sensor_inst->hsdev->usage;
+	if (field->logical)
+		sensor_field->attribute.attrib_id = field->logical;
+	else
+		sensor_field->attribute.attrib_id = field->usage[0].hid;
+
+	sensor_field->attribute.index = index;
+	sensor_field->attribute.report_id = report->id;
+	sensor_field->attribute.units = field->unit;
+	sensor_field->attribute.unit_expo = field->unit_exponent;
+	sensor_field->attribute.size = (field->report_size / 8);
+	sensor_field->attribute.logical_minimum = field->logical_minimum;
+	sensor_field->attribute.logical_maximum = field->logical_maximum;
+
+	if (report_type == HID_FEATURE_REPORT)
+		snprintf(sensor_field->group_name,
+			 sizeof(sensor_field->group_name), "feature-%x-%x",
+			 sensor_field->attribute.index,
+			 sensor_field->attribute.attrib_id);
+	else if (report_type == HID_INPUT_REPORT) {
+		snprintf(sensor_field->group_name,
+			 sizeof(sensor_field->group_name),
+			 "input-%x-%x", sensor_field->attribute.index,
+			 sensor_field->attribute.attrib_id);
+		sensor_inst->input_field_count++;
+		sensor_inst->input_report_size += (field->report_size *
+						   field->report_count) / 8;
+	}
+
+	memset(&sensor_field->hid_custom_attribute_group, 0,
+	       sizeof(struct attribute_group));
+	sensor_inst->sensor_field_count++;
+
+	return 0;
+}
+
+static int hid_sensor_custom_add_fields(struct hid_sensor_custom *sensor_inst,
+					struct hid_report_enum *report_enum,
+					int report_type)
+{
+	int i;
+	int ret;
+	struct hid_report *report;
+	struct hid_field *field;
+	struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev;
+
+	list_for_each_entry(report, &report_enum->report_list, list) {
+		for (i = 0; i < report->maxfield; ++i) {
+			field = report->field[i];
+			if (field->maxusage &&
+			    ((field->usage[0].collection_index >=
+			      hsdev->start_collection_index) &&
+			      (field->usage[0].collection_index <
+			       hsdev->end_collection_index))) {
+
+				ret = hid_sensor_custom_add_field(sensor_inst,
+								  i,
+								  report_type,
+								  report,
+								  field);
+				if (ret)
+					return ret;
+
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int hid_sensor_custom_add_attributes(struct hid_sensor_custom
+								*sensor_inst)
+{
+	struct hid_sensor_hub_device *hsdev = sensor_inst->hsdev;
+	struct hid_device *hdev = hsdev->hdev;
+	int ret = -1;
+	int i, j;
+
+	for (j = 0; j < HID_REPORT_TYPES; ++j) {
+		if (j == HID_OUTPUT_REPORT)
+			continue;
+
+		ret = hid_sensor_custom_add_fields(sensor_inst,
+						   &hdev->report_enum[j], j);
+		if (ret)
+			return ret;
+
+	}
+
+	/* Create sysfs attributes */
+	for (i = 0; i < sensor_inst->sensor_field_count; ++i) {
+		j = 0;
+		while (j < HID_CUSTOM_TOTAL_ATTRS &&
+		       hid_custom_attrs[j].name) {
+			struct device_attribute *device_attr;
+
+			device_attr = &sensor_inst->fields[i].sd_attrs[j];
+
+			snprintf((char *)&sensor_inst->fields[i].attr_name[j],
+				 HID_CUSTOM_NAME_LENGTH, "%s-%s",
+				 sensor_inst->fields[i].group_name,
+				 hid_custom_attrs[j].name);
+			sysfs_attr_init(&device_attr->attr);
+			device_attr->attr.name =
+				(char *)&sensor_inst->fields[i].attr_name[j];
+			device_attr->attr.mode = hid_custom_attrs[j].mode;
+			device_attr->show = show_value;
+			if (hid_custom_attrs[j].mode & S_IWUSR)
+				device_attr->store = store_value;
+			sensor_inst->fields[i].attrs[j] = &device_attr->attr;
+			++j;
+		}
+		sensor_inst->fields[i].attrs[j] = NULL;
+		sensor_inst->fields[i].hid_custom_attribute_group.attrs =
+						sensor_inst->fields[i].attrs;
+		sensor_inst->fields[i].hid_custom_attribute_group.name =
+					sensor_inst->fields[i].group_name;
+		ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
+					 &sensor_inst->fields[i].
+					 hid_custom_attribute_group);
+		if (ret)
+			break;
+
+		/* For power or report field store indexes */
+		if (sensor_inst->fields[i].attribute.attrib_id ==
+					HID_USAGE_SENSOR_PROY_POWER_STATE)
+			sensor_inst->power_state = &sensor_inst->fields[i];
+		else if (sensor_inst->fields[i].attribute.attrib_id ==
+					HID_USAGE_SENSOR_PROP_REPORT_STATE)
+			sensor_inst->report_state = &sensor_inst->fields[i];
+	}
+
+	return ret;
+}
+
+static void hid_sensor_custom_remove_attributes(struct hid_sensor_custom *
+								sensor_inst)
+{
+	int i;
+
+	for (i = 0; i < sensor_inst->sensor_field_count; ++i)
+		sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
+				   &sensor_inst->fields[i].
+				   hid_custom_attribute_group);
+
+	kfree(sensor_inst->fields);
+}
+
+static ssize_t hid_sensor_custom_read(struct file *file, char __user *buf,
+				      size_t count, loff_t *f_ps)
+{
+	struct hid_sensor_custom *sensor_inst;
+	unsigned int copied;
+	int ret;
+
+	sensor_inst = container_of(file->private_data,
+				   struct hid_sensor_custom, custom_dev);
+
+	if (count < sizeof(struct hid_sensor_sample))
+		return -EINVAL;
+
+	do {
+		if (kfifo_is_empty(&sensor_inst->data_fifo)) {
+			if (file->f_flags & O_NONBLOCK)
+				return -EAGAIN;
+
+			ret = wait_event_interruptible(sensor_inst->wait,
+				!kfifo_is_empty(&sensor_inst->data_fifo));
+			if (ret)
+				return ret;
+		}
+		ret = kfifo_to_user(&sensor_inst->data_fifo, buf, count,
+				    &copied);
+		if (ret)
+			return ret;
+
+	} while (copied == 0);
+
+	return copied;
+}
+
+static int hid_sensor_custom_release(struct inode *inode, struct file *file)
+{
+	struct hid_sensor_custom *sensor_inst;
+
+	sensor_inst = container_of(file->private_data,
+				   struct hid_sensor_custom, custom_dev);
+
+	clear_bit(0, &sensor_inst->misc_opened);
+
+	return 0;
+}
+
+static int hid_sensor_custom_open(struct inode *inode, struct file *file)
+{
+	struct hid_sensor_custom *sensor_inst;
+
+	sensor_inst = container_of(file->private_data,
+				   struct hid_sensor_custom, custom_dev);
+	/* We essentially have single reader and writer */
+	if (test_and_set_bit(0, &sensor_inst->misc_opened))
+		return -EBUSY;
+
+	return nonseekable_open(inode, file);
+}
+
+static __poll_t hid_sensor_custom_poll(struct file *file,
+					   struct poll_table_struct *wait)
+{
+	struct hid_sensor_custom *sensor_inst;
+	__poll_t mask = 0;
+
+	sensor_inst = container_of(file->private_data,
+				   struct hid_sensor_custom, custom_dev);
+
+	poll_wait(file, &sensor_inst->wait, wait);
+
+	if (!kfifo_is_empty(&sensor_inst->data_fifo))
+		mask = EPOLLIN | EPOLLRDNORM;
+
+	return mask;
+}
+
+static const struct file_operations hid_sensor_custom_fops = {
+	.open =  hid_sensor_custom_open,
+	.read =  hid_sensor_custom_read,
+	.release = hid_sensor_custom_release,
+	.poll = hid_sensor_custom_poll,
+	.llseek = noop_llseek,
+};
+
+static int hid_sensor_custom_dev_if_add(struct hid_sensor_custom *sensor_inst)
+{
+	int ret;
+
+	ret = kfifo_alloc(&sensor_inst->data_fifo, HID_CUSTOM_FIFO_SIZE,
+			  GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	init_waitqueue_head(&sensor_inst->wait);
+
+	sensor_inst->custom_dev.minor = MISC_DYNAMIC_MINOR;
+	sensor_inst->custom_dev.name = dev_name(&sensor_inst->pdev->dev);
+	sensor_inst->custom_dev.fops = &hid_sensor_custom_fops,
+	ret = misc_register(&sensor_inst->custom_dev);
+	if (ret) {
+		kfifo_free(&sensor_inst->data_fifo);
+		return ret;
+	}
+	return 0;
+}
+
+static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
+								*sensor_inst)
+{
+	wake_up(&sensor_inst->wait);
+	misc_deregister(&sensor_inst->custom_dev);
+	kfifo_free(&sensor_inst->data_fifo);
+
+}
+
+static int hid_sensor_custom_probe(struct platform_device *pdev)
+{
+	struct hid_sensor_custom *sensor_inst;
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+	int ret;
+
+	sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
+				   GFP_KERNEL);
+	if (!sensor_inst)
+		return -ENOMEM;
+
+	sensor_inst->callbacks.capture_sample = hid_sensor_capture_sample;
+	sensor_inst->callbacks.send_event = hid_sensor_send_event;
+	sensor_inst->callbacks.pdev = pdev;
+	sensor_inst->hsdev = hsdev;
+	sensor_inst->pdev = pdev;
+	mutex_init(&sensor_inst->mutex);
+	platform_set_drvdata(pdev, sensor_inst);
+	ret = sensor_hub_register_callback(hsdev, hsdev->usage,
+					   &sensor_inst->callbacks);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "callback reg failed\n");
+		return ret;
+	}
+
+	ret = sysfs_create_group(&sensor_inst->pdev->dev.kobj,
+				 &enable_sensor_attr_group);
+	if (ret)
+		goto err_remove_callback;
+
+	ret = hid_sensor_custom_add_attributes(sensor_inst);
+	if (ret)
+		goto err_remove_group;
+
+	ret = hid_sensor_custom_dev_if_add(sensor_inst);
+	if (ret)
+		goto err_remove_attributes;
+
+	return 0;
+
+err_remove_attributes:
+	hid_sensor_custom_remove_attributes(sensor_inst);
+err_remove_group:
+	sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
+			   &enable_sensor_attr_group);
+err_remove_callback:
+	sensor_hub_remove_callback(hsdev, hsdev->usage);
+
+	return ret;
+}
+
+static int hid_sensor_custom_remove(struct platform_device *pdev)
+{
+	struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
+	struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
+
+	hid_sensor_custom_dev_if_remove(sensor_inst);
+	hid_sensor_custom_remove_attributes(sensor_inst);
+	sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
+			   &enable_sensor_attr_group);
+	sensor_hub_remove_callback(hsdev, hsdev->usage);
+
+	return 0;
+}
+
+static const struct platform_device_id hid_sensor_custom_ids[] = {
+	{
+		.name = "HID-SENSOR-2000e1",
+	},
+	{
+		.name = "HID-SENSOR-2000e2",
+	},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, hid_sensor_custom_ids);
+
+static struct platform_driver hid_sensor_custom_platform_driver = {
+	.id_table = hid_sensor_custom_ids,
+	.driver = {
+		.name	= KBUILD_MODNAME,
+	},
+	.probe		= hid_sensor_custom_probe,
+	.remove		= hid_sensor_custom_remove,
+};
+module_platform_driver(hid_sensor_custom_platform_driver);
+
+MODULE_DESCRIPTION("HID Sensor Custom and Generic sensor Driver");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
new file mode 100644
index 0000000..4256fdc
--- /dev/null
+++ b/drivers/hid/hid-sensor-hub.c
@@ -0,0 +1,786 @@
+/*
+ * HID Sensors Driver
+ * Copyright (c) 2012, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mfd/core.h>
+#include <linux/list.h>
+#include <linux/hid-sensor-ids.h>
+#include <linux/hid-sensor-hub.h>
+#include "hid-ids.h"
+
+#define HID_SENSOR_HUB_ENUM_QUIRK	0x01
+
+/**
+ * struct sensor_hub_data - Hold a instance data for a HID hub device
+ * @hsdev:		Stored hid instance for current hub device.
+ * @mutex:		Mutex to serialize synchronous request.
+ * @lock:		Spin lock to protect pending request structure.
+ * @dyn_callback_list:	Holds callback function
+ * @dyn_callback_lock:	spin lock to protect callback list
+ * @hid_sensor_hub_client_devs:	Stores all MFD cells for a hub instance.
+ * @hid_sensor_client_cnt: Number of MFD cells, (no of sensors attached).
+ * @ref_cnt:		Number of MFD clients have opened this device
+ */
+struct sensor_hub_data {
+	struct mutex mutex;
+	spinlock_t lock;
+	struct list_head dyn_callback_list;
+	spinlock_t dyn_callback_lock;
+	struct mfd_cell *hid_sensor_hub_client_devs;
+	int hid_sensor_client_cnt;
+	unsigned long quirks;
+	int ref_cnt;
+};
+
+/**
+ * struct hid_sensor_hub_callbacks_list - Stores callback list
+ * @list:		list head.
+ * @usage_id:		usage id for a physical device.
+ * @usage_callback:	Stores registered callback functions.
+ * @priv:		Private data for a physical device.
+ */
+struct hid_sensor_hub_callbacks_list {
+	struct list_head list;
+	u32 usage_id;
+	struct hid_sensor_hub_device *hsdev;
+	struct hid_sensor_hub_callbacks *usage_callback;
+	void *priv;
+};
+
+static struct hid_report *sensor_hub_report(int id, struct hid_device *hdev,
+						int dir)
+{
+	struct hid_report *report;
+
+	list_for_each_entry(report, &hdev->report_enum[dir].report_list, list) {
+		if (report->id == id)
+			return report;
+	}
+	hid_warn(hdev, "No report with id 0x%x found\n", id);
+
+	return NULL;
+}
+
+static int sensor_hub_get_physical_device_count(struct hid_device *hdev)
+{
+	int i;
+	int count = 0;
+
+	for (i = 0; i < hdev->maxcollection; ++i) {
+		struct hid_collection *collection = &hdev->collection[i];
+		if (collection->type == HID_COLLECTION_PHYSICAL ||
+		    collection->type == HID_COLLECTION_APPLICATION)
+			++count;
+	}
+
+	return count;
+}
+
+static void sensor_hub_fill_attr_info(
+		struct hid_sensor_hub_attribute_info *info,
+		s32 index, s32 report_id, struct hid_field *field)
+{
+	info->index = index;
+	info->report_id = report_id;
+	info->units = field->unit;
+	info->unit_expo = field->unit_exponent;
+	info->size = (field->report_size * field->report_count)/8;
+	info->logical_minimum = field->logical_minimum;
+	info->logical_maximum = field->logical_maximum;
+}
+
+static struct hid_sensor_hub_callbacks *sensor_hub_get_callback(
+					struct hid_device *hdev,
+					u32 usage_id,
+					int collection_index,
+					struct hid_sensor_hub_device **hsdev,
+					void **priv)
+{
+	struct hid_sensor_hub_callbacks_list *callback;
+	struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+	list_for_each_entry(callback, &pdata->dyn_callback_list, list)
+		if ((callback->usage_id == usage_id ||
+		     callback->usage_id == HID_USAGE_SENSOR_COLLECTION) &&
+			(collection_index >=
+				callback->hsdev->start_collection_index) &&
+			(collection_index <
+				callback->hsdev->end_collection_index)) {
+			*priv = callback->priv;
+			*hsdev = callback->hsdev;
+			spin_unlock_irqrestore(&pdata->dyn_callback_lock,
+					       flags);
+			return callback->usage_callback;
+		}
+	spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+
+	return NULL;
+}
+
+int sensor_hub_register_callback(struct hid_sensor_hub_device *hsdev,
+			u32 usage_id,
+			struct hid_sensor_hub_callbacks *usage_callback)
+{
+	struct hid_sensor_hub_callbacks_list *callback;
+	struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+	list_for_each_entry(callback, &pdata->dyn_callback_list, list)
+		if (callback->usage_id == usage_id &&
+						callback->hsdev == hsdev) {
+			spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+			return -EINVAL;
+		}
+	callback = kzalloc(sizeof(*callback), GFP_ATOMIC);
+	if (!callback) {
+		spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+		return -ENOMEM;
+	}
+	callback->hsdev = hsdev;
+	callback->usage_callback = usage_callback;
+	callback->usage_id = usage_id;
+	callback->priv = NULL;
+	/*
+	 * If there is a handler registered for the collection type, then
+	 * it will handle all reports for sensors in this collection. If
+	 * there is also an individual sensor handler registration, then
+	 * we want to make sure that the reports are directed to collection
+	 * handler, as this may be a fusion sensor. So add collection handlers
+	 * to the beginning of the list, so that they are matched first.
+	 */
+	if (usage_id == HID_USAGE_SENSOR_COLLECTION)
+		list_add(&callback->list, &pdata->dyn_callback_list);
+	else
+		list_add_tail(&callback->list, &pdata->dyn_callback_list);
+	spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_register_callback);
+
+int sensor_hub_remove_callback(struct hid_sensor_hub_device *hsdev,
+				u32 usage_id)
+{
+	struct hid_sensor_hub_callbacks_list *callback;
+	struct sensor_hub_data *pdata = hid_get_drvdata(hsdev->hdev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+	list_for_each_entry(callback, &pdata->dyn_callback_list, list)
+		if (callback->usage_id == usage_id &&
+						callback->hsdev == hsdev) {
+			list_del(&callback->list);
+			kfree(callback);
+			break;
+		}
+	spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_remove_callback);
+
+int sensor_hub_set_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
+			   u32 field_index, int buffer_size, void *buffer)
+{
+	struct hid_report *report;
+	struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
+	__s32 *buf32 = buffer;
+	int i = 0;
+	int remaining_bytes;
+	__s32 value;
+	int ret = 0;
+
+	mutex_lock(&data->mutex);
+	report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
+	if (!report || (field_index >= report->maxfield)) {
+		ret = -EINVAL;
+		goto done_proc;
+	}
+
+	remaining_bytes = buffer_size % sizeof(__s32);
+	buffer_size = buffer_size / sizeof(__s32);
+	if (buffer_size) {
+		for (i = 0; i < buffer_size; ++i) {
+			hid_set_field(report->field[field_index], i,
+				      (__force __s32)cpu_to_le32(*buf32));
+			++buf32;
+		}
+	}
+	if (remaining_bytes) {
+		value = 0;
+		memcpy(&value, (u8 *)buf32, remaining_bytes);
+		hid_set_field(report->field[field_index], i,
+			      (__force __s32)cpu_to_le32(value));
+	}
+	hid_hw_request(hsdev->hdev, report, HID_REQ_SET_REPORT);
+	hid_hw_wait(hsdev->hdev);
+
+done_proc:
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_set_feature);
+
+int sensor_hub_get_feature(struct hid_sensor_hub_device *hsdev, u32 report_id,
+			   u32 field_index, int buffer_size, void *buffer)
+{
+	struct hid_report *report;
+	struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
+	int report_size;
+	int ret = 0;
+	u8 *val_ptr;
+	int buffer_index = 0;
+	int i;
+
+	memset(buffer, 0, buffer_size);
+
+	mutex_lock(&data->mutex);
+	report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
+	if (!report || (field_index >= report->maxfield) ||
+	    report->field[field_index]->report_count < 1) {
+		ret = -EINVAL;
+		goto done_proc;
+	}
+	hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
+	hid_hw_wait(hsdev->hdev);
+
+	/* calculate number of bytes required to read this field */
+	report_size = DIV_ROUND_UP(report->field[field_index]->report_size,
+				   8) *
+				   report->field[field_index]->report_count;
+	if (!report_size) {
+		ret = -EINVAL;
+		goto done_proc;
+	}
+	ret = min(report_size, buffer_size);
+
+	val_ptr = (u8 *)report->field[field_index]->value;
+	for (i = 0; i < report->field[field_index]->report_count; ++i) {
+		if (buffer_index >= ret)
+			break;
+
+		memcpy(&((u8 *)buffer)[buffer_index], val_ptr,
+		       report->field[field_index]->report_size / 8);
+		val_ptr += sizeof(__s32);
+		buffer_index += (report->field[field_index]->report_size / 8);
+	}
+
+done_proc:
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_get_feature);
+
+
+int sensor_hub_input_attr_get_raw_value(struct hid_sensor_hub_device *hsdev,
+					u32 usage_id,
+					u32 attr_usage_id, u32 report_id,
+					enum sensor_hub_read_flags flag,
+					bool is_signed)
+{
+	struct sensor_hub_data *data = hid_get_drvdata(hsdev->hdev);
+	unsigned long flags;
+	struct hid_report *report;
+	int ret_val = 0;
+
+	report = sensor_hub_report(report_id, hsdev->hdev,
+				   HID_INPUT_REPORT);
+	if (!report)
+		return -EINVAL;
+
+	mutex_lock(hsdev->mutex_ptr);
+	if (flag == SENSOR_HUB_SYNC) {
+		memset(&hsdev->pending, 0, sizeof(hsdev->pending));
+		init_completion(&hsdev->pending.ready);
+		hsdev->pending.usage_id = usage_id;
+		hsdev->pending.attr_usage_id = attr_usage_id;
+		hsdev->pending.raw_size = 0;
+
+		spin_lock_irqsave(&data->lock, flags);
+		hsdev->pending.status = true;
+		spin_unlock_irqrestore(&data->lock, flags);
+	}
+	mutex_lock(&data->mutex);
+	hid_hw_request(hsdev->hdev, report, HID_REQ_GET_REPORT);
+	mutex_unlock(&data->mutex);
+	if (flag == SENSOR_HUB_SYNC) {
+		wait_for_completion_interruptible_timeout(
+						&hsdev->pending.ready, HZ*5);
+		switch (hsdev->pending.raw_size) {
+		case 1:
+			if (is_signed)
+				ret_val = *(s8 *)hsdev->pending.raw_data;
+			else
+				ret_val = *(u8 *)hsdev->pending.raw_data;
+			break;
+		case 2:
+			if (is_signed)
+				ret_val = *(s16 *)hsdev->pending.raw_data;
+			else
+				ret_val = *(u16 *)hsdev->pending.raw_data;
+			break;
+		case 4:
+			ret_val = *(u32 *)hsdev->pending.raw_data;
+			break;
+		default:
+			ret_val = 0;
+		}
+		kfree(hsdev->pending.raw_data);
+		hsdev->pending.status = false;
+	}
+	mutex_unlock(hsdev->mutex_ptr);
+
+	return ret_val;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_input_attr_get_raw_value);
+
+int hid_sensor_get_usage_index(struct hid_sensor_hub_device *hsdev,
+				u32 report_id, int field_index, u32 usage_id)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	int i;
+
+	report = sensor_hub_report(report_id, hsdev->hdev, HID_FEATURE_REPORT);
+	if (!report || (field_index >= report->maxfield))
+		goto done_proc;
+
+	field = report->field[field_index];
+	for (i = 0; i < field->maxusage; ++i) {
+		if (field->usage[i].hid == usage_id)
+			return field->usage[i].usage_index;
+	}
+
+done_proc:
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(hid_sensor_get_usage_index);
+
+int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev,
+				u8 type,
+				u32 usage_id,
+				u32 attr_usage_id,
+				struct hid_sensor_hub_attribute_info *info)
+{
+	int ret = -1;
+	int i;
+	struct hid_report *report;
+	struct hid_field *field;
+	struct hid_report_enum *report_enum;
+	struct hid_device *hdev = hsdev->hdev;
+
+	/* Initialize with defaults */
+	info->usage_id = usage_id;
+	info->attrib_id = attr_usage_id;
+	info->report_id = -1;
+	info->index = -1;
+	info->units = -1;
+	info->unit_expo = -1;
+
+	report_enum = &hdev->report_enum[type];
+	list_for_each_entry(report, &report_enum->report_list, list) {
+		for (i = 0; i < report->maxfield; ++i) {
+			field = report->field[i];
+			if (field->maxusage) {
+				if (field->physical == usage_id &&
+					(field->logical == attr_usage_id ||
+					field->usage[0].hid ==
+							attr_usage_id) &&
+					(field->usage[0].collection_index >=
+					hsdev->start_collection_index) &&
+					(field->usage[0].collection_index <
+					hsdev->end_collection_index)) {
+
+					sensor_hub_fill_attr_info(info, i,
+								report->id,
+								field);
+					ret = 0;
+					break;
+				}
+			}
+		}
+
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_input_get_attribute_info);
+
+#ifdef CONFIG_PM
+static int sensor_hub_suspend(struct hid_device *hdev, pm_message_t message)
+{
+	struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
+	struct hid_sensor_hub_callbacks_list *callback;
+	unsigned long flags;
+
+	hid_dbg(hdev, " sensor_hub_suspend\n");
+	spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+	list_for_each_entry(callback, &pdata->dyn_callback_list, list) {
+		if (callback->usage_callback->suspend)
+			callback->usage_callback->suspend(
+					callback->hsdev, callback->priv);
+	}
+	spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+
+	return 0;
+}
+
+static int sensor_hub_resume(struct hid_device *hdev)
+{
+	struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
+	struct hid_sensor_hub_callbacks_list *callback;
+	unsigned long flags;
+
+	hid_dbg(hdev, " sensor_hub_resume\n");
+	spin_lock_irqsave(&pdata->dyn_callback_lock, flags);
+	list_for_each_entry(callback, &pdata->dyn_callback_list, list) {
+		if (callback->usage_callback->resume)
+			callback->usage_callback->resume(
+					callback->hsdev, callback->priv);
+	}
+	spin_unlock_irqrestore(&pdata->dyn_callback_lock, flags);
+
+	return 0;
+}
+
+static int sensor_hub_reset_resume(struct hid_device *hdev)
+{
+	return 0;
+}
+#endif
+
+/*
+ * Handle raw report as sent by device
+ */
+static int sensor_hub_raw_event(struct hid_device *hdev,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	int i;
+	u8 *ptr;
+	int sz;
+	struct sensor_hub_data *pdata = hid_get_drvdata(hdev);
+	unsigned long flags;
+	struct hid_sensor_hub_callbacks *callback = NULL;
+	struct hid_collection *collection = NULL;
+	void *priv = NULL;
+	struct hid_sensor_hub_device *hsdev = NULL;
+
+	hid_dbg(hdev, "sensor_hub_raw_event report id:0x%x size:%d type:%d\n",
+			 report->id, size, report->type);
+	hid_dbg(hdev, "maxfield:%d\n", report->maxfield);
+	if (report->type != HID_INPUT_REPORT)
+		return 1;
+
+	ptr = raw_data;
+	ptr++; /* Skip report id */
+
+	spin_lock_irqsave(&pdata->lock, flags);
+
+	for (i = 0; i < report->maxfield; ++i) {
+		hid_dbg(hdev, "%d collection_index:%x hid:%x sz:%x\n",
+				i, report->field[i]->usage->collection_index,
+				report->field[i]->usage->hid,
+				(report->field[i]->report_size *
+					report->field[i]->report_count)/8);
+		sz = (report->field[i]->report_size *
+					report->field[i]->report_count)/8;
+		collection = &hdev->collection[
+				report->field[i]->usage->collection_index];
+		hid_dbg(hdev, "collection->usage %x\n",
+					collection->usage);
+
+		callback = sensor_hub_get_callback(hdev,
+				report->field[i]->physical,
+				report->field[i]->usage[0].collection_index,
+				&hsdev, &priv);
+		if (!callback) {
+			ptr += sz;
+			continue;
+		}
+		if (hsdev->pending.status && (hsdev->pending.attr_usage_id ==
+					      report->field[i]->usage->hid ||
+					      hsdev->pending.attr_usage_id ==
+					      report->field[i]->logical)) {
+			hid_dbg(hdev, "data was pending ...\n");
+			hsdev->pending.raw_data = kmemdup(ptr, sz, GFP_ATOMIC);
+			if (hsdev->pending.raw_data)
+				hsdev->pending.raw_size = sz;
+			else
+				hsdev->pending.raw_size = 0;
+			complete(&hsdev->pending.ready);
+		}
+		if (callback->capture_sample) {
+			if (report->field[i]->logical)
+				callback->capture_sample(hsdev,
+					report->field[i]->logical, sz, ptr,
+					callback->pdev);
+			else
+				callback->capture_sample(hsdev,
+					report->field[i]->usage->hid, sz, ptr,
+					callback->pdev);
+		}
+		ptr += sz;
+	}
+	if (callback && collection && callback->send_event)
+		callback->send_event(hsdev, collection->usage,
+				callback->pdev);
+	spin_unlock_irqrestore(&pdata->lock, flags);
+
+	return 1;
+}
+
+int sensor_hub_device_open(struct hid_sensor_hub_device *hsdev)
+{
+	int ret = 0;
+	struct sensor_hub_data *data =  hid_get_drvdata(hsdev->hdev);
+
+	mutex_lock(&data->mutex);
+	if (!data->ref_cnt) {
+		ret = hid_hw_open(hsdev->hdev);
+		if (ret) {
+			hid_err(hsdev->hdev, "failed to open hid device\n");
+			mutex_unlock(&data->mutex);
+			return ret;
+		}
+	}
+	data->ref_cnt++;
+	mutex_unlock(&data->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sensor_hub_device_open);
+
+void sensor_hub_device_close(struct hid_sensor_hub_device *hsdev)
+{
+	struct sensor_hub_data *data =  hid_get_drvdata(hsdev->hdev);
+
+	mutex_lock(&data->mutex);
+	data->ref_cnt--;
+	if (!data->ref_cnt)
+		hid_hw_close(hsdev->hdev);
+	mutex_unlock(&data->mutex);
+}
+EXPORT_SYMBOL_GPL(sensor_hub_device_close);
+
+static __u8 *sensor_hub_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	/*
+	 * Checks if the report descriptor of Thinkpad Helix 2 has a logical
+	 * minimum for magnetic flux axis greater than the maximum.
+	 */
+	if (hdev->product == USB_DEVICE_ID_TEXAS_INSTRUMENTS_LENOVO_YOGA &&
+		*rsize == 2558 && rdesc[913] == 0x17 && rdesc[914] == 0x40 &&
+		rdesc[915] == 0x81 && rdesc[916] == 0x08 &&
+		rdesc[917] == 0x00 && rdesc[918] == 0x27 &&
+		rdesc[921] == 0x07 && rdesc[922] == 0x00) {
+		/* Sets negative logical minimum for mag x, y and z */
+		rdesc[914] = rdesc[935] = rdesc[956] = 0xc0;
+		rdesc[915] = rdesc[936] = rdesc[957] = 0x7e;
+		rdesc[916] = rdesc[937] = rdesc[958] = 0xf7;
+		rdesc[917] = rdesc[938] = rdesc[959] = 0xff;
+	}
+
+	return rdesc;
+}
+
+static int sensor_hub_probe(struct hid_device *hdev,
+				const struct hid_device_id *id)
+{
+	int ret;
+	struct sensor_hub_data *sd;
+	int i;
+	char *name;
+	int dev_cnt;
+	struct hid_sensor_hub_device *hsdev;
+	struct hid_sensor_hub_device *last_hsdev = NULL;
+	struct hid_sensor_hub_device *collection_hsdev = NULL;
+
+	sd = devm_kzalloc(&hdev->dev, sizeof(*sd), GFP_KERNEL);
+	if (!sd) {
+		hid_err(hdev, "cannot allocate Sensor data\n");
+		return -ENOMEM;
+	}
+
+	hid_set_drvdata(hdev, sd);
+	sd->quirks = id->driver_data;
+
+	spin_lock_init(&sd->lock);
+	spin_lock_init(&sd->dyn_callback_lock);
+	mutex_init(&sd->mutex);
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+	INIT_LIST_HEAD(&hdev->inputs);
+
+	ret = hid_hw_start(hdev, 0);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+	INIT_LIST_HEAD(&sd->dyn_callback_list);
+	sd->hid_sensor_client_cnt = 0;
+
+	dev_cnt = sensor_hub_get_physical_device_count(hdev);
+	if (dev_cnt > HID_MAX_PHY_DEVICES) {
+		hid_err(hdev, "Invalid Physical device count\n");
+		ret = -EINVAL;
+		goto err_stop_hw;
+	}
+	sd->hid_sensor_hub_client_devs = devm_kcalloc(&hdev->dev,
+						      dev_cnt,
+						      sizeof(struct mfd_cell),
+						      GFP_KERNEL);
+	if (sd->hid_sensor_hub_client_devs == NULL) {
+		hid_err(hdev, "Failed to allocate memory for mfd cells\n");
+		ret = -ENOMEM;
+		goto err_stop_hw;
+	}
+
+	for (i = 0; i < hdev->maxcollection; ++i) {
+		struct hid_collection *collection = &hdev->collection[i];
+
+		if (collection->type == HID_COLLECTION_PHYSICAL ||
+		    collection->type == HID_COLLECTION_APPLICATION) {
+
+			hsdev = devm_kzalloc(&hdev->dev, sizeof(*hsdev),
+					     GFP_KERNEL);
+			if (!hsdev) {
+				hid_err(hdev, "cannot allocate hid_sensor_hub_device\n");
+				ret = -ENOMEM;
+				goto err_stop_hw;
+			}
+			hsdev->hdev = hdev;
+			hsdev->vendor_id = hdev->vendor;
+			hsdev->product_id = hdev->product;
+			hsdev->usage = collection->usage;
+			hsdev->mutex_ptr = devm_kzalloc(&hdev->dev,
+							sizeof(struct mutex),
+							GFP_KERNEL);
+			if (!hsdev->mutex_ptr) {
+				ret = -ENOMEM;
+				goto err_stop_hw;
+			}
+			mutex_init(hsdev->mutex_ptr);
+			hsdev->start_collection_index = i;
+			if (last_hsdev)
+				last_hsdev->end_collection_index = i;
+			last_hsdev = hsdev;
+			name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+					      "HID-SENSOR-%x",
+					      collection->usage);
+			if (name == NULL) {
+				hid_err(hdev, "Failed MFD device name\n");
+				ret = -ENOMEM;
+				goto err_stop_hw;
+			}
+			sd->hid_sensor_hub_client_devs[
+				sd->hid_sensor_client_cnt].name = name;
+			sd->hid_sensor_hub_client_devs[
+				sd->hid_sensor_client_cnt].platform_data =
+							hsdev;
+			sd->hid_sensor_hub_client_devs[
+				sd->hid_sensor_client_cnt].pdata_size =
+							sizeof(*hsdev);
+			hid_dbg(hdev, "Adding %s:%d\n", name,
+					hsdev->start_collection_index);
+			sd->hid_sensor_client_cnt++;
+			if (collection_hsdev)
+				collection_hsdev->end_collection_index = i;
+			if (collection->type == HID_COLLECTION_APPLICATION &&
+			    collection->usage == HID_USAGE_SENSOR_COLLECTION)
+				collection_hsdev = hsdev;
+		}
+	}
+	if (last_hsdev)
+		last_hsdev->end_collection_index = i;
+	if (collection_hsdev)
+		collection_hsdev->end_collection_index = i;
+
+	ret = mfd_add_hotplug_devices(&hdev->dev,
+			sd->hid_sensor_hub_client_devs,
+			sd->hid_sensor_client_cnt);
+	if (ret < 0)
+		goto err_stop_hw;
+
+	return ret;
+
+err_stop_hw:
+	hid_hw_stop(hdev);
+
+	return ret;
+}
+
+static void sensor_hub_remove(struct hid_device *hdev)
+{
+	struct sensor_hub_data *data = hid_get_drvdata(hdev);
+	unsigned long flags;
+	int i;
+
+	hid_dbg(hdev, " hardware removed\n");
+	hid_hw_close(hdev);
+	hid_hw_stop(hdev);
+	spin_lock_irqsave(&data->lock, flags);
+	for (i = 0; i < data->hid_sensor_client_cnt; ++i) {
+		struct hid_sensor_hub_device *hsdev =
+			data->hid_sensor_hub_client_devs[i].platform_data;
+		if (hsdev->pending.status)
+			complete(&hsdev->pending.ready);
+	}
+	spin_unlock_irqrestore(&data->lock, flags);
+	mfd_remove_devices(&hdev->dev);
+	hid_set_drvdata(hdev, NULL);
+	mutex_destroy(&data->mutex);
+}
+
+static const struct hid_device_id sensor_hub_devices[] = {
+	{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_SENSOR_HUB, HID_ANY_ID,
+		     HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, sensor_hub_devices);
+
+static struct hid_driver sensor_hub_driver = {
+	.name = "hid-sensor-hub",
+	.id_table = sensor_hub_devices,
+	.probe = sensor_hub_probe,
+	.remove = sensor_hub_remove,
+	.raw_event = sensor_hub_raw_event,
+	.report_fixup = sensor_hub_report_fixup,
+#ifdef CONFIG_PM
+	.suspend = sensor_hub_suspend,
+	.resume = sensor_hub_resume,
+	.reset_resume = sensor_hub_reset_resume,
+#endif
+};
+module_hid_driver(sensor_hub_driver);
+
+MODULE_DESCRIPTION("HID Sensor Hub driver");
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@intel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c
new file mode 100644
index 0000000..36b6470
--- /dev/null
+++ b/drivers/hid/hid-sjoy.c
@@ -0,0 +1,185 @@
+/*
+ *  Force feedback support for SmartJoy PLUS PS2->USB adapter
+ *
+ *  Copyright (c) 2009 Jussi Kivilinna <jussi.kivilinna@mbnet.fi>
+ *
+ *  Based of hid-pl.c and hid-gaff.c
+ *   Copyright (c) 2007, 2009 Anssi Hannula <anssi.hannula@gmail.com>
+ *   Copyright (c) 2008 Lukasz Lubojanski <lukasz@lubojanski.info>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define DEBUG */
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+#ifdef CONFIG_SMARTJOYPLUS_FF
+
+struct sjoyff_device {
+	struct hid_report *report;
+};
+
+static int hid_sjoyff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct sjoyff_device *sjoyff = data;
+	u32 left, right;
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	dev_dbg(&dev->dev, "called with 0x%08x 0x%08x\n", left, right);
+
+	left = left * 0xff / 0xffff;
+	right = (right != 0); /* on/off only */
+
+	sjoyff->report->field[0]->value[1] = right;
+	sjoyff->report->field[0]->value[2] = left;
+	dev_dbg(&dev->dev, "running with 0x%02x 0x%02x\n", left, right);
+	hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int sjoyff_init(struct hid_device *hid)
+{
+	struct sjoyff_device *sjoyff;
+	struct hid_report *report;
+	struct hid_input *hidinput;
+	struct list_head *report_list =
+			&hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct list_head *report_ptr = report_list;
+	struct input_dev *dev;
+	int error;
+
+	if (list_empty(report_list)) {
+		hid_err(hid, "no output reports found\n");
+		return -ENODEV;
+	}
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		report_ptr = report_ptr->next;
+
+		if (report_ptr == report_list) {
+			hid_err(hid, "required output report is missing\n");
+			return -ENODEV;
+		}
+
+		report = list_entry(report_ptr, struct hid_report, list);
+		if (report->maxfield < 1) {
+			hid_err(hid, "no fields in the report\n");
+			return -ENODEV;
+		}
+
+		if (report->field[0]->report_count < 3) {
+			hid_err(hid, "not enough values in the field\n");
+			return -ENODEV;
+		}
+
+		sjoyff = kzalloc(sizeof(struct sjoyff_device), GFP_KERNEL);
+		if (!sjoyff)
+			return -ENOMEM;
+
+		dev = hidinput->input;
+
+		set_bit(FF_RUMBLE, dev->ffbit);
+
+		error = input_ff_create_memless(dev, sjoyff, hid_sjoyff_play);
+		if (error) {
+			kfree(sjoyff);
+			return error;
+		}
+
+		sjoyff->report = report;
+		sjoyff->report->field[0]->value[0] = 0x01;
+		sjoyff->report->field[0]->value[1] = 0x00;
+		sjoyff->report->field[0]->value[2] = 0x00;
+		hid_hw_request(hid, sjoyff->report, HID_REQ_SET_REPORT);
+	}
+
+	hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
+
+	return 0;
+}
+#else
+static inline int sjoyff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	hdev->quirks |= id->driver_data;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	sjoyff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id sjoy_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO),
+		.driver_data = HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_DUAL_BOX_PRO),
+		.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
+			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO),
+		.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
+			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SUPER_JOY_BOX_3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD),
+		.driver_data = HID_QUIRK_MULTI_INPUT |
+			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_PLAYDOTCOM, USB_DEVICE_ID_PLAYDOTCOM_EMS_USBII),
+		.driver_data = HID_QUIRK_MULTI_INPUT |
+			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, sjoy_devices);
+
+static struct hid_driver sjoy_driver = {
+	.name = "smartjoyplus",
+	.id_table = sjoy_devices,
+	.probe = sjoy_probe,
+};
+module_hid_driver(sjoy_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jussi Kivilinna");
+
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
new file mode 100644
index 0000000..9671a4b
--- /dev/null
+++ b/drivers/hid/hid-sony.c
@@ -0,0 +1,3021 @@
+/*
+ *  HID driver for Sony / PS2 / PS3 / PS4 BD devices.
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2008 Jiri Slaby
+ *  Copyright (c) 2012 David Dillow <dave@thedillows.org>
+ *  Copyright (c) 2006-2013 Jiri Kosina
+ *  Copyright (c) 2013 Colin Leitner <colin.leitner@gmail.com>
+ *  Copyright (c) 2014-2016 Frank Praznik <frank.praznik@gmail.com>
+ *  Copyright (c) 2018 Todd Kelner
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * NOTE: in order for the Sony PS3 BD Remote Control to be found by
+ * a Bluetooth host, the key combination Start+Enter has to be kept pressed
+ * for about 7 seconds with the Bluetooth Host Controller in discovering mode.
+ *
+ * There will be no PIN request from the device.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/input/mt.h>
+#include <linux/crc32.h>
+#include <asm/unaligned.h>
+
+#include "hid-ids.h"
+
+#define VAIO_RDESC_CONSTANT       BIT(0)
+#define SIXAXIS_CONTROLLER_USB    BIT(1)
+#define SIXAXIS_CONTROLLER_BT     BIT(2)
+#define BUZZ_CONTROLLER           BIT(3)
+#define PS3REMOTE                 BIT(4)
+#define DUALSHOCK4_CONTROLLER_USB BIT(5)
+#define DUALSHOCK4_CONTROLLER_BT  BIT(6)
+#define DUALSHOCK4_DONGLE         BIT(7)
+#define MOTION_CONTROLLER_USB     BIT(8)
+#define MOTION_CONTROLLER_BT      BIT(9)
+#define NAVIGATION_CONTROLLER_USB BIT(10)
+#define NAVIGATION_CONTROLLER_BT  BIT(11)
+#define SINO_LITE_CONTROLLER      BIT(12)
+#define FUTUREMAX_DANCE_MAT       BIT(13)
+#define NSG_MR5U_REMOTE_BT        BIT(14)
+#define NSG_MR7U_REMOTE_BT        BIT(15)
+
+#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
+#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
+#define NAVIGATION_CONTROLLER (NAVIGATION_CONTROLLER_USB |\
+				NAVIGATION_CONTROLLER_BT)
+#define DUALSHOCK4_CONTROLLER (DUALSHOCK4_CONTROLLER_USB |\
+				DUALSHOCK4_CONTROLLER_BT | \
+				DUALSHOCK4_DONGLE)
+#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\
+				DUALSHOCK4_CONTROLLER | MOTION_CONTROLLER |\
+				NAVIGATION_CONTROLLER)
+#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER |\
+				MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER)
+#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER |\
+				MOTION_CONTROLLER)
+#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | DUALSHOCK4_CONTROLLER_BT |\
+			MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
+#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
+
+#define MAX_LEDS 4
+#define NSG_MRXU_MAX_X 1667
+#define NSG_MRXU_MAX_Y 1868
+
+
+/* PS/3 Motion controller */
+static u8 motion_rdesc[] = {
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x04,         /*  Usage (Joystick),                   */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0xA1, 0x02,         /*      Collection (Logical),           */
+	0x85, 0x01,         /*          Report ID (1),              */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x15,         /*          Report Count (21),          */
+	0x15, 0x00,         /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x35, 0x00,         /*          Physical Minimum (0),       */
+	0x45, 0x01,         /*          Physical Maximum (1),       */
+	0x05, 0x09,         /*          Usage Page (Button),        */
+	0x19, 0x01,         /*          Usage Minimum (01h),        */
+	0x29, 0x15,         /*          Usage Maximum (15h),        */
+	0x81, 0x02,         /*          Input (Variable),           * Buttons */
+	0x95, 0x0B,         /*          Report Count (11),          */
+	0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+	0x81, 0x03,         /*          Input (Constant, Variable), * Padding */
+	0x15, 0x00,         /*          Logical Minimum (0),        */
+	0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0xA1, 0x00,         /*          Collection (Physical),      */
+	0x75, 0x08,         /*              Report Size (8),        */
+	0x95, 0x01,         /*              Report Count (1),       */
+	0x35, 0x00,         /*              Physical Minimum (0),   */
+	0x46, 0xFF, 0x00,   /*              Physical Maximum (255), */
+	0x09, 0x30,         /*              Usage (X),              */
+	0x81, 0x02,         /*              Input (Variable),       * Trigger */
+	0xC0,               /*          End Collection,             */
+	0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x07,         /*          Report Count (7),           * skip 7 bytes */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x46, 0xFF, 0xFF,   /*          Physical Maximum (65535),   */
+	0x27, 0xFF, 0xFF, 0x00, 0x00, /*      Logical Maximum (65535),    */
+	0x95, 0x03,         /*          Report Count (3),           * 3x Accels */
+	0x09, 0x33,         /*              Usage (rX),             */
+	0x09, 0x34,         /*              Usage (rY),             */
+	0x09, 0x35,         /*              Usage (rZ),             */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+	0x95, 0x03,         /*          Report Count (3),           * Skip Accels 2nd frame */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0x95, 0x03,         /*          Report Count (3),           * 3x Gyros */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+	0x95, 0x03,         /*          Report Count (3),           * Skip Gyros 2nd frame */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x75, 0x0C,         /*          Report Size (12),           */
+	0x46, 0xFF, 0x0F,   /*          Physical Maximum (4095),    */
+	0x26, 0xFF, 0x0F,   /*          Logical Maximum (4095),     */
+	0x95, 0x04,         /*          Report Count (4),           * Skip Temp and Magnetometers */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x46, 0xFF, 0x00,   /*          Physical Maximum (255),     */
+	0x26, 0xFF, 0x00,   /*          Logical Maximum (255),      */
+	0x95, 0x06,         /*          Report Count (6),           * Skip Timestamp and Extension Bytes */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x30,         /*          Report Count (48),          */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0x91, 0x02,         /*          Output (Variable),          */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x30,         /*          Report Count (48),          */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0xB1, 0x02,         /*          Feature (Variable),         */
+	0xC0,               /*      End Collection,                 */
+	0xA1, 0x02,         /*      Collection (Logical),           */
+	0x85, 0x02,         /*          Report ID (2),              */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x30,         /*          Report Count (48),          */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0xB1, 0x02,         /*          Feature (Variable),         */
+	0xC0,               /*      End Collection,                 */
+	0xA1, 0x02,         /*      Collection (Logical),           */
+	0x85, 0xEE,         /*          Report ID (238),            */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x30,         /*          Report Count (48),          */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0xB1, 0x02,         /*          Feature (Variable),         */
+	0xC0,               /*      End Collection,                 */
+	0xA1, 0x02,         /*      Collection (Logical),           */
+	0x85, 0xEF,         /*          Report ID (239),            */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x30,         /*          Report Count (48),          */
+	0x09, 0x01,         /*          Usage (Pointer),            */
+	0xB1, 0x02,         /*          Feature (Variable),         */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+static u8 ps3remote_rdesc[] = {
+	0x05, 0x01,          /* GUsagePage Generic Desktop */
+	0x09, 0x05,          /* LUsage 0x05 [Game Pad] */
+	0xA1, 0x01,          /* MCollection Application (mouse, keyboard) */
+
+	 /* Use collection 1 for joypad buttons */
+	 0xA1, 0x02,         /* MCollection Logical (interrelated data) */
+
+	  /*
+	   * Ignore the 1st byte, maybe it is used for a controller
+	   * number but it's not needed for correct operation
+	   */
+	  0x75, 0x08,        /* GReportSize 0x08 [8] */
+	  0x95, 0x01,        /* GReportCount 0x01 [1] */
+	  0x81, 0x01,        /* MInput 0x01 (Const[0] Arr[1] Abs[2]) */
+
+	  /*
+	   * Bytes from 2nd to 4th are a bitmap for joypad buttons, for these
+	   * buttons multiple keypresses are allowed
+	   */
+	  0x05, 0x09,        /* GUsagePage Button */
+	  0x19, 0x01,        /* LUsageMinimum 0x01 [Button 1 (primary/trigger)] */
+	  0x29, 0x18,        /* LUsageMaximum 0x18 [Button 24] */
+	  0x14,              /* GLogicalMinimum [0] */
+	  0x25, 0x01,        /* GLogicalMaximum 0x01 [1] */
+	  0x75, 0x01,        /* GReportSize 0x01 [1] */
+	  0x95, 0x18,        /* GReportCount 0x18 [24] */
+	  0x81, 0x02,        /* MInput 0x02 (Data[0] Var[1] Abs[2]) */
+
+	  0xC0,              /* MEndCollection */
+
+	 /* Use collection 2 for remote control buttons */
+	 0xA1, 0x02,         /* MCollection Logical (interrelated data) */
+
+	  /* 5th byte is used for remote control buttons */
+	  0x05, 0x09,        /* GUsagePage Button */
+	  0x18,              /* LUsageMinimum [No button pressed] */
+	  0x29, 0xFE,        /* LUsageMaximum 0xFE [Button 254] */
+	  0x14,              /* GLogicalMinimum [0] */
+	  0x26, 0xFE, 0x00,  /* GLogicalMaximum 0x00FE [254] */
+	  0x75, 0x08,        /* GReportSize 0x08 [8] */
+	  0x95, 0x01,        /* GReportCount 0x01 [1] */
+	  0x80,              /* MInput  */
+
+	  /*
+	   * Ignore bytes from 6th to 11th, 6th to 10th are always constant at
+	   * 0xff and 11th is for press indication
+	   */
+	  0x75, 0x08,        /* GReportSize 0x08 [8] */
+	  0x95, 0x06,        /* GReportCount 0x06 [6] */
+	  0x81, 0x01,        /* MInput 0x01 (Const[0] Arr[1] Abs[2]) */
+
+	  /* 12th byte is for battery strength */
+	  0x05, 0x06,        /* GUsagePage Generic Device Controls */
+	  0x09, 0x20,        /* LUsage 0x20 [Battery Strength] */
+	  0x14,              /* GLogicalMinimum [0] */
+	  0x25, 0x05,        /* GLogicalMaximum 0x05 [5] */
+	  0x75, 0x08,        /* GReportSize 0x08 [8] */
+	  0x95, 0x01,        /* GReportCount 0x01 [1] */
+	  0x81, 0x02,        /* MInput 0x02 (Data[0] Var[1] Abs[2]) */
+
+	  0xC0,              /* MEndCollection */
+
+	 0xC0                /* MEndCollection [Game Pad] */
+};
+
+static const unsigned int ps3remote_keymap_joypad_buttons[] = {
+	[0x01] = KEY_SELECT,
+	[0x02] = BTN_THUMBL,		/* L3 */
+	[0x03] = BTN_THUMBR,		/* R3 */
+	[0x04] = BTN_START,
+	[0x05] = KEY_UP,
+	[0x06] = KEY_RIGHT,
+	[0x07] = KEY_DOWN,
+	[0x08] = KEY_LEFT,
+	[0x09] = BTN_TL2,		/* L2 */
+	[0x0a] = BTN_TR2,		/* R2 */
+	[0x0b] = BTN_TL,		/* L1 */
+	[0x0c] = BTN_TR,		/* R1 */
+	[0x0d] = KEY_OPTION,		/* options/triangle */
+	[0x0e] = KEY_BACK,		/* back/circle */
+	[0x0f] = BTN_0,			/* cross */
+	[0x10] = KEY_SCREEN,		/* view/square */
+	[0x11] = KEY_HOMEPAGE,		/* PS button */
+	[0x14] = KEY_ENTER,
+};
+static const unsigned int ps3remote_keymap_remote_buttons[] = {
+	[0x00] = KEY_1,
+	[0x01] = KEY_2,
+	[0x02] = KEY_3,
+	[0x03] = KEY_4,
+	[0x04] = KEY_5,
+	[0x05] = KEY_6,
+	[0x06] = KEY_7,
+	[0x07] = KEY_8,
+	[0x08] = KEY_9,
+	[0x09] = KEY_0,
+	[0x0e] = KEY_ESC,		/* return */
+	[0x0f] = KEY_CLEAR,
+	[0x16] = KEY_EJECTCD,
+	[0x1a] = KEY_MENU,		/* top menu */
+	[0x28] = KEY_TIME,
+	[0x30] = KEY_PREVIOUS,
+	[0x31] = KEY_NEXT,
+	[0x32] = KEY_PLAY,
+	[0x33] = KEY_REWIND,		/* scan back */
+	[0x34] = KEY_FORWARD,		/* scan forward */
+	[0x38] = KEY_STOP,
+	[0x39] = KEY_PAUSE,
+	[0x40] = KEY_CONTEXT_MENU,	/* pop up/menu */
+	[0x60] = KEY_FRAMEBACK,		/* slow/step back */
+	[0x61] = KEY_FRAMEFORWARD,	/* slow/step forward */
+	[0x63] = KEY_SUBTITLE,
+	[0x64] = KEY_AUDIO,
+	[0x65] = KEY_ANGLE,
+	[0x70] = KEY_INFO,		/* display */
+	[0x80] = KEY_BLUE,
+	[0x81] = KEY_RED,
+	[0x82] = KEY_GREEN,
+	[0x83] = KEY_YELLOW,
+};
+
+static const unsigned int buzz_keymap[] = {
+	/*
+	 * The controller has 4 remote buzzers, each with one LED and 5
+	 * buttons.
+	 *
+	 * We use the mapping chosen by the controller, which is:
+	 *
+	 * Key          Offset
+	 * -------------------
+	 * Buzz              1
+	 * Blue              5
+	 * Orange            4
+	 * Green             3
+	 * Yellow            2
+	 *
+	 * So, for example, the orange button on the third buzzer is mapped to
+	 * BTN_TRIGGER_HAPPY14
+	 */
+	 [1] = BTN_TRIGGER_HAPPY1,
+	 [2] = BTN_TRIGGER_HAPPY2,
+	 [3] = BTN_TRIGGER_HAPPY3,
+	 [4] = BTN_TRIGGER_HAPPY4,
+	 [5] = BTN_TRIGGER_HAPPY5,
+	 [6] = BTN_TRIGGER_HAPPY6,
+	 [7] = BTN_TRIGGER_HAPPY7,
+	 [8] = BTN_TRIGGER_HAPPY8,
+	 [9] = BTN_TRIGGER_HAPPY9,
+	[10] = BTN_TRIGGER_HAPPY10,
+	[11] = BTN_TRIGGER_HAPPY11,
+	[12] = BTN_TRIGGER_HAPPY12,
+	[13] = BTN_TRIGGER_HAPPY13,
+	[14] = BTN_TRIGGER_HAPPY14,
+	[15] = BTN_TRIGGER_HAPPY15,
+	[16] = BTN_TRIGGER_HAPPY16,
+	[17] = BTN_TRIGGER_HAPPY17,
+	[18] = BTN_TRIGGER_HAPPY18,
+	[19] = BTN_TRIGGER_HAPPY19,
+	[20] = BTN_TRIGGER_HAPPY20,
+};
+
+/* The Navigation controller is a partial DS3 and uses the same HID report
+ * and hence the same keymap indices, however not not all axes/buttons
+ * are physically present. We use the same axis and button mapping as
+ * the DS3, which uses the Linux gamepad spec.
+ */
+static const unsigned int navigation_absmap[] = {
+	[0x30] = ABS_X,
+	[0x31] = ABS_Y,
+	[0x33] = ABS_Z, /* L2 */
+};
+
+/* Buttons not physically available on the device, but still available
+ * in the reports are explicitly set to 0 for documentation purposes.
+ */
+static const unsigned int navigation_keymap[] = {
+	[0x01] = 0, /* Select */
+	[0x02] = BTN_THUMBL, /* L3 */
+	[0x03] = 0, /* R3 */
+	[0x04] = 0, /* Start */
+	[0x05] = BTN_DPAD_UP, /* Up */
+	[0x06] = BTN_DPAD_RIGHT, /* Right */
+	[0x07] = BTN_DPAD_DOWN, /* Down */
+	[0x08] = BTN_DPAD_LEFT, /* Left */
+	[0x09] = BTN_TL2, /* L2 */
+	[0x0a] = 0, /* R2 */
+	[0x0b] = BTN_TL, /* L1 */
+	[0x0c] = 0, /* R1 */
+	[0x0d] = BTN_NORTH, /* Triangle */
+	[0x0e] = BTN_EAST, /* Circle */
+	[0x0f] = BTN_SOUTH, /* Cross */
+	[0x10] = BTN_WEST, /* Square */
+	[0x11] = BTN_MODE, /* PS */
+};
+
+static const unsigned int sixaxis_absmap[] = {
+	[0x30] = ABS_X,
+	[0x31] = ABS_Y,
+	[0x32] = ABS_RX, /* right stick X */
+	[0x35] = ABS_RY, /* right stick Y */
+};
+
+static const unsigned int sixaxis_keymap[] = {
+	[0x01] = BTN_SELECT, /* Select */
+	[0x02] = BTN_THUMBL, /* L3 */
+	[0x03] = BTN_THUMBR, /* R3 */
+	[0x04] = BTN_START, /* Start */
+	[0x05] = BTN_DPAD_UP, /* Up */
+	[0x06] = BTN_DPAD_RIGHT, /* Right */
+	[0x07] = BTN_DPAD_DOWN, /* Down */
+	[0x08] = BTN_DPAD_LEFT, /* Left */
+	[0x09] = BTN_TL2, /* L2 */
+	[0x0a] = BTN_TR2, /* R2 */
+	[0x0b] = BTN_TL, /* L1 */
+	[0x0c] = BTN_TR, /* R1 */
+	[0x0d] = BTN_NORTH, /* Triangle */
+	[0x0e] = BTN_EAST, /* Circle */
+	[0x0f] = BTN_SOUTH, /* Cross */
+	[0x10] = BTN_WEST, /* Square */
+	[0x11] = BTN_MODE, /* PS */
+};
+
+static const unsigned int ds4_absmap[] = {
+	[0x30] = ABS_X,
+	[0x31] = ABS_Y,
+	[0x32] = ABS_RX, /* right stick X */
+	[0x33] = ABS_Z, /* L2 */
+	[0x34] = ABS_RZ, /* R2 */
+	[0x35] = ABS_RY, /* right stick Y */
+};
+
+static const unsigned int ds4_keymap[] = {
+	[0x1] = BTN_WEST, /* Square */
+	[0x2] = BTN_SOUTH, /* Cross */
+	[0x3] = BTN_EAST, /* Circle */
+	[0x4] = BTN_NORTH, /* Triangle */
+	[0x5] = BTN_TL, /* L1 */
+	[0x6] = BTN_TR, /* R1 */
+	[0x7] = BTN_TL2, /* L2 */
+	[0x8] = BTN_TR2, /* R2 */
+	[0x9] = BTN_SELECT, /* Share */
+	[0xa] = BTN_START, /* Options */
+	[0xb] = BTN_THUMBL, /* L3 */
+	[0xc] = BTN_THUMBR, /* R3 */
+	[0xd] = BTN_MODE, /* PS */
+};
+
+static const struct {int x; int y; } ds4_hat_mapping[] = {
+	{0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1},
+	{0, 0}
+};
+
+static enum power_supply_property sony_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_STATUS,
+};
+
+struct sixaxis_led {
+	u8 time_enabled; /* the total time the led is active (0xff means forever) */
+	u8 duty_length;  /* how long a cycle is in deciseconds (0 means "really fast") */
+	u8 enabled;
+	u8 duty_off; /* % of duty_length the led is off (0xff means 100%) */
+	u8 duty_on;  /* % of duty_length the led is on (0xff mean 100%) */
+} __packed;
+
+struct sixaxis_rumble {
+	u8 padding;
+	u8 right_duration; /* Right motor duration (0xff means forever) */
+	u8 right_motor_on; /* Right (small) motor on/off, only supports values of 0 or 1 (off/on) */
+	u8 left_duration;    /* Left motor duration (0xff means forever) */
+	u8 left_motor_force; /* left (large) motor, supports force values from 0 to 255 */
+} __packed;
+
+struct sixaxis_output_report {
+	u8 report_id;
+	struct sixaxis_rumble rumble;
+	u8 padding[4];
+	u8 leds_bitmap; /* bitmap of enabled LEDs: LED_1 = 0x02, LED_2 = 0x04, ... */
+	struct sixaxis_led led[4];    /* LEDx at (4 - x) */
+	struct sixaxis_led _reserved; /* LED5, not actually soldered */
+} __packed;
+
+union sixaxis_output_report_01 {
+	struct sixaxis_output_report data;
+	u8 buf[36];
+};
+
+struct motion_output_report_02 {
+	u8 type, zero;
+	u8 r, g, b;
+	u8 zero2;
+	u8 rumble;
+};
+
+#define DS4_FEATURE_REPORT_0x02_SIZE 37
+#define DS4_FEATURE_REPORT_0x05_SIZE 41
+#define DS4_FEATURE_REPORT_0x81_SIZE 7
+#define DS4_FEATURE_REPORT_0xA3_SIZE 49
+#define DS4_INPUT_REPORT_0x11_SIZE 78
+#define DS4_OUTPUT_REPORT_0x05_SIZE 32
+#define DS4_OUTPUT_REPORT_0x11_SIZE 78
+#define SIXAXIS_REPORT_0xF2_SIZE 17
+#define SIXAXIS_REPORT_0xF5_SIZE 8
+#define MOTION_REPORT_0x02_SIZE 49
+
+/* Offsets relative to USB input report (0x1). Bluetooth (0x11) requires an
+ * additional +2.
+ */
+#define DS4_INPUT_REPORT_AXIS_OFFSET      1
+#define DS4_INPUT_REPORT_BUTTON_OFFSET    5
+#define DS4_INPUT_REPORT_TIMESTAMP_OFFSET 10
+#define DS4_INPUT_REPORT_GYRO_X_OFFSET   13
+#define DS4_INPUT_REPORT_BATTERY_OFFSET  30
+#define DS4_INPUT_REPORT_TOUCHPAD_OFFSET 33
+
+#define SENSOR_SUFFIX " Motion Sensors"
+#define DS4_TOUCHPAD_SUFFIX " Touchpad"
+
+/* Default to 4ms poll interval, which is same as USB (not adjustable). */
+#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4
+#define DS4_BT_MAX_POLL_INTERVAL_MS 62
+#define DS4_GYRO_RES_PER_DEG_S 1024
+#define DS4_ACC_RES_PER_G      8192
+
+#define SIXAXIS_INPUT_REPORT_ACC_X_OFFSET 41
+#define SIXAXIS_ACC_RES_PER_G 113
+
+static DEFINE_SPINLOCK(sony_dev_list_lock);
+static LIST_HEAD(sony_device_list);
+static DEFINE_IDA(sony_device_id_allocator);
+
+/* Used for calibration of DS4 accelerometer and gyro. */
+struct ds4_calibration_data {
+	int abs_code;
+	short bias;
+	/* Calibration requires scaling against a sensitivity value, which is a
+	 * float. Store sensitivity as a fraction to limit floating point
+	 * calculations until final calibration.
+	 */
+	int sens_numer;
+	int sens_denom;
+};
+
+enum ds4_dongle_state {
+	DONGLE_DISCONNECTED,
+	DONGLE_CALIBRATING,
+	DONGLE_CONNECTED,
+	DONGLE_DISABLED
+};
+
+enum sony_worker {
+	SONY_WORKER_STATE,
+	SONY_WORKER_HOTPLUG
+};
+
+struct sony_sc {
+	spinlock_t lock;
+	struct list_head list_node;
+	struct hid_device *hdev;
+	struct input_dev *touchpad;
+	struct input_dev *sensor_dev;
+	struct led_classdev *leds[MAX_LEDS];
+	unsigned long quirks;
+	struct work_struct hotplug_worker;
+	struct work_struct state_worker;
+	void (*send_output_report)(struct sony_sc *);
+	struct power_supply *battery;
+	struct power_supply_desc battery_desc;
+	int device_id;
+	unsigned fw_version;
+	unsigned hw_version;
+	u8 *output_report_dmabuf;
+
+#ifdef CONFIG_SONY_FF
+	u8 left;
+	u8 right;
+#endif
+
+	u8 mac_address[6];
+	u8 hotplug_worker_initialized;
+	u8 state_worker_initialized;
+	u8 defer_initialization;
+	u8 cable_state;
+	u8 battery_charging;
+	u8 battery_capacity;
+	u8 led_state[MAX_LEDS];
+	u8 led_delay_on[MAX_LEDS];
+	u8 led_delay_off[MAX_LEDS];
+	u8 led_count;
+
+	bool timestamp_initialized;
+	u16 prev_timestamp;
+	unsigned int timestamp_us;
+
+	u8 ds4_bt_poll_interval;
+	enum ds4_dongle_state ds4_dongle_state;
+	/* DS4 calibration data */
+	struct ds4_calibration_data ds4_calib_data[6];
+};
+
+static void sony_set_leds(struct sony_sc *sc);
+
+static inline void sony_schedule_work(struct sony_sc *sc,
+				      enum sony_worker which)
+{
+	switch (which) {
+	case SONY_WORKER_STATE:
+		if (!sc->defer_initialization)
+			schedule_work(&sc->state_worker);
+		break;
+	case SONY_WORKER_HOTPLUG:
+		if (sc->hotplug_worker_initialized)
+			schedule_work(&sc->hotplug_worker);
+		break;
+	}
+}
+
+static ssize_t ds4_show_poll_interval(struct device *dev,
+				struct device_attribute
+				*attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%i\n", sc->ds4_bt_poll_interval);
+}
+
+static ssize_t ds4_store_poll_interval(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+	unsigned long flags;
+	u8 interval;
+
+	if (kstrtou8(buf, 0, &interval))
+		return -EINVAL;
+
+	if (interval > DS4_BT_MAX_POLL_INTERVAL_MS)
+		return -EINVAL;
+
+	spin_lock_irqsave(&sc->lock, flags);
+	sc->ds4_bt_poll_interval = interval;
+	spin_unlock_irqrestore(&sc->lock, flags);
+
+	sony_schedule_work(sc, SONY_WORKER_STATE);
+
+	return count;
+}
+
+static DEVICE_ATTR(bt_poll_interval, 0644, ds4_show_poll_interval,
+		ds4_store_poll_interval);
+
+static ssize_t sony_show_firmware_version(struct device *dev,
+				struct device_attribute
+				*attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "0x%04x\n", sc->fw_version);
+}
+
+static DEVICE_ATTR(firmware_version, 0444, sony_show_firmware_version, NULL);
+
+static ssize_t sony_show_hardware_version(struct device *dev,
+				struct device_attribute
+				*attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "0x%04x\n", sc->hw_version);
+}
+
+static DEVICE_ATTR(hardware_version, 0444, sony_show_hardware_version, NULL);
+
+static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc,
+			     unsigned int *rsize)
+{
+	*rsize = sizeof(motion_rdesc);
+	return motion_rdesc;
+}
+
+static u8 *ps3remote_fixup(struct hid_device *hdev, u8 *rdesc,
+			     unsigned int *rsize)
+{
+	*rsize = sizeof(ps3remote_rdesc);
+	return ps3remote_rdesc;
+}
+
+static int ps3remote_mapping(struct hid_device *hdev, struct hid_input *hi,
+			     struct hid_field *field, struct hid_usage *usage,
+			     unsigned long **bit, int *max)
+{
+	unsigned int key = usage->hid & HID_USAGE;
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+		return -1;
+
+	switch (usage->collection_index) {
+	case 1:
+		if (key >= ARRAY_SIZE(ps3remote_keymap_joypad_buttons))
+			return -1;
+
+		key = ps3remote_keymap_joypad_buttons[key];
+		if (!key)
+			return -1;
+		break;
+	case 2:
+		if (key >= ARRAY_SIZE(ps3remote_keymap_remote_buttons))
+			return -1;
+
+		key = ps3remote_keymap_remote_buttons[key];
+		if (!key)
+			return -1;
+		break;
+	default:
+		return -1;
+	}
+
+	hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+	return 1;
+}
+
+static int navigation_mapping(struct hid_device *hdev, struct hid_input *hi,
+			  struct hid_field *field, struct hid_usage *usage,
+			  unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		unsigned int key = usage->hid & HID_USAGE;
+
+		if (key >= ARRAY_SIZE(sixaxis_keymap))
+			return -1;
+
+		key = navigation_keymap[key];
+		if (!key)
+			return -1;
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+		return 1;
+	} else if (usage->hid == HID_GD_POINTER) {
+		/* See comment in sixaxis_mapping, basically the L2 (and R2)
+		 * triggers are reported through GD Pointer.
+		 * In addition we ignore any analog button 'axes' and only
+		 * support digital buttons.
+		 */
+		switch (usage->usage_index) {
+		case 8: /* L2 */
+			usage->hid = HID_GD_Z;
+			break;
+		default:
+			return -1;
+		}
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, usage->hid & 0xf);
+		return 1;
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
+		unsigned int abs = usage->hid & HID_USAGE;
+
+		if (abs >= ARRAY_SIZE(navigation_absmap))
+			return -1;
+
+		abs = navigation_absmap[abs];
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
+		return 1;
+	}
+
+	return -1;
+}
+
+
+static int sixaxis_mapping(struct hid_device *hdev, struct hid_input *hi,
+			  struct hid_field *field, struct hid_usage *usage,
+			  unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		unsigned int key = usage->hid & HID_USAGE;
+
+		if (key >= ARRAY_SIZE(sixaxis_keymap))
+			return -1;
+
+		key = sixaxis_keymap[key];
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+		return 1;
+	} else if (usage->hid == HID_GD_POINTER) {
+		/* The DS3 provides analog values for most buttons and even
+		 * for HAT axes through GD Pointer. L2 and R2 are reported
+		 * among these as well instead of as GD Z / RZ. Remap L2
+		 * and R2 and ignore other analog 'button axes' as there is
+		 * no good way for reporting them.
+		 */
+		switch (usage->usage_index) {
+		case 8: /* L2 */
+			usage->hid = HID_GD_Z;
+			break;
+		case 9: /* R2 */
+			usage->hid = HID_GD_RZ;
+			break;
+		default:
+			return -1;
+		}
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, usage->hid & 0xf);
+		return 1;
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
+		unsigned int abs = usage->hid & HID_USAGE;
+
+		if (abs >= ARRAY_SIZE(sixaxis_absmap))
+			return -1;
+
+		abs = sixaxis_absmap[abs];
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
+		return 1;
+	}
+
+	return -1;
+}
+
+static int ds4_mapping(struct hid_device *hdev, struct hid_input *hi,
+		       struct hid_field *field, struct hid_usage *usage,
+		       unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON) {
+		unsigned int key = usage->hid & HID_USAGE;
+
+		if (key >= ARRAY_SIZE(ds4_keymap))
+			return -1;
+
+		key = ds4_keymap[key];
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+		return 1;
+	} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
+		unsigned int abs = usage->hid & HID_USAGE;
+
+		/* Let the HID parser deal with the HAT. */
+		if (usage->hid == HID_GD_HATSWITCH)
+			return 0;
+
+		if (abs >= ARRAY_SIZE(ds4_absmap))
+			return -1;
+
+		abs = ds4_absmap[abs];
+		hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
+		return 1;
+	}
+
+	return 0;
+}
+
+static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
+		unsigned int *rsize)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	if (sc->quirks & (SINO_LITE_CONTROLLER | FUTUREMAX_DANCE_MAT))
+		return rdesc;
+
+	/*
+	 * Some Sony RF receivers wrongly declare the mouse pointer as a
+	 * a constant non-data variable.
+	 */
+	if ((sc->quirks & VAIO_RDESC_CONSTANT) && *rsize >= 56 &&
+	    /* usage page: generic desktop controls */
+	    /* rdesc[0] == 0x05 && rdesc[1] == 0x01 && */
+	    /* usage: mouse */
+	    rdesc[2] == 0x09 && rdesc[3] == 0x02 &&
+	    /* input (usage page for x,y axes): constant, variable, relative */
+	    rdesc[54] == 0x81 && rdesc[55] == 0x07) {
+		hid_info(hdev, "Fixing up Sony RF Receiver report descriptor\n");
+		/* input: data, variable, relative */
+		rdesc[55] = 0x06;
+	}
+
+	if (sc->quirks & MOTION_CONTROLLER)
+		return motion_fixup(hdev, rdesc, rsize);
+
+	if (sc->quirks & PS3REMOTE)
+		return ps3remote_fixup(hdev, rdesc, rsize);
+
+	return rdesc;
+}
+
+static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
+{
+	static const u8 sixaxis_battery_capacity[] = { 0, 1, 25, 50, 75, 100 };
+	unsigned long flags;
+	int offset;
+	u8 cable_state, battery_capacity, battery_charging;
+
+	/*
+	 * The sixaxis is charging if the battery value is 0xee
+	 * and it is fully charged if the value is 0xef.
+	 * It does not report the actual level while charging so it
+	 * is set to 100% while charging is in progress.
+	 */
+	offset = (sc->quirks & MOTION_CONTROLLER) ? 12 : 30;
+
+	if (rd[offset] >= 0xee) {
+		battery_capacity = 100;
+		battery_charging = !(rd[offset] & 0x01);
+		cable_state = 1;
+	} else {
+		u8 index = rd[offset] <= 5 ? rd[offset] : 5;
+		battery_capacity = sixaxis_battery_capacity[index];
+		battery_charging = 0;
+		cable_state = 0;
+	}
+
+	spin_lock_irqsave(&sc->lock, flags);
+	sc->cable_state = cable_state;
+	sc->battery_capacity = battery_capacity;
+	sc->battery_charging = battery_charging;
+	spin_unlock_irqrestore(&sc->lock, flags);
+
+	if (sc->quirks & SIXAXIS_CONTROLLER) {
+		int val;
+
+		offset = SIXAXIS_INPUT_REPORT_ACC_X_OFFSET;
+		val = ((rd[offset+1] << 8) | rd[offset]) - 511;
+		input_report_abs(sc->sensor_dev, ABS_X, val);
+
+		/* Y and Z are swapped and inversed */
+		val = 511 - ((rd[offset+5] << 8) | rd[offset+4]);
+		input_report_abs(sc->sensor_dev, ABS_Y, val);
+
+		val = 511 - ((rd[offset+3] << 8) | rd[offset+2]);
+		input_report_abs(sc->sensor_dev, ABS_Z, val);
+
+		input_sync(sc->sensor_dev);
+	}
+}
+
+static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size)
+{
+	struct hid_input *hidinput = list_entry(sc->hdev->inputs.next,
+						struct hid_input, list);
+	struct input_dev *input_dev = hidinput->input;
+	unsigned long flags;
+	int n, m, offset, num_touch_data, max_touch_data;
+	u8 cable_state, battery_capacity, battery_charging;
+	u16 timestamp;
+
+	/* When using Bluetooth the header is 2 bytes longer, so skip these. */
+	int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 2 : 0;
+
+	/* Second bit of third button byte is for the touchpad button. */
+	offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET;
+	input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2);
+
+	/*
+	 * The default behavior of the Dualshock 4 is to send reports using
+	 * report type 1 when running over Bluetooth. However, when feature
+	 * report 2 is requested during the controller initialization it starts
+	 * sending input reports in report 17. Since report 17 is undefined
+	 * in the default HID descriptor, the HID layer won't generate events.
+	 * While it is possible (and this was done before) to fixup the HID
+	 * descriptor to add this mapping, it was better to do this manually.
+	 * The reason is there were various pieces software both open and closed
+	 * source, relying on the descriptors to be the same across various
+	 * operating systems. If the descriptors wouldn't match some
+	 * applications e.g. games on Wine would not be able to function due
+	 * to different descriptors, which such applications are not parsing.
+	 */
+	if (rd[0] == 17) {
+		int value;
+
+		offset = data_offset + DS4_INPUT_REPORT_AXIS_OFFSET;
+		input_report_abs(input_dev, ABS_X, rd[offset]);
+		input_report_abs(input_dev, ABS_Y, rd[offset+1]);
+		input_report_abs(input_dev, ABS_RX, rd[offset+2]);
+		input_report_abs(input_dev, ABS_RY, rd[offset+3]);
+
+		value = rd[offset+4] & 0xf;
+		if (value > 7)
+			value = 8; /* Center 0, 0 */
+		input_report_abs(input_dev, ABS_HAT0X, ds4_hat_mapping[value].x);
+		input_report_abs(input_dev, ABS_HAT0Y, ds4_hat_mapping[value].y);
+
+		input_report_key(input_dev, BTN_WEST, rd[offset+4] & 0x10);
+		input_report_key(input_dev, BTN_SOUTH, rd[offset+4] & 0x20);
+		input_report_key(input_dev, BTN_EAST, rd[offset+4] & 0x40);
+		input_report_key(input_dev, BTN_NORTH, rd[offset+4] & 0x80);
+
+		input_report_key(input_dev, BTN_TL, rd[offset+5] & 0x1);
+		input_report_key(input_dev, BTN_TR, rd[offset+5] & 0x2);
+		input_report_key(input_dev, BTN_TL2, rd[offset+5] & 0x4);
+		input_report_key(input_dev, BTN_TR2, rd[offset+5] & 0x8);
+		input_report_key(input_dev, BTN_SELECT, rd[offset+5] & 0x10);
+		input_report_key(input_dev, BTN_START, rd[offset+5] & 0x20);
+		input_report_key(input_dev, BTN_THUMBL, rd[offset+5] & 0x40);
+		input_report_key(input_dev, BTN_THUMBR, rd[offset+5] & 0x80);
+
+		input_report_key(input_dev, BTN_MODE, rd[offset+6] & 0x1);
+
+		input_report_abs(input_dev, ABS_Z, rd[offset+7]);
+		input_report_abs(input_dev, ABS_RZ, rd[offset+8]);
+
+		input_sync(input_dev);
+	}
+
+	/* Convert timestamp (in 5.33us unit) to timestamp_us */
+	offset = data_offset + DS4_INPUT_REPORT_TIMESTAMP_OFFSET;
+	timestamp = get_unaligned_le16(&rd[offset]);
+	if (!sc->timestamp_initialized) {
+		sc->timestamp_us = ((unsigned int)timestamp * 16) / 3;
+		sc->timestamp_initialized = true;
+	} else {
+		u16 delta;
+
+		if (sc->prev_timestamp > timestamp)
+			delta = (U16_MAX - sc->prev_timestamp + timestamp + 1);
+		else
+			delta = timestamp - sc->prev_timestamp;
+		sc->timestamp_us += (delta * 16) / 3;
+	}
+	sc->prev_timestamp = timestamp;
+	input_event(sc->sensor_dev, EV_MSC, MSC_TIMESTAMP, sc->timestamp_us);
+
+	offset = data_offset + DS4_INPUT_REPORT_GYRO_X_OFFSET;
+	for (n = 0; n < 6; n++) {
+		/* Store data in int for more precision during mult_frac. */
+		int raw_data = (short)((rd[offset+1] << 8) | rd[offset]);
+		struct ds4_calibration_data *calib = &sc->ds4_calib_data[n];
+
+		/* High precision is needed during calibration, but the
+		 * calibrated values are within 32-bit.
+		 * Note: we swap numerator 'x' and 'numer' in mult_frac for
+		 *       precision reasons so we don't need 64-bit.
+		 */
+		int calib_data = mult_frac(calib->sens_numer,
+					   raw_data - calib->bias,
+					   calib->sens_denom);
+
+		input_report_abs(sc->sensor_dev, calib->abs_code, calib_data);
+		offset += 2;
+	}
+	input_sync(sc->sensor_dev);
+
+	/*
+	 * The lower 4 bits of byte 30 (or 32 for BT) contain the battery level
+	 * and the 5th bit contains the USB cable state.
+	 */
+	offset = data_offset + DS4_INPUT_REPORT_BATTERY_OFFSET;
+	cable_state = (rd[offset] >> 4) & 0x01;
+	battery_capacity = rd[offset] & 0x0F;
+
+	/*
+	 * When a USB power source is connected the battery level ranges from
+	 * 0 to 10, and when running on battery power it ranges from 0 to 9.
+	 * A battery level above 10 when plugged in means charge completed.
+	 */
+	if (!cable_state || battery_capacity > 10)
+		battery_charging = 0;
+	else
+		battery_charging = 1;
+
+	if (!cable_state)
+		battery_capacity++;
+	if (battery_capacity > 10)
+		battery_capacity = 10;
+
+	battery_capacity *= 10;
+
+	spin_lock_irqsave(&sc->lock, flags);
+	sc->cable_state = cable_state;
+	sc->battery_capacity = battery_capacity;
+	sc->battery_charging = battery_charging;
+	spin_unlock_irqrestore(&sc->lock, flags);
+
+	/*
+	 * The Dualshock 4 multi-touch trackpad data starts at offset 33 on USB
+	 * and 35 on Bluetooth.
+	 * The first byte indicates the number of touch data in the report.
+	 * Trackpad data starts 2 bytes later (e.g. 35 for USB).
+	 */
+	offset = data_offset + DS4_INPUT_REPORT_TOUCHPAD_OFFSET;
+	max_touch_data = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 4 : 3;
+	if (rd[offset] > 0 && rd[offset] <= max_touch_data)
+		num_touch_data = rd[offset];
+	else
+		num_touch_data = 1;
+	offset += 1;
+
+	for (m = 0; m < num_touch_data; m++) {
+		/* Skip past timestamp */
+		offset += 1;
+
+		/*
+		 * The first 7 bits of the first byte is a counter and bit 8 is
+		 * a touch indicator that is 0 when pressed and 1 when not
+		 * pressed.
+		 * The next 3 bytes are two 12 bit touch coordinates, X and Y.
+		 * The data for the second touch is in the same format and
+		 * immediately follows the data for the first.
+		 */
+		for (n = 0; n < 2; n++) {
+			u16 x, y;
+			bool active;
+
+			x = rd[offset+1] | ((rd[offset+2] & 0xF) << 8);
+			y = ((rd[offset+2] & 0xF0) >> 4) | (rd[offset+3] << 4);
+
+			active = !(rd[offset] >> 7);
+			input_mt_slot(sc->touchpad, n);
+			input_mt_report_slot_state(sc->touchpad, MT_TOOL_FINGER, active);
+
+			if (active) {
+				input_report_abs(sc->touchpad, ABS_MT_POSITION_X, x);
+				input_report_abs(sc->touchpad, ABS_MT_POSITION_Y, y);
+			}
+
+			offset += 4;
+		}
+		input_mt_sync_frame(sc->touchpad);
+		input_sync(sc->touchpad);
+	}
+}
+
+static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
+{
+	int n, offset, relx, rely;
+	u8 active;
+
+	/*
+	 * The NSG-MRxU multi-touch trackpad data starts at offset 1 and
+	 *   the touch-related data starts at offset 2.
+	 * For the first byte, bit 0 is set when touchpad button is pressed.
+	 * Bit 2 is set when a touch is active and the drag (Fn) key is pressed.
+	 * This drag key is mapped to BTN_LEFT.  It is operational only when a 
+	 *   touch point is active.
+	 * Bit 4 is set when only the first touch point is active.
+	 * Bit 6 is set when only the second touch point is active.
+	 * Bits 5 and 7 are set when both touch points are active.
+	 * The next 3 bytes are two 12 bit X/Y coordinates for the first touch.
+	 * The following byte, offset 5, has the touch width and length.
+	 *   Bits 0-4=X (width), bits 5-7=Y (length).
+	 * A signed relative X coordinate is at offset 6.
+	 * The bytes at offset 7-9 are the second touch X/Y coordinates.
+	 * Offset 10 has the second touch width and length.
+	 * Offset 11 has the relative Y coordinate.
+	 */
+	offset = 1;
+
+	input_report_key(sc->touchpad, BTN_LEFT, rd[offset] & 0x0F);
+	active = (rd[offset] >> 4);
+	relx = (s8) rd[offset+5];
+	rely = ((s8) rd[offset+10]) * -1;
+
+	offset++;
+
+	for (n = 0; n < 2; n++) {
+		u16 x, y;
+		u8 contactx, contacty;
+
+		x = rd[offset] | ((rd[offset+1] & 0x0F) << 8);
+		y = ((rd[offset+1] & 0xF0) >> 4) | (rd[offset+2] << 4);
+
+		input_mt_slot(sc->touchpad, n);
+		input_mt_report_slot_state(sc->touchpad, MT_TOOL_FINGER, active & 0x03);
+
+		if (active & 0x03) {
+			contactx = rd[offset+3] & 0x0F;
+			contacty = rd[offset+3] >> 4;
+			input_report_abs(sc->touchpad, ABS_MT_TOUCH_MAJOR,
+				max(contactx, contacty));
+			input_report_abs(sc->touchpad, ABS_MT_TOUCH_MINOR,
+				min(contactx, contacty));
+			input_report_abs(sc->touchpad, ABS_MT_ORIENTATION,
+				(bool) (contactx > contacty));
+			input_report_abs(sc->touchpad, ABS_MT_POSITION_X, x);
+			input_report_abs(sc->touchpad, ABS_MT_POSITION_Y,
+				NSG_MRXU_MAX_Y - y);
+			/*
+			 * The relative coordinates belong to the first touch
+			 * point, when present, or to the second touch point
+			 * when the first is not active.
+			 */
+			if ((n == 0) || ((n == 1) && (active & 0x01))) {
+				input_report_rel(sc->touchpad, REL_X, relx);
+				input_report_rel(sc->touchpad, REL_Y, rely);
+			}
+		}
+
+		offset += 5;
+		active >>= 2;
+	}
+
+	input_mt_sync_frame(sc->touchpad);
+
+	input_sync(sc->touchpad);
+}
+
+static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *rd, int size)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	/*
+	 * Sixaxis HID report has acclerometers/gyro with MSByte first, this
+	 * has to be BYTE_SWAPPED before passing up to joystick interface
+	 */
+	if ((sc->quirks & SIXAXIS_CONTROLLER) && rd[0] == 0x01 && size == 49) {
+		/*
+		 * When connected via Bluetooth the Sixaxis occasionally sends
+		 * a report with the second byte 0xff and the rest zeroed.
+		 *
+		 * This report does not reflect the actual state of the
+		 * controller must be ignored to avoid generating false input
+		 * events.
+		 */
+		if (rd[1] == 0xff)
+			return -EINVAL;
+
+		swap(rd[41], rd[42]);
+		swap(rd[43], rd[44]);
+		swap(rd[45], rd[46]);
+		swap(rd[47], rd[48]);
+
+		sixaxis_parse_report(sc, rd, size);
+	} else if ((sc->quirks & MOTION_CONTROLLER_BT) && rd[0] == 0x01 && size == 49) {
+		sixaxis_parse_report(sc, rd, size);
+	} else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 &&
+			size == 49) {
+		sixaxis_parse_report(sc, rd, size);
+	} else if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 &&
+			size == 64) {
+		dualshock4_parse_report(sc, rd, size);
+	} else if (((sc->quirks & DUALSHOCK4_CONTROLLER_BT) && rd[0] == 0x11 &&
+			size == 78)) {
+		/* CRC check */
+		u8 bthdr = 0xA1;
+		u32 crc;
+		u32 report_crc;
+
+		crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
+		crc = ~crc32_le(crc, rd, DS4_INPUT_REPORT_0x11_SIZE-4);
+		report_crc = get_unaligned_le32(&rd[DS4_INPUT_REPORT_0x11_SIZE-4]);
+		if (crc != report_crc) {
+			hid_dbg(sc->hdev, "DualShock 4 input report's CRC check failed, received crc 0x%0x != 0x%0x\n",
+				report_crc, crc);
+			return -EILSEQ;
+		}
+
+		dualshock4_parse_report(sc, rd, size);
+	} else if ((sc->quirks & DUALSHOCK4_DONGLE) && rd[0] == 0x01 &&
+			size == 64) {
+		unsigned long flags;
+		enum ds4_dongle_state dongle_state;
+
+		/*
+		 * In the case of a DS4 USB dongle, bit[2] of byte 31 indicates
+		 * if a DS4 is actually connected (indicated by '0').
+		 * For non-dongle, this bit is always 0 (connected).
+		 */
+		bool connected = (rd[31] & 0x04) ? false : true;
+
+		spin_lock_irqsave(&sc->lock, flags);
+		dongle_state = sc->ds4_dongle_state;
+		spin_unlock_irqrestore(&sc->lock, flags);
+
+		/*
+		 * The dongle always sends input reports even when no
+		 * DS4 is attached. When a DS4 is connected, we need to
+		 * obtain calibration data before we can use it.
+		 * The code below tracks dongle state and kicks of
+		 * calibration when needed and only allows us to process
+		 * input if a DS4 is actually connected.
+		 */
+		if (dongle_state == DONGLE_DISCONNECTED && connected) {
+			hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n");
+			sony_set_leds(sc);
+
+			spin_lock_irqsave(&sc->lock, flags);
+			sc->ds4_dongle_state = DONGLE_CALIBRATING;
+			spin_unlock_irqrestore(&sc->lock, flags);
+
+			sony_schedule_work(sc, SONY_WORKER_HOTPLUG);
+
+			/* Don't process the report since we don't have
+			 * calibration data, but let hidraw have it anyway.
+			 */
+			return 0;
+		} else if ((dongle_state == DONGLE_CONNECTED ||
+			    dongle_state == DONGLE_DISABLED) && !connected) {
+			hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n");
+
+			spin_lock_irqsave(&sc->lock, flags);
+			sc->ds4_dongle_state = DONGLE_DISCONNECTED;
+			spin_unlock_irqrestore(&sc->lock, flags);
+
+			/* Return 0, so hidraw can get the report. */
+			return 0;
+		} else if (dongle_state == DONGLE_CALIBRATING ||
+			   dongle_state == DONGLE_DISABLED ||
+			   dongle_state == DONGLE_DISCONNECTED) {
+			/* Return 0, so hidraw can get the report. */
+			return 0;
+		}
+
+		dualshock4_parse_report(sc, rd, size);
+
+	} else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) {
+		nsg_mrxu_parse_report(sc, rd, size);
+		return 1;
+	}
+
+	if (sc->defer_initialization) {
+		sc->defer_initialization = 0;
+		sony_schedule_work(sc, SONY_WORKER_STATE);
+	}
+
+	return 0;
+}
+
+static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
+			struct hid_field *field, struct hid_usage *usage,
+			unsigned long **bit, int *max)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	if (sc->quirks & BUZZ_CONTROLLER) {
+		unsigned int key = usage->hid & HID_USAGE;
+
+		if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
+			return -1;
+
+		switch (usage->collection_index) {
+		case 1:
+			if (key >= ARRAY_SIZE(buzz_keymap))
+				return -1;
+
+			key = buzz_keymap[key];
+			if (!key)
+				return -1;
+			break;
+		default:
+			return -1;
+		}
+
+		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
+		return 1;
+	}
+
+	if (sc->quirks & PS3REMOTE)
+		return ps3remote_mapping(hdev, hi, field, usage, bit, max);
+
+	if (sc->quirks & NAVIGATION_CONTROLLER)
+		return navigation_mapping(hdev, hi, field, usage, bit, max);
+
+	if (sc->quirks & SIXAXIS_CONTROLLER)
+		return sixaxis_mapping(hdev, hi, field, usage, bit, max);
+
+	if (sc->quirks & DUALSHOCK4_CONTROLLER)
+		return ds4_mapping(hdev, hi, field, usage, bit, max);
+
+
+	/* Let hid-core decide for the others */
+	return 0;
+}
+
+static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
+		int w, int h, int touch_major, int touch_minor, int orientation)
+{
+	size_t name_sz;
+	char *name;
+	int ret;
+
+	sc->touchpad = devm_input_allocate_device(&sc->hdev->dev);
+	if (!sc->touchpad)
+		return -ENOMEM;
+
+	input_set_drvdata(sc->touchpad, sc);
+	sc->touchpad->dev.parent = &sc->hdev->dev;
+	sc->touchpad->phys = sc->hdev->phys;
+	sc->touchpad->uniq = sc->hdev->uniq;
+	sc->touchpad->id.bustype = sc->hdev->bus;
+	sc->touchpad->id.vendor = sc->hdev->vendor;
+	sc->touchpad->id.product = sc->hdev->product;
+	sc->touchpad->id.version = sc->hdev->version;
+
+	/* Append a suffix to the controller name as there are various
+	 * DS4 compatible non-Sony devices with different names.
+	 */
+	name_sz = strlen(sc->hdev->name) + sizeof(DS4_TOUCHPAD_SUFFIX);
+	name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+	snprintf(name, name_sz, "%s" DS4_TOUCHPAD_SUFFIX, sc->hdev->name);
+	sc->touchpad->name = name;
+
+	/* We map the button underneath the touchpad to BTN_LEFT. */
+	__set_bit(EV_KEY, sc->touchpad->evbit);
+	__set_bit(BTN_LEFT, sc->touchpad->keybit);
+	__set_bit(INPUT_PROP_BUTTONPAD, sc->touchpad->propbit);
+
+	input_set_abs_params(sc->touchpad, ABS_MT_POSITION_X, 0, w, 0, 0);
+	input_set_abs_params(sc->touchpad, ABS_MT_POSITION_Y, 0, h, 0, 0);
+
+	if (touch_major > 0) {
+		input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MAJOR, 
+			0, touch_major, 0, 0);
+		if (touch_minor > 0)
+			input_set_abs_params(sc->touchpad, ABS_MT_TOUCH_MINOR, 
+				0, touch_minor, 0, 0);
+		if (orientation > 0)
+			input_set_abs_params(sc->touchpad, ABS_MT_ORIENTATION, 
+				0, orientation, 0, 0);
+	}
+
+	if (sc->quirks & NSG_MRXU_REMOTE) {
+		__set_bit(EV_REL, sc->touchpad->evbit);
+	}
+
+	ret = input_mt_init_slots(sc->touchpad, touch_count, INPUT_MT_POINTER);
+	if (ret < 0)
+		return ret;
+
+	ret = input_register_device(sc->touchpad);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int sony_register_sensors(struct sony_sc *sc)
+{
+	size_t name_sz;
+	char *name;
+	int ret;
+	int range;
+
+	sc->sensor_dev = devm_input_allocate_device(&sc->hdev->dev);
+	if (!sc->sensor_dev)
+		return -ENOMEM;
+
+	input_set_drvdata(sc->sensor_dev, sc);
+	sc->sensor_dev->dev.parent = &sc->hdev->dev;
+	sc->sensor_dev->phys = sc->hdev->phys;
+	sc->sensor_dev->uniq = sc->hdev->uniq;
+	sc->sensor_dev->id.bustype = sc->hdev->bus;
+	sc->sensor_dev->id.vendor = sc->hdev->vendor;
+	sc->sensor_dev->id.product = sc->hdev->product;
+	sc->sensor_dev->id.version = sc->hdev->version;
+
+	/* Append a suffix to the controller name as there are various
+	 * DS4 compatible non-Sony devices with different names.
+	 */
+	name_sz = strlen(sc->hdev->name) + sizeof(SENSOR_SUFFIX);
+	name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
+	if (!name)
+		return -ENOMEM;
+	snprintf(name, name_sz, "%s" SENSOR_SUFFIX, sc->hdev->name);
+	sc->sensor_dev->name = name;
+
+	if (sc->quirks & SIXAXIS_CONTROLLER) {
+		/* For the DS3 we only support the accelerometer, which works
+		 * quite well even without calibration. The device also has
+		 * a 1-axis gyro, but it is very difficult to manage from within
+		 * the driver even to get data, the sensor is inaccurate and
+		 * the behavior is very different between hardware revisions.
+		 */
+		input_set_abs_params(sc->sensor_dev, ABS_X, -512, 511, 4, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_Y, -512, 511, 4, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_Z, -512, 511, 4, 0);
+		input_abs_set_res(sc->sensor_dev, ABS_X, SIXAXIS_ACC_RES_PER_G);
+		input_abs_set_res(sc->sensor_dev, ABS_Y, SIXAXIS_ACC_RES_PER_G);
+		input_abs_set_res(sc->sensor_dev, ABS_Z, SIXAXIS_ACC_RES_PER_G);
+	} else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
+		range = DS4_ACC_RES_PER_G*4;
+		input_set_abs_params(sc->sensor_dev, ABS_X, -range, range, 16, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_Y, -range, range, 16, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_Z, -range, range, 16, 0);
+		input_abs_set_res(sc->sensor_dev, ABS_X, DS4_ACC_RES_PER_G);
+		input_abs_set_res(sc->sensor_dev, ABS_Y, DS4_ACC_RES_PER_G);
+		input_abs_set_res(sc->sensor_dev, ABS_Z, DS4_ACC_RES_PER_G);
+
+		range = DS4_GYRO_RES_PER_DEG_S*2048;
+		input_set_abs_params(sc->sensor_dev, ABS_RX, -range, range, 16, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_RY, -range, range, 16, 0);
+		input_set_abs_params(sc->sensor_dev, ABS_RZ, -range, range, 16, 0);
+		input_abs_set_res(sc->sensor_dev, ABS_RX, DS4_GYRO_RES_PER_DEG_S);
+		input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S);
+		input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S);
+
+		__set_bit(EV_MSC, sc->sensor_dev->evbit);
+		__set_bit(MSC_TIMESTAMP, sc->sensor_dev->mscbit);
+	}
+
+	__set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit);
+
+	ret = input_register_device(sc->sensor_dev);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Sending HID_REQ_GET_REPORT changes the operation mode of the ps3 controller
+ * to "operational".  Without this, the ps3 controller will not report any
+ * events.
+ */
+static int sixaxis_set_operational_usb(struct hid_device *hdev)
+{
+	const int buf_size =
+		max(SIXAXIS_REPORT_0xF2_SIZE, SIXAXIS_REPORT_0xF5_SIZE);
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(buf_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, 0xf2, buf, SIXAXIS_REPORT_0xF2_SIZE,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		hid_err(hdev, "can't set operational mode: step 1\n");
+		goto out;
+	}
+
+	/*
+	 * Some compatible controllers like the Speedlink Strike FX and
+	 * Gasia need another query plus an USB interrupt to get operational.
+	 */
+	ret = hid_hw_raw_request(hdev, 0xf5, buf, SIXAXIS_REPORT_0xF5_SIZE,
+				 HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		hid_err(hdev, "can't set operational mode: step 2\n");
+		goto out;
+	}
+
+	/*
+	 * But the USB interrupt would cause SHANWAN controllers to
+	 * start rumbling non-stop.
+	 */
+	if (strcmp(hdev->name, "SHANWAN PS3 GamePad")) {
+		ret = hid_hw_output_report(hdev, buf, 1);
+		if (ret < 0) {
+			hid_info(hdev, "can't set operational mode: step 3, ignoring\n");
+			ret = 0;
+		}
+	}
+
+out:
+	kfree(buf);
+
+	return ret;
+}
+
+static int sixaxis_set_operational_bt(struct hid_device *hdev)
+{
+	static const u8 report[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 };
+	u8 *buf;
+	int ret;
+
+	buf = kmemdup(report, sizeof(report), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, buf[0], buf, sizeof(report),
+				  HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+
+	kfree(buf);
+
+	return ret;
+}
+
+/*
+ * Request DS4 calibration data for the motion sensors.
+ * For Bluetooth this also affects the operating mode (see below).
+ */
+static int dualshock4_get_calibration_data(struct sony_sc *sc)
+{
+	u8 *buf;
+	int ret;
+	short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus;
+	short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus;
+	short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus;
+	short gyro_speed_plus, gyro_speed_minus;
+	short acc_x_plus, acc_x_minus;
+	short acc_y_plus, acc_y_minus;
+	short acc_z_plus, acc_z_minus;
+	int speed_2x;
+	int range_2g;
+
+	/* For Bluetooth we use a different request, which supports CRC.
+	 * Note: in Bluetooth mode feature report 0x02 also changes the state
+	 * of the controller, so that it sends input reports of type 0x11.
+	 */
+	if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
+		buf = kmalloc(DS4_FEATURE_REPORT_0x02_SIZE, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		ret = hid_hw_raw_request(sc->hdev, 0x02, buf,
+					 DS4_FEATURE_REPORT_0x02_SIZE,
+					 HID_FEATURE_REPORT,
+					 HID_REQ_GET_REPORT);
+		if (ret < 0)
+			goto err_stop;
+	} else {
+		u8 bthdr = 0xA3;
+		u32 crc;
+		u32 report_crc;
+		int retries;
+
+		buf = kmalloc(DS4_FEATURE_REPORT_0x05_SIZE, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		for (retries = 0; retries < 3; retries++) {
+			ret = hid_hw_raw_request(sc->hdev, 0x05, buf,
+						 DS4_FEATURE_REPORT_0x05_SIZE,
+						 HID_FEATURE_REPORT,
+						 HID_REQ_GET_REPORT);
+			if (ret < 0)
+				goto err_stop;
+
+			/* CRC check */
+			crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
+			crc = ~crc32_le(crc, buf, DS4_FEATURE_REPORT_0x05_SIZE-4);
+			report_crc = get_unaligned_le32(&buf[DS4_FEATURE_REPORT_0x05_SIZE-4]);
+			if (crc != report_crc) {
+				hid_warn(sc->hdev, "DualShock 4 calibration report's CRC check failed, received crc 0x%0x != 0x%0x\n",
+					report_crc, crc);
+				if (retries < 2) {
+					hid_warn(sc->hdev, "Retrying DualShock 4 get calibration report request\n");
+					continue;
+				} else {
+					ret = -EILSEQ;
+					goto err_stop;
+				}
+			} else {
+				break;
+			}
+		}
+	}
+
+	gyro_pitch_bias  = get_unaligned_le16(&buf[1]);
+	gyro_yaw_bias    = get_unaligned_le16(&buf[3]);
+	gyro_roll_bias   = get_unaligned_le16(&buf[5]);
+	if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) {
+		gyro_pitch_plus  = get_unaligned_le16(&buf[7]);
+		gyro_pitch_minus = get_unaligned_le16(&buf[9]);
+		gyro_yaw_plus    = get_unaligned_le16(&buf[11]);
+		gyro_yaw_minus   = get_unaligned_le16(&buf[13]);
+		gyro_roll_plus   = get_unaligned_le16(&buf[15]);
+		gyro_roll_minus  = get_unaligned_le16(&buf[17]);
+	} else {
+		/* BT + Dongle */
+		gyro_pitch_plus  = get_unaligned_le16(&buf[7]);
+		gyro_yaw_plus    = get_unaligned_le16(&buf[9]);
+		gyro_roll_plus   = get_unaligned_le16(&buf[11]);
+		gyro_pitch_minus = get_unaligned_le16(&buf[13]);
+		gyro_yaw_minus   = get_unaligned_le16(&buf[15]);
+		gyro_roll_minus  = get_unaligned_le16(&buf[17]);
+	}
+	gyro_speed_plus  = get_unaligned_le16(&buf[19]);
+	gyro_speed_minus = get_unaligned_le16(&buf[21]);
+	acc_x_plus       = get_unaligned_le16(&buf[23]);
+	acc_x_minus      = get_unaligned_le16(&buf[25]);
+	acc_y_plus       = get_unaligned_le16(&buf[27]);
+	acc_y_minus      = get_unaligned_le16(&buf[29]);
+	acc_z_plus       = get_unaligned_le16(&buf[31]);
+	acc_z_minus      = get_unaligned_le16(&buf[33]);
+
+	/* Set gyroscope calibration and normalization parameters.
+	 * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
+	 */
+	speed_2x = (gyro_speed_plus + gyro_speed_minus);
+	sc->ds4_calib_data[0].abs_code = ABS_RX;
+	sc->ds4_calib_data[0].bias = gyro_pitch_bias;
+	sc->ds4_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+	sc->ds4_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
+
+	sc->ds4_calib_data[1].abs_code = ABS_RY;
+	sc->ds4_calib_data[1].bias = gyro_yaw_bias;
+	sc->ds4_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+	sc->ds4_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
+
+	sc->ds4_calib_data[2].abs_code = ABS_RZ;
+	sc->ds4_calib_data[2].bias = gyro_roll_bias;
+	sc->ds4_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+	sc->ds4_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
+
+	/* Set accelerometer calibration and normalization parameters.
+	 * Data values will be normalized to 1/DS4_ACC_RES_PER_G G.
+	 */
+	range_2g = acc_x_plus - acc_x_minus;
+	sc->ds4_calib_data[3].abs_code = ABS_X;
+	sc->ds4_calib_data[3].bias = acc_x_plus - range_2g / 2;
+	sc->ds4_calib_data[3].sens_numer = 2*DS4_ACC_RES_PER_G;
+	sc->ds4_calib_data[3].sens_denom = range_2g;
+
+	range_2g = acc_y_plus - acc_y_minus;
+	sc->ds4_calib_data[4].abs_code = ABS_Y;
+	sc->ds4_calib_data[4].bias = acc_y_plus - range_2g / 2;
+	sc->ds4_calib_data[4].sens_numer = 2*DS4_ACC_RES_PER_G;
+	sc->ds4_calib_data[4].sens_denom = range_2g;
+
+	range_2g = acc_z_plus - acc_z_minus;
+	sc->ds4_calib_data[5].abs_code = ABS_Z;
+	sc->ds4_calib_data[5].bias = acc_z_plus - range_2g / 2;
+	sc->ds4_calib_data[5].sens_numer = 2*DS4_ACC_RES_PER_G;
+	sc->ds4_calib_data[5].sens_denom = range_2g;
+
+err_stop:
+	kfree(buf);
+	return ret;
+}
+
+static void dualshock4_calibration_work(struct work_struct *work)
+{
+	struct sony_sc *sc = container_of(work, struct sony_sc, hotplug_worker);
+	unsigned long flags;
+	enum ds4_dongle_state dongle_state;
+	int ret;
+
+	ret = dualshock4_get_calibration_data(sc);
+	if (ret < 0) {
+		/* This call is very unlikely to fail for the dongle. When it
+		 * fails we are probably in a very bad state, so mark the
+		 * dongle as disabled. We will re-enable the dongle if a new
+		 * DS4 hotplug is detect from sony_raw_event as any issues
+		 * are likely resolved then (the dongle is quite stupid).
+		 */
+		hid_err(sc->hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
+		dongle_state = DONGLE_DISABLED;
+	} else {
+		hid_info(sc->hdev, "DualShock 4 USB dongle: calibration completed\n");
+		dongle_state = DONGLE_CONNECTED;
+	}
+
+	spin_lock_irqsave(&sc->lock, flags);
+	sc->ds4_dongle_state = dongle_state;
+	spin_unlock_irqrestore(&sc->lock, flags);
+}
+
+static int dualshock4_get_version_info(struct sony_sc *sc)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(DS4_FEATURE_REPORT_0xA3_SIZE, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(sc->hdev, 0xA3, buf,
+				 DS4_FEATURE_REPORT_0xA3_SIZE,
+				 HID_FEATURE_REPORT,
+				 HID_REQ_GET_REPORT);
+	if (ret < 0) {
+		kfree(buf);
+		return ret;
+	}
+
+	sc->hw_version = get_unaligned_le16(&buf[35]);
+	sc->fw_version = get_unaligned_le16(&buf[41]);
+
+	kfree(buf);
+	return 0;
+}
+
+static void sixaxis_set_leds_from_id(struct sony_sc *sc)
+{
+	static const u8 sixaxis_leds[10][4] = {
+				{ 0x01, 0x00, 0x00, 0x00 },
+				{ 0x00, 0x01, 0x00, 0x00 },
+				{ 0x00, 0x00, 0x01, 0x00 },
+				{ 0x00, 0x00, 0x00, 0x01 },
+				{ 0x01, 0x00, 0x00, 0x01 },
+				{ 0x00, 0x01, 0x00, 0x01 },
+				{ 0x00, 0x00, 0x01, 0x01 },
+				{ 0x01, 0x00, 0x01, 0x01 },
+				{ 0x00, 0x01, 0x01, 0x01 },
+				{ 0x01, 0x01, 0x01, 0x01 }
+	};
+
+	int id = sc->device_id;
+
+	BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(sixaxis_leds[0]));
+
+	if (id < 0)
+		return;
+
+	id %= 10;
+	memcpy(sc->led_state, sixaxis_leds[id], sizeof(sixaxis_leds[id]));
+}
+
+static void dualshock4_set_leds_from_id(struct sony_sc *sc)
+{
+	/* The first 4 color/index entries match what the PS4 assigns */
+	static const u8 color_code[7][3] = {
+			/* Blue   */	{ 0x00, 0x00, 0x40 },
+			/* Red	  */	{ 0x40, 0x00, 0x00 },
+			/* Green  */	{ 0x00, 0x40, 0x00 },
+			/* Pink   */	{ 0x20, 0x00, 0x20 },
+			/* Orange */	{ 0x02, 0x01, 0x00 },
+			/* Teal   */	{ 0x00, 0x01, 0x01 },
+			/* White  */	{ 0x01, 0x01, 0x01 }
+	};
+
+	int id = sc->device_id;
+
+	BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(color_code[0]));
+
+	if (id < 0)
+		return;
+
+	id %= 7;
+	memcpy(sc->led_state, color_code[id], sizeof(color_code[id]));
+}
+
+static void buzz_set_leds(struct sony_sc *sc)
+{
+	struct hid_device *hdev = sc->hdev;
+	struct list_head *report_list =
+		&hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next,
+		struct hid_report, list);
+	s32 *value = report->field[0]->value;
+
+	BUILD_BUG_ON(MAX_LEDS < 4);
+
+	value[0] = 0x00;
+	value[1] = sc->led_state[0] ? 0xff : 0x00;
+	value[2] = sc->led_state[1] ? 0xff : 0x00;
+	value[3] = sc->led_state[2] ? 0xff : 0x00;
+	value[4] = sc->led_state[3] ? 0xff : 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+}
+
+static void sony_set_leds(struct sony_sc *sc)
+{
+	if (!(sc->quirks & BUZZ_CONTROLLER))
+		sony_schedule_work(sc, SONY_WORKER_STATE);
+	else
+		buzz_set_leds(sc);
+}
+
+static void sony_led_set_brightness(struct led_classdev *led,
+				    enum led_brightness value)
+{
+	struct device *dev = led->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *drv_data;
+
+	int n;
+	int force_update;
+
+	drv_data = hid_get_drvdata(hdev);
+	if (!drv_data) {
+		hid_err(hdev, "No device data\n");
+		return;
+	}
+
+	/*
+	 * The Sixaxis on USB will override any LED settings sent to it
+	 * and keep flashing all of the LEDs until the PS button is pressed.
+	 * Updates, even if redundant, must be always be sent to the
+	 * controller to avoid having to toggle the state of an LED just to
+	 * stop the flashing later on.
+	 */
+	force_update = !!(drv_data->quirks & SIXAXIS_CONTROLLER_USB);
+
+	for (n = 0; n < drv_data->led_count; n++) {
+		if (led == drv_data->leds[n] && (force_update ||
+			(value != drv_data->led_state[n] ||
+			drv_data->led_delay_on[n] ||
+			drv_data->led_delay_off[n]))) {
+
+			drv_data->led_state[n] = value;
+
+			/* Setting the brightness stops the blinking */
+			drv_data->led_delay_on[n] = 0;
+			drv_data->led_delay_off[n] = 0;
+
+			sony_set_leds(drv_data);
+			break;
+		}
+	}
+}
+
+static enum led_brightness sony_led_get_brightness(struct led_classdev *led)
+{
+	struct device *dev = led->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *drv_data;
+
+	int n;
+
+	drv_data = hid_get_drvdata(hdev);
+	if (!drv_data) {
+		hid_err(hdev, "No device data\n");
+		return LED_OFF;
+	}
+
+	for (n = 0; n < drv_data->led_count; n++) {
+		if (led == drv_data->leds[n])
+			return drv_data->led_state[n];
+	}
+
+	return LED_OFF;
+}
+
+static int sony_led_blink_set(struct led_classdev *led, unsigned long *delay_on,
+				unsigned long *delay_off)
+{
+	struct device *dev = led->dev->parent;
+	struct hid_device *hdev = to_hid_device(dev);
+	struct sony_sc *drv_data = hid_get_drvdata(hdev);
+	int n;
+	u8 new_on, new_off;
+
+	if (!drv_data) {
+		hid_err(hdev, "No device data\n");
+		return -EINVAL;
+	}
+
+	/* Max delay is 255 deciseconds or 2550 milliseconds */
+	if (*delay_on > 2550)
+		*delay_on = 2550;
+	if (*delay_off > 2550)
+		*delay_off = 2550;
+
+	/* Blink at 1 Hz if both values are zero */
+	if (!*delay_on && !*delay_off)
+		*delay_on = *delay_off = 500;
+
+	new_on = *delay_on / 10;
+	new_off = *delay_off / 10;
+
+	for (n = 0; n < drv_data->led_count; n++) {
+		if (led == drv_data->leds[n])
+			break;
+	}
+
+	/* This LED is not registered on this device */
+	if (n >= drv_data->led_count)
+		return -EINVAL;
+
+	/* Don't schedule work if the values didn't change */
+	if (new_on != drv_data->led_delay_on[n] ||
+		new_off != drv_data->led_delay_off[n]) {
+		drv_data->led_delay_on[n] = new_on;
+		drv_data->led_delay_off[n] = new_off;
+		sony_schedule_work(drv_data, SONY_WORKER_STATE);
+	}
+
+	return 0;
+}
+
+static int sony_leds_init(struct sony_sc *sc)
+{
+	struct hid_device *hdev = sc->hdev;
+	int n, ret = 0;
+	int use_ds4_names;
+	struct led_classdev *led;
+	size_t name_sz;
+	char *name;
+	size_t name_len;
+	const char *name_fmt;
+	static const char * const ds4_name_str[] = { "red", "green", "blue",
+						  "global" };
+	u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
+	u8 use_hw_blink[MAX_LEDS] = { 0 };
+
+	BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
+
+	if (sc->quirks & BUZZ_CONTROLLER) {
+		sc->led_count = 4;
+		use_ds4_names = 0;
+		name_len = strlen("::buzz#");
+		name_fmt = "%s::buzz%d";
+		/* Validate expected report characteristics. */
+		if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7))
+			return -ENODEV;
+	} else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
+		dualshock4_set_leds_from_id(sc);
+		sc->led_state[3] = 1;
+		sc->led_count = 4;
+		memset(max_brightness, 255, 3);
+		use_hw_blink[3] = 1;
+		use_ds4_names = 1;
+		name_len = 0;
+		name_fmt = "%s:%s";
+	} else if (sc->quirks & MOTION_CONTROLLER) {
+		sc->led_count = 3;
+		memset(max_brightness, 255, 3);
+		use_ds4_names = 1;
+		name_len = 0;
+		name_fmt = "%s:%s";
+	} else if (sc->quirks & NAVIGATION_CONTROLLER) {
+		static const u8 navigation_leds[4] = {0x01, 0x00, 0x00, 0x00};
+
+		memcpy(sc->led_state, navigation_leds, sizeof(navigation_leds));
+		sc->led_count = 1;
+		memset(use_hw_blink, 1, 4);
+		use_ds4_names = 0;
+		name_len = strlen("::sony#");
+		name_fmt = "%s::sony%d";
+	} else {
+		sixaxis_set_leds_from_id(sc);
+		sc->led_count = 4;
+		memset(use_hw_blink, 1, 4);
+		use_ds4_names = 0;
+		name_len = strlen("::sony#");
+		name_fmt = "%s::sony%d";
+	}
+
+	/*
+	 * Clear LEDs as we have no way of reading their initial state. This is
+	 * only relevant if the driver is loaded after somebody actively set the
+	 * LEDs to on
+	 */
+	sony_set_leds(sc);
+
+	name_sz = strlen(dev_name(&hdev->dev)) + name_len + 1;
+
+	for (n = 0; n < sc->led_count; n++) {
+
+		if (use_ds4_names)
+			name_sz = strlen(dev_name(&hdev->dev)) + strlen(ds4_name_str[n]) + 2;
+
+		led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
+		if (!led) {
+			hid_err(hdev, "Couldn't allocate memory for LED %d\n", n);
+			return -ENOMEM;
+		}
+
+		name = (void *)(&led[1]);
+		if (use_ds4_names)
+			snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev),
+			ds4_name_str[n]);
+		else
+			snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1);
+		led->name = name;
+		led->brightness = sc->led_state[n];
+		led->max_brightness = max_brightness[n];
+		led->flags = LED_CORE_SUSPENDRESUME;
+		led->brightness_get = sony_led_get_brightness;
+		led->brightness_set = sony_led_set_brightness;
+
+		if (use_hw_blink[n])
+			led->blink_set = sony_led_blink_set;
+
+		sc->leds[n] = led;
+
+		ret = devm_led_classdev_register(&hdev->dev, led);
+		if (ret) {
+			hid_err(hdev, "Failed to register LED %d\n", n);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void sixaxis_send_output_report(struct sony_sc *sc)
+{
+	static const union sixaxis_output_report_01 default_report = {
+		.buf = {
+			0x01,
+			0x01, 0xff, 0x00, 0xff, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x00,
+			0xff, 0x27, 0x10, 0x00, 0x32,
+			0xff, 0x27, 0x10, 0x00, 0x32,
+			0xff, 0x27, 0x10, 0x00, 0x32,
+			0xff, 0x27, 0x10, 0x00, 0x32,
+			0x00, 0x00, 0x00, 0x00, 0x00
+		}
+	};
+	struct sixaxis_output_report *report =
+		(struct sixaxis_output_report *)sc->output_report_dmabuf;
+	int n;
+
+	/* Initialize the report with default values */
+	memcpy(report, &default_report, sizeof(struct sixaxis_output_report));
+
+#ifdef CONFIG_SONY_FF
+	report->rumble.right_motor_on = sc->right ? 1 : 0;
+	report->rumble.left_motor_force = sc->left;
+#endif
+
+	report->leds_bitmap |= sc->led_state[0] << 1;
+	report->leds_bitmap |= sc->led_state[1] << 2;
+	report->leds_bitmap |= sc->led_state[2] << 3;
+	report->leds_bitmap |= sc->led_state[3] << 4;
+
+	/* Set flag for all leds off, required for 3rd party INTEC controller */
+	if ((report->leds_bitmap & 0x1E) == 0)
+		report->leds_bitmap |= 0x20;
+
+	/*
+	 * The LEDs in the report are indexed in reverse order to their
+	 * corresponding light on the controller.
+	 * Index 0 = LED 4, index 1 = LED 3, etc...
+	 *
+	 * In the case of both delay values being zero (blinking disabled) the
+	 * default report values should be used or the controller LED will be
+	 * always off.
+	 */
+	for (n = 0; n < 4; n++) {
+		if (sc->led_delay_on[n] || sc->led_delay_off[n]) {
+			report->led[3 - n].duty_off = sc->led_delay_off[n];
+			report->led[3 - n].duty_on = sc->led_delay_on[n];
+		}
+	}
+
+	hid_hw_raw_request(sc->hdev, report->report_id, (u8 *)report,
+			sizeof(struct sixaxis_output_report),
+			HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
+}
+
+static void dualshock4_send_output_report(struct sony_sc *sc)
+{
+	struct hid_device *hdev = sc->hdev;
+	u8 *buf = sc->output_report_dmabuf;
+	int offset;
+
+	/*
+	 * NOTE: The lower 6 bits of buf[1] field of the Bluetooth report
+	 * control the interval at which Dualshock 4 reports data:
+	 * 0x00 - 1ms
+	 * 0x01 - 1ms
+	 * 0x02 - 2ms
+	 * 0x3E - 62ms
+	 * 0x3F - disabled
+	 */
+	if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
+		memset(buf, 0, DS4_OUTPUT_REPORT_0x05_SIZE);
+		buf[0] = 0x05;
+		buf[1] = 0x07; /* blink + LEDs + motor */
+		offset = 4;
+	} else {
+		memset(buf, 0, DS4_OUTPUT_REPORT_0x11_SIZE);
+		buf[0] = 0x11;
+		buf[1] = 0xC0 /* HID + CRC */ | sc->ds4_bt_poll_interval;
+		buf[3] = 0x07; /* blink + LEDs + motor */
+		offset = 6;
+	}
+
+#ifdef CONFIG_SONY_FF
+	buf[offset++] = sc->right;
+	buf[offset++] = sc->left;
+#else
+	offset += 2;
+#endif
+
+	/* LED 3 is the global control */
+	if (sc->led_state[3]) {
+		buf[offset++] = sc->led_state[0];
+		buf[offset++] = sc->led_state[1];
+		buf[offset++] = sc->led_state[2];
+	} else {
+		offset += 3;
+	}
+
+	/* If both delay values are zero the DualShock 4 disables blinking. */
+	buf[offset++] = sc->led_delay_on[3];
+	buf[offset++] = sc->led_delay_off[3];
+
+	if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE))
+		hid_hw_output_report(hdev, buf, DS4_OUTPUT_REPORT_0x05_SIZE);
+	else {
+		/* CRC generation */
+		u8 bthdr = 0xA2;
+		u32 crc;
+
+		crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
+		crc = ~crc32_le(crc, buf, DS4_OUTPUT_REPORT_0x11_SIZE-4);
+		put_unaligned_le32(crc, &buf[74]);
+		hid_hw_output_report(hdev, buf, DS4_OUTPUT_REPORT_0x11_SIZE);
+	}
+}
+
+static void motion_send_output_report(struct sony_sc *sc)
+{
+	struct hid_device *hdev = sc->hdev;
+	struct motion_output_report_02 *report =
+		(struct motion_output_report_02 *)sc->output_report_dmabuf;
+
+	memset(report, 0, MOTION_REPORT_0x02_SIZE);
+
+	report->type = 0x02; /* set leds */
+	report->r = sc->led_state[0];
+	report->g = sc->led_state[1];
+	report->b = sc->led_state[2];
+
+#ifdef CONFIG_SONY_FF
+	report->rumble = max(sc->right, sc->left);
+#endif
+
+	hid_hw_output_report(hdev, (u8 *)report, MOTION_REPORT_0x02_SIZE);
+}
+
+static inline void sony_send_output_report(struct sony_sc *sc)
+{
+	if (sc->send_output_report)
+		sc->send_output_report(sc);
+}
+
+static void sony_state_worker(struct work_struct *work)
+{
+	struct sony_sc *sc = container_of(work, struct sony_sc, state_worker);
+
+	sc->send_output_report(sc);
+}
+
+static int sony_allocate_output_report(struct sony_sc *sc)
+{
+	if ((sc->quirks & SIXAXIS_CONTROLLER) ||
+			(sc->quirks & NAVIGATION_CONTROLLER))
+		sc->output_report_dmabuf =
+			devm_kmalloc(&sc->hdev->dev,
+				sizeof(union sixaxis_output_report_01),
+				GFP_KERNEL);
+	else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT)
+		sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
+						DS4_OUTPUT_REPORT_0x11_SIZE,
+						GFP_KERNEL);
+	else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE))
+		sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
+						DS4_OUTPUT_REPORT_0x05_SIZE,
+						GFP_KERNEL);
+	else if (sc->quirks & MOTION_CONTROLLER)
+		sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
+						MOTION_REPORT_0x02_SIZE,
+						GFP_KERNEL);
+	else
+		return 0;
+
+	if (!sc->output_report_dmabuf)
+		return -ENOMEM;
+
+	return 0;
+}
+
+#ifdef CONFIG_SONY_FF
+static int sony_play_effect(struct input_dev *dev, void *data,
+			    struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct sony_sc *sc = hid_get_drvdata(hid);
+
+	if (effect->type != FF_RUMBLE)
+		return 0;
+
+	sc->left = effect->u.rumble.strong_magnitude / 256;
+	sc->right = effect->u.rumble.weak_magnitude / 256;
+
+	sony_schedule_work(sc, SONY_WORKER_STATE);
+	return 0;
+}
+
+static int sony_init_ff(struct sony_sc *sc)
+{
+	struct hid_input *hidinput = list_entry(sc->hdev->inputs.next,
+						struct hid_input, list);
+	struct input_dev *input_dev = hidinput->input;
+
+	input_set_capability(input_dev, EV_FF, FF_RUMBLE);
+	return input_ff_create_memless(input_dev, NULL, sony_play_effect);
+}
+
+#else
+static int sony_init_ff(struct sony_sc *sc)
+{
+	return 0;
+}
+
+#endif
+
+static int sony_battery_get_property(struct power_supply *psy,
+				     enum power_supply_property psp,
+				     union power_supply_propval *val)
+{
+	struct sony_sc *sc = power_supply_get_drvdata(psy);
+	unsigned long flags;
+	int ret = 0;
+	u8 battery_charging, battery_capacity, cable_state;
+
+	spin_lock_irqsave(&sc->lock, flags);
+	battery_charging = sc->battery_charging;
+	battery_capacity = sc->battery_capacity;
+	cable_state = sc->cable_state;
+	spin_unlock_irqrestore(&sc->lock, flags);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = battery_capacity;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (battery_charging)
+			val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		else
+			if (battery_capacity == 100 && cable_state)
+				val->intval = POWER_SUPPLY_STATUS_FULL;
+			else
+				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int sony_battery_probe(struct sony_sc *sc, int append_dev_id)
+{
+	const char *battery_str_fmt = append_dev_id ?
+		"sony_controller_battery_%pMR_%i" :
+		"sony_controller_battery_%pMR";
+	struct power_supply_config psy_cfg = { .drv_data = sc, };
+	struct hid_device *hdev = sc->hdev;
+	int ret;
+
+	/*
+	 * Set the default battery level to 100% to avoid low battery warnings
+	 * if the battery is polled before the first device report is received.
+	 */
+	sc->battery_capacity = 100;
+
+	sc->battery_desc.properties = sony_battery_props;
+	sc->battery_desc.num_properties = ARRAY_SIZE(sony_battery_props);
+	sc->battery_desc.get_property = sony_battery_get_property;
+	sc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+	sc->battery_desc.use_for_apm = 0;
+	sc->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
+					  battery_str_fmt, sc->mac_address, sc->device_id);
+	if (!sc->battery_desc.name)
+		return -ENOMEM;
+
+	sc->battery = devm_power_supply_register(&hdev->dev, &sc->battery_desc,
+					    &psy_cfg);
+	if (IS_ERR(sc->battery)) {
+		ret = PTR_ERR(sc->battery);
+		hid_err(hdev, "Unable to register battery device\n");
+		return ret;
+	}
+
+	power_supply_powers(sc->battery, &hdev->dev);
+	return 0;
+}
+
+/*
+ * If a controller is plugged in via USB while already connected via Bluetooth
+ * it will show up as two devices. A global list of connected controllers and
+ * their MAC addresses is maintained to ensure that a device is only connected
+ * once.
+ *
+ * Some USB-only devices masquerade as Sixaxis controllers and all have the
+ * same dummy Bluetooth address, so a comparison of the connection type is
+ * required.  Devices are only rejected in the case where two devices have
+ * matching Bluetooth addresses on different bus types.
+ */
+static inline int sony_compare_connection_type(struct sony_sc *sc0,
+						struct sony_sc *sc1)
+{
+	const int sc0_not_bt = !(sc0->quirks & SONY_BT_DEVICE);
+	const int sc1_not_bt = !(sc1->quirks & SONY_BT_DEVICE);
+
+	return sc0_not_bt == sc1_not_bt;
+}
+
+static int sony_check_add_dev_list(struct sony_sc *sc)
+{
+	struct sony_sc *entry;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&sony_dev_list_lock, flags);
+
+	list_for_each_entry(entry, &sony_device_list, list_node) {
+		ret = memcmp(sc->mac_address, entry->mac_address,
+				sizeof(sc->mac_address));
+		if (!ret) {
+			if (sony_compare_connection_type(sc, entry)) {
+				ret = 1;
+			} else {
+				ret = -EEXIST;
+				hid_info(sc->hdev,
+				"controller with MAC address %pMR already connected\n",
+				sc->mac_address);
+			}
+			goto unlock;
+		}
+	}
+
+	ret = 0;
+	list_add(&(sc->list_node), &sony_device_list);
+
+unlock:
+	spin_unlock_irqrestore(&sony_dev_list_lock, flags);
+	return ret;
+}
+
+static void sony_remove_dev_list(struct sony_sc *sc)
+{
+	unsigned long flags;
+
+	if (sc->list_node.next) {
+		spin_lock_irqsave(&sony_dev_list_lock, flags);
+		list_del(&(sc->list_node));
+		spin_unlock_irqrestore(&sony_dev_list_lock, flags);
+	}
+}
+
+static int sony_get_bt_devaddr(struct sony_sc *sc)
+{
+	int ret;
+
+	/* HIDP stores the device MAC address as a string in the uniq field. */
+	ret = strlen(sc->hdev->uniq);
+	if (ret != 17)
+		return -EINVAL;
+
+	ret = sscanf(sc->hdev->uniq,
+		"%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+		&sc->mac_address[5], &sc->mac_address[4], &sc->mac_address[3],
+		&sc->mac_address[2], &sc->mac_address[1], &sc->mac_address[0]);
+
+	if (ret != 6)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int sony_check_add(struct sony_sc *sc)
+{
+	u8 *buf = NULL;
+	int n, ret;
+
+	if ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) ||
+	    (sc->quirks & MOTION_CONTROLLER_BT) ||
+	    (sc->quirks & NAVIGATION_CONTROLLER_BT) ||
+	    (sc->quirks & SIXAXIS_CONTROLLER_BT)) {
+		/*
+		 * sony_get_bt_devaddr() attempts to parse the Bluetooth MAC
+		 * address from the uniq string where HIDP stores it.
+		 * As uniq cannot be guaranteed to be a MAC address in all cases
+		 * a failure of this function should not prevent the connection.
+		 */
+		if (sony_get_bt_devaddr(sc) < 0) {
+			hid_warn(sc->hdev, "UNIQ does not contain a MAC address; duplicate check skipped\n");
+			return 0;
+		}
+	} else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
+		buf = kmalloc(DS4_FEATURE_REPORT_0x81_SIZE, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		/*
+		 * The MAC address of a DS4 controller connected via USB can be
+		 * retrieved with feature report 0x81. The address begins at
+		 * offset 1.
+		 */
+		ret = hid_hw_raw_request(sc->hdev, 0x81, buf,
+				DS4_FEATURE_REPORT_0x81_SIZE, HID_FEATURE_REPORT,
+				HID_REQ_GET_REPORT);
+
+		if (ret != DS4_FEATURE_REPORT_0x81_SIZE) {
+			hid_err(sc->hdev, "failed to retrieve feature report 0x81 with the DualShock 4 MAC address\n");
+			ret = ret < 0 ? ret : -EINVAL;
+			goto out_free;
+		}
+
+		memcpy(sc->mac_address, &buf[1], sizeof(sc->mac_address));
+
+		snprintf(sc->hdev->uniq, sizeof(sc->hdev->uniq),
+			 "%pMR", sc->mac_address);
+	} else if ((sc->quirks & SIXAXIS_CONTROLLER_USB) ||
+			(sc->quirks & NAVIGATION_CONTROLLER_USB)) {
+		buf = kmalloc(SIXAXIS_REPORT_0xF2_SIZE, GFP_KERNEL);
+		if (!buf)
+			return -ENOMEM;
+
+		/*
+		 * The MAC address of a Sixaxis controller connected via USB can
+		 * be retrieved with feature report 0xf2. The address begins at
+		 * offset 4.
+		 */
+		ret = hid_hw_raw_request(sc->hdev, 0xf2, buf,
+				SIXAXIS_REPORT_0xF2_SIZE, HID_FEATURE_REPORT,
+				HID_REQ_GET_REPORT);
+
+		if (ret != SIXAXIS_REPORT_0xF2_SIZE) {
+			hid_err(sc->hdev, "failed to retrieve feature report 0xf2 with the Sixaxis MAC address\n");
+			ret = ret < 0 ? ret : -EINVAL;
+			goto out_free;
+		}
+
+		/*
+		 * The Sixaxis device MAC in the report is big-endian and must
+		 * be byte-swapped.
+		 */
+		for (n = 0; n < 6; n++)
+			sc->mac_address[5-n] = buf[4+n];
+
+		snprintf(sc->hdev->uniq, sizeof(sc->hdev->uniq),
+			 "%pMR", sc->mac_address);
+	} else {
+		return 0;
+	}
+
+	ret = sony_check_add_dev_list(sc);
+
+out_free:
+
+	kfree(buf);
+
+	return ret;
+}
+
+static int sony_set_device_id(struct sony_sc *sc)
+{
+	int ret;
+
+	/*
+	 * Only DualShock 4 or Sixaxis controllers get an id.
+	 * All others are set to -1.
+	 */
+	if ((sc->quirks & SIXAXIS_CONTROLLER) ||
+	    (sc->quirks & DUALSHOCK4_CONTROLLER)) {
+		ret = ida_simple_get(&sony_device_id_allocator, 0, 0,
+					GFP_KERNEL);
+		if (ret < 0) {
+			sc->device_id = -1;
+			return ret;
+		}
+		sc->device_id = ret;
+	} else {
+		sc->device_id = -1;
+	}
+
+	return 0;
+}
+
+static void sony_release_device_id(struct sony_sc *sc)
+{
+	if (sc->device_id >= 0) {
+		ida_simple_remove(&sony_device_id_allocator, sc->device_id);
+		sc->device_id = -1;
+	}
+}
+
+static inline void sony_init_output_report(struct sony_sc *sc,
+				void (*send_output_report)(struct sony_sc *))
+{
+	sc->send_output_report = send_output_report;
+
+	if (!sc->state_worker_initialized)
+		INIT_WORK(&sc->state_worker, sony_state_worker);
+
+	sc->state_worker_initialized = 1;
+}
+
+static inline void sony_cancel_work_sync(struct sony_sc *sc)
+{
+	if (sc->hotplug_worker_initialized)
+		cancel_work_sync(&sc->hotplug_worker);
+	if (sc->state_worker_initialized)
+		cancel_work_sync(&sc->state_worker);
+}
+
+
+static int sony_input_configured(struct hid_device *hdev,
+					struct hid_input *hidinput)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+	int append_dev_id;
+	int ret;
+
+	ret = sony_set_device_id(sc);
+	if (ret < 0) {
+		hid_err(hdev, "failed to allocate the device id\n");
+		goto err_stop;
+	}
+
+	ret = append_dev_id = sony_check_add(sc);
+	if (ret < 0)
+		goto err_stop;
+
+	ret = sony_allocate_output_report(sc);
+	if (ret < 0) {
+		hid_err(hdev, "failed to allocate the output report buffer\n");
+		goto err_stop;
+	}
+
+	if (sc->quirks & NAVIGATION_CONTROLLER_USB) {
+		/*
+		 * The Sony Sixaxis does not handle HID Output Reports on the
+		 * Interrupt EP like it could, so we need to force HID Output
+		 * Reports to use HID_REQ_SET_REPORT on the Control EP.
+		 *
+		 * There is also another issue about HID Output Reports via USB,
+		 * the Sixaxis does not want the report_id as part of the data
+		 * packet, so we have to discard buf[0] when sending the actual
+		 * control message, even for numbered reports, humpf!
+		 *
+		 * Additionally, the Sixaxis on USB isn't properly initialized
+		 * until the PS logo button is pressed and as such won't retain
+		 * any state set by an output report, so the initial
+		 * configuration report is deferred until the first input
+		 * report arrives.
+		 */
+		hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+		hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
+		sc->defer_initialization = 1;
+
+		ret = sixaxis_set_operational_usb(hdev);
+		if (ret < 0) {
+			hid_err(hdev, "Failed to set controller into operational mode\n");
+			goto err_stop;
+		}
+
+		sony_init_output_report(sc, sixaxis_send_output_report);
+	} else if (sc->quirks & NAVIGATION_CONTROLLER_BT) {
+		/*
+		 * The Navigation controller wants output reports sent on the ctrl
+		 * endpoint when connected via Bluetooth.
+		 */
+		hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+
+		ret = sixaxis_set_operational_bt(hdev);
+		if (ret < 0) {
+			hid_err(hdev, "Failed to set controller into operational mode\n");
+			goto err_stop;
+		}
+
+		sony_init_output_report(sc, sixaxis_send_output_report);
+	} else if (sc->quirks & SIXAXIS_CONTROLLER_USB) {
+		/*
+		 * The Sony Sixaxis does not handle HID Output Reports on the
+		 * Interrupt EP and the device only becomes active when the
+		 * PS button is pressed. See comment for Navigation controller
+		 * above for more details.
+		 */
+		hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+		hdev->quirks |= HID_QUIRK_SKIP_OUTPUT_REPORT_ID;
+		sc->defer_initialization = 1;
+
+		ret = sixaxis_set_operational_usb(hdev);
+		if (ret < 0) {
+			hid_err(hdev, "Failed to set controller into operational mode\n");
+			goto err_stop;
+		}
+
+		ret = sony_register_sensors(sc);
+		if (ret) {
+			hid_err(sc->hdev,
+			"Unable to initialize motion sensors: %d\n", ret);
+			goto err_stop;
+		}
+
+		sony_init_output_report(sc, sixaxis_send_output_report);
+	} else if (sc->quirks & SIXAXIS_CONTROLLER_BT) {
+		/*
+		 * The Sixaxis wants output reports sent on the ctrl endpoint
+		 * when connected via Bluetooth.
+		 */
+		hdev->quirks |= HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP;
+
+		ret = sixaxis_set_operational_bt(hdev);
+		if (ret < 0) {
+			hid_err(hdev, "Failed to set controller into operational mode\n");
+			goto err_stop;
+		}
+
+		ret = sony_register_sensors(sc);
+		if (ret) {
+			hid_err(sc->hdev,
+			"Unable to initialize motion sensors: %d\n", ret);
+			goto err_stop;
+		}
+
+		sony_init_output_report(sc, sixaxis_send_output_report);
+	} else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
+		ret = dualshock4_get_calibration_data(sc);
+		if (ret < 0) {
+			hid_err(hdev, "Failed to get calibration data from Dualshock 4\n");
+			goto err_stop;
+		}
+
+		ret = dualshock4_get_version_info(sc);
+		if (ret < 0) {
+			hid_err(sc->hdev, "Failed to get version data from Dualshock 4\n");
+			goto err_stop;
+		}
+
+		ret = device_create_file(&sc->hdev->dev, &dev_attr_firmware_version);
+		if (ret) {
+			/* Make zero for cleanup reasons of sysfs entries. */
+			sc->fw_version = 0;
+			sc->hw_version = 0;
+			hid_err(sc->hdev, "can't create sysfs firmware_version attribute err: %d\n", ret);
+			goto err_stop;
+		}
+
+		ret = device_create_file(&sc->hdev->dev, &dev_attr_hardware_version);
+		if (ret) {
+			sc->hw_version = 0;
+			hid_err(sc->hdev, "can't create sysfs hardware_version attribute err: %d\n", ret);
+			goto err_stop;
+		}
+
+		/*
+		 * The Dualshock 4 touchpad supports 2 touches and has a
+		 * resolution of 1920x942 (44.86 dots/mm).
+		 */
+		ret = sony_register_touchpad(sc, 2, 1920, 942, 0, 0, 0);
+		if (ret) {
+			hid_err(sc->hdev,
+			"Unable to initialize multi-touch slots: %d\n",
+			ret);
+			goto err_stop;
+		}
+
+		ret = sony_register_sensors(sc);
+		if (ret) {
+			hid_err(sc->hdev,
+			"Unable to initialize motion sensors: %d\n", ret);
+			goto err_stop;
+		}
+
+		if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) {
+			sc->ds4_bt_poll_interval = DS4_BT_DEFAULT_POLL_INTERVAL_MS;
+			ret = device_create_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
+			if (ret)
+				hid_warn(sc->hdev,
+				 "can't create sysfs bt_poll_interval attribute err: %d\n",
+				 ret);
+		}
+
+		if (sc->quirks & DUALSHOCK4_DONGLE) {
+			INIT_WORK(&sc->hotplug_worker, dualshock4_calibration_work);
+			sc->hotplug_worker_initialized = 1;
+			sc->ds4_dongle_state = DONGLE_DISCONNECTED;
+		}
+
+		sony_init_output_report(sc, dualshock4_send_output_report);
+	} else if (sc->quirks & NSG_MRXU_REMOTE) {
+		/*
+		 * The NSG-MRxU touchpad supports 2 touches and has a
+		 * resolution of 1667x1868
+		 */
+		ret = sony_register_touchpad(sc, 2,
+			NSG_MRXU_MAX_X, NSG_MRXU_MAX_Y, 15, 15, 1);
+		if (ret) {
+			hid_err(sc->hdev,
+			"Unable to initialize multi-touch slots: %d\n",
+			ret);
+			goto err_stop;
+		}
+
+	} else if (sc->quirks & MOTION_CONTROLLER) {
+		sony_init_output_report(sc, motion_send_output_report);
+	} else {
+		ret = 0;
+	}
+
+	if (sc->quirks & SONY_LED_SUPPORT) {
+		ret = sony_leds_init(sc);
+		if (ret < 0)
+			goto err_stop;
+	}
+
+	if (sc->quirks & SONY_BATTERY_SUPPORT) {
+		ret = sony_battery_probe(sc, append_dev_id);
+		if (ret < 0)
+			goto err_stop;
+
+		/* Open the device to receive reports with battery info */
+		ret = hid_hw_open(hdev);
+		if (ret < 0) {
+			hid_err(hdev, "hw open failed\n");
+			goto err_stop;
+		}
+	}
+
+	if (sc->quirks & SONY_FF_SUPPORT) {
+		ret = sony_init_ff(sc);
+		if (ret < 0)
+			goto err_close;
+	}
+
+	return 0;
+err_close:
+	hid_hw_close(hdev);
+err_stop:
+	/* Piggy back on the default ds4_bt_ poll_interval to determine
+	 * if we need to remove the file as we don't know for sure if we
+	 * executed that logic.
+	 */
+	if (sc->ds4_bt_poll_interval)
+		device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
+	if (sc->fw_version)
+		device_remove_file(&sc->hdev->dev, &dev_attr_firmware_version);
+	if (sc->hw_version)
+		device_remove_file(&sc->hdev->dev, &dev_attr_hardware_version);
+	sony_cancel_work_sync(sc);
+	sony_remove_dev_list(sc);
+	sony_release_device_id(sc);
+	hid_hw_stop(hdev);
+	return ret;
+}
+
+static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	unsigned long quirks = id->driver_data;
+	struct sony_sc *sc;
+	unsigned int connect_mask = HID_CONNECT_DEFAULT;
+
+	if (!strcmp(hdev->name, "FutureMax Dance Mat"))
+		quirks |= FUTUREMAX_DANCE_MAT;
+
+	sc = devm_kzalloc(&hdev->dev, sizeof(*sc), GFP_KERNEL);
+	if (sc == NULL) {
+		hid_err(hdev, "can't alloc sony descriptor\n");
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&sc->lock);
+
+	sc->quirks = quirks;
+	hid_set_drvdata(hdev, sc);
+	sc->hdev = hdev;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	if (sc->quirks & VAIO_RDESC_CONSTANT)
+		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
+	else if (sc->quirks & SIXAXIS_CONTROLLER)
+		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
+
+	/* Patch the hw version on DS3/4 compatible devices, so applications can
+	 * distinguish between the default HID mappings and the mappings defined
+	 * by the Linux game controller spec. This is important for the SDL2
+	 * library, which has a game controller database, which uses device ids
+	 * in combination with version as a key.
+	 */
+	if (sc->quirks & (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER))
+		hdev->version |= 0x8000;
+
+	ret = hid_hw_start(hdev, connect_mask);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	/* sony_input_configured can fail, but this doesn't result
+	 * in hid_hw_start failures (intended). Check whether
+	 * the HID layer claimed the device else fail.
+	 * We don't know the actual reason for the failure, most
+	 * likely it is due to EEXIST in case of double connection
+	 * of USB and Bluetooth, but could have been due to ENOMEM
+	 * or other reasons as well.
+	 */
+	if (!(hdev->claimed & HID_CLAIMED_INPUT)) {
+		hid_err(hdev, "failed to claim input\n");
+		return -ENODEV;
+	}
+
+	return ret;
+}
+
+static void sony_remove(struct hid_device *hdev)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	hid_hw_close(hdev);
+
+	if (sc->quirks & DUALSHOCK4_CONTROLLER_BT)
+		device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
+
+	if (sc->fw_version)
+		device_remove_file(&sc->hdev->dev, &dev_attr_firmware_version);
+
+	if (sc->hw_version)
+		device_remove_file(&sc->hdev->dev, &dev_attr_hardware_version);
+
+	sony_cancel_work_sync(sc);
+
+	sony_remove_dev_list(sc);
+
+	sony_release_device_id(sc);
+
+	hid_hw_stop(hdev);
+}
+
+#ifdef CONFIG_PM
+
+static int sony_suspend(struct hid_device *hdev, pm_message_t message)
+{
+#ifdef CONFIG_SONY_FF
+
+	/* On suspend stop any running force-feedback events */
+	if (SONY_FF_SUPPORT) {
+		struct sony_sc *sc = hid_get_drvdata(hdev);
+
+		sc->left = sc->right = 0;
+		sony_send_output_report(sc);
+	}
+
+#endif
+	return 0;
+}
+
+static int sony_resume(struct hid_device *hdev)
+{
+	struct sony_sc *sc = hid_get_drvdata(hdev);
+
+	/*
+	 * The Sixaxis and navigation controllers on USB need to be
+	 * reinitialized on resume or they won't behave properly.
+	 */
+	if ((sc->quirks & SIXAXIS_CONTROLLER_USB) ||
+		(sc->quirks & NAVIGATION_CONTROLLER_USB)) {
+		sixaxis_set_operational_usb(sc->hdev);
+		sc->defer_initialization = 1;
+	}
+
+	return 0;
+}
+
+#endif
+
+static const struct hid_device_id sony_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER),
+		.driver_data = SIXAXIS_CONTROLLER_USB },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER),
+		.driver_data = NAVIGATION_CONTROLLER_USB },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER),
+		.driver_data = NAVIGATION_CONTROLLER_BT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER),
+		.driver_data = MOTION_CONTROLLER_USB },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER),
+		.driver_data = MOTION_CONTROLLER_BT },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER),
+		.driver_data = SIXAXIS_CONTROLLER_BT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE),
+		.driver_data = VAIO_RDESC_CONSTANT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE),
+		.driver_data = VAIO_RDESC_CONSTANT },
+	/*
+	 * Wired Buzz Controller. Reported as Sony Hub from its USB ID and as
+	 * Logitech joystick from the device descriptor.
+	 */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER),
+		.driver_data = BUZZ_CONTROLLER },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER),
+		.driver_data = BUZZ_CONTROLLER },
+	/* PS3 BD Remote Control */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE),
+		.driver_data = PS3REMOTE },
+	/* Logitech Harmony Adapter for PS3 */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3),
+		.driver_data = PS3REMOTE },
+	/* SMK-Link PS3 BD Remote Control */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE),
+		.driver_data = PS3REMOTE },
+	/* Sony Dualshock 4 controllers for PS4 */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
+		.driver_data = DUALSHOCK4_CONTROLLER_USB },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
+		.driver_data = DUALSHOCK4_CONTROLLER_BT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+		.driver_data = DUALSHOCK4_CONTROLLER_USB },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
+		.driver_data = DUALSHOCK4_CONTROLLER_BT },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE),
+		.driver_data = DUALSHOCK4_DONGLE },
+	/* Nyko Core Controller for PS3 */
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER),
+		.driver_data = SIXAXIS_CONTROLLER_USB | SINO_LITE_CONTROLLER },
+	/* SMK-Link NSG-MR5U Remote Control */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE),
+		.driver_data = NSG_MR5U_REMOTE_BT },
+	/* SMK-Link NSG-MR7U Remote Control */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE),
+		.driver_data = NSG_MR7U_REMOTE_BT },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, sony_devices);
+
+static struct hid_driver sony_driver = {
+	.name             = "sony",
+	.id_table         = sony_devices,
+	.input_mapping    = sony_mapping,
+	.input_configured = sony_input_configured,
+	.probe            = sony_probe,
+	.remove           = sony_remove,
+	.report_fixup     = sony_report_fixup,
+	.raw_event        = sony_raw_event,
+
+#ifdef CONFIG_PM
+	.suspend          = sony_suspend,
+	.resume	          = sony_resume,
+	.reset_resume     = sony_resume,
+#endif
+};
+
+static int __init sony_init(void)
+{
+	dbg_hid("Sony:%s\n", __func__);
+
+	return hid_register_driver(&sony_driver);
+}
+
+static void __exit sony_exit(void)
+{
+	dbg_hid("Sony:%s\n", __func__);
+
+	hid_unregister_driver(&sony_driver);
+	ida_destroy(&sony_device_id_allocator);
+}
+module_init(sony_init);
+module_exit(sony_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-speedlink.c b/drivers/hid/hid-speedlink.c
new file mode 100644
index 0000000..7112f3e
--- /dev/null
+++ b/drivers/hid/hid-speedlink.c
@@ -0,0 +1,81 @@
+/*
+ *  HID driver for Speedlink Vicious and Divine Cezanne (USB mouse).
+ *  Fixes "jumpy" cursor and removes nonexistent keyboard LEDS from
+ *  the HID descriptor.
+ *
+ *  Copyright (c) 2011, 2013 Stefan Kriwanek <dev@stefankriwanek.de>
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+static const struct hid_device_id speedlink_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE)},
+	{ }
+};
+
+static int speedlink_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	/*
+	 * The Cezanne mouse has a second "keyboard" USB endpoint for it is
+	 * able to map keyboard events to the button presses.
+	 * It sends a standard keyboard report descriptor, though, whose
+	 * LEDs we ignore.
+	 */
+	switch (usage->hid & HID_USAGE_PAGE) {
+	case HID_UP_LED:
+		return -1;
+	}
+	return 0;
+}
+
+static int speedlink_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	/* No other conditions due to usage_table. */
+
+	/* This fixes the "jumpy" cursor occuring due to invalid events sent
+	 * by the device. Some devices only send them with value==+256, others
+	 * don't. However, catching abs(value)>=256 is restrictive enough not
+	 * to interfere with devices that were bug-free (has been tested).
+	 */
+	if (abs(value) >= 256)
+		return 1;
+	/* Drop useless distance 0 events (on button clicks etc.) as well */
+	if (value == 0)
+		return 1;
+
+	return 0;
+}
+
+MODULE_DEVICE_TABLE(hid, speedlink_devices);
+
+static const struct hid_usage_id speedlink_grabbed_usages[] = {
+	{ HID_GD_X, EV_REL, 0 },
+	{ HID_GD_Y, EV_REL, 1 },
+	{ HID_ANY_ID - 1, HID_ANY_ID - 1, HID_ANY_ID - 1}
+};
+
+static struct hid_driver speedlink_driver = {
+	.name = "speedlink",
+	.id_table = speedlink_devices,
+	.usage_table = speedlink_grabbed_usages,
+	.input_mapping = speedlink_input_mapping,
+	.event = speedlink_event,
+};
+module_hid_driver(speedlink_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
new file mode 100644
index 0000000..dc4128b
--- /dev/null
+++ b/drivers/hid/hid-steam.c
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID driver for Valve Steam Controller
+ *
+ * Copyright (c) 2018 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
+ *
+ * Supports both the wired and wireless interfaces.
+ *
+ * This controller has a builtin emulation of mouse and keyboard: the right pad
+ * can be used as a mouse, the shoulder buttons are mouse buttons, A and B
+ * buttons are ENTER and ESCAPE, and so on. This is implemented as additional
+ * HID interfaces.
+ *
+ * This is known as the "lizard mode", because apparently lizards like to use
+ * the computer from the coach, without a proper mouse and keyboard.
+ *
+ * This driver will disable the lizard mode when the input device is opened
+ * and re-enable it when the input device is closed, so as not to break user
+ * mode behaviour. The lizard_mode parameter can be used to change that.
+ *
+ * There are a few user space applications (notably Steam Client) that use
+ * the hidraw interface directly to create input devices (XTest, uinput...).
+ * In order to avoid breaking them this driver creates a layered hidraw device,
+ * so it can detect when the client is running and then:
+ *  - it will not send any command to the controller.
+ *  - this input device will be removed, to avoid double input of the same
+ *    user action.
+ * When the client is closed, this input device will be created again.
+ *
+ * For additional functions, such as changing the right-pad margin or switching
+ * the led, you can use the user-space tool at:
+ *
+ *   https://github.com/rodrigorc/steamctrl
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/rcupdate.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include "hid-ids.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>");
+
+static bool lizard_mode = true;
+
+static DEFINE_MUTEX(steam_devices_lock);
+static LIST_HEAD(steam_devices);
+
+#define STEAM_QUIRK_WIRELESS		BIT(0)
+
+/* Touch pads are 40 mm in diameter and 65535 units */
+#define STEAM_PAD_RESOLUTION 1638
+/* Trigger runs are about 5 mm and 256 units */
+#define STEAM_TRIGGER_RESOLUTION 51
+/* Joystick runs are about 5 mm and 256 units */
+#define STEAM_JOYSTICK_RESOLUTION 51
+
+#define STEAM_PAD_FUZZ 256
+
+/*
+ * Commands that can be sent in a feature report.
+ * Thanks to Valve for some valuable hints.
+ */
+#define STEAM_CMD_SET_MAPPINGS		0x80
+#define STEAM_CMD_CLEAR_MAPPINGS	0x81
+#define STEAM_CMD_GET_MAPPINGS		0x82
+#define STEAM_CMD_GET_ATTRIB		0x83
+#define STEAM_CMD_GET_ATTRIB_LABEL	0x84
+#define STEAM_CMD_DEFAULT_MAPPINGS	0x85
+#define STEAM_CMD_FACTORY_RESET		0x86
+#define STEAM_CMD_WRITE_REGISTER	0x87
+#define STEAM_CMD_CLEAR_REGISTER	0x88
+#define STEAM_CMD_READ_REGISTER		0x89
+#define STEAM_CMD_GET_REGISTER_LABEL	0x8a
+#define STEAM_CMD_GET_REGISTER_MAX	0x8b
+#define STEAM_CMD_GET_REGISTER_DEFAULT	0x8c
+#define STEAM_CMD_SET_MODE		0x8d
+#define STEAM_CMD_DEFAULT_MOUSE		0x8e
+#define STEAM_CMD_FORCEFEEDBAK		0x8f
+#define STEAM_CMD_REQUEST_COMM_STATUS	0xb4
+#define STEAM_CMD_GET_SERIAL		0xae
+
+/* Some useful register ids */
+#define STEAM_REG_LPAD_MODE		0x07
+#define STEAM_REG_RPAD_MODE		0x08
+#define STEAM_REG_RPAD_MARGIN		0x18
+#define STEAM_REG_LED			0x2d
+#define STEAM_REG_GYRO_MODE		0x30
+
+/* Raw event identifiers */
+#define STEAM_EV_INPUT_DATA		0x01
+#define STEAM_EV_CONNECT		0x03
+#define STEAM_EV_BATTERY		0x04
+
+/* Values for GYRO_MODE (bitmask) */
+#define STEAM_GYRO_MODE_OFF		0x0000
+#define STEAM_GYRO_MODE_STEERING	0x0001
+#define STEAM_GYRO_MODE_TILT		0x0002
+#define STEAM_GYRO_MODE_SEND_ORIENTATION	0x0004
+#define STEAM_GYRO_MODE_SEND_RAW_ACCEL		0x0008
+#define STEAM_GYRO_MODE_SEND_RAW_GYRO		0x0010
+
+/* Other random constants */
+#define STEAM_SERIAL_LEN 10
+
+struct steam_device {
+	struct list_head list;
+	spinlock_t lock;
+	struct hid_device *hdev, *client_hdev;
+	struct mutex mutex;
+	bool client_opened;
+	struct input_dev __rcu *input;
+	unsigned long quirks;
+	struct work_struct work_connect;
+	bool connected;
+	char serial_no[STEAM_SERIAL_LEN + 1];
+	struct power_supply_desc battery_desc;
+	struct power_supply __rcu *battery;
+	u8 battery_charge;
+	u16 voltage;
+};
+
+static int steam_recv_report(struct steam_device *steam,
+		u8 *data, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/*
+	 * The report ID is always 0, so strip the first byte from the output.
+	 * hid_report_len() is not counting the report ID, so +1 to the length
+	 * or else we get a EOVERFLOW. We are safe from a buffer overflow
+	 * because hid_alloc_report_buf() allocates +7 bytes.
+	 */
+	ret = hid_hw_raw_request(steam->hdev, 0x00,
+			buf, hid_report_len(r) + 1,
+			HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+	if (ret > 0)
+		memcpy(data, buf + 1, min(size, ret - 1));
+	kfree(buf);
+	return ret;
+}
+
+static int steam_send_report(struct steam_device *steam,
+		u8 *cmd, int size)
+{
+	struct hid_report *r;
+	u8 *buf;
+	unsigned int retries = 50;
+	int ret;
+
+	r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0];
+	if (hid_report_len(r) < 64)
+		return -EINVAL;
+
+	buf = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/* The report ID is always 0 */
+	memcpy(buf + 1, cmd, size);
+
+	/*
+	 * Sometimes the wireless controller fails with EPIPE
+	 * when sending a feature report.
+	 * Doing a HID_REQ_GET_REPORT and waiting for a while
+	 * seems to fix that.
+	 */
+	do {
+		ret = hid_hw_raw_request(steam->hdev, 0,
+				buf, size + 1,
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+		if (ret != -EPIPE)
+			break;
+		msleep(20);
+	} while (--retries);
+
+	kfree(buf);
+	if (ret < 0)
+		hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__,
+				ret, size, cmd);
+	return ret;
+}
+
+static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd)
+{
+	return steam_send_report(steam, &cmd, 1);
+}
+
+static int steam_write_registers(struct steam_device *steam,
+		/* u8 reg, u16 val */...)
+{
+	/* Send: 0x87 len (reg valLo valHi)* */
+	u8 reg;
+	u16 val;
+	u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
+	va_list args;
+
+	va_start(args, steam);
+	for (;;) {
+		reg = va_arg(args, int);
+		if (reg == 0)
+			break;
+		val = va_arg(args, int);
+		cmd[cmd[1] + 2] = reg;
+		cmd[cmd[1] + 3] = val & 0xff;
+		cmd[cmd[1] + 4] = val >> 8;
+		cmd[1] += 3;
+	}
+	va_end(args);
+
+	return steam_send_report(steam, cmd, 2 + cmd[1]);
+}
+
+static int steam_get_serial(struct steam_device *steam)
+{
+	/*
+	 * Send: 0xae 0x15 0x01
+	 * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
+	 */
+	int ret;
+	u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01};
+	u8 reply[3 + STEAM_SERIAL_LEN + 1];
+
+	ret = steam_send_report(steam, cmd, sizeof(cmd));
+	if (ret < 0)
+		return ret;
+	ret = steam_recv_report(steam, reply, sizeof(reply));
+	if (ret < 0)
+		return ret;
+	if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01)
+		return -EIO;
+	reply[3 + STEAM_SERIAL_LEN] = 0;
+	strlcpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
+	return 0;
+}
+
+/*
+ * This command requests the wireless adaptor to post an event
+ * with the connection status. Useful if this driver is loaded when
+ * the controller is already connected.
+ */
+static inline int steam_request_conn_status(struct steam_device *steam)
+{
+	return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
+}
+
+static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
+{
+	if (enable) {
+		/* enable esc, enter, cursors */
+		steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
+		/* enable mouse */
+		steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE);
+		steam_write_registers(steam,
+			STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
+			0);
+	} else {
+		/* disable esc, enter, cursor */
+		steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+		steam_write_registers(steam,
+			STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+			STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
+			0);
+	}
+}
+
+static int steam_input_open(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+	int ret;
+
+	ret = hid_hw_open(steam->hdev);
+	if (ret)
+		return ret;
+
+	mutex_lock(&steam->mutex);
+	if (!steam->client_opened && lizard_mode)
+		steam_set_lizard_mode(steam, false);
+	mutex_unlock(&steam->mutex);
+	return 0;
+}
+
+static void steam_input_close(struct input_dev *dev)
+{
+	struct steam_device *steam = input_get_drvdata(dev);
+
+	mutex_lock(&steam->mutex);
+	if (!steam->client_opened && lizard_mode)
+		steam_set_lizard_mode(steam, true);
+	mutex_unlock(&steam->mutex);
+
+	hid_hw_close(steam->hdev);
+}
+
+static enum power_supply_property steam_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int steam_battery_get_property(struct power_supply *psy,
+				enum power_supply_property psp,
+				union power_supply_propval *val)
+{
+	struct steam_device *steam = power_supply_get_drvdata(psy);
+	unsigned long flags;
+	s16 volts;
+	u8 batt;
+	int ret = 0;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	volts = steam->voltage;
+	batt = steam->battery_charge;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = volts * 1000; /* mV -> uV */
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = batt;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	return ret;
+}
+
+static int steam_battery_register(struct steam_device *steam)
+{
+	struct power_supply *battery;
+	struct power_supply_config battery_cfg = { .drv_data = steam, };
+	unsigned long flags;
+	int ret;
+
+	steam->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+	steam->battery_desc.properties = steam_battery_props;
+	steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props);
+	steam->battery_desc.get_property = steam_battery_get_property;
+	steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev,
+			GFP_KERNEL, "steam-controller-%s-battery",
+			steam->serial_no);
+	if (!steam->battery_desc.name)
+		return -ENOMEM;
+
+	/* avoid the warning of 0% battery while waiting for the first info */
+	spin_lock_irqsave(&steam->lock, flags);
+	steam->voltage = 3000;
+	steam->battery_charge = 100;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	battery = power_supply_register(&steam->hdev->dev,
+			&steam->battery_desc, &battery_cfg);
+	if (IS_ERR(battery)) {
+		ret = PTR_ERR(battery);
+		hid_err(steam->hdev,
+				"%s:power_supply_register failed with error %d\n",
+				__func__, ret);
+		return ret;
+	}
+	rcu_assign_pointer(steam->battery, battery);
+	power_supply_powers(battery, &steam->hdev->dev);
+	return 0;
+}
+
+static int steam_input_register(struct steam_device *steam)
+{
+	struct hid_device *hdev = steam->hdev;
+	struct input_dev *input;
+	int ret;
+
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+	if (input) {
+		dbg_hid("%s: already connected\n", __func__);
+		return 0;
+	}
+
+	input = input_allocate_device();
+	if (!input)
+		return -ENOMEM;
+
+	input_set_drvdata(input, steam);
+	input->dev.parent = &hdev->dev;
+	input->open = steam_input_open;
+	input->close = steam_input_close;
+
+	input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ?
+		"Wireless Steam Controller" :
+		"Steam Controller";
+	input->phys = hdev->phys;
+	input->uniq = steam->serial_no;
+	input->id.bustype = hdev->bus;
+	input->id.vendor = hdev->vendor;
+	input->id.product = hdev->product;
+	input->id.version = hdev->version;
+
+	input_set_capability(input, EV_KEY, BTN_TR2);
+	input_set_capability(input, EV_KEY, BTN_TL2);
+	input_set_capability(input, EV_KEY, BTN_TR);
+	input_set_capability(input, EV_KEY, BTN_TL);
+	input_set_capability(input, EV_KEY, BTN_Y);
+	input_set_capability(input, EV_KEY, BTN_B);
+	input_set_capability(input, EV_KEY, BTN_X);
+	input_set_capability(input, EV_KEY, BTN_A);
+	input_set_capability(input, EV_KEY, BTN_DPAD_UP);
+	input_set_capability(input, EV_KEY, BTN_DPAD_RIGHT);
+	input_set_capability(input, EV_KEY, BTN_DPAD_LEFT);
+	input_set_capability(input, EV_KEY, BTN_DPAD_DOWN);
+	input_set_capability(input, EV_KEY, BTN_SELECT);
+	input_set_capability(input, EV_KEY, BTN_MODE);
+	input_set_capability(input, EV_KEY, BTN_START);
+	input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
+	input_set_capability(input, EV_KEY, BTN_GEAR_UP);
+	input_set_capability(input, EV_KEY, BTN_THUMBR);
+	input_set_capability(input, EV_KEY, BTN_THUMBL);
+	input_set_capability(input, EV_KEY, BTN_THUMB);
+	input_set_capability(input, EV_KEY, BTN_THUMB2);
+
+	input_set_abs_params(input, ABS_HAT2Y, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_HAT2X, 0, 255, 0, 0);
+	input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_Y, -32767, 32767, 0, 0);
+	input_set_abs_params(input, ABS_RX, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_RY, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT0X, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_set_abs_params(input, ABS_HAT0Y, -32767, 32767,
+			STEAM_PAD_FUZZ, 0);
+	input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
+	input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT0X, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT2Y, STEAM_TRIGGER_RESOLUTION);
+	input_abs_set_res(input, ABS_HAT2X, STEAM_TRIGGER_RESOLUTION);
+
+	ret = input_register_device(input);
+	if (ret)
+		goto input_register_fail;
+
+	rcu_assign_pointer(steam->input, input);
+	return 0;
+
+input_register_fail:
+	input_free_device(input);
+	return ret;
+}
+
+static void steam_input_unregister(struct steam_device *steam)
+{
+	struct input_dev *input;
+	rcu_read_lock();
+	input = rcu_dereference(steam->input);
+	rcu_read_unlock();
+	if (!input)
+		return;
+	RCU_INIT_POINTER(steam->input, NULL);
+	synchronize_rcu();
+	input_unregister_device(input);
+}
+
+static void steam_battery_unregister(struct steam_device *steam)
+{
+	struct power_supply *battery;
+
+	rcu_read_lock();
+	battery = rcu_dereference(steam->battery);
+	rcu_read_unlock();
+
+	if (!battery)
+		return;
+	RCU_INIT_POINTER(steam->battery, NULL);
+	synchronize_rcu();
+	power_supply_unregister(battery);
+}
+
+static int steam_register(struct steam_device *steam)
+{
+	int ret;
+
+	/*
+	 * This function can be called several times in a row with the
+	 * wireless adaptor, without steam_unregister() between them, because
+	 * another client send a get_connection_status command, for example.
+	 * The battery and serial number are set just once per device.
+	 */
+	if (!steam->serial_no[0]) {
+		/*
+		 * Unlikely, but getting the serial could fail, and it is not so
+		 * important, so make up a serial number and go on.
+		 */
+		if (steam_get_serial(steam) < 0)
+			strlcpy(steam->serial_no, "XXXXXXXXXX",
+					sizeof(steam->serial_no));
+
+		hid_info(steam->hdev, "Steam Controller '%s' connected",
+				steam->serial_no);
+
+		/* ignore battery errors, we can live without it */
+		if (steam->quirks & STEAM_QUIRK_WIRELESS)
+			steam_battery_register(steam);
+
+		mutex_lock(&steam_devices_lock);
+		list_add(&steam->list, &steam_devices);
+		mutex_unlock(&steam_devices_lock);
+	}
+
+	mutex_lock(&steam->mutex);
+	if (!steam->client_opened) {
+		steam_set_lizard_mode(steam, lizard_mode);
+		ret = steam_input_register(steam);
+	} else {
+		ret = 0;
+	}
+	mutex_unlock(&steam->mutex);
+
+	return ret;
+}
+
+static void steam_unregister(struct steam_device *steam)
+{
+	steam_battery_unregister(steam);
+	steam_input_unregister(steam);
+	if (steam->serial_no[0]) {
+		hid_info(steam->hdev, "Steam Controller '%s' disconnected",
+				steam->serial_no);
+		mutex_lock(&steam_devices_lock);
+		list_del(&steam->list);
+		mutex_unlock(&steam_devices_lock);
+		steam->serial_no[0] = 0;
+	}
+}
+
+static void steam_work_connect_cb(struct work_struct *work)
+{
+	struct steam_device *steam = container_of(work, struct steam_device,
+							work_connect);
+	unsigned long flags;
+	bool connected;
+	int ret;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	connected = steam->connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (connected) {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(steam->hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+		}
+	} else {
+		steam_unregister(steam);
+	}
+}
+
+static bool steam_is_valve_interface(struct hid_device *hdev)
+{
+	struct hid_report_enum *rep_enum;
+
+	/*
+	 * The wired device creates 3 interfaces:
+	 *  0: emulated mouse.
+	 *  1: emulated keyboard.
+	 *  2: the real game pad.
+	 * The wireless device creates 5 interfaces:
+	 *  0: emulated keyboard.
+	 *  1-4: slots where up to 4 real game pads will be connected to.
+	 * We know which one is the real gamepad interface because they are the
+	 * only ones with a feature report.
+	 */
+	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+	return !list_empty(&rep_enum->report_list);
+}
+
+static int steam_client_ll_parse(struct hid_device *hdev)
+{
+	struct steam_device *steam = hdev->driver_data;
+
+	return hid_parse_report(hdev, steam->hdev->dev_rdesc,
+			steam->hdev->dev_rsize);
+}
+
+static int steam_client_ll_start(struct hid_device *hdev)
+{
+	return 0;
+}
+
+static void steam_client_ll_stop(struct hid_device *hdev)
+{
+}
+
+static int steam_client_ll_open(struct hid_device *hdev)
+{
+	struct steam_device *steam = hdev->driver_data;
+	int ret;
+
+	ret = hid_hw_open(steam->hdev);
+	if (ret)
+		return ret;
+
+	mutex_lock(&steam->mutex);
+	steam->client_opened = true;
+	mutex_unlock(&steam->mutex);
+
+	steam_input_unregister(steam);
+
+	return ret;
+}
+
+static void steam_client_ll_close(struct hid_device *hdev)
+{
+	struct steam_device *steam = hdev->driver_data;
+
+	mutex_lock(&steam->mutex);
+	steam->client_opened = false;
+	mutex_unlock(&steam->mutex);
+
+	hid_hw_close(steam->hdev);
+	if (steam->connected) {
+		steam_set_lizard_mode(steam, lizard_mode);
+		steam_input_register(steam);
+	}
+}
+
+static int steam_client_ll_raw_request(struct hid_device *hdev,
+				unsigned char reportnum, u8 *buf,
+				size_t count, unsigned char report_type,
+				int reqtype)
+{
+	struct steam_device *steam = hdev->driver_data;
+
+	return hid_hw_raw_request(steam->hdev, reportnum, buf, count,
+			report_type, reqtype);
+}
+
+static struct hid_ll_driver steam_client_ll_driver = {
+	.parse = steam_client_ll_parse,
+	.start = steam_client_ll_start,
+	.stop = steam_client_ll_stop,
+	.open = steam_client_ll_open,
+	.close = steam_client_ll_close,
+	.raw_request = steam_client_ll_raw_request,
+};
+
+static struct hid_device *steam_create_client_hid(struct hid_device *hdev)
+{
+	struct hid_device *client_hdev;
+
+	client_hdev = hid_allocate_device();
+	if (IS_ERR(client_hdev))
+		return client_hdev;
+
+	client_hdev->ll_driver = &steam_client_ll_driver;
+	client_hdev->dev.parent = hdev->dev.parent;
+	client_hdev->bus = hdev->bus;
+	client_hdev->vendor = hdev->vendor;
+	client_hdev->product = hdev->product;
+	client_hdev->version = hdev->version;
+	client_hdev->type = hdev->type;
+	client_hdev->country = hdev->country;
+	strlcpy(client_hdev->name, hdev->name,
+			sizeof(client_hdev->name));
+	strlcpy(client_hdev->phys, hdev->phys,
+			sizeof(client_hdev->phys));
+	/*
+	 * Since we use the same device info than the real interface to
+	 * trick userspace, we will be calling steam_probe recursively.
+	 * We need to recognize the client interface somehow.
+	 */
+	client_hdev->group = HID_GROUP_STEAM;
+	return client_hdev;
+}
+
+static int steam_probe(struct hid_device *hdev,
+				const struct hid_device_id *id)
+{
+	struct steam_device *steam;
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev,
+			"%s:parse of hid interface failed\n", __func__);
+		return ret;
+	}
+
+	/*
+	 * The virtual client_dev is only used for hidraw.
+	 * Also avoid the recursive probe.
+	 */
+	if (hdev->group == HID_GROUP_STEAM)
+		return hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	/*
+	 * The non-valve interfaces (mouse and keyboard emulation) are
+	 * connected without changes.
+	 */
+	if (!steam_is_valve_interface(hdev))
+		return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+
+	steam = devm_kzalloc(&hdev->dev, sizeof(*steam), GFP_KERNEL);
+	if (!steam) {
+		ret = -ENOMEM;
+		goto steam_alloc_fail;
+	}
+	steam->hdev = hdev;
+	hid_set_drvdata(hdev, steam);
+	spin_lock_init(&steam->lock);
+	mutex_init(&steam->mutex);
+	steam->quirks = id->driver_data;
+	INIT_WORK(&steam->work_connect, steam_work_connect_cb);
+
+	steam->client_hdev = steam_create_client_hid(hdev);
+	if (IS_ERR(steam->client_hdev)) {
+		ret = PTR_ERR(steam->client_hdev);
+		goto client_hdev_fail;
+	}
+	steam->client_hdev->driver_data = steam;
+
+	/*
+	 * With the real steam controller interface, do not connect hidraw.
+	 * Instead, create the client_hid and connect that.
+	 */
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_HIDRAW);
+	if (ret)
+		goto hid_hw_start_fail;
+
+	ret = hid_add_device(steam->client_hdev);
+	if (ret)
+		goto client_hdev_add_fail;
+
+	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+		ret = hid_hw_open(hdev);
+		if (ret) {
+			hid_err(hdev,
+				"%s:hid_hw_open for wireless\n",
+				__func__);
+			goto hid_hw_open_fail;
+		}
+		hid_info(hdev, "Steam wireless receiver connected");
+		steam_request_conn_status(steam);
+	} else {
+		ret = steam_register(steam);
+		if (ret) {
+			hid_err(hdev,
+				"%s:steam_register failed with error %d\n",
+				__func__, ret);
+			goto input_register_fail;
+		}
+	}
+
+	return 0;
+
+hid_hw_open_fail:
+input_register_fail:
+client_hdev_add_fail:
+	hid_hw_stop(hdev);
+hid_hw_start_fail:
+	hid_destroy_device(steam->client_hdev);
+client_hdev_fail:
+	cancel_work_sync(&steam->work_connect);
+steam_alloc_fail:
+	hid_err(hdev, "%s: failed with error %d\n",
+			__func__, ret);
+	return ret;
+}
+
+static void steam_remove(struct hid_device *hdev)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+
+	if (!steam || hdev->group == HID_GROUP_STEAM) {
+		hid_hw_stop(hdev);
+		return;
+	}
+
+	hid_destroy_device(steam->client_hdev);
+	steam->client_opened = false;
+	cancel_work_sync(&steam->work_connect);
+	if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+		hid_info(hdev, "Steam wireless receiver disconnected");
+		hid_hw_close(hdev);
+	}
+	hid_hw_stop(hdev);
+	steam_unregister(steam);
+}
+
+static void steam_do_connect_event(struct steam_device *steam, bool connected)
+{
+	unsigned long flags;
+	bool changed;
+
+	spin_lock_irqsave(&steam->lock, flags);
+	changed = steam->connected != connected;
+	steam->connected = connected;
+	spin_unlock_irqrestore(&steam->lock, flags);
+
+	if (changed && schedule_work(&steam->work_connect) == 0)
+		dbg_hid("%s: connected=%d event already queued\n",
+				__func__, connected);
+}
+
+/*
+ * Some input data in the protocol has the opposite sign.
+ * Clamp the values to 32767..-32767 so that the range is
+ * symmetrical and can be negated safely.
+ */
+static inline s16 steam_le16(u8 *data)
+{
+	s16 x = (s16) le16_to_cpup((__le16 *)data);
+
+	return x == -32768 ? -32767 : x;
+}
+
+/*
+ * The size for this message payload is 60.
+ * The known values are:
+ *  (* values are not sent through wireless)
+ *  (* accelerator/gyro is disabled by default)
+ *  Offset| Type  | Mapped to |Meaning
+ * -------+-------+-----------+--------------------------
+ *  4-7   | u32   | --        | sequence number
+ *  8-10  | 24bit | see below | buttons
+ *  11    | u8    | ABS_HAT2Y | left trigger
+ *  12    | u8    | ABS_HAT2X | right trigger
+ *  13-15 | --    | --        | always 0
+ *  16-17 | s16   | ABS_X/ABS_HAT0X     | X value
+ *  18-19 | s16   | ABS_Y/ABS_HAT0Y     | Y value
+ *  20-21 | s16   | ABS_RX    | right-pad X value
+ *  22-23 | s16   | ABS_RY    | right-pad Y value
+ *  24-25 | s16   | --        | * left trigger
+ *  26-27 | s16   | --        | * right trigger
+ *  28-29 | s16   | --        | * accelerometer X value
+ *  30-31 | s16   | --        | * accelerometer Y value
+ *  32-33 | s16   | --        | * accelerometer Z value
+ *  34-35 | s16   | --        | gyro X value
+ *  36-36 | s16   | --        | gyro Y value
+ *  38-39 | s16   | --        | gyro Z value
+ *  40-41 | s16   | --        | quaternion W value
+ *  42-43 | s16   | --        | quaternion X value
+ *  44-45 | s16   | --        | quaternion Y value
+ *  46-47 | s16   | --        | quaternion Z value
+ *  48-49 | --    | --        | always 0
+ *  50-51 | s16   | --        | * left trigger (uncalibrated)
+ *  52-53 | s16   | --        | * right trigger (uncalibrated)
+ *  54-55 | s16   | --        | * joystick X value (uncalibrated)
+ *  56-57 | s16   | --        | * joystick Y value (uncalibrated)
+ *  58-59 | s16   | --        | * left-pad X value
+ *  60-61 | s16   | --        | * left-pad Y value
+ *  62-63 | u16   | --        | * battery voltage
+ *
+ * The buttons are:
+ *  Bit  | Mapped to  | Description
+ * ------+------------+--------------------------------
+ *  8.0  | BTN_TR2    | right trigger fully pressed
+ *  8.1  | BTN_TL2    | left trigger fully pressed
+ *  8.2  | BTN_TR     | right shoulder
+ *  8.3  | BTN_TL     | left shoulder
+ *  8.4  | BTN_Y      | button Y
+ *  8.5  | BTN_B      | button B
+ *  8.6  | BTN_X      | button X
+ *  8.7  | BTN_A      | button A
+ *  9.0  | BTN_DPAD_UP    | lef-pad up
+ *  9.1  | BTN_DPAD_RIGHT | lef-pad right
+ *  9.2  | BTN_DPAD_LEFT  | lef-pad left
+ *  9.3  | BTN_DPAD_DOWN  | lef-pad down
+ *  9.4  | BTN_SELECT | menu left
+ *  9.5  | BTN_MODE   | steam logo
+ *  9.6  | BTN_START  | menu right
+ *  9.7  | BTN_GEAR_DOWN | left back lever
+ * 10.0  | BTN_GEAR_UP   | right back lever
+ * 10.1  | --         | left-pad clicked
+ * 10.2  | BTN_THUMBR | right-pad clicked
+ * 10.3  | BTN_THUMB  | left-pad touched (but see explanation below)
+ * 10.4  | BTN_THUMB2 | right-pad touched
+ * 10.5  | --         | unknown
+ * 10.6  | BTN_THUMBL | joystick clicked
+ * 10.7  | --         | lpad_and_joy
+ */
+
+static void steam_do_input_event(struct steam_device *steam,
+		struct input_dev *input, u8 *data)
+{
+	/* 24 bits of buttons */
+	u8 b8, b9, b10;
+	s16 x, y;
+	bool lpad_touched, lpad_and_joy;
+
+	b8 = data[8];
+	b9 = data[9];
+	b10 = data[10];
+
+	input_report_abs(input, ABS_HAT2Y, data[11]);
+	input_report_abs(input, ABS_HAT2X, data[12]);
+
+	/*
+	 * These two bits tells how to interpret the values X and Y.
+	 * lpad_and_joy tells that the joystick and the lpad are used at the
+	 * same time.
+	 * lpad_touched tells whether X/Y are to be read as lpad coord or
+	 * joystick values.
+	 * (lpad_touched || lpad_and_joy) tells if the lpad is really touched.
+	 */
+	lpad_touched = b10 & BIT(3);
+	lpad_and_joy = b10 & BIT(7);
+	x = steam_le16(data + 16);
+	y = -steam_le16(data + 18);
+
+	input_report_abs(input, lpad_touched ? ABS_HAT0X : ABS_X, x);
+	input_report_abs(input, lpad_touched ? ABS_HAT0Y : ABS_Y, y);
+	/* Check if joystick is centered */
+	if (lpad_touched && !lpad_and_joy) {
+		input_report_abs(input, ABS_X, 0);
+		input_report_abs(input, ABS_Y, 0);
+	}
+	/* Check if lpad is untouched */
+	if (!(lpad_touched || lpad_and_joy)) {
+		input_report_abs(input, ABS_HAT0X, 0);
+		input_report_abs(input, ABS_HAT0Y, 0);
+	}
+
+	input_report_abs(input, ABS_RX, steam_le16(data + 20));
+	input_report_abs(input, ABS_RY, -steam_le16(data + 22));
+
+	input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0)));
+	input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1)));
+	input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2)));
+	input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3)));
+	input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4)));
+	input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5)));
+	input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6)));
+	input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7)));
+	input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4)));
+	input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5)));
+	input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6)));
+	input_event(input, EV_KEY, BTN_GEAR_DOWN, !!(b9 & BIT(7)));
+	input_event(input, EV_KEY, BTN_GEAR_UP, !!(b10 & BIT(0)));
+	input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & BIT(2)));
+	input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6)));
+	input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy);
+	input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(4)));
+	input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0)));
+	input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1)));
+	input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2)));
+	input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3)));
+
+	input_sync(input);
+}
+
+/*
+ * The size for this message payload is 11.
+ * The known values are:
+ *  Offset| Type  | Meaning
+ * -------+-------+---------------------------
+ *  4-7   | u32   | sequence number
+ *  8-11  | --    | always 0
+ *  12-13 | u16   | voltage (mV)
+ *  14    | u8    | battery percent
+ */
+static void steam_do_battery_event(struct steam_device *steam,
+		struct power_supply *battery, u8 *data)
+{
+	unsigned long flags;
+
+	s16 volts = steam_le16(data + 12);
+	u8 batt = data[14];
+
+	/* Creating the battery may have failed */
+	rcu_read_lock();
+	battery = rcu_dereference(steam->battery);
+	if (likely(battery)) {
+		spin_lock_irqsave(&steam->lock, flags);
+		steam->voltage = volts;
+		steam->battery_charge = batt;
+		spin_unlock_irqrestore(&steam->lock, flags);
+		power_supply_changed(battery);
+	}
+	rcu_read_unlock();
+}
+
+static int steam_raw_event(struct hid_device *hdev,
+			struct hid_report *report, u8 *data,
+			int size)
+{
+	struct steam_device *steam = hid_get_drvdata(hdev);
+	struct input_dev *input;
+	struct power_supply *battery;
+
+	if (!steam)
+		return 0;
+
+	if (steam->client_opened)
+		hid_input_report(steam->client_hdev, HID_FEATURE_REPORT,
+				data, size, 0);
+	/*
+	 * All messages are size=64, all values little-endian.
+	 * The format is:
+	 *  Offset| Meaning
+	 * -------+--------------------------------------------
+	 *  0-1   | always 0x01, 0x00, maybe protocol version?
+	 *  2     | type of message
+	 *  3     | length of the real payload (not checked)
+	 *  4-n   | payload data, depends on the type
+	 *
+	 * There are these known types of message:
+	 *  0x01: input data (60 bytes)
+	 *  0x03: wireless connect/disconnect (1 byte)
+	 *  0x04: battery status (11 bytes)
+	 */
+
+	if (size != 64 || data[0] != 1 || data[1] != 0)
+		return 0;
+
+	switch (data[2]) {
+	case STEAM_EV_INPUT_DATA:
+		if (steam->client_opened)
+			return 0;
+		rcu_read_lock();
+		input = rcu_dereference(steam->input);
+		if (likely(input))
+			steam_do_input_event(steam, input, data);
+		rcu_read_unlock();
+		break;
+	case STEAM_EV_CONNECT:
+		/*
+		 * The payload of this event is a single byte:
+		 *  0x01: disconnected.
+		 *  0x02: connected.
+		 */
+		switch (data[4]) {
+		case 0x01:
+			steam_do_connect_event(steam, false);
+			break;
+		case 0x02:
+			steam_do_connect_event(steam, true);
+			break;
+		}
+		break;
+	case STEAM_EV_BATTERY:
+		if (steam->quirks & STEAM_QUIRK_WIRELESS) {
+			rcu_read_lock();
+			battery = rcu_dereference(steam->battery);
+			if (likely(battery)) {
+				steam_do_battery_event(steam, battery, data);
+			} else {
+				dbg_hid(
+					"%s: battery data without connect event\n",
+					__func__);
+				steam_do_connect_event(steam, true);
+			}
+			rcu_read_unlock();
+		}
+		break;
+	}
+	return 0;
+}
+
+static int steam_param_set_lizard_mode(const char *val,
+					const struct kernel_param *kp)
+{
+	struct steam_device *steam;
+	int ret;
+
+	ret = param_set_bool(val, kp);
+	if (ret)
+		return ret;
+
+	mutex_lock(&steam_devices_lock);
+	list_for_each_entry(steam, &steam_devices, list) {
+		mutex_lock(&steam->mutex);
+		if (!steam->client_opened)
+			steam_set_lizard_mode(steam, lizard_mode);
+		mutex_unlock(&steam->mutex);
+	}
+	mutex_unlock(&steam_devices_lock);
+	return 0;
+}
+
+static const struct kernel_param_ops steam_lizard_mode_ops = {
+	.set	= steam_param_set_lizard_mode,
+	.get	= param_get_bool,
+};
+
+module_param_cb(lizard_mode, &steam_lizard_mode_ops, &lizard_mode, 0644);
+MODULE_PARM_DESC(lizard_mode,
+	"Enable mouse and keyboard emulation (lizard mode) when the gamepad is not in use");
+
+static const struct hid_device_id steam_controllers[] = {
+	{ /* Wired Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER)
+	},
+	{ /* Wireless Steam Controller */
+	  HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+		USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS),
+	  .driver_data = STEAM_QUIRK_WIRELESS
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(hid, steam_controllers);
+
+static struct hid_driver steam_controller_driver = {
+	.name = "hid-steam",
+	.id_table = steam_controllers,
+	.probe = steam_probe,
+	.remove = steam_remove,
+	.raw_event = steam_raw_event,
+};
+
+module_hid_driver(steam_controller_driver);
diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c
new file mode 100644
index 0000000..ec18768
--- /dev/null
+++ b/drivers/hid/hid-steelseries.c
@@ -0,0 +1,388 @@
+/*
+ *  HID driver for Steelseries SRW-S1
+ *
+ *  Copyright (c) 2013 Simon Wood
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+    (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+#define SRWS1_NUMBER_LEDS 15
+struct steelseries_srws1_data {
+	__u16 led_state;
+	/* the last element is used for setting all leds simultaneously */
+	struct led_classdev *led[SRWS1_NUMBER_LEDS + 1];
+};
+#endif
+
+/* Fixed report descriptor for Steelseries SRW-S1 wheel controller
+ *
+ * The original descriptor hides the sensitivity and assists dials
+ * a custom vendor usage page. This inserts a patch to make them
+ * appear in the 'Generic Desktop' usage.
+ */
+
+static __u8 steelseries_srws1_rdesc_fixed[] = {
+0x05, 0x01,         /*  Usage Page (Desktop)                */
+0x09, 0x08,         /*  Usage (MultiAxis), Changed          */
+0xA1, 0x01,         /*  Collection (Application),           */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x95, 0x01,         /*          Report Count (1),           */
+0x05, 0x01,         /* Changed  Usage Page (Desktop),       */
+0x09, 0x30,         /* Changed  Usage (X),                  */
+0x16, 0xF8, 0xF8,   /*          Logical Minimum (-1800),    */
+0x26, 0x08, 0x07,   /*          Logical Maximum (1800),     */
+0x65, 0x14,         /*          Unit (Degrees),             */
+0x55, 0x0F,         /*          Unit Exponent (15),         */
+0x75, 0x10,         /*          Report Size (16),           */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x31,         /* Changed  Usage (Y),                  */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+0x75, 0x0C,         /*          Report Size (12),           */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x32,         /* Changed  Usage (Z),                  */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+0x75, 0x0C,         /*          Report Size (12),           */
+0x81, 0x02,         /*          Input (Variable),           */
+0x05, 0x01,         /*          Usage Page (Desktop),       */
+0x09, 0x39,         /*          Usage (Hat Switch),         */
+0x25, 0x07,         /*          Logical Maximum (7),        */
+0x35, 0x00,         /*          Physical Minimum (0),       */
+0x46, 0x3B, 0x01,   /*          Physical Maximum (315),     */
+0x65, 0x14,         /*          Unit (Degrees),             */
+0x75, 0x04,         /*          Report Size (4),            */
+0x95, 0x01,         /*          Report Count (1),           */
+0x81, 0x02,         /*          Input (Variable),           */
+0x25, 0x01,         /*          Logical Maximum (1),        */
+0x45, 0x01,         /*          Physical Maximum (1),       */
+0x65, 0x00,         /*          Unit,                       */
+0x75, 0x01,         /*          Report Size (1),            */
+0x95, 0x03,         /*          Report Count (3),           */
+0x81, 0x01,         /*          Input (Constant),           */
+0x05, 0x09,         /*          Usage Page (Button),        */
+0x19, 0x01,         /*          Usage Minimum (01h),        */
+0x29, 0x11,         /*          Usage Maximum (11h),        */
+0x95, 0x11,         /*          Report Count (17),          */
+0x81, 0x02,         /*          Input (Variable),           */
+                    /*   ---- Dial patch starts here ----   */
+0x05, 0x01,         /*          Usage Page (Desktop),       */
+0x09, 0x33,         /*          Usage (RX),                 */
+0x75, 0x04,         /*          Report Size (4),            */
+0x95, 0x02,         /*          Report Count (2),           */
+0x15, 0x00,         /*          Logical Minimum (0),        */
+0x25, 0x0b,         /*          Logical Maximum (b),        */
+0x81, 0x02,         /*          Input (Variable),           */
+0x09, 0x35,         /*          Usage (RZ),                 */
+0x75, 0x04,         /*          Report Size (4),            */
+0x95, 0x01,         /*          Report Count (1),           */
+0x25, 0x03,         /*          Logical Maximum (3),        */
+0x81, 0x02,         /*          Input (Variable),           */
+                    /*    ---- Dial patch ends here ----    */
+0x06, 0x00, 0xFF,   /*          Usage Page (FF00h),         */
+0x09, 0x01,         /*          Usage (01h),                */
+0x75, 0x04,         /* Changed  Report Size (4),            */
+0x95, 0x0D,         /* Changed  Report Count (13),          */
+0x81, 0x02,         /*          Input (Variable),           */
+0xC0,               /*      End Collection,                 */
+0xA1, 0x02,         /*      Collection (Logical),           */
+0x09, 0x02,         /*          Usage (02h),                */
+0x75, 0x08,         /*          Report Size (8),            */
+0x95, 0x10,         /*          Report Count (16),          */
+0x91, 0x02,         /*          Output (Variable),          */
+0xC0,               /*      End Collection,                 */
+0xC0                /*  End Collection                      */
+};
+
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+    (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+static void steelseries_srws1_set_leds(struct hid_device *hdev, __u16 leds)
+{
+	struct list_head *report_list = &hdev->report_enum[HID_OUTPUT_REPORT].report_list;
+	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
+	__s32 *value = report->field[0]->value;
+
+	value[0] = 0x40;
+	value[1] = leds & 0xFF;
+	value[2] = leds >> 8;
+	value[3] = 0x00;
+	value[4] = 0x00;
+	value[5] = 0x00;
+	value[6] = 0x00;
+	value[7] = 0x00;
+	value[8] = 0x00;
+	value[9] = 0x00;
+	value[10] = 0x00;
+	value[11] = 0x00;
+	value[12] = 0x00;
+	value[13] = 0x00;
+	value[14] = 0x00;
+	value[15] = 0x00;
+
+	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+
+	/* Note: LED change does not show on device until the device is read/polled */
+}
+
+static void steelseries_srws1_led_all_set_brightness(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return;
+	}
+
+	if (value == LED_OFF)
+		drv_data->led_state = 0;
+	else
+		drv_data->led_state = (1 << (SRWS1_NUMBER_LEDS + 1)) - 1;
+
+	steelseries_srws1_set_leds(hid, drv_data->led_state);
+}
+
+static enum led_brightness steelseries_srws1_led_all_get_brightness(struct led_classdev *led_cdev)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct steelseries_srws1_data *drv_data;
+
+	drv_data = hid_get_drvdata(hid);
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return LED_OFF;
+	}
+
+	return (drv_data->led_state >> SRWS1_NUMBER_LEDS) ? LED_FULL : LED_OFF;
+}
+
+static void steelseries_srws1_led_set_brightness(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct steelseries_srws1_data *drv_data = hid_get_drvdata(hid);
+	int i, state = 0;
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return;
+	}
+
+	for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
+		if (led_cdev != drv_data->led[i])
+			continue;
+
+		state = (drv_data->led_state >> i) & 1;
+		if (value == LED_OFF && state) {
+			drv_data->led_state &= ~(1 << i);
+			steelseries_srws1_set_leds(hid, drv_data->led_state);
+		} else if (value != LED_OFF && !state) {
+			drv_data->led_state |= 1 << i;
+			steelseries_srws1_set_leds(hid, drv_data->led_state);
+		}
+		break;
+	}
+}
+
+static enum led_brightness steelseries_srws1_led_get_brightness(struct led_classdev *led_cdev)
+{
+	struct device *dev = led_cdev->dev->parent;
+	struct hid_device *hid = to_hid_device(dev);
+	struct steelseries_srws1_data *drv_data;
+	int i, value = 0;
+
+	drv_data = hid_get_drvdata(hid);
+
+	if (!drv_data) {
+		hid_err(hid, "Device data not found.");
+		return LED_OFF;
+	}
+
+	for (i = 0; i < SRWS1_NUMBER_LEDS; i++)
+		if (led_cdev == drv_data->led[i]) {
+			value = (drv_data->led_state >> i) & 1;
+			break;
+		}
+
+	return value ? LED_FULL : LED_OFF;
+}
+
+static int steelseries_srws1_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret, i;
+	struct led_classdev *led;
+	size_t name_sz;
+	char *name;
+
+	struct steelseries_srws1_data *drv_data = kzalloc(sizeof(*drv_data), GFP_KERNEL);
+
+	if (drv_data == NULL) {
+		hid_err(hdev, "can't alloc SRW-S1 memory\n");
+		return -ENOMEM;
+	}
+
+	hid_set_drvdata(hdev, drv_data);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err_free;
+	}
+
+	if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) {
+		ret = -ENODEV;
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err_free;
+	}
+
+	/* register led subsystem */
+	drv_data->led_state = 0;
+	for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++)
+		drv_data->led[i] = NULL;
+
+	steelseries_srws1_set_leds(hdev, 0);
+
+	name_sz = strlen(hdev->uniq) + 16;
+
+	/* 'ALL', for setting all LEDs simultaneously */
+	led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+	if (!led) {
+		hid_err(hdev, "can't allocate memory for LED ALL\n");
+		goto err_led;
+	}
+
+	name = (void *)(&led[1]);
+	snprintf(name, name_sz, "SRWS1::%s::RPMALL", hdev->uniq);
+	led->name = name;
+	led->brightness = 0;
+	led->max_brightness = 1;
+	led->brightness_get = steelseries_srws1_led_all_get_brightness;
+	led->brightness_set = steelseries_srws1_led_all_set_brightness;
+
+	drv_data->led[SRWS1_NUMBER_LEDS] = led;
+	ret = led_classdev_register(&hdev->dev, led);
+	if (ret)
+		goto err_led;
+
+	/* Each individual LED */
+	for (i = 0; i < SRWS1_NUMBER_LEDS; i++) {
+		led = kzalloc(sizeof(struct led_classdev)+name_sz, GFP_KERNEL);
+		if (!led) {
+			hid_err(hdev, "can't allocate memory for LED %d\n", i);
+			goto err_led;
+		}
+
+		name = (void *)(&led[1]);
+		snprintf(name, name_sz, "SRWS1::%s::RPM%d", hdev->uniq, i+1);
+		led->name = name;
+		led->brightness = 0;
+		led->max_brightness = 1;
+		led->brightness_get = steelseries_srws1_led_get_brightness;
+		led->brightness_set = steelseries_srws1_led_set_brightness;
+
+		drv_data->led[i] = led;
+		ret = led_classdev_register(&hdev->dev, led);
+
+		if (ret) {
+			hid_err(hdev, "failed to register LED %d. Aborting.\n", i);
+err_led:
+			/* Deregister all LEDs (if any) */
+			for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
+				led = drv_data->led[i];
+				drv_data->led[i] = NULL;
+				if (!led)
+					continue;
+				led_classdev_unregister(led);
+				kfree(led);
+			}
+			goto out;	/* but let the driver continue without LEDs */
+		}
+	}
+out:
+	return 0;
+err_free:
+	kfree(drv_data);
+	return ret;
+}
+
+static void steelseries_srws1_remove(struct hid_device *hdev)
+{
+	int i;
+	struct led_classdev *led;
+
+	struct steelseries_srws1_data *drv_data = hid_get_drvdata(hdev);
+
+	if (drv_data) {
+		/* Deregister LEDs (if any) */
+		for (i = 0; i < SRWS1_NUMBER_LEDS + 1; i++) {
+			led = drv_data->led[i];
+			drv_data->led[i] = NULL;
+			if (!led)
+				continue;
+			led_classdev_unregister(led);
+			kfree(led);
+		}
+
+	}
+
+	hid_hw_stop(hdev);
+	kfree(drv_data);
+	return;
+}
+#endif
+
+static __u8 *steelseries_srws1_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 115 && rdesc[11] == 0x02 && rdesc[13] == 0xc8
+			&& rdesc[29] == 0xbb && rdesc[40] == 0xc5) {
+		hid_info(hdev, "Fixing up Steelseries SRW-S1 report descriptor\n");
+		rdesc = steelseries_srws1_rdesc_fixed;
+		*rsize = sizeof(steelseries_srws1_rdesc_fixed);
+	}
+	return rdesc;
+}
+
+static const struct hid_device_id steelseries_srws1_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_STEELSERIES, USB_DEVICE_ID_STEELSERIES_SRWS1) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, steelseries_srws1_devices);
+
+static struct hid_driver steelseries_srws1_driver = {
+	.name = "steelseries_srws1",
+	.id_table = steelseries_srws1_devices,
+#if IS_BUILTIN(CONFIG_LEDS_CLASS) || \
+    (IS_MODULE(CONFIG_LEDS_CLASS) && IS_MODULE(CONFIG_HID_STEELSERIES))
+	.probe = steelseries_srws1_probe,
+	.remove = steelseries_srws1_remove,
+#endif
+	.report_fixup = steelseries_srws1_report_fixup
+};
+
+module_hid_driver(steelseries_srws1_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-sunplus.c b/drivers/hid/hid-sunplus.c
new file mode 100644
index 0000000..91072fa
--- /dev/null
+++ b/drivers/hid/hid-sunplus.c
@@ -0,0 +1,68 @@
+/*
+ *  HID driver for some sunplus "special" devices
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2006-2007 Jiri Kosina
+ *  Copyright (c) 2008 Jiri Slaby
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+static __u8 *sp_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	if (*rsize >= 112 && rdesc[104] == 0x26 && rdesc[105] == 0x80 &&
+			rdesc[106] == 0x03) {
+		hid_info(hdev, "fixing up Sunplus Wireless Desktop report descriptor\n");
+		rdesc[105] = rdesc[110] = 0x03;
+		rdesc[106] = rdesc[111] = 0x21;
+	}
+	return rdesc;
+}
+
+#define sp_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+		EV_KEY, (c))
+static int sp_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x2003: sp_map_key_clear(KEY_ZOOMIN);		break;
+	case 0x2103: sp_map_key_clear(KEY_ZOOMOUT);	break;
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+static const struct hid_device_id sp_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_SUNPLUS, USB_DEVICE_ID_SUNPLUS_WDESKTOP) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, sp_devices);
+
+static struct hid_driver sp_driver = {
+	.name = "sunplus",
+	.id_table = sp_devices,
+	.report_fixup = sp_report_fixup,
+	.input_mapping = sp_input_mapping,
+};
+module_hid_driver(sp_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-tivo.c b/drivers/hid/hid-tivo.c
new file mode 100644
index 0000000..d986969
--- /dev/null
+++ b/drivers/hid/hid-tivo.c
@@ -0,0 +1,80 @@
+/*
+ *  HID driver for TiVo Slide Bluetooth remote
+ *
+ *  Copyright (c) 2011 Jarod Wilson <jarod@redhat.com>
+ *  based on the hid-topseed driver, which is in turn, based on hid-cherry...
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#define HID_UP_TIVOVENDOR	0xffff0000
+#define tivo_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+
+static int tivo_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	switch (usage->hid & HID_USAGE_PAGE) {
+	case HID_UP_TIVOVENDOR:
+		switch (usage->hid & HID_USAGE) {
+		/* TiVo button */
+		case 0x3d: tivo_map_key_clear(KEY_MEDIA);	break;
+		/* Live TV */
+		case 0x3e: tivo_map_key_clear(KEY_TV);		break;
+		/* Red thumbs down */
+		case 0x41: tivo_map_key_clear(KEY_KPMINUS);	break;
+		/* Green thumbs up */
+		case 0x42: tivo_map_key_clear(KEY_KPPLUS);	break;
+		default:
+			return 0;
+		}
+		break;
+	case HID_UP_CONSUMER:
+		switch (usage->hid & HID_USAGE) {
+		/* Enter/Last (default mapping: KEY_LAST) */
+		case 0x083: tivo_map_key_clear(KEY_ENTER);	break;
+		/* Info (default mapping: KEY_PROPS) */
+		case 0x209: tivo_map_key_clear(KEY_INFO);	break;
+		default:
+			return 0;
+		}
+		break;
+	default:
+		return 0;
+	}
+
+	/* This means we found a matching mapping here, else, look in the
+	 * standard hid mappings in hid-input.c */
+	return 1;
+}
+
+static const struct hid_device_id tivo_devices[] = {
+	/* TiVo Slide Bluetooth remote, pairs with a Broadcom dongle */
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_BT) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TIVO, USB_DEVICE_ID_TIVO_SLIDE_PRO) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, tivo_devices);
+
+static struct hid_driver tivo_driver = {
+	.name = "tivo_slide",
+	.id_table = tivo_devices,
+	.input_mapping = tivo_input_mapping,
+};
+module_hid_driver(tivo_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jarod Wilson <jarod@redhat.com>");
diff --git a/drivers/hid/hid-tmff.c b/drivers/hid/hid-tmff.c
new file mode 100644
index 0000000..bea8def
--- /dev/null
+++ b/drivers/hid/hid-tmff.c
@@ -0,0 +1,266 @@
+/*
+ * Force feedback support for various HID compliant devices by ThrustMaster:
+ *    ThrustMaster FireStorm Dual Power 2
+ * and possibly others whose device ids haven't been added.
+ *
+ *  Modified to support ThrustMaster devices by Zinx Verituse
+ *  on 2003-01-25 from the Logitech force feedback driver,
+ *  which is by Johann Deneux.
+ *
+ *  Copyright (c) 2003 Zinx Verituse <zinx@epicsol.org>
+ *  Copyright (c) 2002 Johann Deneux
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+static const signed short ff_rumble[] = {
+	FF_RUMBLE,
+	-1
+};
+
+static const signed short ff_joystick[] = {
+	FF_CONSTANT,
+	-1
+};
+
+#ifdef CONFIG_THRUSTMASTER_FF
+
+/* Usages for thrustmaster devices I know about */
+#define THRUSTMASTER_USAGE_FF	(HID_UP_GENDESK | 0xbb)
+
+struct tmff_device {
+	struct hid_report *report;
+	struct hid_field *ff_field;
+};
+
+/* Changes values from 0 to 0xffff into values from minimum to maximum */
+static inline int tmff_scale_u16(unsigned int in, int minimum, int maximum)
+{
+	int ret;
+
+	ret = (in * (maximum - minimum) / 0xffff) + minimum;
+	if (ret < minimum)
+		return minimum;
+	if (ret > maximum)
+		return maximum;
+	return ret;
+}
+
+/* Changes values from -0x80 to 0x7f into values from minimum to maximum */
+static inline int tmff_scale_s8(int in, int minimum, int maximum)
+{
+	int ret;
+
+	ret = (((in + 0x80) * (maximum - minimum)) / 0xff) + minimum;
+	if (ret < minimum)
+		return minimum;
+	if (ret > maximum)
+		return maximum;
+	return ret;
+}
+
+static int tmff_play(struct input_dev *dev, void *data,
+		struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct tmff_device *tmff = data;
+	struct hid_field *ff_field = tmff->ff_field;
+	int x, y;
+	int left, right;	/* Rumbling */
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		x = tmff_scale_s8(effect->u.ramp.start_level,
+					ff_field->logical_minimum,
+					ff_field->logical_maximum);
+		y = tmff_scale_s8(effect->u.ramp.end_level,
+					ff_field->logical_minimum,
+					ff_field->logical_maximum);
+
+		dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
+		ff_field->value[0] = x;
+		ff_field->value[1] = y;
+		hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);
+		break;
+
+	case FF_RUMBLE:
+		left = tmff_scale_u16(effect->u.rumble.weak_magnitude,
+					ff_field->logical_minimum,
+					ff_field->logical_maximum);
+		right = tmff_scale_u16(effect->u.rumble.strong_magnitude,
+					ff_field->logical_minimum,
+					ff_field->logical_maximum);
+
+		dbg_hid("(left,right)=(%08x, %08x)\n", left, right);
+		ff_field->value[0] = left;
+		ff_field->value[1] = right;
+		hid_hw_request(hid, tmff->report, HID_REQ_SET_REPORT);
+		break;
+	}
+	return 0;
+}
+
+static int tmff_init(struct hid_device *hid, const signed short *ff_bits)
+{
+	struct tmff_device *tmff;
+	struct hid_report *report;
+	struct list_head *report_list;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+							struct hid_input, list);
+	struct input_dev *input_dev = hidinput->input;
+	int error;
+	int i;
+
+	tmff = kzalloc(sizeof(struct tmff_device), GFP_KERNEL);
+	if (!tmff)
+		return -ENOMEM;
+
+	/* Find the report to use */
+	report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
+	list_for_each_entry(report, report_list, list) {
+		int fieldnum;
+
+		for (fieldnum = 0; fieldnum < report->maxfield; ++fieldnum) {
+			struct hid_field *field = report->field[fieldnum];
+
+			if (field->maxusage <= 0)
+				continue;
+
+			switch (field->usage[0].hid) {
+			case THRUSTMASTER_USAGE_FF:
+				if (field->report_count < 2) {
+					hid_warn(hid, "ignoring FF field with report_count < 2\n");
+					continue;
+				}
+
+				if (field->logical_maximum ==
+						field->logical_minimum) {
+					hid_warn(hid, "ignoring FF field with logical_maximum == logical_minimum\n");
+					continue;
+				}
+
+				if (tmff->report && tmff->report != report) {
+					hid_warn(hid, "ignoring FF field in other report\n");
+					continue;
+				}
+
+				if (tmff->ff_field && tmff->ff_field != field) {
+					hid_warn(hid, "ignoring duplicate FF field\n");
+					continue;
+				}
+
+				tmff->report = report;
+				tmff->ff_field = field;
+
+				for (i = 0; ff_bits[i] >= 0; i++)
+					set_bit(ff_bits[i], input_dev->ffbit);
+
+				break;
+
+			default:
+				hid_warn(hid, "ignoring unknown output usage %08x\n",
+					 field->usage[0].hid);
+				continue;
+			}
+		}
+	}
+
+	if (!tmff->report) {
+		hid_err(hid, "can't find FF field in output reports\n");
+		error = -ENODEV;
+		goto fail;
+	}
+
+	error = input_ff_create_memless(input_dev, tmff, tmff_play);
+	if (error)
+		goto fail;
+
+	hid_info(hid, "force feedback for ThrustMaster devices by Zinx Verituse <zinx@epicsol.org>\n");
+	return 0;
+
+fail:
+	kfree(tmff);
+	return error;
+}
+#else
+static inline int tmff_init(struct hid_device *hid, const signed short *ff_bits)
+{
+	return 0;
+}
+#endif
+
+static int tm_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	tmff_init(hdev, (void *)id->driver_data);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id tm_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb300),
+		.driver_data = (unsigned long)ff_rumble },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb304),   /* FireStorm Dual Power 2 (and 3) */
+		.driver_data = (unsigned long)ff_rumble },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb323),   /* Dual Trigger 3-in-1 (PC Mode) */
+		.driver_data = (unsigned long)ff_rumble },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb324),   /* Dual Trigger 3-in-1 (PS3 Mode) */
+		.driver_data = (unsigned long)ff_rumble },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb605),   /* NASCAR PRO FF2 Wheel */
+		.driver_data = (unsigned long)ff_joystick },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb651),	/* FGT Rumble Force Wheel */
+		.driver_data = (unsigned long)ff_rumble },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb653),	/* RGT Force Feedback CLUTCH Raging Wheel */
+		.driver_data = (unsigned long)ff_joystick },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb654),	/* FGT Force Feedback Wheel */
+		.driver_data = (unsigned long)ff_joystick },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THRUSTMASTER, 0xb65a),	/* F430 Force Feedback Wheel */
+		.driver_data = (unsigned long)ff_joystick },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, tm_devices);
+
+static struct hid_driver tm_driver = {
+	.name = "thrustmaster",
+	.id_table = tm_devices,
+	.probe = tm_probe,
+};
+module_hid_driver(tm_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-topseed.c b/drivers/hid/hid-topseed.c
new file mode 100644
index 0000000..8a5b843
--- /dev/null
+++ b/drivers/hid/hid-topseed.c
@@ -0,0 +1,81 @@
+/*
+ *  HID driver for TopSeed Cyberlink remote
+ *
+ *  Copyright (c) 2008 Lev Babiev
+ *  based on hid-cherry driver
+ *
+ *  Modified to also support BTC "Emprex 3009URF III Vista MCE Remote" by
+ *  Wayne Thomas 2010.
+ *
+ *  Modified to support Conceptronic CLLRCMCE by
+ *  Kees Bakker 2010.
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+#define ts_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_LOGIVENDOR)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	case 0x00d: ts_map_key_clear(KEY_MEDIA);	break;
+	case 0x024: ts_map_key_clear(KEY_MENU);		break;
+	case 0x025: ts_map_key_clear(KEY_TV);		break;
+	case 0x027: ts_map_key_clear(KEY_MODE);		break;
+	case 0x031: ts_map_key_clear(KEY_AUDIO);	break;
+	case 0x032: ts_map_key_clear(KEY_TEXT);		break;
+	case 0x033: ts_map_key_clear(KEY_CHANNEL);	break;
+	case 0x047: ts_map_key_clear(KEY_MP3);		break;
+	case 0x048: ts_map_key_clear(KEY_TV2);		break;
+	case 0x049: ts_map_key_clear(KEY_CAMERA);	break;
+	case 0x04a: ts_map_key_clear(KEY_VIDEO);	break;
+	case 0x04b: ts_map_key_clear(KEY_ANGLE);	break;
+	case 0x04c: ts_map_key_clear(KEY_LANGUAGE);	break;
+	case 0x04d: ts_map_key_clear(KEY_SUBTITLE);	break;
+	case 0x050: ts_map_key_clear(KEY_RADIO);	break;
+	case 0x05a: ts_map_key_clear(KEY_TEXT);		break;
+	case 0x05b: ts_map_key_clear(KEY_RED);		break;
+	case 0x05c: ts_map_key_clear(KEY_GREEN);	break;
+	case 0x05d: ts_map_key_clear(KEY_YELLOW);	break;
+	case 0x05e: ts_map_key_clear(KEY_BLUE);		break;
+	default:
+		return 0;
+	}
+
+	return 1;
+}
+
+static const struct hid_device_id ts_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED, USB_DEVICE_ID_TOPSEED_CYBERLINK) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BTC, USB_DEVICE_ID_BTC_EMPREX_REMOTE_2) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TOPSEED2, USB_DEVICE_ID_TOPSEED2_RF_COMBO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_WIRELESS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, ts_devices);
+
+static struct hid_driver ts_driver = {
+	.name = "topseed",
+	.id_table = ts_devices,
+	.input_mapping = ts_input_mapping,
+};
+module_hid_driver(ts_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-twinhan.c b/drivers/hid/hid-twinhan.c
new file mode 100644
index 0000000..c08c364
--- /dev/null
+++ b/drivers/hid/hid-twinhan.c
@@ -0,0 +1,136 @@
+/*
+ * HID driver for TwinHan IR remote control
+ *
+ * Based on hid-gyration.c
+ *
+ * Copyright (c) 2009 Bruno Prémont <bonbons@linux-vserver.org>
+ */
+
+/*
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+/*	Remote control key layout + listing:
+ *
+ * 	Full Screen                              Power
+ *	KEY_SCREEN                          KEY_POWER2
+ *
+ *	1                     2                      3
+ *	KEY_NUMERIC_1   KEY_NUMERIC_2    KEY_NUMERIC_3
+ *
+ *	4                     5                      6
+ *	KEY_NUMERIC_4   KEY_NUMERIC_5    KEY_NUMERIC_6
+ *
+ *	7                     8                      9
+ *	KEY_NUMERIC_7   KEY_NUMERIC_8    KEY_NUMERIC_9
+ *
+ *	REC                   0               Favorite
+ *	KEY_RECORD      KEY_NUMERIC_0    KEY_FAVORITES
+ *
+ *	Rewind                                 Forward
+ *	KEY_REWIND           CH+           KEY_FORWARD
+ *	               KEY_CHANNELUP
+ *
+ *	VOL-                  >                   VOL+
+ *	KEY_VOLUMEDOWN    KEY_PLAY        KEY_VOLUMEUP
+ *
+ *	                     CH-
+ *	              KEY_CHANNELDOWN
+ *	Recall                                    Stop
+ *	KEY_RESTART                           KEY_STOP
+ *
+ *	Timeshift/Pause     Mute                Cancel
+ *	KEY_PAUSE         KEY_MUTE          KEY_CANCEL
+ *
+ *	Capture            Preview                 EPG
+ *	KEY_PRINT        KEY_PROGRAM           KEY_EPG
+ *
+ *	Record List          Tab              Teletext
+ *	KEY_LIST            KEY_TAB           KEY_TEXT
+ */
+
+#define th_map_key_clear(c)	hid_map_usage_clear(hi, usage, bit, max, \
+					EV_KEY, (c))
+static int twinhan_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD)
+		return 0;
+
+	switch (usage->hid & HID_USAGE) {
+	/* Map all keys from Twinhan Remote */
+	case 0x004: th_map_key_clear(KEY_TEXT);         break;
+	case 0x006: th_map_key_clear(KEY_RESTART);      break;
+	case 0x008: th_map_key_clear(KEY_EPG);          break;
+	case 0x00c: th_map_key_clear(KEY_REWIND);       break;
+	case 0x00e: th_map_key_clear(KEY_PROGRAM);      break;
+	case 0x00f: th_map_key_clear(KEY_LIST);         break;
+	case 0x010: th_map_key_clear(KEY_MUTE);         break;
+	case 0x011: th_map_key_clear(KEY_FORWARD);      break;
+	case 0x013: th_map_key_clear(KEY_PRINT);        break;
+	case 0x017: th_map_key_clear(KEY_PAUSE);        break;
+	case 0x019: th_map_key_clear(KEY_FAVORITES);    break;
+	case 0x01d: th_map_key_clear(KEY_SCREEN);       break;
+	case 0x01e: th_map_key_clear(KEY_NUMERIC_1);    break;
+	case 0x01f: th_map_key_clear(KEY_NUMERIC_2);    break;
+	case 0x020: th_map_key_clear(KEY_NUMERIC_3);    break;
+	case 0x021: th_map_key_clear(KEY_NUMERIC_4);    break;
+	case 0x022: th_map_key_clear(KEY_NUMERIC_5);    break;
+	case 0x023: th_map_key_clear(KEY_NUMERIC_6);    break;
+	case 0x024: th_map_key_clear(KEY_NUMERIC_7);    break;
+	case 0x025: th_map_key_clear(KEY_NUMERIC_8);    break;
+	case 0x026: th_map_key_clear(KEY_NUMERIC_9);    break;
+	case 0x027: th_map_key_clear(KEY_NUMERIC_0);    break;
+	case 0x028: th_map_key_clear(KEY_PLAY);         break;
+	case 0x029: th_map_key_clear(KEY_CANCEL);       break;
+	case 0x02b: th_map_key_clear(KEY_TAB);          break;
+	/* Power       = 0x0e0 + 0x0e1 + 0x0e2 + 0x03f */
+	case 0x03f: th_map_key_clear(KEY_POWER2);       break;
+	case 0x04a: th_map_key_clear(KEY_RECORD);       break;
+	case 0x04b: th_map_key_clear(KEY_CHANNELUP);    break;
+	case 0x04d: th_map_key_clear(KEY_STOP);         break;
+	case 0x04e: th_map_key_clear(KEY_CHANNELDOWN);  break;
+	/* Volume down = 0x0e1 + 0x051                 */
+	case 0x051: th_map_key_clear(KEY_VOLUMEDOWN);   break;
+	/* Volume up   = 0x0e1 + 0x052                 */
+	case 0x052: th_map_key_clear(KEY_VOLUMEUP);     break;
+	/* Kill the extra keys used for multi-key "power" and "volume" keys
+	 * as well as continuously to release CTRL,ALT,META,... keys */
+	case 0x0e0:
+	case 0x0e1:
+	case 0x0e2:
+	case 0x0e3:
+	case 0x0e4:
+	case 0x0e5:
+	case 0x0e6:
+	case 0x0e7:
+	default:
+		return -1;
+	}
+	return 1;
+}
+
+static const struct hid_device_id twinhan_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_TWINHAN, USB_DEVICE_ID_TWINHAN_IR_REMOTE) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, twinhan_devices);
+
+static struct hid_driver twinhan_driver = {
+	.name = "twinhan",
+	.id_table = twinhan_devices,
+	.input_mapping = twinhan_input_mapping,
+};
+module_hid_driver(twinhan_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic.c
new file mode 100644
index 0000000..56b196d
--- /dev/null
+++ b/drivers/hid/hid-uclogic.c
@@ -0,0 +1,1090 @@
+/*
+ *  HID driver for UC-Logic devices not fully compliant with HID standard
+ *
+ *  Copyright (c) 2010-2014 Nikolai Kondrashov
+ *  Copyright (c) 2013 Martin Rusko
+ */
+
+/*
+ * 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 <asm/unaligned.h>
+#include "usbhid/usbhid.h"
+
+#include "hid-ids.h"
+
+/* Size of the original descriptor of WPXXXXU tablets */
+#define WPXXXXU_RDESC_ORIG_SIZE	212
+
+/* Fixed WP4030U report descriptor */
+static __u8 wp4030u_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0xA0, 0x0F,   /*          Physical Maximum (4000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0xB8, 0x0B,   /*          Physical Maximum (3000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Fixed WP5540U report descriptor */
+static __u8 wp5540u_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x7C, 0x15,   /*          Physical Maximum (5500),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0xA0, 0x0F,   /*          Physical Maximum (4000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x08,         /*      Report ID (8),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x05, 0x09,         /*          Usage Page (Button),        */
+	0x19, 0x01,         /*          Usage Minimum (01h),        */
+	0x29, 0x03,         /*          Usage Maximum (03h),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x15, 0x81,         /*          Logical Minimum (-127),     */
+	0x25, 0x7F,         /*          Logical Maximum (127),      */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Fixed WP8060U report descriptor */
+static __u8 wp8060u_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x40, 0x1F,   /*          Physical Maximum (8000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x70, 0x17,   /*          Physical Maximum (6000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x08,         /*      Report ID (8),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x05, 0x09,         /*          Usage Page (Button),        */
+	0x19, 0x01,         /*          Usage Minimum (01h),        */
+	0x29, 0x03,         /*          Usage Maximum (03h),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x15, 0x81,         /*          Logical Minimum (-127),     */
+	0x25, 0x7F,         /*          Logical Maximum (127),      */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original descriptor of WP1062 tablet */
+#define WP1062_RDESC_ORIG_SIZE	254
+
+/* Fixed WP1062 report descriptor */
+static __u8 wp1062_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x04,         /*          Report Count (4),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x10, 0x27,   /*          Physical Maximum (10000),   */
+	0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0xB7, 0x19,   /*          Physical Maximum (6583),    */
+	0x26, 0x6E, 0x33,   /*          Logical Maximum (13166),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original descriptor of PF1209 tablet */
+#define PF1209_RDESC_ORIG_SIZE	234
+
+/* Fixed PF1209 report descriptor */
+static __u8 pf1209_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0xE0, 0x2E,   /*          Physical Maximum (12000),   */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x28, 0x23,   /*          Physical Maximum (9000),    */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x08,         /*      Report ID (8),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x05, 0x09,         /*          Usage Page (Button),        */
+	0x19, 0x01,         /*          Usage Minimum (01h),        */
+	0x29, 0x03,         /*          Usage Maximum (03h),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x15, 0x81,         /*          Logical Minimum (-127),     */
+	0x25, 0x7F,         /*          Logical Maximum (127),      */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original descriptors of TWHL850 tablet */
+#define TWHL850_RDESC_ORIG_SIZE0	182
+#define TWHL850_RDESC_ORIG_SIZE1	161
+#define TWHL850_RDESC_ORIG_SIZE2	92
+
+/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */
+static __u8 twhl850_rdesc_fixed0[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x40, 0x1F,   /*          Physical Maximum (8000),    */
+	0x26, 0x00, 0x7D,   /*          Logical Maximum (32000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x88, 0x13,   /*          Physical Maximum (5000),    */
+	0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */
+static __u8 twhl850_rdesc_fixed1[] = {
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x01,         /*      Report ID (1),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x05, 0x09,         /*          Usage Page (Button),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x19, 0x01,         /*          Usage Minimum (01h),        */
+	0x29, 0x03,         /*          Usage Maximum (03h),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x05,         /*          Report Count (5),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x16, 0x00, 0x80,   /*          Logical Minimum (-32768),   */
+	0x26, 0xFF, 0x7F,   /*          Logical Maximum (32767),    */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */
+static __u8 twhl850_rdesc_fixed2[] = {
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x06,         /*  Usage (Keyboard),                   */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x03,         /*      Report ID (3),                  */
+	0x05, 0x07,         /*      Usage Page (Keyboard),          */
+	0x14,               /*      Logical Minimum (0),            */
+	0x19, 0xE0,         /*      Usage Minimum (KB Leftcontrol), */
+	0x29, 0xE7,         /*      Usage Maximum (KB Right GUI),   */
+	0x25, 0x01,         /*      Logical Maximum (1),            */
+	0x75, 0x01,         /*      Report Size (1),                */
+	0x95, 0x08,         /*      Report Count (8),               */
+	0x81, 0x02,         /*      Input (Variable),               */
+	0x18,               /*      Usage Minimum (None),           */
+	0x29, 0xFF,         /*      Usage Maximum (FFh),            */
+	0x26, 0xFF, 0x00,   /*      Logical Maximum (255),          */
+	0x75, 0x08,         /*      Report Size (8),                */
+	0x95, 0x06,         /*      Report Count (6),               */
+	0x80,               /*      Input,                          */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original descriptors of TWHA60 tablet */
+#define TWHA60_RDESC_ORIG_SIZE0 254
+#define TWHA60_RDESC_ORIG_SIZE1 139
+
+/* Fixed TWHA60 report descriptor, interface 0 (stylus) */
+static __u8 twha60_rdesc_fixed0[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x09,         /*      Report ID (9),                  */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x04,         /*          Report Count (4),           */
+	0x81, 0x01,         /*          Input (Constant),           */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x10, 0x27,   /*          Physical Maximum (10000),   */
+	0x27, 0x3F, 0x9C,
+		0x00, 0x00, /*          Logical Maximum (39999),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x6A, 0x18,   /*          Physical Maximum (6250),    */
+	0x26, 0xA7, 0x61,   /*          Logical Maximum (24999),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */
+static __u8 twha60_rdesc_fixed1[] = {
+	0x05, 0x01, /*  Usage Page (Desktop),       */
+	0x09, 0x06, /*  Usage (Keyboard),           */
+	0xA1, 0x01, /*  Collection (Application),   */
+	0x85, 0x05, /*      Report ID (5),          */
+	0x05, 0x07, /*      Usage Page (Keyboard),  */
+	0x14,       /*      Logical Minimum (0),    */
+	0x25, 0x01, /*      Logical Maximum (1),    */
+	0x75, 0x01, /*      Report Size (1),        */
+	0x95, 0x08, /*      Report Count (8),       */
+	0x81, 0x01, /*      Input (Constant),       */
+	0x95, 0x0C, /*      Report Count (12),      */
+	0x19, 0x3A, /*      Usage Minimum (KB F1),  */
+	0x29, 0x45, /*      Usage Maximum (KB F12), */
+	0x81, 0x02, /*      Input (Variable),       */
+	0x95, 0x0C, /*      Report Count (12),      */
+	0x19, 0x68, /*      Usage Minimum (KB F13), */
+	0x29, 0x73, /*      Usage Maximum (KB F24), */
+	0x81, 0x02, /*      Input (Variable),       */
+	0x95, 0x08, /*      Report Count (8),       */
+	0x81, 0x01, /*      Input (Constant),       */
+	0xC0        /*  End Collection              */
+};
+
+/* Report descriptor template placeholder head */
+#define UCLOGIC_PH_HEAD	0xFE, 0xED, 0x1D
+
+/* Report descriptor template placeholder IDs */
+enum uclogic_ph_id {
+	UCLOGIC_PH_ID_X_LM,
+	UCLOGIC_PH_ID_X_PM,
+	UCLOGIC_PH_ID_Y_LM,
+	UCLOGIC_PH_ID_Y_PM,
+	UCLOGIC_PH_ID_PRESSURE_LM,
+	UCLOGIC_PH_ID_NUM
+};
+
+/* Report descriptor template placeholder */
+#define UCLOGIC_PH(_ID) UCLOGIC_PH_HEAD, UCLOGIC_PH_ID_##_ID
+#define UCLOGIC_PEN_REPORT_ID	0x07
+
+/* Fixed report descriptor template */
+static const __u8 uclogic_tablet_rdesc_template[] = {
+	0x05, 0x0D,             /*  Usage Page (Digitizer),                 */
+	0x09, 0x02,             /*  Usage (Pen),                            */
+	0xA1, 0x01,             /*  Collection (Application),               */
+	0x85, 0x07,             /*      Report ID (7),                      */
+	0x09, 0x20,             /*      Usage (Stylus),                     */
+	0xA0,                   /*      Collection (Physical),              */
+	0x14,                   /*          Logical Minimum (0),            */
+	0x25, 0x01,             /*          Logical Maximum (1),            */
+	0x75, 0x01,             /*          Report Size (1),                */
+	0x09, 0x42,             /*          Usage (Tip Switch),             */
+	0x09, 0x44,             /*          Usage (Barrel Switch),          */
+	0x09, 0x46,             /*          Usage (Tablet Pick),            */
+	0x95, 0x03,             /*          Report Count (3),               */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0x95, 0x03,             /*          Report Count (3),               */
+	0x81, 0x03,             /*          Input (Constant, Variable),     */
+	0x09, 0x32,             /*          Usage (In Range),               */
+	0x95, 0x01,             /*          Report Count (1),               */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0x95, 0x01,             /*          Report Count (1),               */
+	0x81, 0x03,             /*          Input (Constant, Variable),     */
+	0x75, 0x10,             /*          Report Size (16),               */
+	0x95, 0x01,             /*          Report Count (1),               */
+	0xA4,                   /*          Push,                           */
+	0x05, 0x01,             /*          Usage Page (Desktop),           */
+	0x65, 0x13,             /*          Unit (Inch),                    */
+	0x55, 0xFD,             /*          Unit Exponent (-3),             */
+	0x34,                   /*          Physical Minimum (0),           */
+	0x09, 0x30,             /*          Usage (X),                      */
+	0x27, UCLOGIC_PH(X_LM), /*          Logical Maximum (PLACEHOLDER),  */
+	0x47, UCLOGIC_PH(X_PM), /*          Physical Maximum (PLACEHOLDER), */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0x09, 0x31,             /*          Usage (Y),                      */
+	0x27, UCLOGIC_PH(Y_LM), /*          Logical Maximum (PLACEHOLDER),  */
+	0x47, UCLOGIC_PH(Y_PM), /*          Physical Maximum (PLACEHOLDER), */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0xB4,                   /*          Pop,                            */
+	0x09, 0x30,             /*          Usage (Tip Pressure),           */
+	0x27,
+	UCLOGIC_PH(PRESSURE_LM),/*          Logical Maximum (PLACEHOLDER),  */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0xC0,                   /*      End Collection,                     */
+	0xC0                    /*  End Collection                          */
+};
+
+/* Fixed virtual pad report descriptor */
+static const __u8 uclogic_buttonpad_rdesc[] = {
+	0x05, 0x01,             /*  Usage Page (Desktop),                   */
+	0x09, 0x07,             /*  Usage (Keypad),                         */
+	0xA1, 0x01,             /*  Collection (Application),               */
+	0x85, 0xF7,             /*      Report ID (247),                    */
+	0x05, 0x0D,             /*      Usage Page (Digitizers),            */
+	0x09, 0x39,             /*      Usage (Tablet Function Keys),       */
+	0xA0,                   /*      Collection (Physical),              */
+	0x05, 0x09,             /*          Usage Page (Button),            */
+	0x75, 0x01,             /*          Report Size (1),                */
+	0x95, 0x18,             /*          Report Count (24),              */
+	0x81, 0x03,             /*          Input (Constant, Variable),     */
+	0x19, 0x01,             /*          Usage Minimum (01h),            */
+	0x29, 0x08,             /*          Usage Maximum (08h),            */
+	0x95, 0x08,             /*          Report Count (8),               */
+	0x81, 0x02,             /*          Input (Variable),               */
+	0xC0,                   /*      End Collection                      */
+	0xC0                    /*  End Collection                          */
+};
+
+/* Parameter indices */
+enum uclogic_prm {
+	UCLOGIC_PRM_X_LM	= 1,
+	UCLOGIC_PRM_Y_LM	= 2,
+	UCLOGIC_PRM_PRESSURE_LM	= 4,
+	UCLOGIC_PRM_RESOLUTION	= 5,
+	UCLOGIC_PRM_NUM
+};
+
+/* Driver data */
+struct uclogic_drvdata {
+	__u8 *rdesc;
+	unsigned int rsize;
+	bool invert_pen_inrange;
+	bool ignore_pen_usage;
+	bool has_virtual_pad_interface;
+};
+
+static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+					unsigned int *rsize)
+{
+	struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+	__u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if (drvdata->rdesc != NULL) {
+		rdesc = drvdata->rdesc;
+		*rsize = drvdata->rsize;
+		return rdesc;
+	}
+
+	switch (hdev->product) {
+	case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209:
+		if (*rsize == PF1209_RDESC_ORIG_SIZE) {
+			rdesc = pf1209_rdesc_fixed;
+			*rsize = sizeof(pf1209_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U:
+		if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
+			rdesc = wp4030u_rdesc_fixed;
+			*rsize = sizeof(wp4030u_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U:
+		if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
+			rdesc = wp5540u_rdesc_fixed;
+			*rsize = sizeof(wp5540u_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U:
+		if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) {
+			rdesc = wp8060u_rdesc_fixed;
+			*rsize = sizeof(wp8060u_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062:
+		if (*rsize == WP1062_RDESC_ORIG_SIZE) {
+			rdesc = wp1062_rdesc_fixed;
+			*rsize = sizeof(wp1062_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850:
+		switch (iface_num) {
+		case 0:
+			if (*rsize == TWHL850_RDESC_ORIG_SIZE0) {
+				rdesc = twhl850_rdesc_fixed0;
+				*rsize = sizeof(twhl850_rdesc_fixed0);
+			}
+			break;
+		case 1:
+			if (*rsize == TWHL850_RDESC_ORIG_SIZE1) {
+				rdesc = twhl850_rdesc_fixed1;
+				*rsize = sizeof(twhl850_rdesc_fixed1);
+			}
+			break;
+		case 2:
+			if (*rsize == TWHL850_RDESC_ORIG_SIZE2) {
+				rdesc = twhl850_rdesc_fixed2;
+				*rsize = sizeof(twhl850_rdesc_fixed2);
+			}
+			break;
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
+		switch (iface_num) {
+		case 0:
+			if (*rsize == TWHA60_RDESC_ORIG_SIZE0) {
+				rdesc = twha60_rdesc_fixed0;
+				*rsize = sizeof(twha60_rdesc_fixed0);
+			}
+			break;
+		case 1:
+			if (*rsize == TWHA60_RDESC_ORIG_SIZE1) {
+				rdesc = twha60_rdesc_fixed1;
+				*rsize = sizeof(twha60_rdesc_fixed1);
+			}
+			break;
+		}
+		break;
+	}
+
+	return rdesc;
+}
+
+static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+		struct hid_field *field, struct hid_usage *usage,
+		unsigned long **bit, int *max)
+{
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	/* discard the unused pen interface */
+	if ((drvdata->ignore_pen_usage) &&
+	    (field->application == HID_DG_PEN))
+		return -1;
+
+	/* let hid-core decide what to do */
+	return 0;
+}
+
+static int uclogic_input_configured(struct hid_device *hdev,
+		struct hid_input *hi)
+{
+	char *name;
+	const char *suffix = NULL;
+	struct hid_field *field;
+	size_t len;
+
+	/* no report associated (HID_QUIRK_MULTI_INPUT not set) */
+	if (!hi->report)
+		return 0;
+
+	field = hi->report->field[0];
+
+	switch (field->application) {
+	case HID_GD_KEYBOARD:
+		suffix = "Keyboard";
+		break;
+	case HID_GD_MOUSE:
+		suffix = "Mouse";
+		break;
+	case HID_GD_KEYPAD:
+		suffix = "Pad";
+		break;
+	case HID_DG_PEN:
+		suffix = "Pen";
+		break;
+	case HID_CP_CONSUMER_CONTROL:
+		suffix = "Consumer Control";
+		break;
+	case HID_GD_SYSTEM_CONTROL:
+		suffix = "System Control";
+		break;
+	}
+
+	if (suffix) {
+		len = strlen(hdev->name) + 2 + strlen(suffix);
+		name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
+		if (name) {
+			snprintf(name, len, "%s %s", hdev->name, suffix);
+			hi->input->name = name;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * Enable fully-functional tablet mode and determine device parameters.
+ *
+ * @hdev:	HID device
+ */
+static int uclogic_tablet_enable(struct hid_device *hdev)
+{
+	int rc;
+	struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+	__le16 *buf = NULL;
+	size_t len;
+	s32 params[UCLOGIC_PH_ID_NUM];
+	s32 resolution;
+	__u8 *p;
+	s32 v;
+
+	/*
+	 * Read string descriptor containing tablet parameters. The specific
+	 * string descriptor and data were discovered by sniffing the Windows
+	 * driver traffic.
+	 * NOTE: This enables fully-functional tablet mode.
+	 */
+	len = UCLOGIC_PRM_NUM * sizeof(*buf);
+	buf = kmalloc(len, GFP_KERNEL);
+	if (buf == NULL) {
+		rc = -ENOMEM;
+		goto cleanup;
+	}
+	rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
+				USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+				(USB_DT_STRING << 8) + 0x64,
+				0x0409, buf, len,
+				USB_CTRL_GET_TIMEOUT);
+	if (rc == -EPIPE) {
+		hid_err(hdev, "device parameters not found\n");
+		rc = -ENODEV;
+		goto cleanup;
+	} else if (rc < 0) {
+		hid_err(hdev, "failed to get device parameters: %d\n", rc);
+		rc = -ENODEV;
+		goto cleanup;
+	} else if (rc != len) {
+		hid_err(hdev, "invalid device parameters\n");
+		rc = -ENODEV;
+		goto cleanup;
+	}
+
+	/* Extract device parameters */
+	params[UCLOGIC_PH_ID_X_LM] = le16_to_cpu(buf[UCLOGIC_PRM_X_LM]);
+	params[UCLOGIC_PH_ID_Y_LM] = le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]);
+	params[UCLOGIC_PH_ID_PRESSURE_LM] =
+		le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]);
+	resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]);
+	if (resolution == 0) {
+		params[UCLOGIC_PH_ID_X_PM] = 0;
+		params[UCLOGIC_PH_ID_Y_PM] = 0;
+	} else {
+		params[UCLOGIC_PH_ID_X_PM] = params[UCLOGIC_PH_ID_X_LM] *
+						1000 / resolution;
+		params[UCLOGIC_PH_ID_Y_PM] = params[UCLOGIC_PH_ID_Y_LM] *
+						1000 / resolution;
+	}
+
+	/* Allocate fixed report descriptor */
+	drvdata->rdesc = devm_kzalloc(&hdev->dev,
+				sizeof(uclogic_tablet_rdesc_template),
+				GFP_KERNEL);
+	if (drvdata->rdesc == NULL) {
+		rc = -ENOMEM;
+		goto cleanup;
+	}
+	drvdata->rsize = sizeof(uclogic_tablet_rdesc_template);
+
+	/* Format fixed report descriptor */
+	memcpy(drvdata->rdesc, uclogic_tablet_rdesc_template,
+		drvdata->rsize);
+	for (p = drvdata->rdesc;
+	     p <= drvdata->rdesc + drvdata->rsize - 4;) {
+		if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D &&
+		    p[3] < ARRAY_SIZE(params)) {
+			v = params[p[3]];
+			put_unaligned(cpu_to_le32(v), (s32 *)p);
+			p += 4;
+		} else {
+			p++;
+		}
+	}
+
+	rc = 0;
+
+cleanup:
+	kfree(buf);
+	return rc;
+}
+
+/**
+ * Enable actual button mode.
+ *
+ * @hdev:	HID device
+ */
+static int uclogic_button_enable(struct hid_device *hdev)
+{
+	int rc;
+	struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+	char *str_buf;
+	size_t str_len = 16;
+	unsigned char *rdesc;
+	size_t rdesc_len;
+
+	str_buf = kzalloc(str_len, GFP_KERNEL);
+	if (str_buf == NULL) {
+		rc = -ENOMEM;
+		goto cleanup;
+	}
+
+	/* Enable abstract keyboard mode */
+	rc = usb_string(usb_dev, 0x7b, str_buf, str_len);
+	if (rc == -EPIPE) {
+		hid_info(hdev, "button mode setting not found\n");
+		rc = 0;
+		goto cleanup;
+	} else if (rc < 0) {
+		hid_err(hdev, "failed to enable abstract keyboard\n");
+		goto cleanup;
+	} else if (strncmp(str_buf, "HK On", rc)) {
+		hid_info(hdev, "invalid answer when requesting buttons: '%s'\n",
+			str_buf);
+		rc = -EINVAL;
+		goto cleanup;
+	}
+
+	/* Re-allocate fixed report descriptor */
+	rdesc_len = drvdata->rsize + sizeof(uclogic_buttonpad_rdesc);
+	rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL);
+	if (!rdesc) {
+		rc = -ENOMEM;
+		goto cleanup;
+	}
+
+	memcpy(rdesc, drvdata->rdesc, drvdata->rsize);
+
+	/* Append the buttonpad descriptor */
+	memcpy(rdesc + drvdata->rsize, uclogic_buttonpad_rdesc,
+	       sizeof(uclogic_buttonpad_rdesc));
+
+	/* clean up old rdesc and use the new one */
+	drvdata->rsize = rdesc_len;
+	devm_kfree(&hdev->dev, drvdata->rdesc);
+	drvdata->rdesc = rdesc;
+
+	rc = 0;
+
+cleanup:
+	kfree(str_buf);
+	return rc;
+}
+
+static int uclogic_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int rc;
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_device *udev = hid_to_usb_dev(hdev);
+	struct uclogic_drvdata *drvdata;
+
+	/*
+	 * libinput requires the pad interface to be on a different node
+	 * than the pen, so use QUIRK_MULTI_INPUT for all tablets.
+	 */
+	hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+
+	/* Allocate and assign driver data */
+	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+	if (drvdata == NULL)
+		return -ENOMEM;
+
+	hid_set_drvdata(hdev, drvdata);
+
+	switch (id->product) {
+	case USB_DEVICE_ID_HUION_TABLET:
+	case USB_DEVICE_ID_YIYNOVA_TABLET:
+	case USB_DEVICE_ID_UGEE_TABLET_81:
+	case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3:
+	case USB_DEVICE_ID_UGEE_TABLET_45:
+		/* If this is the pen interface */
+		if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
+			rc = uclogic_tablet_enable(hdev);
+			if (rc) {
+				hid_err(hdev, "tablet enabling failed\n");
+				return rc;
+			}
+			drvdata->invert_pen_inrange = true;
+
+			rc = uclogic_button_enable(hdev);
+			drvdata->has_virtual_pad_interface = !rc;
+		} else {
+			drvdata->ignore_pen_usage = true;
+		}
+		break;
+	case USB_DEVICE_ID_UGTIZER_TABLET_GP0610:
+	case USB_DEVICE_ID_UGEE_TABLET_EX07S:
+		/* If this is the pen interface */
+		if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
+			rc = uclogic_tablet_enable(hdev);
+			if (rc) {
+				hid_err(hdev, "tablet enabling failed\n");
+				return rc;
+			}
+			drvdata->invert_pen_inrange = true;
+		} else {
+			drvdata->ignore_pen_usage = true;
+		}
+		break;
+	case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
+		/*
+		 * If it is the three-interface version, which is known to
+		 * respond to initialization.
+		 */
+		if (udev->config->desc.bNumInterfaces == 3) {
+			/* If it is the pen interface */
+			if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
+				rc = uclogic_tablet_enable(hdev);
+				if (rc) {
+					hid_err(hdev, "tablet enabling failed\n");
+					return rc;
+				}
+				drvdata->invert_pen_inrange = true;
+
+				rc = uclogic_button_enable(hdev);
+				drvdata->has_virtual_pad_interface = !rc;
+			} else {
+				drvdata->ignore_pen_usage = true;
+			}
+		}
+		break;
+	}
+
+	rc = hid_parse(hdev);
+	if (rc) {
+		hid_err(hdev, "parse failed\n");
+		return rc;
+	}
+
+	rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (rc) {
+		hid_err(hdev, "hw start failed\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report,
+			u8 *data, int size)
+{
+	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+	if ((report->type == HID_INPUT_REPORT) &&
+	    (report->id == UCLOGIC_PEN_REPORT_ID) &&
+	    (size >= 2)) {
+		if (drvdata->has_virtual_pad_interface && (data[1] & 0x20))
+			/* Change to virtual frame button report ID */
+			data[0] = 0xf7;
+		else if (drvdata->invert_pen_inrange)
+			/* Invert the in-range bit */
+			data[1] ^= 0x40;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id uclogic_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+				USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_81) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UGEE_TABLET_45) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, uclogic_devices);
+
+static struct hid_driver uclogic_driver = {
+	.name = "uclogic",
+	.id_table = uclogic_devices,
+	.probe = uclogic_probe,
+	.report_fixup = uclogic_report_fixup,
+	.raw_event = uclogic_raw_event,
+	.input_mapping = uclogic_input_mapping,
+	.input_configured = uclogic_input_configured,
+};
+module_hid_driver(uclogic_driver);
+
+MODULE_AUTHOR("Martin Rusko");
+MODULE_AUTHOR("Nikolai Kondrashov");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-udraw-ps3.c b/drivers/hid/hid-udraw-ps3.c
new file mode 100644
index 0000000..88ea390
--- /dev/null
+++ b/drivers/hid/hid-udraw-ps3.c
@@ -0,0 +1,474 @@
+/*
+ * HID driver for THQ PS3 uDraw tablet
+ *
+ * Copyright (C) 2016 Red Hat Inc. All Rights Reserved
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_DESCRIPTION("PS3 uDraw tablet driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Protocol information from:
+ * http://brandonw.net/udraw/
+ * and the source code of:
+ * https://vvvv.org/contribution/udraw-hid
+ */
+
+/*
+ * The device is setup with multiple input devices:
+ * - the touch area which works as a touchpad
+ * - the tablet area which works as a touchpad/drawing tablet
+ * - a joypad with a d-pad, and 7 buttons
+ * - an accelerometer device
+ */
+
+enum {
+	TOUCH_NONE,
+	TOUCH_PEN,
+	TOUCH_FINGER,
+	TOUCH_TWOFINGER
+};
+
+enum {
+	AXIS_X,
+	AXIS_Y,
+	AXIS_Z
+};
+
+/*
+ * Accelerometer min/max values
+ * in order, X, Y and Z
+ */
+static struct {
+	int min;
+	int max;
+} accel_limits[] = {
+	[AXIS_X] = { 490, 534 },
+	[AXIS_Y] = { 490, 534 },
+	[AXIS_Z] = { 492, 536 }
+};
+
+#define DEVICE_NAME "THQ uDraw Game Tablet for PS3"
+/* resolution in pixels */
+#define RES_X 1920
+#define RES_Y 1080
+/* size in mm */
+#define WIDTH  160
+#define HEIGHT 90
+#define PRESSURE_OFFSET 113
+#define MAX_PRESSURE (255 - PRESSURE_OFFSET)
+
+struct udraw {
+	struct input_dev *joy_input_dev;
+	struct input_dev *touch_input_dev;
+	struct input_dev *pen_input_dev;
+	struct input_dev *accel_input_dev;
+	struct hid_device *hdev;
+
+	/*
+	 * The device's two-finger support is pretty unreliable, as
+	 * the device could report a single touch when the two fingers
+	 * are too close together, and the distance between fingers, even
+	 * though reported is not in the same unit as the touches.
+	 *
+	 * We'll make do without it, and try to report the first touch
+	 * as reliably as possible.
+	 */
+	int last_one_finger_x;
+	int last_one_finger_y;
+	int last_two_finger_x;
+	int last_two_finger_y;
+};
+
+static int clamp_accel(int axis, int offset)
+{
+	axis = clamp(axis,
+			accel_limits[offset].min,
+			accel_limits[offset].max);
+	axis = (axis - accel_limits[offset].min) /
+			((accel_limits[offset].max -
+			  accel_limits[offset].min) * 0xFF);
+	return axis;
+}
+
+static int udraw_raw_event(struct hid_device *hdev, struct hid_report *report,
+	 u8 *data, int len)
+{
+	struct udraw *udraw = hid_get_drvdata(hdev);
+	int touch;
+	int x, y, z;
+
+	if (len != 27)
+		return 0;
+
+	if (data[11] == 0x00)
+		touch = TOUCH_NONE;
+	else if (data[11] == 0x40)
+		touch = TOUCH_PEN;
+	else if (data[11] == 0x80)
+		touch = TOUCH_FINGER;
+	else
+		touch = TOUCH_TWOFINGER;
+
+	/* joypad */
+	input_report_key(udraw->joy_input_dev, BTN_WEST, data[0] & 1);
+	input_report_key(udraw->joy_input_dev, BTN_SOUTH, !!(data[0] & 2));
+	input_report_key(udraw->joy_input_dev, BTN_EAST, !!(data[0] & 4));
+	input_report_key(udraw->joy_input_dev, BTN_NORTH, !!(data[0] & 8));
+
+	input_report_key(udraw->joy_input_dev, BTN_SELECT, !!(data[1] & 1));
+	input_report_key(udraw->joy_input_dev, BTN_START, !!(data[1] & 2));
+	input_report_key(udraw->joy_input_dev, BTN_MODE, !!(data[1] & 16));
+
+	x = y = 0;
+	switch (data[2]) {
+	case 0x0:
+		y = -127;
+		break;
+	case 0x1:
+		y = -127;
+		x = 127;
+		break;
+	case 0x2:
+		x = 127;
+		break;
+	case 0x3:
+		y = 127;
+		x = 127;
+		break;
+	case 0x4:
+		y = 127;
+		break;
+	case 0x5:
+		y = 127;
+		x = -127;
+		break;
+	case 0x6:
+		x = -127;
+		break;
+	case 0x7:
+		y = -127;
+		x = -127;
+		break;
+	default:
+		break;
+	}
+
+	input_report_abs(udraw->joy_input_dev, ABS_X, x);
+	input_report_abs(udraw->joy_input_dev, ABS_Y, y);
+
+	input_sync(udraw->joy_input_dev);
+
+	/* For pen and touchpad */
+	x = y = 0;
+	if (touch != TOUCH_NONE) {
+		if (data[15] != 0x0F)
+			x = data[15] * 256 + data[17];
+		if (data[16] != 0x0F)
+			y = data[16] * 256 + data[18];
+	}
+
+	if (touch == TOUCH_FINGER) {
+		/* Save the last one-finger touch */
+		udraw->last_one_finger_x = x;
+		udraw->last_one_finger_y = y;
+		udraw->last_two_finger_x = -1;
+		udraw->last_two_finger_y = -1;
+	} else if (touch == TOUCH_TWOFINGER) {
+		/*
+		 * We have a problem because x/y is the one for the
+		 * second finger but we want the first finger given
+		 * to user-space otherwise it'll look as if it jumped.
+		 *
+		 * See the udraw struct definition for why this was
+		 * implemented this way.
+		 */
+		if (udraw->last_two_finger_x == -1) {
+			/* Save the position of the 2nd finger */
+			udraw->last_two_finger_x = x;
+			udraw->last_two_finger_y = y;
+
+			x = udraw->last_one_finger_x;
+			y = udraw->last_one_finger_y;
+		} else {
+			/*
+			 * Offset the 2-finger coords using the
+			 * saved data from the first finger
+			 */
+			x = x - (udraw->last_two_finger_x
+				- udraw->last_one_finger_x);
+			y = y - (udraw->last_two_finger_y
+				- udraw->last_one_finger_y);
+		}
+	}
+
+	/* touchpad */
+	if (touch == TOUCH_FINGER || touch == TOUCH_TWOFINGER) {
+		input_report_key(udraw->touch_input_dev, BTN_TOUCH, 1);
+		input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER,
+				touch == TOUCH_FINGER);
+		input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP,
+				touch == TOUCH_TWOFINGER);
+
+		input_report_abs(udraw->touch_input_dev, ABS_X, x);
+		input_report_abs(udraw->touch_input_dev, ABS_Y, y);
+	} else {
+		input_report_key(udraw->touch_input_dev, BTN_TOUCH, 0);
+		input_report_key(udraw->touch_input_dev, BTN_TOOL_FINGER, 0);
+		input_report_key(udraw->touch_input_dev, BTN_TOOL_DOUBLETAP, 0);
+	}
+	input_sync(udraw->touch_input_dev);
+
+	/* pen */
+	if (touch == TOUCH_PEN) {
+		int level;
+
+		level = clamp(data[13] - PRESSURE_OFFSET,
+				0, MAX_PRESSURE);
+
+		input_report_key(udraw->pen_input_dev, BTN_TOUCH, (level != 0));
+		input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 1);
+		input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, level);
+		input_report_abs(udraw->pen_input_dev, ABS_X, x);
+		input_report_abs(udraw->pen_input_dev, ABS_Y, y);
+	} else {
+		input_report_key(udraw->pen_input_dev, BTN_TOUCH, 0);
+		input_report_key(udraw->pen_input_dev, BTN_TOOL_PEN, 0);
+		input_report_abs(udraw->pen_input_dev, ABS_PRESSURE, 0);
+	}
+	input_sync(udraw->pen_input_dev);
+
+	/* accel */
+	x = (data[19] + (data[20] << 8));
+	x = clamp_accel(x, AXIS_X);
+	y = (data[21] + (data[22] << 8));
+	y = clamp_accel(y, AXIS_Y);
+	z = (data[23] + (data[24] << 8));
+	z = clamp_accel(z, AXIS_Z);
+	input_report_abs(udraw->accel_input_dev, ABS_X, x);
+	input_report_abs(udraw->accel_input_dev, ABS_Y, y);
+	input_report_abs(udraw->accel_input_dev, ABS_Z, z);
+	input_sync(udraw->accel_input_dev);
+
+	/* let hidraw and hiddev handle the report */
+	return 0;
+}
+
+static int udraw_open(struct input_dev *dev)
+{
+	struct udraw *udraw = input_get_drvdata(dev);
+
+	return hid_hw_open(udraw->hdev);
+}
+
+static void udraw_close(struct input_dev *dev)
+{
+	struct udraw *udraw = input_get_drvdata(dev);
+
+	hid_hw_close(udraw->hdev);
+}
+
+static struct input_dev *allocate_and_setup(struct hid_device *hdev,
+		const char *name)
+{
+	struct input_dev *input_dev;
+
+	input_dev = devm_input_allocate_device(&hdev->dev);
+	if (!input_dev)
+		return NULL;
+
+	input_dev->name = name;
+	input_dev->phys = hdev->phys;
+	input_dev->dev.parent = &hdev->dev;
+	input_dev->open = udraw_open;
+	input_dev->close = udraw_close;
+	input_dev->uniq = hdev->uniq;
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor  = hdev->vendor;
+	input_dev->id.product = hdev->product;
+	input_dev->id.version = hdev->version;
+	input_set_drvdata(input_dev, hid_get_drvdata(hdev));
+
+	return input_dev;
+}
+
+static bool udraw_setup_touch(struct udraw *udraw,
+		struct hid_device *hdev)
+{
+	struct input_dev *input_dev;
+
+	input_dev = allocate_and_setup(hdev, DEVICE_NAME " Touchpad");
+	if (!input_dev)
+		return false;
+
+	input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+
+	input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);
+	input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);
+	input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);
+	input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);
+
+	set_bit(BTN_TOUCH, input_dev->keybit);
+	set_bit(BTN_TOOL_FINGER, input_dev->keybit);
+	set_bit(BTN_TOOL_DOUBLETAP, input_dev->keybit);
+
+	set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+	udraw->touch_input_dev = input_dev;
+
+	return true;
+}
+
+static bool udraw_setup_pen(struct udraw *udraw,
+		struct hid_device *hdev)
+{
+	struct input_dev *input_dev;
+
+	input_dev = allocate_and_setup(hdev, DEVICE_NAME " Pen");
+	if (!input_dev)
+		return false;
+
+	input_dev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+
+	input_set_abs_params(input_dev, ABS_X, 0, RES_X, 1, 0);
+	input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH);
+	input_set_abs_params(input_dev, ABS_Y, 0, RES_Y, 1, 0);
+	input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT);
+	input_set_abs_params(input_dev, ABS_PRESSURE,
+			0, MAX_PRESSURE, 0, 0);
+
+	set_bit(BTN_TOUCH, input_dev->keybit);
+	set_bit(BTN_TOOL_PEN, input_dev->keybit);
+
+	set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+	udraw->pen_input_dev = input_dev;
+
+	return true;
+}
+
+static bool udraw_setup_accel(struct udraw *udraw,
+		struct hid_device *hdev)
+{
+	struct input_dev *input_dev;
+
+	input_dev = allocate_and_setup(hdev, DEVICE_NAME " Accelerometer");
+	if (!input_dev)
+		return false;
+
+	input_dev->evbit[0] = BIT(EV_ABS);
+
+	/* 1G accel is reported as ~256, so clamp to 2G */
+	input_set_abs_params(input_dev, ABS_X, -512, 512, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, -512, 512, 0, 0);
+	input_set_abs_params(input_dev, ABS_Z, -512, 512, 0, 0);
+
+	set_bit(INPUT_PROP_ACCELEROMETER, input_dev->propbit);
+
+	udraw->accel_input_dev = input_dev;
+
+	return true;
+}
+
+static bool udraw_setup_joypad(struct udraw *udraw,
+		struct hid_device *hdev)
+{
+	struct input_dev *input_dev;
+
+	input_dev = allocate_and_setup(hdev, DEVICE_NAME " Joypad");
+	if (!input_dev)
+		return false;
+
+	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+
+	set_bit(BTN_SOUTH, input_dev->keybit);
+	set_bit(BTN_NORTH, input_dev->keybit);
+	set_bit(BTN_EAST, input_dev->keybit);
+	set_bit(BTN_WEST, input_dev->keybit);
+	set_bit(BTN_SELECT, input_dev->keybit);
+	set_bit(BTN_START, input_dev->keybit);
+	set_bit(BTN_MODE, input_dev->keybit);
+
+	input_set_abs_params(input_dev, ABS_X, -127, 127, 0, 0);
+	input_set_abs_params(input_dev, ABS_Y, -127, 127, 0, 0);
+
+	udraw->joy_input_dev = input_dev;
+
+	return true;
+}
+
+static int udraw_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	struct udraw *udraw;
+	int ret;
+
+	udraw = devm_kzalloc(&hdev->dev, sizeof(struct udraw), GFP_KERNEL);
+	if (!udraw)
+		return -ENOMEM;
+
+	udraw->hdev = hdev;
+	udraw->last_two_finger_x = -1;
+	udraw->last_two_finger_y = -1;
+
+	hid_set_drvdata(hdev, udraw);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	if (!udraw_setup_joypad(udraw, hdev) ||
+	    !udraw_setup_touch(udraw, hdev) ||
+	    !udraw_setup_pen(udraw, hdev) ||
+	    !udraw_setup_accel(udraw, hdev)) {
+		hid_err(hdev, "could not allocate interfaces\n");
+		return -ENOMEM;
+	}
+
+	ret = input_register_device(udraw->joy_input_dev) ||
+		input_register_device(udraw->touch_input_dev) ||
+		input_register_device(udraw->pen_input_dev) ||
+		input_register_device(udraw->accel_input_dev);
+	if (ret) {
+		hid_err(hdev, "failed to register interfaces\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id udraw_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_THQ, USB_DEVICE_ID_THQ_PS3_UDRAW) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, udraw_devices);
+
+static struct hid_driver udraw_driver = {
+	.name = "hid-udraw",
+	.id_table = udraw_devices,
+	.raw_event = udraw_raw_event,
+	.probe = udraw_probe,
+};
+module_hid_driver(udraw_driver);
diff --git a/drivers/hid/hid-waltop.c b/drivers/hid/hid-waltop.c
new file mode 100644
index 0000000..a91aabe
--- /dev/null
+++ b/drivers/hid/hid-waltop.c
@@ -0,0 +1,748 @@
+/*
+ *  HID driver for Waltop devices not fully compliant with HID standard
+ *
+ *  Copyright (c) 2010 Nikolai Kondrashov
+ */
+
+/*
+ * 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 "hid-ids.h"
+
+/*
+ * There exists an official driver on the manufacturer's website, which
+ * wasn't submitted to the kernel, for some reason. The official driver
+ * doesn't seem to support extra features of some tablets, like wheels.
+ *
+ * It shows that the feature report ID 2 could be used to control any waltop
+ * tablet input mode, switching it between "default", "tablet" and "ink".
+ *
+ * This driver only uses "default" mode for all the supported tablets. This
+ * mode tries to be HID-compatible (not very successfully), but cripples the
+ * resolution of some tablets.
+ *
+ * The "tablet" mode uses some proprietary, yet decipherable protocol, which
+ * represents the correct resolution, but is possibly HID-incompatible (i.e.
+ * indescribable by a report descriptor).
+ *
+ * The purpose of the "ink" mode is unknown.
+ *
+ * The feature reports needed for switching to each mode are these:
+ *
+ * 02 16 00     default
+ * 02 16 01     tablet
+ * 02 16 02     ink
+ */
+
+/* Size of the original report descriptor of Slim Tablet 5.8 inch */
+#define SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE	222
+
+/* Fixed Slim Tablet 5.8 inch descriptor */
+static __u8 slim_tablet_5_8_inch_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x88, 0x13,   /*          Physical Maximum (5000),    */
+	0x26, 0x10, 0x27,   /*          Logical Maximum (10000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0xB8, 0x0B,   /*          Physical Maximum (3000),    */
+	0x26, 0x70, 0x17,   /*          Logical Maximum (6000),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of Slim Tablet 12.1 inch */
+#define SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE	269
+
+/* Fixed Slim Tablet 12.1 inch descriptor */
+static __u8 slim_tablet_12_1_inch_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x10, 0x27,   /*          Physical Maximum (10000),   */
+	0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x6A, 0x18,   /*          Physical Maximum (6250),    */
+	0x26, 0xD4, 0x30,   /*          Logical Maximum (12500),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of Q Pad */
+#define Q_PAD_RDESC_ORIG_SIZE	241
+
+/* Fixed Q Pad descriptor */
+static __u8 q_pad_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x70, 0x17,   /*          Physical Maximum (6000),    */
+	0x26, 0x00, 0x30,   /*          Logical Maximum (12288),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x94, 0x11,   /*          Physical Maximum (4500),    */
+	0x26, 0x00, 0x24,   /*          Logical Maximum (9216),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of tablet with PID 0038 */
+#define PID_0038_RDESC_ORIG_SIZE	241
+
+/*
+ * Fixed report descriptor for tablet with PID 0038.
+ */
+static __u8 pid_0038_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x2E, 0x22,   /*          Physical Maximum (8750),    */
+	0x26, 0x00, 0x46,   /*          Logical Maximum (17920),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x82, 0x14,   /*          Physical Maximum (5250),    */
+	0x26, 0x00, 0x2A,   /*          Logical Maximum (10752),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of Media Tablet 10.6 inch */
+#define MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE	300
+
+/* Fixed Media Tablet 10.6 inch descriptor */
+static __u8 media_tablet_10_6_inch_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0x28, 0x23,   /*          Physical Maximum (9000),    */
+	0x26, 0x50, 0x46,   /*          Logical Maximum (18000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x7C, 0x15,   /*          Physical Maximum (5500),    */
+	0x26, 0xF8, 0x2A,   /*          Logical Maximum (11000),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x01,         /*      Report ID (1),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x0B, 0x38, 0x02,   /*          Usage (Consumer AC Pan),    */
+		0x0C, 0x00,
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x0C,         /*  Usage Page (Consumer),              */
+	0x09, 0x01,         /*  Usage (Consumer Control),           */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0D,         /*      Report ID (13),                 */
+	0x95, 0x01,         /*      Report Count (1),               */
+	0x75, 0x10,         /*      Report Size (16),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0x0A, 0x2F, 0x02,   /*      Usage (AC Zoom),                */
+	0x0A, 0x2E, 0x02,   /*      Usage (AC Zoom Out),            */
+	0x0A, 0x2D, 0x02,   /*      Usage (AC Zoom In),             */
+	0x09, 0xB6,         /*      Usage (Scan Previous Track),    */
+	0x09, 0xB5,         /*      Usage (Scan Next Track),        */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x0A, 0x2E, 0x02,   /*      Usage (AC Zoom Out),            */
+	0x0A, 0x2D, 0x02,   /*      Usage (AC Zoom In),             */
+	0x15, 0x0C,         /*      Logical Minimum (12),           */
+	0x25, 0x17,         /*      Logical Maximum (23),           */
+	0x75, 0x05,         /*      Report Size (5),                */
+	0x80,               /*      Input,                          */
+	0x75, 0x03,         /*      Report Size (3),                */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0x75, 0x20,         /*      Report Size (32),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0xC0,               /*  End Collection,                     */
+	0x09, 0x01,         /*  Usage (Consumer Control),           */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0C,         /*      Report ID (12),                 */
+	0x75, 0x01,         /*      Report Size (1),                */
+	0x09, 0xE9,         /*      Usage (Volume Inc),             */
+	0x09, 0xEA,         /*      Usage (Volume Dec),             */
+	0x09, 0xE2,         /*      Usage (Mute),                   */
+	0x14,               /*      Logical Minimum (0),            */
+	0x25, 0x01,         /*      Logical Maximum (1),            */
+	0x95, 0x03,         /*      Report Count (3),               */
+	0x81, 0x06,         /*      Input (Variable, Relative),     */
+	0x95, 0x35,         /*      Report Count (53),              */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of Media Tablet 14.1 inch */
+#define MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE	309
+
+/* Fixed Media Tablet 14.1 inch descriptor */
+static __u8 media_tablet_14_1_inch_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x04,         /*          Report Size (4),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x80,               /*          Input,                      */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x34,               /*          Physical Minimum (0),       */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x46, 0xE0, 0x2E,   /*          Physical Maximum (12000),   */
+	0x26, 0xFF, 0x3F,   /*          Logical Maximum (16383),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x46, 0x52, 0x1C,   /*          Physical Maximum (7250),    */
+	0x26, 0xFF, 0x3F,   /*          Logical Maximum (16383),    */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x01,         /*      Report ID (1),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x0B, 0x38, 0x02,   /*          Usage (Consumer AC Pan),    */
+		0x0C, 0x00,
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x0C,         /*  Usage Page (Consumer),              */
+	0x09, 0x01,         /*  Usage (Consumer Control),           */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0D,         /*      Report ID (13),                 */
+	0x95, 0x01,         /*      Report Count (1),               */
+	0x75, 0x10,         /*      Report Size (16),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0x0A, 0x2F, 0x02,   /*      Usage (AC Zoom),                */
+	0x0A, 0x2E, 0x02,   /*      Usage (AC Zoom Out),            */
+	0x0A, 0x2D, 0x02,   /*      Usage (AC Zoom In),             */
+	0x09, 0xB6,         /*      Usage (Scan Previous Track),    */
+	0x09, 0xB5,         /*      Usage (Scan Next Track),        */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x08,               /*      Usage (00h),                    */
+	0x0A, 0x2E, 0x02,   /*      Usage (AC Zoom Out),            */
+	0x0A, 0x2D, 0x02,   /*      Usage (AC Zoom In),             */
+	0x15, 0x0C,         /*      Logical Minimum (12),           */
+	0x25, 0x17,         /*      Logical Maximum (23),           */
+	0x75, 0x05,         /*      Report Size (5),                */
+	0x80,               /*      Input,                          */
+	0x75, 0x03,         /*      Report Size (3),                */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0x75, 0x20,         /*      Report Size (32),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0xC0,               /*  End Collection,                     */
+	0x09, 0x01,         /*  Usage (Consumer Control),           */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0C,         /*      Report ID (12),                 */
+	0x75, 0x01,         /*      Report Size (1),                */
+	0x09, 0xE9,         /*      Usage (Volume Inc),             */
+	0x09, 0xEA,         /*      Usage (Volume Dec),             */
+	0x09, 0xE2,         /*      Usage (Mute),                   */
+	0x14,               /*      Logical Minimum (0),            */
+	0x25, 0x01,         /*      Logical Maximum (1),            */
+	0x95, 0x03,         /*      Report Count (3),               */
+	0x81, 0x06,         /*      Input (Variable, Relative),     */
+	0x75, 0x05,         /*      Report Size (5),                */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0xC0                /*  End Collection                      */
+};
+
+/* Size of the original report descriptor of Sirius Battery Free Tablet */
+#define SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE	335
+
+/* Fixed Sirius Battery Free Tablet descriptor */
+static __u8 sirius_battery_free_tablet_rdesc_fixed[] = {
+	0x05, 0x0D,         /*  Usage Page (Digitizer),             */
+	0x09, 0x02,         /*  Usage (Pen),                        */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x10,         /*      Report ID (16),                 */
+	0x09, 0x20,         /*      Usage (Stylus),                 */
+	0xA0,               /*      Collection (Physical),          */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x15, 0x01,         /*          Logical Minimum (1),        */
+	0x25, 0x03,         /*          Logical Maximum (3),        */
+	0x75, 0x02,         /*          Report Size (2),            */
+	0x09, 0x42,         /*          Usage (Tip Switch),         */
+	0x09, 0x44,         /*          Usage (Barrel Switch),      */
+	0x09, 0x46,         /*          Usage (Tablet Pick),        */
+	0x80,               /*          Input,                      */
+	0x14,               /*          Logical Minimum (0),        */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x01,         /*          Report Size (1),            */
+	0x09, 0x3C,         /*          Usage (Invert),             */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x09, 0x32,         /*          Usage (In Range),           */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0xA4,               /*          Push,                       */
+	0x05, 0x01,         /*          Usage Page (Desktop),       */
+	0x55, 0xFD,         /*          Unit Exponent (-3),         */
+	0x65, 0x13,         /*          Unit (Inch),                */
+	0x34,               /*          Physical Minimum (0),       */
+	0x14,               /*          Logical Minimum (0),        */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x46, 0x10, 0x27,   /*          Physical Maximum (10000),   */
+	0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */
+	0x09, 0x30,         /*          Usage (X),                  */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0x46, 0x70, 0x17,   /*          Physical Maximum (6000),    */
+	0x26, 0xE0, 0x2E,   /*          Logical Maximum (12000),    */
+	0x09, 0x31,         /*          Usage (Y),                  */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0x75, 0x10,         /*          Report Size (16),           */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x14,               /*          Logical Minimum (0),        */
+	0x26, 0xFF, 0x03,   /*          Logical Maximum (1023),     */
+	0x09, 0x30,         /*          Usage (Tip Pressure),       */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xA4,               /*          Push,                       */
+	0x55, 0xFE,         /*          Unit Exponent (-2),         */
+	0x65, 0x12,         /*          Unit (Radians),             */
+	0x35, 0x97,         /*          Physical Minimum (-105),    */
+	0x45, 0x69,         /*          Physical Maximum (105),     */
+	0x15, 0x97,         /*          Logical Minimum (-105),     */
+	0x25, 0x69,         /*          Logical Maximum (105),      */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x02,         /*          Report Count (2),           */
+	0x09, 0x3D,         /*          Usage (X Tilt),             */
+	0x09, 0x3E,         /*          Usage (Y Tilt),             */
+	0x81, 0x02,         /*          Input (Variable),           */
+	0xB4,               /*          Pop,                        */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x02,         /*  Usage (Mouse),                      */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x01,         /*      Report ID (1),                  */
+	0x09, 0x01,         /*      Usage (Pointer),                */
+	0xA0,               /*      Collection (Physical),          */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0x09, 0x38,         /*          Usage (Wheel),              */
+	0x15, 0xFF,         /*          Logical Minimum (-1),       */
+	0x25, 0x01,         /*          Logical Maximum (1),        */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x01,         /*          Report Count (1),           */
+	0x81, 0x06,         /*          Input (Variable, Relative), */
+	0x75, 0x08,         /*          Report Size (8),            */
+	0x95, 0x03,         /*          Report Count (3),           */
+	0x81, 0x03,         /*          Input (Constant, Variable), */
+	0xC0,               /*      End Collection,                 */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x01,         /*  Usage Page (Desktop),               */
+	0x09, 0x06,         /*  Usage (Keyboard),                   */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0D,         /*      Report ID (13),                 */
+	0x05, 0x07,         /*      Usage Page (Keyboard),          */
+	0x19, 0xE0,         /*      Usage Minimum (KB Leftcontrol), */
+	0x29, 0xE7,         /*      Usage Maximum (KB Right GUI),   */
+	0x14,               /*      Logical Minimum (0),            */
+	0x25, 0x01,         /*      Logical Maximum (1),            */
+	0x75, 0x01,         /*      Report Size (1),                */
+	0x95, 0x08,         /*      Report Count (8),               */
+	0x81, 0x02,         /*      Input (Variable),               */
+	0x75, 0x08,         /*      Report Size (8),                */
+	0x95, 0x01,         /*      Report Count (1),               */
+	0x81, 0x01,         /*      Input (Constant),               */
+	0x18,               /*      Usage Minimum (None),           */
+	0x29, 0x65,         /*      Usage Maximum (KB Application), */
+	0x14,               /*      Logical Minimum (0),            */
+	0x25, 0x65,         /*      Logical Maximum (101),          */
+	0x75, 0x08,         /*      Report Size (8),                */
+	0x95, 0x05,         /*      Report Count (5),               */
+	0x80,               /*      Input,                          */
+	0xC0,               /*  End Collection,                     */
+	0x05, 0x0C,         /*  Usage Page (Consumer),              */
+	0x09, 0x01,         /*  Usage (Consumer Control),           */
+	0xA1, 0x01,         /*  Collection (Application),           */
+	0x85, 0x0C,         /*      Report ID (12),                 */
+	0x09, 0xE9,         /*      Usage (Volume Inc),             */
+	0x09, 0xEA,         /*      Usage (Volume Dec),             */
+	0x14,               /*      Logical Minimum (0),            */
+	0x25, 0x01,         /*      Logical Maximum (1),            */
+	0x75, 0x01,         /*      Report Size (1),                */
+	0x95, 0x02,         /*      Report Count (2),               */
+	0x81, 0x02,         /*      Input (Variable),               */
+	0x75, 0x06,         /*      Report Size (6),                */
+	0x95, 0x01,         /*      Report Count (1),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0x75, 0x10,         /*      Report Size (16),               */
+	0x95, 0x03,         /*      Report Count (3),               */
+	0x81, 0x03,         /*      Input (Constant, Variable),     */
+	0xC0                /*  End Collection                      */
+};
+
+static __u8 *waltop_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+		unsigned int *rsize)
+{
+	switch (hdev->product) {
+	case USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH:
+		if (*rsize == SLIM_TABLET_5_8_INCH_RDESC_ORIG_SIZE) {
+			rdesc = slim_tablet_5_8_inch_rdesc_fixed;
+			*rsize = sizeof(slim_tablet_5_8_inch_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH:
+		if (*rsize == SLIM_TABLET_12_1_INCH_RDESC_ORIG_SIZE) {
+			rdesc = slim_tablet_12_1_inch_rdesc_fixed;
+			*rsize = sizeof(slim_tablet_12_1_inch_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_Q_PAD:
+		if (*rsize == Q_PAD_RDESC_ORIG_SIZE) {
+			rdesc = q_pad_rdesc_fixed;
+			*rsize = sizeof(q_pad_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_PID_0038:
+		if (*rsize == PID_0038_RDESC_ORIG_SIZE) {
+			rdesc = pid_0038_rdesc_fixed;
+			*rsize = sizeof(pid_0038_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH:
+		if (*rsize == MEDIA_TABLET_10_6_INCH_RDESC_ORIG_SIZE) {
+			rdesc = media_tablet_10_6_inch_rdesc_fixed;
+			*rsize = sizeof(media_tablet_10_6_inch_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH:
+		if (*rsize == MEDIA_TABLET_14_1_INCH_RDESC_ORIG_SIZE) {
+			rdesc = media_tablet_14_1_inch_rdesc_fixed;
+			*rsize = sizeof(media_tablet_14_1_inch_rdesc_fixed);
+		}
+		break;
+	case USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET:
+		if (*rsize == SIRIUS_BATTERY_FREE_TABLET_RDESC_ORIG_SIZE) {
+			rdesc = sirius_battery_free_tablet_rdesc_fixed;
+			*rsize = sizeof(sirius_battery_free_tablet_rdesc_fixed);
+		}
+		break;
+	}
+	return rdesc;
+}
+
+static int waltop_raw_event(struct hid_device *hdev, struct hid_report *report,
+		     u8 *data, int size)
+{
+	/* If this is a pen input report */
+	if (report->type == HID_INPUT_REPORT && report->id == 16 && size >= 8) {
+		/*
+		 * Ignore reported pressure when a barrel button is pressed,
+		 * because it is rarely correct.
+		 */
+
+		/* If a barrel button is pressed */
+		if ((data[1] & 0xF) > 1) {
+			/* Report zero pressure */
+			data[6] = 0;
+			data[7] = 0;
+		}
+	}
+
+	/* If this is a pen input report of Sirius Battery Free Tablet */
+	if (hdev->product == USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET &&
+	    report->type == HID_INPUT_REPORT &&
+	    report->id == 16 &&
+	    size == 10) {
+		/*
+		 * The tablet reports tilt as roughly sin(a)*21 (18 means 60
+		 * degrees).
+		 *
+		 * This array stores angles as radians * 100, corresponding to
+		 * reported values up to 60 degrees, as expected by userspace.
+		 */
+		static const s8 tilt_to_radians[] = {
+			0, 5, 10, 14, 19, 24, 29, 34, 40, 45,
+			50, 56, 62, 68, 74, 81, 88, 96, 105
+		};
+
+		s8 tilt_x = (s8)data[8];
+		s8 tilt_y = (s8)data[9];
+		s8 sign_x = tilt_x >= 0 ? 1 : -1;
+		s8 sign_y = tilt_y >= 0 ? 1 : -1;
+
+		tilt_x *= sign_x;
+		tilt_y *= sign_y;
+
+		/*
+		 * Reverse the Y Tilt direction to match the HID standard and
+		 * userspace expectations. See HID Usage Tables v1.12 16.3.2
+		 * Tilt Orientation.
+		 */
+		sign_y *= -1;
+
+		/*
+		 * This effectively clamps reported tilt to 60 degrees - the
+		 * range expected by userspace
+		 */
+		if (tilt_x > ARRAY_SIZE(tilt_to_radians) - 1)
+			tilt_x = ARRAY_SIZE(tilt_to_radians) - 1;
+		if (tilt_y > ARRAY_SIZE(tilt_to_radians) - 1)
+			tilt_y = ARRAY_SIZE(tilt_to_radians) - 1;
+
+		data[8] = tilt_to_radians[tilt_x] * sign_x;
+		data[9] = tilt_to_radians[tilt_y] * sign_y;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id waltop_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_SLIM_TABLET_5_8_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_SLIM_TABLET_12_1_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_Q_PAD) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_PID_0038) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_MEDIA_TABLET_10_6_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+				USB_DEVICE_ID_WALTOP_MEDIA_TABLET_14_1_INCH) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WALTOP,
+			 USB_DEVICE_ID_WALTOP_SIRIUS_BATTERY_FREE_TABLET) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, waltop_devices);
+
+static struct hid_driver waltop_driver = {
+	.name = "waltop",
+	.id_table = waltop_devices,
+	.report_fixup = waltop_report_fixup,
+	.raw_event = waltop_raw_event,
+};
+module_hid_driver(waltop_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
new file mode 100644
index 0000000..7780da4
--- /dev/null
+++ b/drivers/hid/hid-wiimote-core.c
@@ -0,0 +1,1889 @@
+/*
+ * HID driver for Nintendo Wii / Wii U peripherals
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include "hid-ids.h"
+#include "hid-wiimote.h"
+
+/* output queue handling */
+
+static int wiimote_hid_send(struct hid_device *hdev, __u8 *buffer,
+			    size_t count)
+{
+	__u8 *buf;
+	int ret;
+
+	if (!hdev->ll_driver->output_report)
+		return -ENODEV;
+
+	buf = kmemdup(buffer, count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_output_report(hdev, buf, count);
+
+	kfree(buf);
+	return ret;
+}
+
+static void wiimote_queue_worker(struct work_struct *work)
+{
+	struct wiimote_queue *queue = container_of(work, struct wiimote_queue,
+						   worker);
+	struct wiimote_data *wdata = container_of(queue, struct wiimote_data,
+						  queue);
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&wdata->queue.lock, flags);
+
+	while (wdata->queue.head != wdata->queue.tail) {
+		spin_unlock_irqrestore(&wdata->queue.lock, flags);
+		ret = wiimote_hid_send(wdata->hdev,
+				 wdata->queue.outq[wdata->queue.tail].data,
+				 wdata->queue.outq[wdata->queue.tail].size);
+		if (ret < 0) {
+			spin_lock_irqsave(&wdata->state.lock, flags);
+			wiimote_cmd_abort(wdata);
+			spin_unlock_irqrestore(&wdata->state.lock, flags);
+		}
+		spin_lock_irqsave(&wdata->queue.lock, flags);
+
+		wdata->queue.tail = (wdata->queue.tail + 1) % WIIMOTE_BUFSIZE;
+	}
+
+	spin_unlock_irqrestore(&wdata->queue.lock, flags);
+}
+
+static void wiimote_queue(struct wiimote_data *wdata, const __u8 *buffer,
+								size_t count)
+{
+	unsigned long flags;
+	__u8 newhead;
+
+	if (count > HID_MAX_BUFFER_SIZE) {
+		hid_warn(wdata->hdev, "Sending too large output report\n");
+
+		spin_lock_irqsave(&wdata->queue.lock, flags);
+		goto out_error;
+	}
+
+	/*
+	 * Copy new request into our output queue and check whether the
+	 * queue is full. If it is full, discard this request.
+	 * If it is empty we need to start a new worker that will
+	 * send out the buffer to the hid device.
+	 * If the queue is not empty, then there must be a worker
+	 * that is currently sending out our buffer and this worker
+	 * will reschedule itself until the queue is empty.
+	 */
+
+	spin_lock_irqsave(&wdata->queue.lock, flags);
+
+	memcpy(wdata->queue.outq[wdata->queue.head].data, buffer, count);
+	wdata->queue.outq[wdata->queue.head].size = count;
+	newhead = (wdata->queue.head + 1) % WIIMOTE_BUFSIZE;
+
+	if (wdata->queue.head == wdata->queue.tail) {
+		wdata->queue.head = newhead;
+		schedule_work(&wdata->queue.worker);
+	} else if (newhead != wdata->queue.tail) {
+		wdata->queue.head = newhead;
+	} else {
+		hid_warn(wdata->hdev, "Output queue is full");
+		goto out_error;
+	}
+
+	goto out_unlock;
+
+out_error:
+	wiimote_cmd_abort(wdata);
+out_unlock:
+	spin_unlock_irqrestore(&wdata->queue.lock, flags);
+}
+
+/*
+ * This sets the rumble bit on the given output report if rumble is
+ * currently enabled.
+ * \cmd1 must point to the second byte in the output report => &cmd[1]
+ * This must be called on nearly every output report before passing it
+ * into the output queue!
+ */
+static inline void wiiproto_keep_rumble(struct wiimote_data *wdata, __u8 *cmd1)
+{
+	if (wdata->state.flags & WIIPROTO_FLAG_RUMBLE)
+		*cmd1 |= 0x01;
+}
+
+void wiiproto_req_rumble(struct wiimote_data *wdata, __u8 rumble)
+{
+	__u8 cmd[2];
+
+	rumble = !!rumble;
+	if (rumble == !!(wdata->state.flags & WIIPROTO_FLAG_RUMBLE))
+		return;
+
+	if (rumble)
+		wdata->state.flags |= WIIPROTO_FLAG_RUMBLE;
+	else
+		wdata->state.flags &= ~WIIPROTO_FLAG_RUMBLE;
+
+	cmd[0] = WIIPROTO_REQ_RUMBLE;
+	cmd[1] = 0;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+void wiiproto_req_leds(struct wiimote_data *wdata, int leds)
+{
+	__u8 cmd[2];
+
+	leds &= WIIPROTO_FLAGS_LEDS;
+	if ((wdata->state.flags & WIIPROTO_FLAGS_LEDS) == leds)
+		return;
+	wdata->state.flags = (wdata->state.flags & ~WIIPROTO_FLAGS_LEDS) | leds;
+
+	cmd[0] = WIIPROTO_REQ_LED;
+	cmd[1] = 0;
+
+	if (leds & WIIPROTO_FLAG_LED1)
+		cmd[1] |= 0x10;
+	if (leds & WIIPROTO_FLAG_LED2)
+		cmd[1] |= 0x20;
+	if (leds & WIIPROTO_FLAG_LED3)
+		cmd[1] |= 0x40;
+	if (leds & WIIPROTO_FLAG_LED4)
+		cmd[1] |= 0x80;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+/*
+ * Check what peripherals of the wiimote are currently
+ * active and select a proper DRM that supports all of
+ * the requested data inputs.
+ *
+ * Not all combinations are actually supported. The following
+ * combinations work only with limitations:
+ *  - IR cam in extended or full mode disables any data transmission
+ *    of extension controllers. There is no DRM mode that supports
+ *    extension bytes plus extended/full IR.
+ *  - IR cam with accelerometer and extension *_EXT8 is not supported.
+ *    However, all extensions that need *_EXT8 are devices that don't
+ *    support IR cameras. Hence, this shouldn't happen under normal
+ *    operation.
+ *  - *_EXT16 is only supported in combination with buttons and
+ *    accelerometer. No IR or similar can be active simultaneously. As
+ *    above, all modules that require it are mutually exclusive with
+ *    IR/etc. so this doesn't matter.
+ */
+static __u8 select_drm(struct wiimote_data *wdata)
+{
+	__u8 ir = wdata->state.flags & WIIPROTO_FLAGS_IR;
+	bool ext;
+
+	ext = (wdata->state.flags & WIIPROTO_FLAG_EXT_USED) ||
+	      (wdata->state.flags & WIIPROTO_FLAG_MP_USED);
+
+	/* some 3rd-party balance-boards are hard-coded to KEE, *sigh* */
+	if (wdata->state.devtype == WIIMOTE_DEV_BALANCE_BOARD) {
+		if (ext)
+			return WIIPROTO_REQ_DRM_KEE;
+		else
+			return WIIPROTO_REQ_DRM_K;
+	}
+
+	if (ir == WIIPROTO_FLAG_IR_BASIC) {
+		if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) {
+			/* GEN10 and ealier devices bind IR formats to DRMs.
+			 * Hence, we cannot use DRM_KAI here as it might be
+			 * bound to IR_EXT. Use DRM_KAIE unconditionally so we
+			 * work with all devices and our parsers can use the
+			 * fixed formats, too. */
+			return WIIPROTO_REQ_DRM_KAIE;
+		} else {
+			return WIIPROTO_REQ_DRM_KIE;
+		}
+	} else if (ir == WIIPROTO_FLAG_IR_EXT) {
+		return WIIPROTO_REQ_DRM_KAI;
+	} else if (ir == WIIPROTO_FLAG_IR_FULL) {
+		return WIIPROTO_REQ_DRM_SKAI1;
+	} else {
+		if (wdata->state.flags & WIIPROTO_FLAG_ACCEL) {
+			if (ext)
+				return WIIPROTO_REQ_DRM_KAE;
+			else
+				return WIIPROTO_REQ_DRM_KA;
+		} else {
+			if (ext)
+				return WIIPROTO_REQ_DRM_KEE;
+			else
+				return WIIPROTO_REQ_DRM_K;
+		}
+	}
+}
+
+void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm)
+{
+	__u8 cmd[3];
+
+	if (wdata->state.flags & WIIPROTO_FLAG_DRM_LOCKED)
+		drm = wdata->state.drm;
+	else if (drm == WIIPROTO_REQ_NULL)
+		drm = select_drm(wdata);
+
+	cmd[0] = WIIPROTO_REQ_DRM;
+	cmd[1] = 0;
+	cmd[2] = drm;
+
+	wdata->state.drm = drm;
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+void wiiproto_req_status(struct wiimote_data *wdata)
+{
+	__u8 cmd[2];
+
+	cmd[0] = WIIPROTO_REQ_SREQ;
+	cmd[1] = 0;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel)
+{
+	accel = !!accel;
+	if (accel == !!(wdata->state.flags & WIIPROTO_FLAG_ACCEL))
+		return;
+
+	if (accel)
+		wdata->state.flags |= WIIPROTO_FLAG_ACCEL;
+	else
+		wdata->state.flags &= ~WIIPROTO_FLAG_ACCEL;
+
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+}
+
+void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags)
+{
+	__u8 cmd[2];
+
+	cmd[0] = WIIPROTO_REQ_IR1;
+	cmd[1] = flags;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags)
+{
+	__u8 cmd[2];
+
+	cmd[0] = WIIPROTO_REQ_IR2;
+	cmd[1] = flags;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+#define wiiproto_req_wreg(wdata, os, buf, sz) \
+			wiiproto_req_wmem((wdata), false, (os), (buf), (sz))
+
+#define wiiproto_req_weeprom(wdata, os, buf, sz) \
+			wiiproto_req_wmem((wdata), true, (os), (buf), (sz))
+
+static void wiiproto_req_wmem(struct wiimote_data *wdata, bool eeprom,
+				__u32 offset, const __u8 *buf, __u8 size)
+{
+	__u8 cmd[22];
+
+	if (size > 16 || size == 0) {
+		hid_warn(wdata->hdev, "Invalid length %d wmem request\n", size);
+		return;
+	}
+
+	memset(cmd, 0, sizeof(cmd));
+	cmd[0] = WIIPROTO_REQ_WMEM;
+	cmd[2] = (offset >> 16) & 0xff;
+	cmd[3] = (offset >> 8) & 0xff;
+	cmd[4] = offset & 0xff;
+	cmd[5] = size;
+	memcpy(&cmd[6], buf, size);
+
+	if (!eeprom)
+		cmd[1] |= 0x04;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom, __u32 offset,
+								__u16 size)
+{
+	__u8 cmd[7];
+
+	if (size == 0) {
+		hid_warn(wdata->hdev, "Invalid length %d rmem request\n", size);
+		return;
+	}
+
+	cmd[0] = WIIPROTO_REQ_RMEM;
+	cmd[1] = 0;
+	cmd[2] = (offset >> 16) & 0xff;
+	cmd[3] = (offset >> 8) & 0xff;
+	cmd[4] = offset & 0xff;
+	cmd[5] = (size >> 8) & 0xff;
+	cmd[6] = size & 0xff;
+
+	if (!eeprom)
+		cmd[1] |= 0x04;
+
+	wiiproto_keep_rumble(wdata, &cmd[1]);
+	wiimote_queue(wdata, cmd, sizeof(cmd));
+}
+
+/* requries the cmd-mutex to be held */
+int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset,
+						const __u8 *wmem, __u8 size)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_WMEM, 0);
+	wiiproto_req_wreg(wdata, offset, wmem, size);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_wait(wdata);
+	if (!ret && wdata->state.cmd_err)
+		ret = -EIO;
+
+	return ret;
+}
+
+/* requries the cmd-mutex to be held */
+ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset, __u8 *rmem,
+								__u8 size)
+{
+	unsigned long flags;
+	ssize_t ret;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.cmd_read_size = size;
+	wdata->state.cmd_read_buf = rmem;
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_RMEM, offset & 0xffff);
+	wiiproto_req_rreg(wdata, offset, size);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_wait(wdata);
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.cmd_read_buf = NULL;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	if (!ret) {
+		if (wdata->state.cmd_read_size == 0)
+			ret = -EIO;
+		else
+			ret = wdata->state.cmd_read_size;
+	}
+
+	return ret;
+}
+
+/* requires the cmd-mutex to be held */
+static int wiimote_cmd_init_ext(struct wiimote_data *wdata)
+{
+	__u8 wmem;
+	int ret;
+
+	/* initialize extension */
+	wmem = 0x55;
+	ret = wiimote_cmd_write(wdata, 0xa400f0, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	/* disable default encryption */
+	wmem = 0x0;
+	ret = wiimote_cmd_write(wdata, 0xa400fb, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* requires the cmd-mutex to be held */
+static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata, __u8 *rmem)
+{
+	int ret;
+
+	/* read extension ID */
+	ret = wiimote_cmd_read(wdata, 0xa400fa, rmem, 6);
+	if (ret != 6)
+		return WIIMOTE_EXT_NONE;
+
+	hid_dbg(wdata->hdev, "extension ID: %6phC\n", rmem);
+
+	if (rmem[0] == 0xff && rmem[1] == 0xff && rmem[2] == 0xff &&
+	    rmem[3] == 0xff && rmem[4] == 0xff && rmem[5] == 0xff)
+		return WIIMOTE_EXT_NONE;
+
+	if (rmem[4] == 0x00 && rmem[5] == 0x00)
+		return WIIMOTE_EXT_NUNCHUK;
+	if (rmem[4] == 0x01 && rmem[5] == 0x01)
+		return WIIMOTE_EXT_CLASSIC_CONTROLLER;
+	if (rmem[4] == 0x04 && rmem[5] == 0x02)
+		return WIIMOTE_EXT_BALANCE_BOARD;
+	if (rmem[4] == 0x01 && rmem[5] == 0x20)
+		return WIIMOTE_EXT_PRO_CONTROLLER;
+	if (rmem[0] == 0x01 && rmem[1] == 0x00 &&
+	    rmem[4] == 0x01 && rmem[5] == 0x03)
+		return WIIMOTE_EXT_DRUMS;
+	if (rmem[0] == 0x00 && rmem[1] == 0x00 &&
+	    rmem[4] == 0x01 && rmem[5] == 0x03)
+		return WIIMOTE_EXT_GUITAR;
+
+	return WIIMOTE_EXT_UNKNOWN;
+}
+
+/* requires the cmd-mutex to be held */
+static int wiimote_cmd_init_mp(struct wiimote_data *wdata)
+{
+	__u8 wmem;
+	int ret;
+
+	/* initialize MP */
+	wmem = 0x55;
+	ret = wiimote_cmd_write(wdata, 0xa600f0, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	/* disable default encryption */
+	wmem = 0x0;
+	ret = wiimote_cmd_write(wdata, 0xa600fb, &wmem, sizeof(wmem));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* requires the cmd-mutex to be held */
+static bool wiimote_cmd_map_mp(struct wiimote_data *wdata, __u8 exttype)
+{
+	__u8 wmem;
+
+	/* map MP with correct pass-through mode */
+	switch (exttype) {
+	case WIIMOTE_EXT_CLASSIC_CONTROLLER:
+	case WIIMOTE_EXT_DRUMS:
+	case WIIMOTE_EXT_GUITAR:
+		wmem = 0x07;
+		break;
+	case WIIMOTE_EXT_NUNCHUK:
+		wmem = 0x05;
+		break;
+	default:
+		wmem = 0x04;
+		break;
+	}
+
+	return wiimote_cmd_write(wdata, 0xa600fe, &wmem, sizeof(wmem));
+}
+
+/* requires the cmd-mutex to be held */
+static bool wiimote_cmd_read_mp(struct wiimote_data *wdata, __u8 *rmem)
+{
+	int ret;
+
+	/* read motion plus ID */
+	ret = wiimote_cmd_read(wdata, 0xa600fa, rmem, 6);
+	if (ret != 6)
+		return false;
+
+	hid_dbg(wdata->hdev, "motion plus ID: %6phC\n", rmem);
+
+	if (rmem[5] == 0x05)
+		return true;
+
+	hid_info(wdata->hdev, "unknown motion plus ID: %6phC\n", rmem);
+
+	return false;
+}
+
+/* requires the cmd-mutex to be held */
+static __u8 wiimote_cmd_read_mp_mapped(struct wiimote_data *wdata)
+{
+	int ret;
+	__u8 rmem[6];
+
+	/* read motion plus ID */
+	ret = wiimote_cmd_read(wdata, 0xa400fa, rmem, 6);
+	if (ret != 6)
+		return WIIMOTE_MP_NONE;
+
+	hid_dbg(wdata->hdev, "mapped motion plus ID: %6phC\n", rmem);
+
+	if (rmem[0] == 0xff && rmem[1] == 0xff && rmem[2] == 0xff &&
+	    rmem[3] == 0xff && rmem[4] == 0xff && rmem[5] == 0xff)
+		return WIIMOTE_MP_NONE;
+
+	if (rmem[4] == 0x04 && rmem[5] == 0x05)
+		return WIIMOTE_MP_SINGLE;
+	else if (rmem[4] == 0x05 && rmem[5] == 0x05)
+		return WIIMOTE_MP_PASSTHROUGH_NUNCHUK;
+	else if (rmem[4] == 0x07 && rmem[5] == 0x05)
+		return WIIMOTE_MP_PASSTHROUGH_CLASSIC;
+
+	return WIIMOTE_MP_UNKNOWN;
+}
+
+/* device module handling */
+
+static const __u8 * const wiimote_devtype_mods[WIIMOTE_DEV_NUM] = {
+	[WIIMOTE_DEV_PENDING] = (const __u8[]){
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_UNKNOWN] = (const __u8[]){
+		WIIMOD_NO_MP,
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_GENERIC] = (const __u8[]){
+		WIIMOD_KEYS,
+		WIIMOD_RUMBLE,
+		WIIMOD_BATTERY,
+		WIIMOD_LED1,
+		WIIMOD_LED2,
+		WIIMOD_LED3,
+		WIIMOD_LED4,
+		WIIMOD_ACCEL,
+		WIIMOD_IR,
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_GEN10] = (const __u8[]){
+		WIIMOD_KEYS,
+		WIIMOD_RUMBLE,
+		WIIMOD_BATTERY,
+		WIIMOD_LED1,
+		WIIMOD_LED2,
+		WIIMOD_LED3,
+		WIIMOD_LED4,
+		WIIMOD_ACCEL,
+		WIIMOD_IR,
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_GEN20] = (const __u8[]){
+		WIIMOD_KEYS,
+		WIIMOD_RUMBLE,
+		WIIMOD_BATTERY,
+		WIIMOD_LED1,
+		WIIMOD_LED2,
+		WIIMOD_LED3,
+		WIIMOD_LED4,
+		WIIMOD_ACCEL,
+		WIIMOD_IR,
+		WIIMOD_BUILTIN_MP,
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_BALANCE_BOARD] = (const __u8[]) {
+		WIIMOD_BATTERY,
+		WIIMOD_LED1,
+		WIIMOD_NO_MP,
+		WIIMOD_NULL,
+	},
+	[WIIMOTE_DEV_PRO_CONTROLLER] = (const __u8[]) {
+		WIIMOD_BATTERY,
+		WIIMOD_LED1,
+		WIIMOD_LED2,
+		WIIMOD_LED3,
+		WIIMOD_LED4,
+		WIIMOD_NO_MP,
+		WIIMOD_NULL,
+	},
+};
+
+static void wiimote_modules_load(struct wiimote_data *wdata,
+				 unsigned int devtype)
+{
+	bool need_input = false;
+	const __u8 *mods, *iter;
+	const struct wiimod_ops *ops;
+	int ret;
+
+	mods = wiimote_devtype_mods[devtype];
+
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		if (wiimod_table[*iter]->flags & WIIMOD_FLAG_INPUT) {
+			need_input = true;
+			break;
+		}
+	}
+
+	if (need_input) {
+		wdata->input = input_allocate_device();
+		if (!wdata->input)
+			return;
+
+		input_set_drvdata(wdata->input, wdata);
+		wdata->input->dev.parent = &wdata->hdev->dev;
+		wdata->input->id.bustype = wdata->hdev->bus;
+		wdata->input->id.vendor = wdata->hdev->vendor;
+		wdata->input->id.product = wdata->hdev->product;
+		wdata->input->id.version = wdata->hdev->version;
+		wdata->input->name = WIIMOTE_NAME;
+	}
+
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (!ops->probe)
+			continue;
+
+		ret = ops->probe(ops, wdata);
+		if (ret)
+			goto error;
+	}
+
+	if (wdata->input) {
+		ret = input_register_device(wdata->input);
+		if (ret)
+			goto error;
+	}
+
+	spin_lock_irq(&wdata->state.lock);
+	wdata->state.devtype = devtype;
+	spin_unlock_irq(&wdata->state.lock);
+	return;
+
+error:
+	for ( ; iter-- != mods; ) {
+		ops = wiimod_table[*iter];
+		if (ops->remove)
+			ops->remove(ops, wdata);
+	}
+
+	if (wdata->input) {
+		input_free_device(wdata->input);
+		wdata->input = NULL;
+	}
+}
+
+static void wiimote_modules_unload(struct wiimote_data *wdata)
+{
+	const __u8 *mods, *iter;
+	const struct wiimod_ops *ops;
+	unsigned long flags;
+
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.devtype = WIIMOTE_DEV_UNKNOWN;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	/* find end of list */
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter)
+		/* empty */ ;
+
+	if (wdata->input) {
+		input_get_device(wdata->input);
+		input_unregister_device(wdata->input);
+	}
+
+	for ( ; iter-- != mods; ) {
+		ops = wiimod_table[*iter];
+		if (ops->remove)
+			ops->remove(ops, wdata);
+	}
+
+	if (wdata->input) {
+		input_put_device(wdata->input);
+		wdata->input = NULL;
+	}
+}
+
+/* device extension handling */
+
+static void wiimote_ext_load(struct wiimote_data *wdata, unsigned int ext)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+	int ret;
+
+	ops = wiimod_ext_table[ext];
+
+	if (ops->probe) {
+		ret = ops->probe(ops, wdata);
+		if (ret)
+			ext = WIIMOTE_EXT_UNKNOWN;
+	}
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.exttype = ext;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_ext_unload(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+
+	ops = wiimod_ext_table[wdata->state.exttype];
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.exttype = WIIMOTE_EXT_UNKNOWN;
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	if (ops->remove)
+		ops->remove(ops, wdata);
+}
+
+static void wiimote_mp_load(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+	int ret;
+	__u8 mode = 2;
+
+	ops = &wiimod_mp;
+	if (ops->probe) {
+		ret = ops->probe(ops, wdata);
+		if (ret)
+			mode = 1;
+	}
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.mp = mode;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_mp_unload(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+	const struct wiimod_ops *ops;
+
+	if (wdata->state.mp < 2)
+		return;
+
+	ops = &wiimod_mp;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.mp = 0;
+	wdata->state.flags &= ~WIIPROTO_FLAG_MP_USED;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	if (ops->remove)
+		ops->remove(ops, wdata);
+}
+
+/* device (re-)initialization and detection */
+
+static const char *wiimote_devtype_names[WIIMOTE_DEV_NUM] = {
+	[WIIMOTE_DEV_PENDING] = "Pending",
+	[WIIMOTE_DEV_UNKNOWN] = "Unknown",
+	[WIIMOTE_DEV_GENERIC] = "Generic",
+	[WIIMOTE_DEV_GEN10] = "Nintendo Wii Remote (Gen 1)",
+	[WIIMOTE_DEV_GEN20] = "Nintendo Wii Remote Plus (Gen 2)",
+	[WIIMOTE_DEV_BALANCE_BOARD] = "Nintendo Wii Balance Board",
+	[WIIMOTE_DEV_PRO_CONTROLLER] = "Nintendo Wii U Pro Controller",
+};
+
+/* Try to guess the device type based on all collected information. We
+ * first try to detect by static extension types, then VID/PID and the
+ * device name. If we cannot detect the device, we use
+ * WIIMOTE_DEV_GENERIC so all modules will get probed on the device. */
+static void wiimote_init_set_type(struct wiimote_data *wdata,
+				  __u8 exttype)
+{
+	__u8 devtype = WIIMOTE_DEV_GENERIC;
+	__u16 vendor, product;
+	const char *name;
+
+	vendor = wdata->hdev->vendor;
+	product = wdata->hdev->product;
+	name = wdata->hdev->name;
+
+	if (exttype == WIIMOTE_EXT_BALANCE_BOARD) {
+		devtype = WIIMOTE_DEV_BALANCE_BOARD;
+		goto done;
+	} else if (exttype == WIIMOTE_EXT_PRO_CONTROLLER) {
+		devtype = WIIMOTE_DEV_PRO_CONTROLLER;
+		goto done;
+	}
+
+	if (!strcmp(name, "Nintendo RVL-CNT-01")) {
+		devtype = WIIMOTE_DEV_GEN10;
+		goto done;
+	} else if (!strcmp(name, "Nintendo RVL-CNT-01-TR")) {
+		devtype = WIIMOTE_DEV_GEN20;
+		goto done;
+	} else if (!strcmp(name, "Nintendo RVL-WBC-01")) {
+		devtype = WIIMOTE_DEV_BALANCE_BOARD;
+		goto done;
+	} else if (!strcmp(name, "Nintendo RVL-CNT-01-UC")) {
+		devtype = WIIMOTE_DEV_PRO_CONTROLLER;
+		goto done;
+	}
+
+	if (vendor == USB_VENDOR_ID_NINTENDO) {
+		if (product == USB_DEVICE_ID_NINTENDO_WIIMOTE) {
+			devtype = WIIMOTE_DEV_GEN10;
+			goto done;
+		} else if (product == USB_DEVICE_ID_NINTENDO_WIIMOTE2) {
+			devtype = WIIMOTE_DEV_GEN20;
+			goto done;
+		}
+	}
+
+done:
+	if (devtype == WIIMOTE_DEV_GENERIC)
+		hid_info(wdata->hdev, "cannot detect device; NAME: %s VID: %04x PID: %04x EXT: %04x\n",
+			name, vendor, product, exttype);
+	else
+		hid_info(wdata->hdev, "detected device: %s\n",
+			 wiimote_devtype_names[devtype]);
+
+	wiimote_modules_load(wdata, devtype);
+}
+
+static void wiimote_init_detect(struct wiimote_data *wdata)
+{
+	__u8 exttype = WIIMOTE_EXT_NONE, extdata[6];
+	bool ext;
+	int ret;
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	spin_lock_irq(&wdata->state.lock);
+	wdata->state.devtype = WIIMOTE_DEV_UNKNOWN;
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_SREQ, 0);
+	wiiproto_req_status(wdata);
+	spin_unlock_irq(&wdata->state.lock);
+
+	ret = wiimote_cmd_wait_noint(wdata);
+	if (ret)
+		goto out_release;
+
+	spin_lock_irq(&wdata->state.lock);
+	ext = wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED;
+	spin_unlock_irq(&wdata->state.lock);
+
+	if (!ext)
+		goto out_release;
+
+	wiimote_cmd_init_ext(wdata);
+	exttype = wiimote_cmd_read_ext(wdata, extdata);
+
+out_release:
+	wiimote_cmd_release(wdata);
+	wiimote_init_set_type(wdata, exttype);
+
+	/* schedule MP timer */
+	spin_lock_irq(&wdata->state.lock);
+	if (!(wdata->state.flags & WIIPROTO_FLAG_BUILTIN_MP) &&
+	    !(wdata->state.flags & WIIPROTO_FLAG_NO_MP))
+		mod_timer(&wdata->timer, jiffies + HZ * 4);
+	spin_unlock_irq(&wdata->state.lock);
+}
+
+/*
+ * MP hotplug events are not generated by the wiimote. Therefore, we need
+ * polling to detect it. We use a 4s interval for polling MP registers. This
+ * seems reasonable considering applications can trigger it manually via
+ * sysfs requests.
+ */
+static void wiimote_init_poll_mp(struct wiimote_data *wdata)
+{
+	bool mp;
+	__u8 mpdata[6];
+
+	wiimote_cmd_acquire_noint(wdata);
+	wiimote_cmd_init_mp(wdata);
+	mp = wiimote_cmd_read_mp(wdata, mpdata);
+	wiimote_cmd_release(wdata);
+
+	/* load/unload MP module if it changed */
+	if (mp) {
+		if (!wdata->state.mp) {
+			hid_info(wdata->hdev, "detected extension: Nintendo Wii Motion Plus\n");
+			wiimote_mp_load(wdata);
+		}
+	} else if (wdata->state.mp) {
+		wiimote_mp_unload(wdata);
+	}
+
+	mod_timer(&wdata->timer, jiffies + HZ * 4);
+}
+
+/*
+ * Check whether the wiimote is in the expected state. The extension registers
+ * may change during hotplug and initialization so we might get hotplug events
+ * that we caused by remapping some memory.
+ * We use some heuristics here to check known states. If the wiimote is in the
+ * expected state, we can ignore the hotplug event.
+ *
+ * Returns "true" if the device is in expected state, "false" if we should
+ * redo hotplug handling and extension initialization.
+ */
+static bool wiimote_init_check(struct wiimote_data *wdata)
+{
+	__u32 flags;
+	__u8 type, data[6];
+	bool ret, poll_mp;
+
+	spin_lock_irq(&wdata->state.lock);
+	flags = wdata->state.flags;
+	spin_unlock_irq(&wdata->state.lock);
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	/* If MP is used and active, but the extension is not, we expect:
+	 *   read_mp_mapped() == WIIMOTE_MP_SINGLE
+	 *   state.flags == !EXT_ACTIVE && !MP_PLUGGED && MP_ACTIVE
+	 * We do not check EXT_PLUGGED because it might change during
+	 * initialization of MP without extensions.
+	 *  - If MP is unplugged/replugged, read_mp_mapped() fails
+	 *  - If EXT is plugged, MP_PLUGGED will get set */
+	if (wdata->state.exttype == WIIMOTE_EXT_NONE &&
+	    wdata->state.mp > 0 && (flags & WIIPROTO_FLAG_MP_USED)) {
+		type = wiimote_cmd_read_mp_mapped(wdata);
+		ret = type == WIIMOTE_MP_SINGLE;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: !EXT && MP\n");
+
+		/* while MP is mapped, we get EXT_PLUGGED events */
+		poll_mp = false;
+
+		goto out_release;
+	}
+
+	/* If MP is unused, but the extension port is used, we expect:
+	 *   read_ext == state.exttype
+	 *   state.flags == !MP_ACTIVE && EXT_ACTIVE
+	 * - If MP is plugged/unplugged, our timer detects it
+	 * - If EXT is unplugged/replugged, EXT_ACTIVE will become unset */
+	if (!(flags & WIIPROTO_FLAG_MP_USED) &&
+	    wdata->state.exttype != WIIMOTE_EXT_NONE) {
+		type = wiimote_cmd_read_ext(wdata, data);
+		ret = type == wdata->state.exttype;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: EXT && !MP\n");
+
+		/* poll MP for hotplug events */
+		poll_mp = true;
+
+		goto out_release;
+	}
+
+	/* If neither MP nor an extension are used, we expect:
+	 *   read_ext() == WIIMOTE_EXT_NONE
+	 *   state.flags == !MP_ACTIVE && !EXT_ACTIVE && !EXT_PLUGGED
+	 * No need to perform any action in this case as everything is
+	 * disabled already.
+	 * - If MP is plugged/unplugged, our timer detects it
+	 * - If EXT is plugged, EXT_PLUGGED will be set */
+	if (!(flags & WIIPROTO_FLAG_MP_USED) &&
+	    wdata->state.exttype == WIIMOTE_EXT_NONE) {
+		type = wiimote_cmd_read_ext(wdata, data);
+		ret = type == wdata->state.exttype;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		ret = ret && !(wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: !EXT && !MP\n");
+
+		/* poll MP for hotplug events */
+		poll_mp = true;
+
+		goto out_release;
+	}
+
+	/* The trickiest part is if both EXT and MP are active. We cannot read
+	 * the EXT ID, anymore, because MP is mapped over it. However, we use
+	 * a handy trick here:
+	 *   - EXT_ACTIVE is unset whenever !MP_PLUGGED is sent
+	 * MP_PLUGGED might be re-sent again before we are scheduled, but
+	 * EXT_ACTIVE will stay unset.
+	 * So it is enough to check for mp_mapped() and MP_ACTIVE and
+	 * EXT_ACTIVE. EXT_PLUGGED is a sanity check. */
+	if (wdata->state.exttype != WIIMOTE_EXT_NONE &&
+	    wdata->state.mp > 0 && (flags & WIIPROTO_FLAG_MP_USED)) {
+		type = wiimote_cmd_read_mp_mapped(wdata);
+		ret = type != WIIMOTE_MP_NONE;
+		ret = ret && type != WIIMOTE_MP_UNKNOWN;
+		ret = ret && type != WIIMOTE_MP_SINGLE;
+
+		spin_lock_irq(&wdata->state.lock);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE);
+		ret = ret && (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE);
+		spin_unlock_irq(&wdata->state.lock);
+
+		if (!ret)
+			hid_dbg(wdata->hdev, "state left: EXT && MP\n");
+
+		/* while MP is mapped, we get EXT_PLUGGED events */
+		poll_mp = false;
+
+		goto out_release;
+	}
+
+	/* unknown state */
+	ret = false;
+
+out_release:
+	wiimote_cmd_release(wdata);
+
+	/* only poll for MP if requested and if state didn't change */
+	if (ret && poll_mp && !(flags & WIIPROTO_FLAG_BUILTIN_MP) &&
+	    !(flags & WIIPROTO_FLAG_NO_MP))
+		wiimote_init_poll_mp(wdata);
+
+	return ret;
+}
+
+static const char *wiimote_exttype_names[WIIMOTE_EXT_NUM] = {
+	[WIIMOTE_EXT_NONE] = "None",
+	[WIIMOTE_EXT_UNKNOWN] = "Unknown",
+	[WIIMOTE_EXT_NUNCHUK] = "Nintendo Wii Nunchuk",
+	[WIIMOTE_EXT_CLASSIC_CONTROLLER] = "Nintendo Wii Classic Controller",
+	[WIIMOTE_EXT_BALANCE_BOARD] = "Nintendo Wii Balance Board",
+	[WIIMOTE_EXT_PRO_CONTROLLER] = "Nintendo Wii U Pro Controller",
+	[WIIMOTE_EXT_DRUMS] = "Nintendo Wii Drums",
+	[WIIMOTE_EXT_GUITAR] = "Nintendo Wii Guitar",
+};
+
+/*
+ * Handle hotplug events
+ * If we receive an hotplug event and the device-check failed, we deinitialize
+ * the extension ports, re-read all extension IDs and set the device into
+ * the desired state. This involves mapping MP into the main extension
+ * registers, setting up extension passthrough modes and initializing the
+ * requested extensions.
+ */
+static void wiimote_init_hotplug(struct wiimote_data *wdata)
+{
+	__u8 exttype, extdata[6], mpdata[6];
+	__u32 flags;
+	bool mp;
+
+	hid_dbg(wdata->hdev, "detect extensions..\n");
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	spin_lock_irq(&wdata->state.lock);
+
+	/* get state snapshot that we will then work on */
+	flags = wdata->state.flags;
+
+	/* disable event forwarding temporarily */
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+	wdata->state.flags &= ~WIIPROTO_FLAG_MP_ACTIVE;
+
+	spin_unlock_irq(&wdata->state.lock);
+
+	/* init extension and MP (deactivates current extension or MP) */
+	wiimote_cmd_init_ext(wdata);
+	if (flags & WIIPROTO_FLAG_NO_MP) {
+		mp = false;
+	} else {
+		wiimote_cmd_init_mp(wdata);
+		mp = wiimote_cmd_read_mp(wdata, mpdata);
+	}
+	exttype = wiimote_cmd_read_ext(wdata, extdata);
+
+	wiimote_cmd_release(wdata);
+
+	/* load/unload extension module if it changed */
+	if (exttype != wdata->state.exttype) {
+		/* unload previous extension */
+		wiimote_ext_unload(wdata);
+
+		if (exttype == WIIMOTE_EXT_UNKNOWN) {
+			hid_info(wdata->hdev, "cannot detect extension; %6phC\n",
+				 extdata);
+		} else if (exttype == WIIMOTE_EXT_NONE) {
+			spin_lock_irq(&wdata->state.lock);
+			wdata->state.exttype = WIIMOTE_EXT_NONE;
+			spin_unlock_irq(&wdata->state.lock);
+		} else {
+			hid_info(wdata->hdev, "detected extension: %s\n",
+				 wiimote_exttype_names[exttype]);
+			/* try loading new extension */
+			wiimote_ext_load(wdata, exttype);
+		}
+	}
+
+	/* load/unload MP module if it changed */
+	if (mp) {
+		if (!wdata->state.mp) {
+			hid_info(wdata->hdev, "detected extension: Nintendo Wii Motion Plus\n");
+			wiimote_mp_load(wdata);
+		}
+	} else if (wdata->state.mp) {
+		wiimote_mp_unload(wdata);
+	}
+
+	/* if MP is not used, do not map or activate it */
+	if (!(flags & WIIPROTO_FLAG_MP_USED))
+		mp = false;
+
+	/* map MP into main extension registers if used */
+	if (mp) {
+		wiimote_cmd_acquire_noint(wdata);
+		wiimote_cmd_map_mp(wdata, exttype);
+		wiimote_cmd_release(wdata);
+
+		/* delete MP hotplug timer */
+		del_timer_sync(&wdata->timer);
+	} else {
+		/* reschedule MP hotplug timer */
+		if (!(flags & WIIPROTO_FLAG_BUILTIN_MP) &&
+		    !(flags & WIIPROTO_FLAG_NO_MP))
+			mod_timer(&wdata->timer, jiffies + HZ * 4);
+	}
+
+	spin_lock_irq(&wdata->state.lock);
+
+	/* enable data forwarding again and set expected hotplug state */
+	if (mp) {
+		wdata->state.flags |= WIIPROTO_FLAG_MP_ACTIVE;
+		if (wdata->state.exttype == WIIMOTE_EXT_NONE) {
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+		} else {
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags |= WIIPROTO_FLAG_MP_PLUGGED;
+			wdata->state.flags |= WIIPROTO_FLAG_EXT_ACTIVE;
+		}
+	} else if (wdata->state.exttype != WIIMOTE_EXT_NONE) {
+		wdata->state.flags |= WIIPROTO_FLAG_EXT_ACTIVE;
+	}
+
+	/* request status report for hotplug state updates */
+	wiiproto_req_status(wdata);
+
+	spin_unlock_irq(&wdata->state.lock);
+
+	hid_dbg(wdata->hdev, "detected extensions: MP: %d EXT: %d\n",
+		wdata->state.mp, wdata->state.exttype);
+}
+
+static void wiimote_init_worker(struct work_struct *work)
+{
+	struct wiimote_data *wdata = container_of(work, struct wiimote_data,
+						  init_worker);
+	bool changed = false;
+
+	if (wdata->state.devtype == WIIMOTE_DEV_PENDING) {
+		wiimote_init_detect(wdata);
+		changed = true;
+	}
+
+	if (changed || !wiimote_init_check(wdata))
+		wiimote_init_hotplug(wdata);
+
+	if (changed)
+		kobject_uevent(&wdata->hdev->dev.kobj, KOBJ_CHANGE);
+}
+
+void __wiimote_schedule(struct wiimote_data *wdata)
+{
+	if (!(wdata->state.flags & WIIPROTO_FLAG_EXITING))
+		schedule_work(&wdata->init_worker);
+}
+
+static void wiimote_schedule(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	__wiimote_schedule(wdata);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static void wiimote_init_timeout(struct timer_list *t)
+{
+	struct wiimote_data *wdata = from_timer(wdata, t, timer);
+
+	wiimote_schedule(wdata);
+}
+
+/* protocol handlers */
+
+static void handler_keys(struct wiimote_data *wdata, const __u8 *payload)
+{
+	const __u8 *iter, *mods;
+	const struct wiimod_ops *ops;
+
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_keys) {
+		ops->in_keys(wdata, payload);
+		return;
+	}
+
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (ops->in_keys) {
+			ops->in_keys(wdata, payload);
+			break;
+		}
+	}
+}
+
+static void handler_accel(struct wiimote_data *wdata, const __u8 *payload)
+{
+	const __u8 *iter, *mods;
+	const struct wiimod_ops *ops;
+
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_accel) {
+		ops->in_accel(wdata, payload);
+		return;
+	}
+
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (ops->in_accel) {
+			ops->in_accel(wdata, payload);
+			break;
+		}
+	}
+}
+
+static bool valid_ext_handler(const struct wiimod_ops *ops, size_t len)
+{
+	if (!ops->in_ext)
+		return false;
+	if ((ops->flags & WIIMOD_FLAG_EXT8) && len < 8)
+		return false;
+	if ((ops->flags & WIIMOD_FLAG_EXT16) && len < 16)
+		return false;
+
+	return true;
+}
+
+static void handler_ext(struct wiimote_data *wdata, const __u8 *payload,
+			size_t len)
+{
+	static const __u8 invalid[21] = { 0xff, 0xff, 0xff, 0xff,
+					  0xff, 0xff, 0xff, 0xff,
+					  0xff, 0xff, 0xff, 0xff,
+					  0xff, 0xff, 0xff, 0xff,
+					  0xff, 0xff, 0xff, 0xff,
+					  0xff };
+	const __u8 *iter, *mods;
+	const struct wiimod_ops *ops;
+	bool is_mp;
+
+	if (len > 21)
+		len = 21;
+	if (len < 6 || !memcmp(payload, invalid, len))
+		return;
+
+	/* if MP is active, track MP slot hotplugging */
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		/* this bit is set for invalid events (eg. during hotplug) */
+		if (payload[5] & 0x01)
+			return;
+
+		if (payload[4] & 0x01) {
+			if (!(wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED)) {
+				hid_dbg(wdata->hdev, "MP hotplug: 1\n");
+				wdata->state.flags |= WIIPROTO_FLAG_MP_PLUGGED;
+				__wiimote_schedule(wdata);
+			}
+		} else {
+			if (wdata->state.flags & WIIPROTO_FLAG_MP_PLUGGED) {
+				hid_dbg(wdata->hdev, "MP hotplug: 0\n");
+				wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+				wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+				__wiimote_schedule(wdata);
+			}
+		}
+
+		/* detect MP data that is sent interleaved with EXT data */
+		is_mp = payload[5] & 0x02;
+	} else {
+		is_mp = false;
+	}
+
+	/* ignore EXT events if no extension is active */
+	if (!(wdata->state.flags & WIIPROTO_FLAG_EXT_ACTIVE) && !is_mp)
+		return;
+
+	/* try forwarding to extension handler, first */
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (is_mp && ops->in_mp) {
+		ops->in_mp(wdata, payload);
+		return;
+	} else if (!is_mp && valid_ext_handler(ops, len)) {
+		ops->in_ext(wdata, payload);
+		return;
+	}
+
+	/* try forwarding to MP handler */
+	ops = &wiimod_mp;
+	if (is_mp && ops->in_mp) {
+		ops->in_mp(wdata, payload);
+		return;
+	} else if (!is_mp && valid_ext_handler(ops, len)) {
+		ops->in_ext(wdata, payload);
+		return;
+	}
+
+	/* try forwarding to loaded modules */
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (is_mp && ops->in_mp) {
+			ops->in_mp(wdata, payload);
+			return;
+		} else if (!is_mp && valid_ext_handler(ops, len)) {
+			ops->in_ext(wdata, payload);
+			return;
+		}
+	}
+}
+
+#define ir_to_input0(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 0)
+#define ir_to_input1(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 1)
+#define ir_to_input2(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 2)
+#define ir_to_input3(wdata, ir, packed) handler_ir((wdata), (ir), (packed), 3)
+
+static void handler_ir(struct wiimote_data *wdata, const __u8 *payload,
+		       bool packed, unsigned int id)
+{
+	const __u8 *iter, *mods;
+	const struct wiimod_ops *ops;
+
+	ops = wiimod_ext_table[wdata->state.exttype];
+	if (ops->in_ir) {
+		ops->in_ir(wdata, payload, packed, id);
+		return;
+	}
+
+	mods = wiimote_devtype_mods[wdata->state.devtype];
+	for (iter = mods; *iter != WIIMOD_NULL; ++iter) {
+		ops = wiimod_table[*iter];
+		if (ops->in_ir) {
+			ops->in_ir(wdata, payload, packed, id);
+			break;
+		}
+	}
+}
+
+/* reduced status report with "BB BB" key data only */
+static void handler_status_K(struct wiimote_data *wdata,
+			     const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+
+	/* on status reports the drm is reset so we need to resend the drm */
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+}
+
+/* extended status report with "BB BB LF 00 00 VV" data */
+static void handler_status(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_status_K(wdata, payload);
+
+	/* update extension status */
+	if (payload[2] & 0x02) {
+		if (!(wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED)) {
+			hid_dbg(wdata->hdev, "EXT hotplug: 1\n");
+			wdata->state.flags |= WIIPROTO_FLAG_EXT_PLUGGED;
+			__wiimote_schedule(wdata);
+		}
+	} else {
+		if (wdata->state.flags & WIIPROTO_FLAG_EXT_PLUGGED) {
+			hid_dbg(wdata->hdev, "EXT hotplug: 0\n");
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_PLUGGED;
+			wdata->state.flags &= ~WIIPROTO_FLAG_EXT_ACTIVE;
+			wdata->state.flags &= ~WIIPROTO_FLAG_MP_ACTIVE;
+			__wiimote_schedule(wdata);
+		}
+	}
+
+	wdata->state.cmd_battery = payload[5];
+	if (wiimote_cmd_pending(wdata, WIIPROTO_REQ_SREQ, 0))
+		wiimote_cmd_complete(wdata);
+}
+
+/* reduced generic report with "BB BB" key data only */
+static void handler_generic_K(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+}
+
+static void handler_data(struct wiimote_data *wdata, const __u8 *payload)
+{
+	__u16 offset = payload[3] << 8 | payload[4];
+	__u8 size = (payload[2] >> 4) + 1;
+	__u8 err = payload[2] & 0x0f;
+
+	handler_keys(wdata, payload);
+
+	if (wiimote_cmd_pending(wdata, WIIPROTO_REQ_RMEM, offset)) {
+		if (err)
+			size = 0;
+		else if (size > wdata->state.cmd_read_size)
+			size = wdata->state.cmd_read_size;
+
+		wdata->state.cmd_read_size = size;
+		if (wdata->state.cmd_read_buf)
+			memcpy(wdata->state.cmd_read_buf, &payload[5], size);
+		wiimote_cmd_complete(wdata);
+	}
+}
+
+static void handler_return(struct wiimote_data *wdata, const __u8 *payload)
+{
+	__u8 err = payload[3];
+	__u8 cmd = payload[2];
+
+	handler_keys(wdata, payload);
+
+	if (wiimote_cmd_pending(wdata, cmd, 0)) {
+		wdata->state.cmd_err = err;
+		wiimote_cmd_complete(wdata);
+	} else if (err) {
+		hid_warn(wdata->hdev, "Remote error %hhu on req %hhu\n", err,
+									cmd);
+	}
+}
+
+static void handler_drm_KA(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_accel(wdata, payload);
+}
+
+static void handler_drm_KE(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_ext(wdata, &payload[2], 8);
+}
+
+static void handler_drm_KAI(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_accel(wdata, payload);
+	ir_to_input0(wdata, &payload[5], false);
+	ir_to_input1(wdata, &payload[8], false);
+	ir_to_input2(wdata, &payload[11], false);
+	ir_to_input3(wdata, &payload[14], false);
+}
+
+static void handler_drm_KEE(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_ext(wdata, &payload[2], 19);
+}
+
+static void handler_drm_KIE(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	ir_to_input0(wdata, &payload[2], false);
+	ir_to_input1(wdata, &payload[4], true);
+	ir_to_input2(wdata, &payload[7], false);
+	ir_to_input3(wdata, &payload[9], true);
+	handler_ext(wdata, &payload[12], 9);
+}
+
+static void handler_drm_KAE(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_accel(wdata, payload);
+	handler_ext(wdata, &payload[5], 16);
+}
+
+static void handler_drm_KAIE(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+	handler_accel(wdata, payload);
+	ir_to_input0(wdata, &payload[5], false);
+	ir_to_input1(wdata, &payload[7], true);
+	ir_to_input2(wdata, &payload[10], false);
+	ir_to_input3(wdata, &payload[12], true);
+	handler_ext(wdata, &payload[15], 6);
+}
+
+static void handler_drm_E(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_ext(wdata, payload, 21);
+}
+
+static void handler_drm_SKAI1(struct wiimote_data *wdata, const __u8 *payload)
+{
+	handler_keys(wdata, payload);
+
+	wdata->state.accel_split[0] = payload[2];
+	wdata->state.accel_split[1] = (payload[0] >> 1) & (0x10 | 0x20);
+	wdata->state.accel_split[1] |= (payload[1] << 1) & (0x40 | 0x80);
+
+	ir_to_input0(wdata, &payload[3], false);
+	ir_to_input1(wdata, &payload[12], false);
+}
+
+static void handler_drm_SKAI2(struct wiimote_data *wdata, const __u8 *payload)
+{
+	__u8 buf[5];
+
+	handler_keys(wdata, payload);
+
+	wdata->state.accel_split[1] |= (payload[0] >> 5) & (0x01 | 0x02);
+	wdata->state.accel_split[1] |= (payload[1] >> 3) & (0x04 | 0x08);
+
+	buf[0] = 0;
+	buf[1] = 0;
+	buf[2] = wdata->state.accel_split[0];
+	buf[3] = payload[2];
+	buf[4] = wdata->state.accel_split[1];
+	handler_accel(wdata, buf);
+
+	ir_to_input2(wdata, &payload[3], false);
+	ir_to_input3(wdata, &payload[12], false);
+}
+
+struct wiiproto_handler {
+	__u8 id;
+	size_t size;
+	void (*func)(struct wiimote_data *wdata, const __u8 *payload);
+};
+
+static struct wiiproto_handler handlers[] = {
+	{ .id = WIIPROTO_REQ_STATUS, .size = 6, .func = handler_status },
+	{ .id = WIIPROTO_REQ_STATUS, .size = 2, .func = handler_status_K },
+	{ .id = WIIPROTO_REQ_DATA, .size = 21, .func = handler_data },
+	{ .id = WIIPROTO_REQ_DATA, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_RETURN, .size = 4, .func = handler_return },
+	{ .id = WIIPROTO_REQ_RETURN, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_K, .size = 2, .func = handler_keys },
+	{ .id = WIIPROTO_REQ_DRM_KA, .size = 5, .func = handler_drm_KA },
+	{ .id = WIIPROTO_REQ_DRM_KA, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KE, .size = 10, .func = handler_drm_KE },
+	{ .id = WIIPROTO_REQ_DRM_KE, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KAI, .size = 17, .func = handler_drm_KAI },
+	{ .id = WIIPROTO_REQ_DRM_KAI, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KEE, .size = 21, .func = handler_drm_KEE },
+	{ .id = WIIPROTO_REQ_DRM_KEE, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KAE, .size = 21, .func = handler_drm_KAE },
+	{ .id = WIIPROTO_REQ_DRM_KAE, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KIE, .size = 21, .func = handler_drm_KIE },
+	{ .id = WIIPROTO_REQ_DRM_KIE, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_KAIE, .size = 21, .func = handler_drm_KAIE },
+	{ .id = WIIPROTO_REQ_DRM_KAIE, .size = 2, .func = handler_generic_K },
+	{ .id = WIIPROTO_REQ_DRM_E, .size = 21, .func = handler_drm_E },
+	{ .id = WIIPROTO_REQ_DRM_SKAI1, .size = 21, .func = handler_drm_SKAI1 },
+	{ .id = WIIPROTO_REQ_DRM_SKAI2, .size = 21, .func = handler_drm_SKAI2 },
+	{ .id = 0 }
+};
+
+static int wiimote_hid_event(struct hid_device *hdev, struct hid_report *report,
+							u8 *raw_data, int size)
+{
+	struct wiimote_data *wdata = hid_get_drvdata(hdev);
+	struct wiiproto_handler *h;
+	int i;
+	unsigned long flags;
+
+	if (size < 1)
+		return -EINVAL;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+
+	for (i = 0; handlers[i].id; ++i) {
+		h = &handlers[i];
+		if (h->id == raw_data[0] && h->size < size) {
+			h->func(wdata, &raw_data[1]);
+			break;
+		}
+	}
+
+	if (!handlers[i].id)
+		hid_warn(hdev, "Unhandled report %hhu size %d\n", raw_data[0],
+									size);
+
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static ssize_t wiimote_ext_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	__u8 type;
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	type = wdata->state.exttype;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	switch (type) {
+	case WIIMOTE_EXT_NONE:
+		return sprintf(buf, "none\n");
+	case WIIMOTE_EXT_NUNCHUK:
+		return sprintf(buf, "nunchuk\n");
+	case WIIMOTE_EXT_CLASSIC_CONTROLLER:
+		return sprintf(buf, "classic\n");
+	case WIIMOTE_EXT_BALANCE_BOARD:
+		return sprintf(buf, "balanceboard\n");
+	case WIIMOTE_EXT_PRO_CONTROLLER:
+		return sprintf(buf, "procontroller\n");
+	case WIIMOTE_EXT_DRUMS:
+		return sprintf(buf, "drums\n");
+	case WIIMOTE_EXT_GUITAR:
+		return sprintf(buf, "guitar\n");
+	case WIIMOTE_EXT_UNKNOWN:
+		/* fallthrough */
+	default:
+		return sprintf(buf, "unknown\n");
+	}
+}
+
+static ssize_t wiimote_ext_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+
+	if (!strcmp(buf, "scan")) {
+		wiimote_schedule(wdata);
+	} else {
+		return -EINVAL;
+	}
+
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static DEVICE_ATTR(extension, S_IRUGO | S_IWUSR | S_IWGRP, wiimote_ext_show,
+		   wiimote_ext_store);
+
+static ssize_t wiimote_dev_show(struct device *dev,
+				struct device_attribute *attr,
+				char *buf)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	__u8 type;
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	type = wdata->state.devtype;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	switch (type) {
+	case WIIMOTE_DEV_GENERIC:
+		return sprintf(buf, "generic\n");
+	case WIIMOTE_DEV_GEN10:
+		return sprintf(buf, "gen10\n");
+	case WIIMOTE_DEV_GEN20:
+		return sprintf(buf, "gen20\n");
+	case WIIMOTE_DEV_BALANCE_BOARD:
+		return sprintf(buf, "balanceboard\n");
+	case WIIMOTE_DEV_PRO_CONTROLLER:
+		return sprintf(buf, "procontroller\n");
+	case WIIMOTE_DEV_PENDING:
+		return sprintf(buf, "pending\n");
+	case WIIMOTE_DEV_UNKNOWN:
+		/* fallthrough */
+	default:
+		return sprintf(buf, "unknown\n");
+	}
+}
+
+static DEVICE_ATTR(devtype, S_IRUGO, wiimote_dev_show, NULL);
+
+static struct wiimote_data *wiimote_create(struct hid_device *hdev)
+{
+	struct wiimote_data *wdata;
+
+	wdata = kzalloc(sizeof(*wdata), GFP_KERNEL);
+	if (!wdata)
+		return NULL;
+
+	wdata->hdev = hdev;
+	hid_set_drvdata(hdev, wdata);
+
+	spin_lock_init(&wdata->queue.lock);
+	INIT_WORK(&wdata->queue.worker, wiimote_queue_worker);
+
+	spin_lock_init(&wdata->state.lock);
+	init_completion(&wdata->state.ready);
+	mutex_init(&wdata->state.sync);
+	wdata->state.drm = WIIPROTO_REQ_DRM_K;
+	wdata->state.cmd_battery = 0xff;
+
+	INIT_WORK(&wdata->init_worker, wiimote_init_worker);
+	timer_setup(&wdata->timer, wiimote_init_timeout, 0);
+
+	return wdata;
+}
+
+static void wiimote_destroy(struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	wiidebug_deinit(wdata);
+
+	/* prevent init_worker from being scheduled again */
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXITING;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	cancel_work_sync(&wdata->init_worker);
+	del_timer_sync(&wdata->timer);
+
+	device_remove_file(&wdata->hdev->dev, &dev_attr_devtype);
+	device_remove_file(&wdata->hdev->dev, &dev_attr_extension);
+
+	wiimote_mp_unload(wdata);
+	wiimote_ext_unload(wdata);
+	wiimote_modules_unload(wdata);
+	cancel_work_sync(&wdata->queue.worker);
+	hid_hw_close(wdata->hdev);
+	hid_hw_stop(wdata->hdev);
+
+	kfree(wdata);
+}
+
+static int wiimote_hid_probe(struct hid_device *hdev,
+				const struct hid_device_id *id)
+{
+	struct wiimote_data *wdata;
+	int ret;
+
+	hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+	wdata = wiimote_create(hdev);
+	if (!wdata) {
+		hid_err(hdev, "Can't alloc device\n");
+		return -ENOMEM;
+	}
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "HID parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+	if (ret) {
+		hid_err(hdev, "HW start failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_open(hdev);
+	if (ret) {
+		hid_err(hdev, "cannot start hardware I/O\n");
+		goto err_stop;
+	}
+
+	ret = device_create_file(&hdev->dev, &dev_attr_extension);
+	if (ret) {
+		hid_err(hdev, "cannot create sysfs attribute\n");
+		goto err_close;
+	}
+
+	ret = device_create_file(&hdev->dev, &dev_attr_devtype);
+	if (ret) {
+		hid_err(hdev, "cannot create sysfs attribute\n");
+		goto err_ext;
+	}
+
+	ret = wiidebug_init(wdata);
+	if (ret)
+		goto err_free;
+
+	hid_info(hdev, "New device registered\n");
+
+	/* schedule device detection */
+	wiimote_schedule(wdata);
+
+	return 0;
+
+err_free:
+	wiimote_destroy(wdata);
+	return ret;
+
+err_ext:
+	device_remove_file(&wdata->hdev->dev, &dev_attr_extension);
+err_close:
+	hid_hw_close(hdev);
+err_stop:
+	hid_hw_stop(hdev);
+err:
+	input_free_device(wdata->ir);
+	input_free_device(wdata->accel);
+	kfree(wdata);
+	return ret;
+}
+
+static void wiimote_hid_remove(struct hid_device *hdev)
+{
+	struct wiimote_data *wdata = hid_get_drvdata(hdev);
+
+	hid_info(hdev, "Device removed\n");
+	wiimote_destroy(wdata);
+}
+
+static const struct hid_device_id wiimote_hid_devices[] = {
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+				USB_DEVICE_ID_NINTENDO_WIIMOTE) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
+				USB_DEVICE_ID_NINTENDO_WIIMOTE2) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, wiimote_hid_devices);
+
+static struct hid_driver wiimote_hid_driver = {
+	.name = "wiimote",
+	.id_table = wiimote_hid_devices,
+	.probe = wiimote_hid_probe,
+	.remove = wiimote_hid_remove,
+	.raw_event = wiimote_hid_event,
+};
+module_hid_driver(wiimote_hid_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("Driver for Nintendo Wii / Wii U peripherals");
diff --git a/drivers/hid/hid-wiimote-debug.c b/drivers/hid/hid-wiimote-debug.c
new file mode 100644
index 0000000..c13fb5b
--- /dev/null
+++ b/drivers/hid/hid-wiimote-debug.c
@@ -0,0 +1,225 @@
+/*
+ * Debug support for HID Nintendo Wii / Wii U peripherals
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include "hid-wiimote.h"
+
+struct wiimote_debug {
+	struct wiimote_data *wdata;
+	struct dentry *eeprom;
+	struct dentry *drm;
+};
+
+static ssize_t wiidebug_eeprom_read(struct file *f, char __user *u, size_t s,
+								loff_t *off)
+{
+	struct wiimote_debug *dbg = f->private_data;
+	struct wiimote_data *wdata = dbg->wdata;
+	unsigned long flags;
+	ssize_t ret;
+	char buf[16];
+	__u16 size = 0;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0xffffff)
+		return 0;
+	if (s > 16)
+		s = 16;
+
+	ret = wiimote_cmd_acquire(wdata);
+	if (ret)
+		return ret;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.cmd_read_size = s;
+	wdata->state.cmd_read_buf = buf;
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_RMEM, *off & 0xffff);
+	wiiproto_req_reeprom(wdata, *off, s);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_wait(wdata);
+	if (!ret)
+		size = wdata->state.cmd_read_size;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.cmd_read_buf = NULL;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	wiimote_cmd_release(wdata);
+
+	if (ret)
+		return ret;
+	else if (size == 0)
+		return -EIO;
+
+	if (copy_to_user(u, buf, size))
+		return -EFAULT;
+
+	*off += size;
+	ret = size;
+
+	return ret;
+}
+
+static const struct file_operations wiidebug_eeprom_fops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.read = wiidebug_eeprom_read,
+	.llseek = generic_file_llseek,
+};
+
+static const char *wiidebug_drmmap[] = {
+	[WIIPROTO_REQ_NULL] = "NULL",
+	[WIIPROTO_REQ_DRM_K] = "K",
+	[WIIPROTO_REQ_DRM_KA] = "KA",
+	[WIIPROTO_REQ_DRM_KE] = "KE",
+	[WIIPROTO_REQ_DRM_KAI] = "KAI",
+	[WIIPROTO_REQ_DRM_KEE] = "KEE",
+	[WIIPROTO_REQ_DRM_KAE] = "KAE",
+	[WIIPROTO_REQ_DRM_KIE] = "KIE",
+	[WIIPROTO_REQ_DRM_KAIE] = "KAIE",
+	[WIIPROTO_REQ_DRM_E] = "E",
+	[WIIPROTO_REQ_DRM_SKAI1] = "SKAI1",
+	[WIIPROTO_REQ_DRM_SKAI2] = "SKAI2",
+	[WIIPROTO_REQ_MAX] = NULL
+};
+
+static int wiidebug_drm_show(struct seq_file *f, void *p)
+{
+	struct wiimote_debug *dbg = f->private;
+	const char *str = NULL;
+	unsigned long flags;
+	__u8 drm;
+
+	spin_lock_irqsave(&dbg->wdata->state.lock, flags);
+	drm = dbg->wdata->state.drm;
+	spin_unlock_irqrestore(&dbg->wdata->state.lock, flags);
+
+	if (drm < WIIPROTO_REQ_MAX)
+		str = wiidebug_drmmap[drm];
+	if (!str)
+		str = "unknown";
+
+	seq_printf(f, "%s\n", str);
+
+	return 0;
+}
+
+static int wiidebug_drm_open(struct inode *i, struct file *f)
+{
+	return single_open(f, wiidebug_drm_show, i->i_private);
+}
+
+static ssize_t wiidebug_drm_write(struct file *f, const char __user *u,
+							size_t s, loff_t *off)
+{
+	struct seq_file *sf = f->private_data;
+	struct wiimote_debug *dbg = sf->private;
+	unsigned long flags;
+	char buf[16];
+	ssize_t len;
+	int i;
+
+	if (s == 0)
+		return -EINVAL;
+
+	len = min((size_t) 15, s);
+	if (copy_from_user(buf, u, len))
+		return -EFAULT;
+
+	buf[len] = 0;
+
+	for (i = 0; i < WIIPROTO_REQ_MAX; ++i) {
+		if (!wiidebug_drmmap[i])
+			continue;
+		if (!strcasecmp(buf, wiidebug_drmmap[i]))
+			break;
+	}
+
+	if (i == WIIPROTO_REQ_MAX)
+		i = simple_strtoul(buf, NULL, 16);
+
+	spin_lock_irqsave(&dbg->wdata->state.lock, flags);
+	dbg->wdata->state.flags &= ~WIIPROTO_FLAG_DRM_LOCKED;
+	wiiproto_req_drm(dbg->wdata, (__u8) i);
+	if (i != WIIPROTO_REQ_NULL)
+		dbg->wdata->state.flags |= WIIPROTO_FLAG_DRM_LOCKED;
+	spin_unlock_irqrestore(&dbg->wdata->state.lock, flags);
+
+	return len;
+}
+
+static const struct file_operations wiidebug_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = wiidebug_drm_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.write = wiidebug_drm_write,
+	.release = single_release,
+};
+
+int wiidebug_init(struct wiimote_data *wdata)
+{
+	struct wiimote_debug *dbg;
+	unsigned long flags;
+	int ret = -ENOMEM;
+
+	dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
+	if (!dbg)
+		return -ENOMEM;
+
+	dbg->wdata = wdata;
+
+	dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR,
+		dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops);
+	if (!dbg->eeprom)
+		goto err;
+
+	dbg->drm = debugfs_create_file("drm", S_IRUSR,
+			dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops);
+	if (!dbg->drm)
+		goto err_drm;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->debug = dbg;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+
+err_drm:
+	debugfs_remove(dbg->eeprom);
+err:
+	kfree(dbg);
+	return ret;
+}
+
+void wiidebug_deinit(struct wiimote_data *wdata)
+{
+	struct wiimote_debug *dbg = wdata->debug;
+	unsigned long flags;
+
+	if (!dbg)
+		return;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->debug = NULL;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	debugfs_remove(dbg->drm);
+	debugfs_remove(dbg->eeprom);
+	kfree(dbg);
+}
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c
new file mode 100644
index 0000000..aa72eb9
--- /dev/null
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -0,0 +1,2644 @@
+/*
+ * Device Modules for Nintendo Wii / Wii U HID Driver
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+/*
+ * Wiimote Modules
+ * Nintendo devices provide different peripherals and many new devices lack
+ * initial features like the IR camera. Therefore, each peripheral device is
+ * implemented as an independent module and we probe on each device only the
+ * modules for the hardware that really is available.
+ *
+ * Module registration is sequential. Unregistration is done in reverse order.
+ * After device detection, the needed modules are loaded. Users can trigger
+ * re-detection which causes all modules to be unloaded and then reload the
+ * modules for the new detected device.
+ *
+ * wdata->input is a shared input device. It is always initialized prior to
+ * module registration. If at least one registered module is marked as
+ * WIIMOD_FLAG_INPUT, then the input device will get registered after all
+ * modules were registered.
+ * Please note that it is unregistered _before_ the "remove" callbacks are
+ * called. This guarantees that no input interaction is done, anymore. However,
+ * the wiimote core keeps a reference to the input device so it is freed only
+ * after all modules were removed. It is safe to send events to unregistered
+ * input devices.
+ */
+
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/spinlock.h>
+#include "hid-wiimote.h"
+
+/*
+ * Keys
+ * The initial Wii Remote provided a bunch of buttons that are reported as
+ * part of the core protocol. Many later devices dropped these and report
+ * invalid data in the core button reports. Load this only on devices which
+ * correctly send button reports.
+ * It uses the shared input device.
+ */
+
+static const __u16 wiimod_keys_map[] = {
+	KEY_LEFT,	/* WIIPROTO_KEY_LEFT */
+	KEY_RIGHT,	/* WIIPROTO_KEY_RIGHT */
+	KEY_UP,		/* WIIPROTO_KEY_UP */
+	KEY_DOWN,	/* WIIPROTO_KEY_DOWN */
+	KEY_NEXT,	/* WIIPROTO_KEY_PLUS */
+	KEY_PREVIOUS,	/* WIIPROTO_KEY_MINUS */
+	BTN_1,		/* WIIPROTO_KEY_ONE */
+	BTN_2,		/* WIIPROTO_KEY_TWO */
+	BTN_A,		/* WIIPROTO_KEY_A */
+	BTN_B,		/* WIIPROTO_KEY_B */
+	BTN_MODE,	/* WIIPROTO_KEY_HOME */
+};
+
+static void wiimod_keys_in_keys(struct wiimote_data *wdata, const __u8 *keys)
+{
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_LEFT],
+							!!(keys[0] & 0x01));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_RIGHT],
+							!!(keys[0] & 0x02));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_DOWN],
+							!!(keys[0] & 0x04));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_UP],
+							!!(keys[0] & 0x08));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_PLUS],
+							!!(keys[0] & 0x10));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_TWO],
+							!!(keys[1] & 0x01));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_ONE],
+							!!(keys[1] & 0x02));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_B],
+							!!(keys[1] & 0x04));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_A],
+							!!(keys[1] & 0x08));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_MINUS],
+							!!(keys[1] & 0x10));
+	input_report_key(wdata->input, wiimod_keys_map[WIIPROTO_KEY_HOME],
+							!!(keys[1] & 0x80));
+	input_sync(wdata->input);
+}
+
+static int wiimod_keys_probe(const struct wiimod_ops *ops,
+			     struct wiimote_data *wdata)
+{
+	unsigned int i;
+
+	set_bit(EV_KEY, wdata->input->evbit);
+	for (i = 0; i < WIIPROTO_KEY_COUNT; ++i)
+		set_bit(wiimod_keys_map[i], wdata->input->keybit);
+
+	return 0;
+}
+
+static const struct wiimod_ops wiimod_keys = {
+	.flags = WIIMOD_FLAG_INPUT,
+	.arg = 0,
+	.probe = wiimod_keys_probe,
+	.remove = NULL,
+	.in_keys = wiimod_keys_in_keys,
+};
+
+/*
+ * Rumble
+ * Nearly all devices provide a rumble feature. A small motor for
+ * force-feedback effects. We provide an FF_RUMBLE memless ff device on the
+ * shared input device if this module is loaded.
+ * The rumble motor is controlled via a flag on almost every output report so
+ * the wiimote core handles the rumble flag. But if a device doesn't provide
+ * the rumble motor, this flag shouldn't be set.
+ */
+
+/* used by wiimod_rumble and wiipro_rumble */
+static void wiimod_rumble_worker(struct work_struct *work)
+{
+	struct wiimote_data *wdata = container_of(work, struct wiimote_data,
+						  rumble_worker);
+
+	spin_lock_irq(&wdata->state.lock);
+	wiiproto_req_rumble(wdata, wdata->state.cache_rumble);
+	spin_unlock_irq(&wdata->state.lock);
+}
+
+static int wiimod_rumble_play(struct input_dev *dev, void *data,
+			      struct ff_effect *eff)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	__u8 value;
+
+	/*
+	 * The wiimote supports only a single rumble motor so if any magnitude
+	 * is set to non-zero then we start the rumble motor. If both are set to
+	 * zero, we stop the rumble motor.
+	 */
+
+	if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
+		value = 1;
+	else
+		value = 0;
+
+	/* Locking state.lock here might deadlock with input_event() calls.
+	 * schedule_work acts as barrier. Merging multiple changes is fine. */
+	wdata->state.cache_rumble = value;
+	schedule_work(&wdata->rumble_worker);
+
+	return 0;
+}
+
+static int wiimod_rumble_probe(const struct wiimod_ops *ops,
+			       struct wiimote_data *wdata)
+{
+	INIT_WORK(&wdata->rumble_worker, wiimod_rumble_worker);
+
+	set_bit(FF_RUMBLE, wdata->input->ffbit);
+	if (input_ff_create_memless(wdata->input, NULL, wiimod_rumble_play))
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void wiimod_rumble_remove(const struct wiimod_ops *ops,
+				 struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	cancel_work_sync(&wdata->rumble_worker);
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiiproto_req_rumble(wdata, 0);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static const struct wiimod_ops wiimod_rumble = {
+	.flags = WIIMOD_FLAG_INPUT,
+	.arg = 0,
+	.probe = wiimod_rumble_probe,
+	.remove = wiimod_rumble_remove,
+};
+
+/*
+ * Battery
+ * 1 byte of battery capacity information is sent along every protocol status
+ * report. The wiimote core caches it but we try to update it on every
+ * user-space request.
+ * This is supported by nearly every device so it's almost always enabled.
+ */
+
+static enum power_supply_property wiimod_battery_props[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_SCOPE,
+};
+
+static int wiimod_battery_get_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       union power_supply_propval *val)
+{
+	struct wiimote_data *wdata = power_supply_get_drvdata(psy);
+	int ret = 0, state;
+	unsigned long flags;
+
+	if (psp == POWER_SUPPLY_PROP_SCOPE) {
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		return 0;
+	} else if (psp != POWER_SUPPLY_PROP_CAPACITY) {
+		return -EINVAL;
+	}
+
+	ret = wiimote_cmd_acquire(wdata);
+	if (ret)
+		return ret;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_SREQ, 0);
+	wiiproto_req_status(wdata);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	wiimote_cmd_wait(wdata);
+	wiimote_cmd_release(wdata);
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	state = wdata->state.cmd_battery;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	val->intval = state * 100 / 255;
+	return ret;
+}
+
+static int wiimod_battery_probe(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	struct power_supply_config psy_cfg = { .drv_data = wdata, };
+	int ret;
+
+	wdata->battery_desc.properties = wiimod_battery_props;
+	wdata->battery_desc.num_properties = ARRAY_SIZE(wiimod_battery_props);
+	wdata->battery_desc.get_property = wiimod_battery_get_property;
+	wdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+	wdata->battery_desc.use_for_apm = 0;
+	wdata->battery_desc.name = kasprintf(GFP_KERNEL, "wiimote_battery_%s",
+					     wdata->hdev->uniq);
+	if (!wdata->battery_desc.name)
+		return -ENOMEM;
+
+	wdata->battery = power_supply_register(&wdata->hdev->dev,
+					       &wdata->battery_desc,
+					       &psy_cfg);
+	if (IS_ERR(wdata->battery)) {
+		hid_err(wdata->hdev, "cannot register battery device\n");
+		ret = PTR_ERR(wdata->battery);
+		goto err_free;
+	}
+
+	power_supply_powers(wdata->battery, &wdata->hdev->dev);
+	return 0;
+
+err_free:
+	kfree(wdata->battery_desc.name);
+	wdata->battery_desc.name = NULL;
+	return ret;
+}
+
+static void wiimod_battery_remove(const struct wiimod_ops *ops,
+				  struct wiimote_data *wdata)
+{
+	if (!wdata->battery_desc.name)
+		return;
+
+	power_supply_unregister(wdata->battery);
+	kfree(wdata->battery_desc.name);
+	wdata->battery_desc.name = NULL;
+}
+
+static const struct wiimod_ops wiimod_battery = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_battery_probe,
+	.remove = wiimod_battery_remove,
+};
+
+/*
+ * LED
+ * 0 to 4 player LEDs are supported by devices. The "arg" field of the
+ * wiimod_ops structure specifies which LED this module controls. This allows
+ * to register a limited number of LEDs.
+ * State is managed by wiimote core.
+ */
+
+static enum led_brightness wiimod_led_get(struct led_classdev *led_dev)
+{
+	struct device *dev = led_dev->dev->parent;
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	int i;
+	unsigned long flags;
+	bool value = false;
+
+	for (i = 0; i < 4; ++i) {
+		if (wdata->leds[i] == led_dev) {
+			spin_lock_irqsave(&wdata->state.lock, flags);
+			value = wdata->state.flags & WIIPROTO_FLAG_LED(i + 1);
+			spin_unlock_irqrestore(&wdata->state.lock, flags);
+			break;
+		}
+	}
+
+	return value ? LED_FULL : LED_OFF;
+}
+
+static void wiimod_led_set(struct led_classdev *led_dev,
+			   enum led_brightness value)
+{
+	struct device *dev = led_dev->dev->parent;
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	int i;
+	unsigned long flags;
+	__u8 state, flag;
+
+	for (i = 0; i < 4; ++i) {
+		if (wdata->leds[i] == led_dev) {
+			flag = WIIPROTO_FLAG_LED(i + 1);
+			spin_lock_irqsave(&wdata->state.lock, flags);
+			state = wdata->state.flags;
+			if (value == LED_OFF)
+				wiiproto_req_leds(wdata, state & ~flag);
+			else
+				wiiproto_req_leds(wdata, state | flag);
+			spin_unlock_irqrestore(&wdata->state.lock, flags);
+			break;
+		}
+	}
+}
+
+static int wiimod_led_probe(const struct wiimod_ops *ops,
+			    struct wiimote_data *wdata)
+{
+	struct device *dev = &wdata->hdev->dev;
+	size_t namesz = strlen(dev_name(dev)) + 9;
+	struct led_classdev *led;
+	unsigned long flags;
+	char *name;
+	int ret;
+
+	led = kzalloc(sizeof(struct led_classdev) + namesz, GFP_KERNEL);
+	if (!led)
+		return -ENOMEM;
+
+	name = (void*)&led[1];
+	snprintf(name, namesz, "%s:blue:p%lu", dev_name(dev), ops->arg);
+	led->name = name;
+	led->brightness = 0;
+	led->max_brightness = 1;
+	led->brightness_get = wiimod_led_get;
+	led->brightness_set = wiimod_led_set;
+
+	wdata->leds[ops->arg] = led;
+	ret = led_classdev_register(dev, led);
+	if (ret)
+		goto err_free;
+
+	/* enable LED1 to stop initial LED-blinking */
+	if (ops->arg == 0) {
+		spin_lock_irqsave(&wdata->state.lock, flags);
+		wiiproto_req_leds(wdata, WIIPROTO_FLAG_LED1);
+		spin_unlock_irqrestore(&wdata->state.lock, flags);
+	}
+
+	return 0;
+
+err_free:
+	wdata->leds[ops->arg] = NULL;
+	kfree(led);
+	return ret;
+}
+
+static void wiimod_led_remove(const struct wiimod_ops *ops,
+			      struct wiimote_data *wdata)
+{
+	if (!wdata->leds[ops->arg])
+		return;
+
+	led_classdev_unregister(wdata->leds[ops->arg]);
+	kfree(wdata->leds[ops->arg]);
+	wdata->leds[ops->arg] = NULL;
+}
+
+static const struct wiimod_ops wiimod_leds[4] = {
+	{
+		.flags = 0,
+		.arg = 0,
+		.probe = wiimod_led_probe,
+		.remove = wiimod_led_remove,
+	},
+	{
+		.flags = 0,
+		.arg = 1,
+		.probe = wiimod_led_probe,
+		.remove = wiimod_led_remove,
+	},
+	{
+		.flags = 0,
+		.arg = 2,
+		.probe = wiimod_led_probe,
+		.remove = wiimod_led_remove,
+	},
+	{
+		.flags = 0,
+		.arg = 3,
+		.probe = wiimod_led_probe,
+		.remove = wiimod_led_remove,
+	},
+};
+
+/*
+ * Accelerometer
+ * 3 axis accelerometer data is part of nearly all DRMs. If not supported by a
+ * device, it's mostly cleared to 0. This module parses this data and provides
+ * it via a separate input device.
+ */
+
+static void wiimod_accel_in_accel(struct wiimote_data *wdata,
+				  const __u8 *accel)
+{
+	__u16 x, y, z;
+
+	if (!(wdata->state.flags & WIIPROTO_FLAG_ACCEL))
+		return;
+
+	/*
+	 * payload is: BB BB XX YY ZZ
+	 * Accelerometer data is encoded into 3 10bit values. XX, YY and ZZ
+	 * contain the upper 8 bits of each value. The lower 2 bits are
+	 * contained in the buttons data BB BB.
+	 * Bits 6 and 7 of the first buttons byte BB is the lower 2 bits of the
+	 * X accel value. Bit 5 of the second buttons byte is the 2nd bit of Y
+	 * accel value and bit 6 is the second bit of the Z value.
+	 * The first bit of Y and Z values is not available and always set to 0.
+	 * 0x200 is returned on no movement.
+	 */
+
+	x = accel[2] << 2;
+	y = accel[3] << 2;
+	z = accel[4] << 2;
+
+	x |= (accel[0] >> 5) & 0x3;
+	y |= (accel[1] >> 4) & 0x2;
+	z |= (accel[1] >> 5) & 0x2;
+
+	input_report_abs(wdata->accel, ABS_RX, x - 0x200);
+	input_report_abs(wdata->accel, ABS_RY, y - 0x200);
+	input_report_abs(wdata->accel, ABS_RZ, z - 0x200);
+	input_sync(wdata->accel);
+}
+
+static int wiimod_accel_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiiproto_req_accel(wdata, true);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_accel_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiiproto_req_accel(wdata, false);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_accel_probe(const struct wiimod_ops *ops,
+			      struct wiimote_data *wdata)
+{
+	int ret;
+
+	wdata->accel = input_allocate_device();
+	if (!wdata->accel)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->accel, wdata);
+	wdata->accel->open = wiimod_accel_open;
+	wdata->accel->close = wiimod_accel_close;
+	wdata->accel->dev.parent = &wdata->hdev->dev;
+	wdata->accel->id.bustype = wdata->hdev->bus;
+	wdata->accel->id.vendor = wdata->hdev->vendor;
+	wdata->accel->id.product = wdata->hdev->product;
+	wdata->accel->id.version = wdata->hdev->version;
+	wdata->accel->name = WIIMOTE_NAME " Accelerometer";
+
+	set_bit(EV_ABS, wdata->accel->evbit);
+	set_bit(ABS_RX, wdata->accel->absbit);
+	set_bit(ABS_RY, wdata->accel->absbit);
+	set_bit(ABS_RZ, wdata->accel->absbit);
+	input_set_abs_params(wdata->accel, ABS_RX, -500, 500, 2, 4);
+	input_set_abs_params(wdata->accel, ABS_RY, -500, 500, 2, 4);
+	input_set_abs_params(wdata->accel, ABS_RZ, -500, 500, 2, 4);
+
+	ret = input_register_device(wdata->accel);
+	if (ret) {
+		hid_err(wdata->hdev, "cannot register input device\n");
+		goto err_free;
+	}
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->accel);
+	wdata->accel = NULL;
+	return ret;
+}
+
+static void wiimod_accel_remove(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	if (!wdata->accel)
+		return;
+
+	input_unregister_device(wdata->accel);
+	wdata->accel = NULL;
+}
+
+static const struct wiimod_ops wiimod_accel = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_accel_probe,
+	.remove = wiimod_accel_remove,
+	.in_accel = wiimod_accel_in_accel,
+};
+
+/*
+ * IR Cam
+ * Up to 4 IR sources can be tracked by a normal Wii Remote. The IR cam needs
+ * to be initialized with a fairly complex procedure and consumes a lot of
+ * power. Therefore, as long as no application uses the IR input device, it is
+ * kept offline.
+ * Nearly no other device than the normal Wii Remotes supports the IR cam so
+ * you can disable this module for these devices.
+ */
+
+static void wiimod_ir_in_ir(struct wiimote_data *wdata, const __u8 *ir,
+			    bool packed, unsigned int id)
+{
+	__u16 x, y;
+	__u8 xid, yid;
+	bool sync = false;
+
+	if (!(wdata->state.flags & WIIPROTO_FLAGS_IR))
+		return;
+
+	switch (id) {
+	case 0:
+		xid = ABS_HAT0X;
+		yid = ABS_HAT0Y;
+		break;
+	case 1:
+		xid = ABS_HAT1X;
+		yid = ABS_HAT1Y;
+		break;
+	case 2:
+		xid = ABS_HAT2X;
+		yid = ABS_HAT2Y;
+		break;
+	case 3:
+		xid = ABS_HAT3X;
+		yid = ABS_HAT3Y;
+		sync = true;
+		break;
+	default:
+		return;
+	}
+
+	/*
+	 * Basic IR data is encoded into 3 bytes. The first two bytes are the
+	 * lower 8 bit of the X/Y data, the 3rd byte contains the upper 2 bits
+	 * of both.
+	 * If data is packed, then the 3rd byte is put first and slightly
+	 * reordered. This allows to interleave packed and non-packed data to
+	 * have two IR sets in 5 bytes instead of 6.
+	 * The resulting 10bit X/Y values are passed to the ABS_HAT? input dev.
+	 */
+
+	if (packed) {
+		x = ir[1] | ((ir[0] & 0x03) << 8);
+		y = ir[2] | ((ir[0] & 0x0c) << 6);
+	} else {
+		x = ir[0] | ((ir[2] & 0x30) << 4);
+		y = ir[1] | ((ir[2] & 0xc0) << 2);
+	}
+
+	input_report_abs(wdata->ir, xid, x);
+	input_report_abs(wdata->ir, yid, y);
+
+	if (sync)
+		input_sync(wdata->ir);
+}
+
+static int wiimod_ir_change(struct wiimote_data *wdata, __u16 mode)
+{
+	int ret;
+	unsigned long flags;
+	__u8 format = 0;
+	static const __u8 data_enable[] = { 0x01 };
+	static const __u8 data_sens1[] = { 0x02, 0x00, 0x00, 0x71, 0x01,
+						0x00, 0xaa, 0x00, 0x64 };
+	static const __u8 data_sens2[] = { 0x63, 0x03 };
+	static const __u8 data_fin[] = { 0x08 };
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+
+	if (mode == (wdata->state.flags & WIIPROTO_FLAGS_IR)) {
+		spin_unlock_irqrestore(&wdata->state.lock, flags);
+		return 0;
+	}
+
+	if (mode == 0) {
+		wdata->state.flags &= ~WIIPROTO_FLAGS_IR;
+		wiiproto_req_ir1(wdata, 0);
+		wiiproto_req_ir2(wdata, 0);
+		wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+		spin_unlock_irqrestore(&wdata->state.lock, flags);
+		return 0;
+	}
+
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_acquire(wdata);
+	if (ret)
+		return ret;
+
+	/* send PIXEL CLOCK ENABLE cmd first */
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_IR1, 0);
+	wiiproto_req_ir1(wdata, 0x06);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_wait(wdata);
+	if (ret)
+		goto unlock;
+	if (wdata->state.cmd_err) {
+		ret = -EIO;
+		goto unlock;
+	}
+
+	/* enable IR LOGIC */
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiimote_cmd_set(wdata, WIIPROTO_REQ_IR2, 0);
+	wiiproto_req_ir2(wdata, 0x06);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	ret = wiimote_cmd_wait(wdata);
+	if (ret)
+		goto unlock;
+	if (wdata->state.cmd_err) {
+		ret = -EIO;
+		goto unlock;
+	}
+
+	/* enable IR cam but do not make it send data, yet */
+	ret = wiimote_cmd_write(wdata, 0xb00030, data_enable,
+							sizeof(data_enable));
+	if (ret)
+		goto unlock;
+
+	/* write first sensitivity block */
+	ret = wiimote_cmd_write(wdata, 0xb00000, data_sens1,
+							sizeof(data_sens1));
+	if (ret)
+		goto unlock;
+
+	/* write second sensitivity block */
+	ret = wiimote_cmd_write(wdata, 0xb0001a, data_sens2,
+							sizeof(data_sens2));
+	if (ret)
+		goto unlock;
+
+	/* put IR cam into desired state */
+	switch (mode) {
+		case WIIPROTO_FLAG_IR_FULL:
+			format = 5;
+			break;
+		case WIIPROTO_FLAG_IR_EXT:
+			format = 3;
+			break;
+		case WIIPROTO_FLAG_IR_BASIC:
+			format = 1;
+			break;
+	}
+	ret = wiimote_cmd_write(wdata, 0xb00033, &format, sizeof(format));
+	if (ret)
+		goto unlock;
+
+	/* make IR cam send data */
+	ret = wiimote_cmd_write(wdata, 0xb00030, data_fin, sizeof(data_fin));
+	if (ret)
+		goto unlock;
+
+	/* request new DRM mode compatible to IR mode */
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAGS_IR;
+	wdata->state.flags |= mode & WIIPROTO_FLAGS_IR;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+unlock:
+	wiimote_cmd_release(wdata);
+	return ret;
+}
+
+static int wiimod_ir_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+
+	return wiimod_ir_change(wdata, WIIPROTO_FLAG_IR_BASIC);
+}
+
+static void wiimod_ir_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+
+	wiimod_ir_change(wdata, 0);
+}
+
+static int wiimod_ir_probe(const struct wiimod_ops *ops,
+			   struct wiimote_data *wdata)
+{
+	int ret;
+
+	wdata->ir = input_allocate_device();
+	if (!wdata->ir)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->ir, wdata);
+	wdata->ir->open = wiimod_ir_open;
+	wdata->ir->close = wiimod_ir_close;
+	wdata->ir->dev.parent = &wdata->hdev->dev;
+	wdata->ir->id.bustype = wdata->hdev->bus;
+	wdata->ir->id.vendor = wdata->hdev->vendor;
+	wdata->ir->id.product = wdata->hdev->product;
+	wdata->ir->id.version = wdata->hdev->version;
+	wdata->ir->name = WIIMOTE_NAME " IR";
+
+	set_bit(EV_ABS, wdata->ir->evbit);
+	set_bit(ABS_HAT0X, wdata->ir->absbit);
+	set_bit(ABS_HAT0Y, wdata->ir->absbit);
+	set_bit(ABS_HAT1X, wdata->ir->absbit);
+	set_bit(ABS_HAT1Y, wdata->ir->absbit);
+	set_bit(ABS_HAT2X, wdata->ir->absbit);
+	set_bit(ABS_HAT2Y, wdata->ir->absbit);
+	set_bit(ABS_HAT3X, wdata->ir->absbit);
+	set_bit(ABS_HAT3Y, wdata->ir->absbit);
+	input_set_abs_params(wdata->ir, ABS_HAT0X, 0, 1023, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT0Y, 0, 767, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT1X, 0, 1023, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT1Y, 0, 767, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT2X, 0, 1023, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT2Y, 0, 767, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT3X, 0, 1023, 2, 4);
+	input_set_abs_params(wdata->ir, ABS_HAT3Y, 0, 767, 2, 4);
+
+	ret = input_register_device(wdata->ir);
+	if (ret) {
+		hid_err(wdata->hdev, "cannot register input device\n");
+		goto err_free;
+	}
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->ir);
+	wdata->ir = NULL;
+	return ret;
+}
+
+static void wiimod_ir_remove(const struct wiimod_ops *ops,
+			     struct wiimote_data *wdata)
+{
+	if (!wdata->ir)
+		return;
+
+	input_unregister_device(wdata->ir);
+	wdata->ir = NULL;
+}
+
+static const struct wiimod_ops wiimod_ir = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_ir_probe,
+	.remove = wiimod_ir_remove,
+	.in_ir = wiimod_ir_in_ir,
+};
+
+/*
+ * Nunchuk Extension
+ * The Nintendo Wii Nunchuk was the first official extension published by
+ * Nintendo. It provides two additional keys and a separate accelerometer. It
+ * can be hotplugged to standard Wii Remotes.
+ */
+
+enum wiimod_nunchuk_keys {
+	WIIMOD_NUNCHUK_KEY_C,
+	WIIMOD_NUNCHUK_KEY_Z,
+	WIIMOD_NUNCHUK_KEY_NUM,
+};
+
+static const __u16 wiimod_nunchuk_map[] = {
+	BTN_C,		/* WIIMOD_NUNCHUK_KEY_C */
+	BTN_Z,		/* WIIMOD_NUNCHUK_KEY_Z */
+};
+
+static void wiimod_nunchuk_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__s16 x, y, z, bx, by;
+
+	/*   Byte |   8    7 |  6    5 |  4    3 |  2 |  1  |
+	 *   -----+----------+---------+---------+----+-----+
+	 *    1   |              Button X <7:0>             |
+	 *    2   |              Button Y <7:0>             |
+	 *   -----+----------+---------+---------+----+-----+
+	 *    3   |               Speed X <9:2>             |
+	 *    4   |               Speed Y <9:2>             |
+	 *    5   |               Speed Z <9:2>             |
+	 *   -----+----------+---------+---------+----+-----+
+	 *    6   | Z <1:0>  | Y <1:0> | X <1:0> | BC | BZ  |
+	 *   -----+----------+---------+---------+----+-----+
+	 * Button X/Y is the analog stick. Speed X, Y and Z are the
+	 * accelerometer data in the same format as the wiimote's accelerometer.
+	 * The 6th byte contains the LSBs of the accelerometer data.
+	 * BC and BZ are the C and Z buttons: 0 means pressed
+	 *
+	 * If reported interleaved with motionp, then the layout changes. The
+	 * 5th and 6th byte changes to:
+	 *   -----+-----------------------------------+-----+
+	 *    5   |            Speed Z <9:3>          | EXT |
+	 *   -----+--------+-----+-----+----+----+----+-----+
+	 *    6   |Z <2:1> |Y <1>|X <1>| BC | BZ | 0  |  0  |
+	 *   -----+--------+-----+-----+----+----+----+-----+
+	 * All three accelerometer values lose their LSB. The other data is
+	 * still available but slightly moved.
+	 *
+	 * Center data for button values is 128. Center value for accelerometer
+	 * values it 512 / 0x200
+	 */
+
+	bx = ext[0];
+	by = ext[1];
+	bx -= 128;
+	by -= 128;
+
+	x = ext[2] << 2;
+	y = ext[3] << 2;
+	z = ext[4] << 2;
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		x |= (ext[5] >> 3) & 0x02;
+		y |= (ext[5] >> 4) & 0x02;
+		z &= ~0x4;
+		z |= (ext[5] >> 5) & 0x06;
+	} else {
+		x |= (ext[5] >> 2) & 0x03;
+		y |= (ext[5] >> 4) & 0x03;
+		z |= (ext[5] >> 6) & 0x03;
+	}
+
+	x -= 0x200;
+	y -= 0x200;
+	z -= 0x200;
+
+	input_report_abs(wdata->extension.input, ABS_HAT0X, bx);
+	input_report_abs(wdata->extension.input, ABS_HAT0Y, by);
+
+	input_report_abs(wdata->extension.input, ABS_RX, x);
+	input_report_abs(wdata->extension.input, ABS_RY, y);
+	input_report_abs(wdata->extension.input, ABS_RZ, z);
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		input_report_key(wdata->extension.input,
+			wiimod_nunchuk_map[WIIMOD_NUNCHUK_KEY_Z],
+			!(ext[5] & 0x04));
+		input_report_key(wdata->extension.input,
+			wiimod_nunchuk_map[WIIMOD_NUNCHUK_KEY_C],
+			!(ext[5] & 0x08));
+	} else {
+		input_report_key(wdata->extension.input,
+			wiimod_nunchuk_map[WIIMOD_NUNCHUK_KEY_Z],
+			!(ext[5] & 0x01));
+		input_report_key(wdata->extension.input,
+			wiimod_nunchuk_map[WIIMOD_NUNCHUK_KEY_C],
+			!(ext[5] & 0x02));
+	}
+
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_nunchuk_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_nunchuk_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_nunchuk_probe(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	int ret, i;
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->extension.input, wdata);
+	wdata->extension.input->open = wiimod_nunchuk_open;
+	wdata->extension.input->close = wiimod_nunchuk_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Nunchuk";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	for (i = 0; i < WIIMOD_NUNCHUK_KEY_NUM; ++i)
+		set_bit(wiimod_nunchuk_map[i],
+			wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_HAT0X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT0Y, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0X, -120, 120, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0Y, -120, 120, 2, 4);
+	set_bit(ABS_RX, wdata->extension.input->absbit);
+	set_bit(ABS_RY, wdata->extension.input->absbit);
+	set_bit(ABS_RZ, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_RX, -500, 500, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_RY, -500, 500, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_RZ, -500, 500, 2, 4);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_nunchuk_remove(const struct wiimod_ops *ops,
+				  struct wiimote_data *wdata)
+{
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+}
+
+static const struct wiimod_ops wiimod_nunchuk = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_nunchuk_probe,
+	.remove = wiimod_nunchuk_remove,
+	.in_ext = wiimod_nunchuk_in_ext,
+};
+
+/*
+ * Classic Controller
+ * Another official extension from Nintendo. It provides a classic
+ * gamecube-like controller that can be hotplugged on the Wii Remote.
+ * It has several hardware buttons and switches that are all reported via
+ * a normal extension device.
+ */
+
+enum wiimod_classic_keys {
+	WIIMOD_CLASSIC_KEY_A,
+	WIIMOD_CLASSIC_KEY_B,
+	WIIMOD_CLASSIC_KEY_X,
+	WIIMOD_CLASSIC_KEY_Y,
+	WIIMOD_CLASSIC_KEY_ZL,
+	WIIMOD_CLASSIC_KEY_ZR,
+	WIIMOD_CLASSIC_KEY_PLUS,
+	WIIMOD_CLASSIC_KEY_MINUS,
+	WIIMOD_CLASSIC_KEY_HOME,
+	WIIMOD_CLASSIC_KEY_LEFT,
+	WIIMOD_CLASSIC_KEY_RIGHT,
+	WIIMOD_CLASSIC_KEY_UP,
+	WIIMOD_CLASSIC_KEY_DOWN,
+	WIIMOD_CLASSIC_KEY_LT,
+	WIIMOD_CLASSIC_KEY_RT,
+	WIIMOD_CLASSIC_KEY_NUM,
+};
+
+static const __u16 wiimod_classic_map[] = {
+	BTN_A,		/* WIIMOD_CLASSIC_KEY_A */
+	BTN_B,		/* WIIMOD_CLASSIC_KEY_B */
+	BTN_X,		/* WIIMOD_CLASSIC_KEY_X */
+	BTN_Y,		/* WIIMOD_CLASSIC_KEY_Y */
+	BTN_TL2,	/* WIIMOD_CLASSIC_KEY_ZL */
+	BTN_TR2,	/* WIIMOD_CLASSIC_KEY_ZR */
+	KEY_NEXT,	/* WIIMOD_CLASSIC_KEY_PLUS */
+	KEY_PREVIOUS,	/* WIIMOD_CLASSIC_KEY_MINUS */
+	BTN_MODE,	/* WIIMOD_CLASSIC_KEY_HOME */
+	KEY_LEFT,	/* WIIMOD_CLASSIC_KEY_LEFT */
+	KEY_RIGHT,	/* WIIMOD_CLASSIC_KEY_RIGHT */
+	KEY_UP,		/* WIIMOD_CLASSIC_KEY_UP */
+	KEY_DOWN,	/* WIIMOD_CLASSIC_KEY_DOWN */
+	BTN_TL,		/* WIIMOD_CLASSIC_KEY_LT */
+	BTN_TR,		/* WIIMOD_CLASSIC_KEY_RT */
+};
+
+static void wiimod_classic_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__s8 rx, ry, lx, ly, lt, rt;
+
+	/*   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   | RX <5:4>  |              LX <5:0>             |
+	 *    2   | RX <3:2>  |              LY <5:0>             |
+	 *   -----+-----+-----+-----+-----------------------------+
+	 *    3   |RX<1>| LT <5:4>  |         RY <5:1>            |
+	 *   -----+-----+-----------+-----------------------------+
+	 *    4   |     LT <3:1>    |         RT <5:1>            |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   | BDR | BDD | BLT | B-  | BH  | B+  | BRT |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   | BZL | BB  | BY  | BA  | BX  | BZR | BDL | BDU |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 * All buttons are 0 if pressed
+	 * RX and RY are right analog stick
+	 * LX and LY are left analog stick
+	 * LT is left trigger, RT is right trigger
+	 * BLT is 0 if left trigger is fully pressed
+	 * BRT is 0 if right trigger is fully pressed
+	 * BDR, BDD, BDL, BDU form the D-Pad with right, down, left, up buttons
+	 * BZL is left Z button and BZR is right Z button
+	 * B-, BH, B+ are +, HOME and - buttons
+	 * BB, BY, BA, BX are A, B, X, Y buttons
+	 * LSB of RX, RY, LT, and RT are not transmitted and always 0.
+	 *
+	 * With motionp enabled it changes slightly to this:
+	 *   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   | RX <5:4>  |          LX <5:1>           | BDU |
+	 *    2   | RX <3:2>  |          LY <5:1>           | BDL |
+	 *   -----+-----+-----+-----+-----------------------+-----+
+	 *    3   |RX<1>| LT <5:4>  |         RY <5:1>            |
+	 *   -----+-----+-----------+-----------------------------+
+	 *    4   |     LT <3:1>    |         RT <5:1>            |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   | BDR | BDD | BLT | B-  | BH  | B+  | BRT | EXT |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   | BZL | BB  | BY  | BA  | BX  | BZR |  0  |  0  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 * Only the LSBs of LX and LY are lost. BDU and BDL are moved, the rest
+	 * is the same as before.
+	 */
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		lx = ext[0] & 0x3e;
+		ly = ext[1] & 0x3e;
+	} else {
+		lx = ext[0] & 0x3f;
+		ly = ext[1] & 0x3f;
+	}
+
+	rx = (ext[0] >> 3) & 0x18;
+	rx |= (ext[1] >> 5) & 0x06;
+	rx |= (ext[2] >> 7) & 0x01;
+	ry = ext[2] & 0x1f;
+
+	rt = ext[3] & 0x1f;
+	lt = (ext[2] >> 2) & 0x18;
+	lt |= (ext[3] >> 5) & 0x07;
+
+	rx <<= 1;
+	ry <<= 1;
+	rt <<= 1;
+	lt <<= 1;
+
+	input_report_abs(wdata->extension.input, ABS_HAT1X, lx - 0x20);
+	input_report_abs(wdata->extension.input, ABS_HAT1Y, ly - 0x20);
+	input_report_abs(wdata->extension.input, ABS_HAT2X, rx - 0x20);
+	input_report_abs(wdata->extension.input, ABS_HAT2Y, ry - 0x20);
+	input_report_abs(wdata->extension.input, ABS_HAT3X, rt);
+	input_report_abs(wdata->extension.input, ABS_HAT3Y, lt);
+
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_RIGHT],
+			 !(ext[4] & 0x80));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_DOWN],
+			 !(ext[4] & 0x40));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_LT],
+			 !(ext[4] & 0x20));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_MINUS],
+			 !(ext[4] & 0x10));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_HOME],
+			 !(ext[4] & 0x08));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_PLUS],
+			 !(ext[4] & 0x04));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_RT],
+			 !(ext[4] & 0x02));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_ZL],
+			 !(ext[5] & 0x80));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_B],
+			 !(ext[5] & 0x40));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_Y],
+			 !(ext[5] & 0x20));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_A],
+			 !(ext[5] & 0x10));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_X],
+			 !(ext[5] & 0x08));
+	input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_ZR],
+			 !(ext[5] & 0x04));
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT],
+			 !(ext[1] & 0x01));
+		input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP],
+			 !(ext[0] & 0x01));
+	} else {
+		input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT],
+			 !(ext[5] & 0x02));
+		input_report_key(wdata->extension.input,
+			 wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP],
+			 !(ext[5] & 0x01));
+	}
+
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_classic_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_classic_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_classic_probe(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	int ret, i;
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->extension.input, wdata);
+	wdata->extension.input->open = wiimod_classic_open;
+	wdata->extension.input->close = wiimod_classic_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Classic Controller";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	for (i = 0; i < WIIMOD_CLASSIC_KEY_NUM; ++i)
+		set_bit(wiimod_classic_map[i],
+			wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_HAT1X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT1Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT2X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT2Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT3X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT3Y, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1X, -30, 30, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1Y, -30, 30, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT2X, -30, 30, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT2Y, -30, 30, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT3X, -30, 30, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT3Y, -30, 30, 1, 1);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_classic_remove(const struct wiimod_ops *ops,
+				  struct wiimote_data *wdata)
+{
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+}
+
+static const struct wiimod_ops wiimod_classic = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_classic_probe,
+	.remove = wiimod_classic_remove,
+	.in_ext = wiimod_classic_in_ext,
+};
+
+/*
+ * Balance Board Extension
+ * The Nintendo Wii Balance Board provides four hardware weight sensor plus a
+ * single push button. No other peripherals are available. However, the
+ * balance-board data is sent via a standard Wii Remote extension. All other
+ * data for non-present hardware is zeroed out.
+ * Some 3rd party devices react allergic if we try to access normal Wii Remote
+ * hardware, so this extension module should be the only module that is loaded
+ * on balance boards.
+ * The balance board needs 8 bytes extension data instead of basic 6 bytes so
+ * it needs the WIIMOD_FLAG_EXT8 flag.
+ */
+
+static void wiimod_bboard_in_keys(struct wiimote_data *wdata, const __u8 *keys)
+{
+	input_report_key(wdata->extension.input, BTN_A,
+			 !!(keys[1] & 0x08));
+	input_sync(wdata->extension.input);
+}
+
+static void wiimod_bboard_in_ext(struct wiimote_data *wdata,
+				 const __u8 *ext)
+{
+	__s32 val[4], tmp, div;
+	unsigned int i;
+	struct wiimote_state *s = &wdata->state;
+
+	/*
+	 * Balance board data layout:
+	 *
+	 *   Byte |  8  7  6  5  4  3  2  1  |
+	 *   -----+--------------------------+
+	 *    1   |    Top Right <15:8>      |
+	 *    2   |    Top Right  <7:0>      |
+	 *   -----+--------------------------+
+	 *    3   | Bottom Right <15:8>      |
+	 *    4   | Bottom Right  <7:0>      |
+	 *   -----+--------------------------+
+	 *    5   |     Top Left <15:8>      |
+	 *    6   |     Top Left  <7:0>      |
+	 *   -----+--------------------------+
+	 *    7   |  Bottom Left <15:8>      |
+	 *    8   |  Bottom Left  <7:0>      |
+	 *   -----+--------------------------+
+	 *
+	 * These values represent the weight-measurements of the Wii-balance
+	 * board with 16bit precision.
+	 *
+	 * The balance-board is never reported interleaved with motionp.
+	 */
+
+	val[0] = ext[0];
+	val[0] <<= 8;
+	val[0] |= ext[1];
+
+	val[1] = ext[2];
+	val[1] <<= 8;
+	val[1] |= ext[3];
+
+	val[2] = ext[4];
+	val[2] <<= 8;
+	val[2] |= ext[5];
+
+	val[3] = ext[6];
+	val[3] <<= 8;
+	val[3] |= ext[7];
+
+	/* apply calibration data */
+	for (i = 0; i < 4; i++) {
+		if (val[i] <= s->calib_bboard[i][0]) {
+			tmp = 0;
+		} else if (val[i] < s->calib_bboard[i][1]) {
+			tmp = val[i] - s->calib_bboard[i][0];
+			tmp *= 1700;
+			div = s->calib_bboard[i][1] - s->calib_bboard[i][0];
+			tmp /= div ? div : 1;
+		} else {
+			tmp = val[i] - s->calib_bboard[i][1];
+			tmp *= 1700;
+			div = s->calib_bboard[i][2] - s->calib_bboard[i][1];
+			tmp /= div ? div : 1;
+			tmp += 1700;
+		}
+		val[i] = tmp;
+	}
+
+	input_report_abs(wdata->extension.input, ABS_HAT0X, val[0]);
+	input_report_abs(wdata->extension.input, ABS_HAT0Y, val[1]);
+	input_report_abs(wdata->extension.input, ABS_HAT1X, val[2]);
+	input_report_abs(wdata->extension.input, ABS_HAT1Y, val[3]);
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_bboard_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_bboard_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static ssize_t wiimod_bboard_calib_show(struct device *dev,
+					struct device_attribute *attr,
+					char *out)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	int i, j, ret;
+	__u16 val;
+	__u8 buf[24], offs;
+
+	ret = wiimote_cmd_acquire(wdata);
+	if (ret)
+		return ret;
+
+	ret = wiimote_cmd_read(wdata, 0xa40024, buf, 12);
+	if (ret != 12) {
+		wiimote_cmd_release(wdata);
+		return ret < 0 ? ret : -EIO;
+	}
+	ret = wiimote_cmd_read(wdata, 0xa40024 + 12, buf + 12, 12);
+	if (ret != 12) {
+		wiimote_cmd_release(wdata);
+		return ret < 0 ? ret : -EIO;
+	}
+
+	wiimote_cmd_release(wdata);
+
+	spin_lock_irq(&wdata->state.lock);
+	offs = 0;
+	for (i = 0; i < 3; ++i) {
+		for (j = 0; j < 4; ++j) {
+			wdata->state.calib_bboard[j][i] = buf[offs];
+			wdata->state.calib_bboard[j][i] <<= 8;
+			wdata->state.calib_bboard[j][i] |= buf[offs + 1];
+			offs += 2;
+		}
+	}
+	spin_unlock_irq(&wdata->state.lock);
+
+	ret = 0;
+	for (i = 0; i < 3; ++i) {
+		for (j = 0; j < 4; ++j) {
+			val = wdata->state.calib_bboard[j][i];
+			if (i == 2 && j == 3)
+				ret += sprintf(&out[ret], "%04x\n", val);
+			else
+				ret += sprintf(&out[ret], "%04x:", val);
+		}
+	}
+
+	return ret;
+}
+
+static DEVICE_ATTR(bboard_calib, S_IRUGO, wiimod_bboard_calib_show, NULL);
+
+static int wiimod_bboard_probe(const struct wiimod_ops *ops,
+			       struct wiimote_data *wdata)
+{
+	int ret, i, j;
+	__u8 buf[24], offs;
+
+	wiimote_cmd_acquire_noint(wdata);
+
+	ret = wiimote_cmd_read(wdata, 0xa40024, buf, 12);
+	if (ret != 12) {
+		wiimote_cmd_release(wdata);
+		return ret < 0 ? ret : -EIO;
+	}
+	ret = wiimote_cmd_read(wdata, 0xa40024 + 12, buf + 12, 12);
+	if (ret != 12) {
+		wiimote_cmd_release(wdata);
+		return ret < 0 ? ret : -EIO;
+	}
+
+	wiimote_cmd_release(wdata);
+
+	offs = 0;
+	for (i = 0; i < 3; ++i) {
+		for (j = 0; j < 4; ++j) {
+			wdata->state.calib_bboard[j][i] = buf[offs];
+			wdata->state.calib_bboard[j][i] <<= 8;
+			wdata->state.calib_bboard[j][i] |= buf[offs + 1];
+			offs += 2;
+		}
+	}
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	ret = device_create_file(&wdata->hdev->dev,
+				 &dev_attr_bboard_calib);
+	if (ret) {
+		hid_err(wdata->hdev, "cannot create sysfs attribute\n");
+		goto err_free;
+	}
+
+	input_set_drvdata(wdata->extension.input, wdata);
+	wdata->extension.input->open = wiimod_bboard_open;
+	wdata->extension.input->close = wiimod_bboard_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Balance Board";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	set_bit(BTN_A, wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_HAT0X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT0Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT1X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT1Y, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0X, 0, 65535, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0Y, 0, 65535, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1X, 0, 65535, 2, 4);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1Y, 0, 65535, 2, 4);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_file;
+
+	return 0;
+
+err_file:
+	device_remove_file(&wdata->hdev->dev,
+			   &dev_attr_bboard_calib);
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_bboard_remove(const struct wiimod_ops *ops,
+				 struct wiimote_data *wdata)
+{
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	device_remove_file(&wdata->hdev->dev,
+			   &dev_attr_bboard_calib);
+}
+
+static const struct wiimod_ops wiimod_bboard = {
+	.flags = WIIMOD_FLAG_EXT8,
+	.arg = 0,
+	.probe = wiimod_bboard_probe,
+	.remove = wiimod_bboard_remove,
+	.in_keys = wiimod_bboard_in_keys,
+	.in_ext = wiimod_bboard_in_ext,
+};
+
+/*
+ * Pro Controller
+ * Released with the Wii U was the Nintendo Wii U Pro Controller. It does not
+ * work together with the classic Wii, but only with the new Wii U. However, it
+ * uses the same protocol and provides a builtin "classic controller pro"
+ * extension, few standard buttons, a rumble motor, 4 LEDs and a battery.
+ * We provide all these via a standard extension device as the device doesn't
+ * feature an extension port.
+ */
+
+enum wiimod_pro_keys {
+	WIIMOD_PRO_KEY_A,
+	WIIMOD_PRO_KEY_B,
+	WIIMOD_PRO_KEY_X,
+	WIIMOD_PRO_KEY_Y,
+	WIIMOD_PRO_KEY_PLUS,
+	WIIMOD_PRO_KEY_MINUS,
+	WIIMOD_PRO_KEY_HOME,
+	WIIMOD_PRO_KEY_LEFT,
+	WIIMOD_PRO_KEY_RIGHT,
+	WIIMOD_PRO_KEY_UP,
+	WIIMOD_PRO_KEY_DOWN,
+	WIIMOD_PRO_KEY_TL,
+	WIIMOD_PRO_KEY_TR,
+	WIIMOD_PRO_KEY_ZL,
+	WIIMOD_PRO_KEY_ZR,
+	WIIMOD_PRO_KEY_THUMBL,
+	WIIMOD_PRO_KEY_THUMBR,
+	WIIMOD_PRO_KEY_NUM,
+};
+
+static const __u16 wiimod_pro_map[] = {
+	BTN_EAST,	/* WIIMOD_PRO_KEY_A */
+	BTN_SOUTH,	/* WIIMOD_PRO_KEY_B */
+	BTN_NORTH,	/* WIIMOD_PRO_KEY_X */
+	BTN_WEST,	/* WIIMOD_PRO_KEY_Y */
+	BTN_START,	/* WIIMOD_PRO_KEY_PLUS */
+	BTN_SELECT,	/* WIIMOD_PRO_KEY_MINUS */
+	BTN_MODE,	/* WIIMOD_PRO_KEY_HOME */
+	BTN_DPAD_LEFT,	/* WIIMOD_PRO_KEY_LEFT */
+	BTN_DPAD_RIGHT,	/* WIIMOD_PRO_KEY_RIGHT */
+	BTN_DPAD_UP,	/* WIIMOD_PRO_KEY_UP */
+	BTN_DPAD_DOWN,	/* WIIMOD_PRO_KEY_DOWN */
+	BTN_TL,		/* WIIMOD_PRO_KEY_TL */
+	BTN_TR,		/* WIIMOD_PRO_KEY_TR */
+	BTN_TL2,	/* WIIMOD_PRO_KEY_ZL */
+	BTN_TR2,	/* WIIMOD_PRO_KEY_ZR */
+	BTN_THUMBL,	/* WIIMOD_PRO_KEY_THUMBL */
+	BTN_THUMBR,	/* WIIMOD_PRO_KEY_THUMBR */
+};
+
+static void wiimod_pro_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__s16 rx, ry, lx, ly;
+
+	/*   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   |                   LX <7:0>                    |
+	 *   -----+-----------------------+-----------------------+
+	 *    2   |  0     0     0     0  |       LX <11:8>       |
+	 *   -----+-----------------------+-----------------------+
+	 *    3   |                   RX <7:0>                    |
+	 *   -----+-----------------------+-----------------------+
+	 *    4   |  0     0     0     0  |       RX <11:8>       |
+	 *   -----+-----------------------+-----------------------+
+	 *    5   |                   LY <7:0>                    |
+	 *   -----+-----------------------+-----------------------+
+	 *    6   |  0     0     0     0  |       LY <11:8>       |
+	 *   -----+-----------------------+-----------------------+
+	 *    7   |                   RY <7:0>                    |
+	 *   -----+-----------------------+-----------------------+
+	 *    8   |  0     0     0     0  |       RY <11:8>       |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    9   | BDR | BDD | BLT | B-  | BH  | B+  | BRT |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *   10   | BZL | BB  | BY  | BA  | BX  | BZR | BDL | BDU |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *   11   |  1  |     BATTERY     | USB |CHARG|LTHUM|RTHUM|
+	 *   -----+-----+-----------------+-----------+-----+-----+
+	 * All buttons are low-active (0 if pressed)
+	 * RX and RY are right analog stick
+	 * LX and LY are left analog stick
+	 * BLT is left trigger, BRT is right trigger.
+	 * BDR, BDD, BDL, BDU form the D-Pad with right, down, left, up buttons
+	 * BZL is left Z button and BZR is right Z button
+	 * B-, BH, B+ are +, HOME and - buttons
+	 * BB, BY, BA, BX are A, B, X, Y buttons
+	 *
+	 * Bits marked as 0/1 are unknown and never changed during tests.
+	 *
+	 * Not entirely verified:
+	 *   CHARG: 1 if uncharging, 0 if charging
+	 *   USB: 1 if not connected, 0 if connected
+	 *   BATTERY: battery capacity from 000 (empty) to 100 (full)
+	 */
+
+	lx = (ext[0] & 0xff) | ((ext[1] & 0x0f) << 8);
+	rx = (ext[2] & 0xff) | ((ext[3] & 0x0f) << 8);
+	ly = (ext[4] & 0xff) | ((ext[5] & 0x0f) << 8);
+	ry = (ext[6] & 0xff) | ((ext[7] & 0x0f) << 8);
+
+	/* zero-point offsets */
+	lx -= 0x800;
+	ly = 0x800 - ly;
+	rx -= 0x800;
+	ry = 0x800 - ry;
+
+	/* Trivial automatic calibration. We don't know any calibration data
+	 * in the EEPROM so we must use the first report to calibrate the
+	 * null-position of the analog sticks. Users can retrigger calibration
+	 * via sysfs, or set it explicitly. If data is off more than abs(500),
+	 * we skip calibration as the sticks are likely to be moved already. */
+	if (!(wdata->state.flags & WIIPROTO_FLAG_PRO_CALIB_DONE)) {
+		wdata->state.flags |= WIIPROTO_FLAG_PRO_CALIB_DONE;
+		if (abs(lx) < 500)
+			wdata->state.calib_pro_sticks[0] = -lx;
+		if (abs(ly) < 500)
+			wdata->state.calib_pro_sticks[1] = -ly;
+		if (abs(rx) < 500)
+			wdata->state.calib_pro_sticks[2] = -rx;
+		if (abs(ry) < 500)
+			wdata->state.calib_pro_sticks[3] = -ry;
+	}
+
+	/* apply calibration data */
+	lx += wdata->state.calib_pro_sticks[0];
+	ly += wdata->state.calib_pro_sticks[1];
+	rx += wdata->state.calib_pro_sticks[2];
+	ry += wdata->state.calib_pro_sticks[3];
+
+	input_report_abs(wdata->extension.input, ABS_X, lx);
+	input_report_abs(wdata->extension.input, ABS_Y, ly);
+	input_report_abs(wdata->extension.input, ABS_RX, rx);
+	input_report_abs(wdata->extension.input, ABS_RY, ry);
+
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_RIGHT],
+			 !(ext[8] & 0x80));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_DOWN],
+			 !(ext[8] & 0x40));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_TL],
+			 !(ext[8] & 0x20));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_MINUS],
+			 !(ext[8] & 0x10));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_HOME],
+			 !(ext[8] & 0x08));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_PLUS],
+			 !(ext[8] & 0x04));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_TR],
+			 !(ext[8] & 0x02));
+
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_ZL],
+			 !(ext[9] & 0x80));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_B],
+			 !(ext[9] & 0x40));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_Y],
+			 !(ext[9] & 0x20));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_A],
+			 !(ext[9] & 0x10));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_X],
+			 !(ext[9] & 0x08));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_ZR],
+			 !(ext[9] & 0x04));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_LEFT],
+			 !(ext[9] & 0x02));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_UP],
+			 !(ext[9] & 0x01));
+
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_THUMBL],
+			 !(ext[10] & 0x02));
+	input_report_key(wdata->extension.input,
+			 wiimod_pro_map[WIIMOD_PRO_KEY_THUMBR],
+			 !(ext[10] & 0x01));
+
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_pro_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_pro_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_pro_play(struct input_dev *dev, void *data,
+			   struct ff_effect *eff)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	__u8 value;
+
+	/*
+	 * The wiimote supports only a single rumble motor so if any magnitude
+	 * is set to non-zero then we start the rumble motor. If both are set to
+	 * zero, we stop the rumble motor.
+	 */
+
+	if (eff->u.rumble.strong_magnitude || eff->u.rumble.weak_magnitude)
+		value = 1;
+	else
+		value = 0;
+
+	/* Locking state.lock here might deadlock with input_event() calls.
+	 * schedule_work acts as barrier. Merging multiple changes is fine. */
+	wdata->state.cache_rumble = value;
+	schedule_work(&wdata->rumble_worker);
+
+	return 0;
+}
+
+static ssize_t wiimod_pro_calib_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *out)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	int r;
+
+	r = 0;
+	r += sprintf(&out[r], "%+06hd:", wdata->state.calib_pro_sticks[0]);
+	r += sprintf(&out[r], "%+06hd ", wdata->state.calib_pro_sticks[1]);
+	r += sprintf(&out[r], "%+06hd:", wdata->state.calib_pro_sticks[2]);
+	r += sprintf(&out[r], "%+06hd\n", wdata->state.calib_pro_sticks[3]);
+
+	return r;
+}
+
+static ssize_t wiimod_pro_calib_store(struct device *dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+	struct wiimote_data *wdata = dev_to_wii(dev);
+	int r;
+	s16 x1, y1, x2, y2;
+
+	if (!strncmp(buf, "scan\n", 5)) {
+		spin_lock_irq(&wdata->state.lock);
+		wdata->state.flags &= ~WIIPROTO_FLAG_PRO_CALIB_DONE;
+		spin_unlock_irq(&wdata->state.lock);
+	} else {
+		r = sscanf(buf, "%hd:%hd %hd:%hd", &x1, &y1, &x2, &y2);
+		if (r != 4)
+			return -EINVAL;
+
+		spin_lock_irq(&wdata->state.lock);
+		wdata->state.flags |= WIIPROTO_FLAG_PRO_CALIB_DONE;
+		spin_unlock_irq(&wdata->state.lock);
+
+		wdata->state.calib_pro_sticks[0] = x1;
+		wdata->state.calib_pro_sticks[1] = y1;
+		wdata->state.calib_pro_sticks[2] = x2;
+		wdata->state.calib_pro_sticks[3] = y2;
+	}
+
+	return strnlen(buf, PAGE_SIZE);
+}
+
+static DEVICE_ATTR(pro_calib, S_IRUGO|S_IWUSR|S_IWGRP, wiimod_pro_calib_show,
+		   wiimod_pro_calib_store);
+
+static int wiimod_pro_probe(const struct wiimod_ops *ops,
+			    struct wiimote_data *wdata)
+{
+	int ret, i;
+	unsigned long flags;
+
+	INIT_WORK(&wdata->rumble_worker, wiimod_rumble_worker);
+	wdata->state.calib_pro_sticks[0] = 0;
+	wdata->state.calib_pro_sticks[1] = 0;
+	wdata->state.calib_pro_sticks[2] = 0;
+	wdata->state.calib_pro_sticks[3] = 0;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_PRO_CALIB_DONE;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, wdata->extension.input->ffbit);
+	input_set_drvdata(wdata->extension.input, wdata);
+
+	if (input_ff_create_memless(wdata->extension.input, NULL,
+				    wiimod_pro_play)) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	ret = device_create_file(&wdata->hdev->dev,
+				 &dev_attr_pro_calib);
+	if (ret) {
+		hid_err(wdata->hdev, "cannot create sysfs attribute\n");
+		goto err_free;
+	}
+
+	wdata->extension.input->open = wiimod_pro_open;
+	wdata->extension.input->close = wiimod_pro_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Pro Controller";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	for (i = 0; i < WIIMOD_PRO_KEY_NUM; ++i)
+		set_bit(wiimod_pro_map[i],
+			wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_X, wdata->extension.input->absbit);
+	set_bit(ABS_Y, wdata->extension.input->absbit);
+	set_bit(ABS_RX, wdata->extension.input->absbit);
+	set_bit(ABS_RY, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_X, -0x400, 0x400, 4, 100);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_Y, -0x400, 0x400, 4, 100);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_RX, -0x400, 0x400, 4, 100);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_RY, -0x400, 0x400, 4, 100);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_file;
+
+	return 0;
+
+err_file:
+	device_remove_file(&wdata->hdev->dev,
+			   &dev_attr_pro_calib);
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_pro_remove(const struct wiimod_ops *ops,
+			      struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	cancel_work_sync(&wdata->rumble_worker);
+	device_remove_file(&wdata->hdev->dev,
+			   &dev_attr_pro_calib);
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wiiproto_req_rumble(wdata, 0);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static const struct wiimod_ops wiimod_pro = {
+	.flags = WIIMOD_FLAG_EXT16,
+	.arg = 0,
+	.probe = wiimod_pro_probe,
+	.remove = wiimod_pro_remove,
+	.in_ext = wiimod_pro_in_ext,
+};
+
+/*
+ * Drums
+ * Guitar-Hero, Rock-Band and other games came bundled with drums which can
+ * be plugged as extension to a Wiimote. Drum-reports are still not entirely
+ * figured out, but the most important information is known.
+ * We create a separate device for drums and report all information via this
+ * input device.
+ */
+
+static inline void wiimod_drums_report_pressure(struct wiimote_data *wdata,
+						__u8 none, __u8 which,
+						__u8 pressure, __u8 onoff,
+						__u8 *store, __u16 code,
+						__u8 which_code)
+{
+	static const __u8 default_pressure = 3;
+
+	if (!none && which == which_code) {
+		*store = pressure;
+		input_report_abs(wdata->extension.input, code, *store);
+	} else if (onoff != !!*store) {
+		*store = onoff ? default_pressure : 0;
+		input_report_abs(wdata->extension.input, code, *store);
+	}
+}
+
+static void wiimod_drums_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__u8 pressure, which, none, hhp, sx, sy;
+	__u8 o, r, y, g, b, bass, bm, bp;
+
+	/*   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   |  0  |  0  |              SX <5:0>             |
+	 *    2   |  0  |  0  |              SY <5:0>             |
+	 *   -----+-----+-----+-----------------------------+-----+
+	 *    3   | HPP | NON |         WHICH <5:1>         |  ?  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    4   |   SOFT <7:5>    |  0  |  1  |  1  |  0  |  ?  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   |  ?  |  1  |  1  | B-  |  1  | B+  |  1  |  ?  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   |  O  |  R  |  Y  |  G  |  B  | BSS |  1  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 * All buttons are 0 if pressed
+	 *
+	 * With Motion+ enabled, the following bits will get invalid:
+	 *   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   |  0  |  0  |              SX <5:1>       |XXXXX|
+	 *    2   |  0  |  0  |              SY <5:1>       |XXXXX|
+	 *   -----+-----+-----+-----------------------------+-----+
+	 *    3   | HPP | NON |         WHICH <5:1>         |  ?  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    4   |   SOFT <7:5>    |  0  |  1  |  1  |  0  |  ?  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   |  ?  |  1  |  1  | B-  |  1  | B+  |  1  |XXXXX|
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   |  O  |  R  |  Y  |  G  |  B  | BSS |XXXXX|XXXXX|
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 */
+
+	pressure = 7 - (ext[3] >> 5);
+	which = (ext[2] >> 1) & 0x1f;
+	none = !!(ext[2] & 0x40);
+	hhp = !(ext[2] & 0x80);
+	sx = ext[0] & 0x3f;
+	sy = ext[1] & 0x3f;
+	o = !(ext[5] & 0x80);
+	r = !(ext[5] & 0x40);
+	y = !(ext[5] & 0x20);
+	g = !(ext[5] & 0x10);
+	b = !(ext[5] & 0x08);
+	bass = !(ext[5] & 0x04);
+	bm = !(ext[4] & 0x10);
+	bp = !(ext[4] & 0x04);
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		sx &= 0x3e;
+		sy &= 0x3e;
+	}
+
+	wiimod_drums_report_pressure(wdata, none, which, pressure,
+				     o, &wdata->state.pressure_drums[0],
+				     ABS_HAT2Y, 0x0e);
+	wiimod_drums_report_pressure(wdata, none, which, pressure,
+				     r, &wdata->state.pressure_drums[1],
+				     ABS_HAT0X, 0x19);
+	wiimod_drums_report_pressure(wdata, none, which, pressure,
+				     y, &wdata->state.pressure_drums[2],
+				     ABS_HAT2X, 0x11);
+	wiimod_drums_report_pressure(wdata, none, which, pressure,
+				     g, &wdata->state.pressure_drums[3],
+				     ABS_HAT1X, 0x12);
+	wiimod_drums_report_pressure(wdata, none, which, pressure,
+				     b, &wdata->state.pressure_drums[4],
+				     ABS_HAT0Y, 0x0f);
+
+	/* Bass shares pressure with hi-hat (set via hhp) */
+	wiimod_drums_report_pressure(wdata, none, hhp ? 0xff : which, pressure,
+				     bass, &wdata->state.pressure_drums[5],
+				     ABS_HAT3X, 0x1b);
+	/* Hi-hat has no on/off values, just pressure. Force to off/0. */
+	wiimod_drums_report_pressure(wdata, none, hhp ? which : 0xff, pressure,
+				     0, &wdata->state.pressure_drums[6],
+				     ABS_HAT3Y, 0x0e);
+
+	input_report_abs(wdata->extension.input, ABS_X, sx - 0x20);
+	input_report_abs(wdata->extension.input, ABS_Y, sy - 0x20);
+
+	input_report_key(wdata->extension.input, BTN_START, bp);
+	input_report_key(wdata->extension.input, BTN_SELECT, bm);
+
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_drums_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_drums_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_drums_probe(const struct wiimod_ops *ops,
+			      struct wiimote_data *wdata)
+{
+	int ret;
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->extension.input, wdata);
+	wdata->extension.input->open = wiimod_drums_open;
+	wdata->extension.input->close = wiimod_drums_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Drums";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	set_bit(BTN_START, wdata->extension.input->keybit);
+	set_bit(BTN_SELECT, wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_X, wdata->extension.input->absbit);
+	set_bit(ABS_Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT0X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT0Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT1X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT2X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT2Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT3X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT3Y, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_X, -32, 31, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_Y, -32, 31, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0X, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0Y, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1X, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT2X, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT2Y, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT3X, 0, 7, 0, 0);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT3Y, 0, 7, 0, 0);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_drums_remove(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+}
+
+static const struct wiimod_ops wiimod_drums = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_drums_probe,
+	.remove = wiimod_drums_remove,
+	.in_ext = wiimod_drums_in_ext,
+};
+
+/*
+ * Guitar
+ * Guitar-Hero, Rock-Band and other games came bundled with guitars which can
+ * be plugged as extension to a Wiimote.
+ * We create a separate device for guitars and report all information via this
+ * input device.
+ */
+
+enum wiimod_guitar_keys {
+	WIIMOD_GUITAR_KEY_G,
+	WIIMOD_GUITAR_KEY_R,
+	WIIMOD_GUITAR_KEY_Y,
+	WIIMOD_GUITAR_KEY_B,
+	WIIMOD_GUITAR_KEY_O,
+	WIIMOD_GUITAR_KEY_UP,
+	WIIMOD_GUITAR_KEY_DOWN,
+	WIIMOD_GUITAR_KEY_PLUS,
+	WIIMOD_GUITAR_KEY_MINUS,
+	WIIMOD_GUITAR_KEY_NUM,
+};
+
+static const __u16 wiimod_guitar_map[] = {
+	BTN_1,			/* WIIMOD_GUITAR_KEY_G */
+	BTN_2,			/* WIIMOD_GUITAR_KEY_R */
+	BTN_3,			/* WIIMOD_GUITAR_KEY_Y */
+	BTN_4,			/* WIIMOD_GUITAR_KEY_B */
+	BTN_5,			/* WIIMOD_GUITAR_KEY_O */
+	BTN_DPAD_UP,		/* WIIMOD_GUITAR_KEY_UP */
+	BTN_DPAD_DOWN,		/* WIIMOD_GUITAR_KEY_DOWN */
+	BTN_START,		/* WIIMOD_GUITAR_KEY_PLUS */
+	BTN_SELECT,		/* WIIMOD_GUITAR_KEY_MINUS */
+};
+
+static void wiimod_guitar_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__u8 sx, sy, tb, wb, bd, bm, bp, bo, br, bb, bg, by, bu;
+
+	/*   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   |  0  |  0  |              SX <5:0>             |
+	 *    2   |  0  |  0  |              SY <5:0>             |
+	 *   -----+-----+-----+-----+-----------------------------+
+	 *    3   |  0  |  0  |  0  |      TB <4:0>               |
+	 *   -----+-----+-----+-----+-----------------------------+
+	 *    4   |  0  |  0  |  0  |      WB <4:0>               |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   |  1  | BD  |  1  | B-  |  1  | B+  |  1  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   | BO  | BR  | BB  | BG  | BY  |  1  |  1  | BU  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 * All buttons are 0 if pressed
+	 *
+	 * With Motion+ enabled, it will look like this:
+	 *   Byte |  8  |  7  |  6  |  5  |  4  |  3  |  2  |  1  |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    1   |  0  |  0  |              SX <5:1>       | BU  |
+	 *    2   |  0  |  0  |              SY <5:1>       |  1  |
+	 *   -----+-----+-----+-----+-----------------------+-----+
+	 *    3   |  0  |  0  |  0  |      TB <4:0>               |
+	 *   -----+-----+-----+-----+-----------------------------+
+	 *    4   |  0  |  0  |  0  |      WB <4:0>               |
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    5   |  1  | BD  |  1  | B-  |  1  | B+  |  1  |XXXXX|
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 *    6   | BO  | BR  | BB  | BG  | BY  |  1  |XXXXX|XXXXX|
+	 *   -----+-----+-----+-----+-----+-----+-----+-----+-----+
+	 */
+
+	sx = ext[0] & 0x3f;
+	sy = ext[1] & 0x3f;
+	tb = ext[2] & 0x1f;
+	wb = ext[3] & 0x1f;
+	bd = !(ext[4] & 0x40);
+	bm = !(ext[4] & 0x10);
+	bp = !(ext[4] & 0x04);
+	bo = !(ext[5] & 0x80);
+	br = !(ext[5] & 0x40);
+	bb = !(ext[5] & 0x20);
+	bg = !(ext[5] & 0x10);
+	by = !(ext[5] & 0x08);
+	bu = !(ext[5] & 0x01);
+
+	if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+		bu = !(ext[0] & 0x01);
+		sx &= 0x3e;
+		sy &= 0x3e;
+	}
+
+	input_report_abs(wdata->extension.input, ABS_X, sx - 0x20);
+	input_report_abs(wdata->extension.input, ABS_Y, sy - 0x20);
+	input_report_abs(wdata->extension.input, ABS_HAT0X, tb);
+	input_report_abs(wdata->extension.input, ABS_HAT1X, wb - 0x10);
+
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_G],
+			 bg);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_R],
+			 br);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_Y],
+			 by);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_B],
+			 bb);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_O],
+			 bo);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_UP],
+			 bu);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_DOWN],
+			 bd);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_PLUS],
+			 bp);
+	input_report_key(wdata->extension.input,
+			 wiimod_guitar_map[WIIMOD_GUITAR_KEY_MINUS],
+			 bm);
+
+	input_sync(wdata->extension.input);
+}
+
+static int wiimod_guitar_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_guitar_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_guitar_probe(const struct wiimod_ops *ops,
+			       struct wiimote_data *wdata)
+{
+	int ret, i;
+
+	wdata->extension.input = input_allocate_device();
+	if (!wdata->extension.input)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->extension.input, wdata);
+	wdata->extension.input->open = wiimod_guitar_open;
+	wdata->extension.input->close = wiimod_guitar_close;
+	wdata->extension.input->dev.parent = &wdata->hdev->dev;
+	wdata->extension.input->id.bustype = wdata->hdev->bus;
+	wdata->extension.input->id.vendor = wdata->hdev->vendor;
+	wdata->extension.input->id.product = wdata->hdev->product;
+	wdata->extension.input->id.version = wdata->hdev->version;
+	wdata->extension.input->name = WIIMOTE_NAME " Guitar";
+
+	set_bit(EV_KEY, wdata->extension.input->evbit);
+	for (i = 0; i < WIIMOD_GUITAR_KEY_NUM; ++i)
+		set_bit(wiimod_guitar_map[i],
+			wdata->extension.input->keybit);
+
+	set_bit(EV_ABS, wdata->extension.input->evbit);
+	set_bit(ABS_X, wdata->extension.input->absbit);
+	set_bit(ABS_Y, wdata->extension.input->absbit);
+	set_bit(ABS_HAT0X, wdata->extension.input->absbit);
+	set_bit(ABS_HAT1X, wdata->extension.input->absbit);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_X, -32, 31, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_Y, -32, 31, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT0X, 0, 0x1f, 1, 1);
+	input_set_abs_params(wdata->extension.input,
+			     ABS_HAT1X, 0, 0x0f, 1, 1);
+
+	ret = input_register_device(wdata->extension.input);
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+	return ret;
+}
+
+static void wiimod_guitar_remove(const struct wiimod_ops *ops,
+				 struct wiimote_data *wdata)
+{
+	if (!wdata->extension.input)
+		return;
+
+	input_unregister_device(wdata->extension.input);
+	wdata->extension.input = NULL;
+}
+
+static const struct wiimod_ops wiimod_guitar = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_guitar_probe,
+	.remove = wiimod_guitar_remove,
+	.in_ext = wiimod_guitar_in_ext,
+};
+
+/*
+ * Builtin Motion Plus
+ * This module simply sets the WIIPROTO_FLAG_BUILTIN_MP protocol flag which
+ * disables polling for Motion-Plus. This should be set only for devices which
+ * don't allow MP hotplugging.
+ */
+
+static int wiimod_builtin_mp_probe(const struct wiimod_ops *ops,
+				   struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_BUILTIN_MP;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_builtin_mp_remove(const struct wiimod_ops *ops,
+				     struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_BUILTIN_MP;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static const struct wiimod_ops wiimod_builtin_mp = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_builtin_mp_probe,
+	.remove = wiimod_builtin_mp_remove,
+};
+
+/*
+ * No Motion Plus
+ * This module simply sets the WIIPROTO_FLAG_NO_MP protocol flag which
+ * disables motion-plus. This is needed for devices that advertise this but we
+ * don't know how to use it (or whether it is actually present).
+ */
+
+static int wiimod_no_mp_probe(const struct wiimod_ops *ops,
+			      struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_NO_MP;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_no_mp_remove(const struct wiimod_ops *ops,
+				struct wiimote_data *wdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_NO_MP;
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static const struct wiimod_ops wiimod_no_mp = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_no_mp_probe,
+	.remove = wiimod_no_mp_remove,
+};
+
+/*
+ * Motion Plus
+ * The Motion Plus extension provides rotation sensors (gyro) as a small
+ * extension device for Wii Remotes. Many devices have them built-in so
+ * you cannot see them from the outside.
+ * Motion Plus extensions are special because they are on a separate extension
+ * port and allow other extensions to be used simultaneously. This is all
+ * handled by the Wiimote Core so we don't have to deal with it.
+ */
+
+static void wiimod_mp_in_mp(struct wiimote_data *wdata, const __u8 *ext)
+{
+	__s32 x, y, z;
+
+	/*        |   8    7    6    5    4    3 |  2  |  1  |
+	 *   -----+------------------------------+-----+-----+
+	 *    1   |               Yaw Speed <7:0>            |
+	 *    2   |              Roll Speed <7:0>            |
+	 *    3   |             Pitch Speed <7:0>            |
+	 *   -----+------------------------------+-----+-----+
+	 *    4   |       Yaw Speed <13:8>       | Yaw |Pitch|
+	 *   -----+------------------------------+-----+-----+
+	 *    5   |      Roll Speed <13:8>       |Roll | Ext |
+	 *   -----+------------------------------+-----+-----+
+	 *    6   |     Pitch Speed <13:8>       |  1  |  0  |
+	 *   -----+------------------------------+-----+-----+
+	 * The single bits Yaw, Roll, Pitch in the lower right corner specify
+	 * whether the wiimote is rotating fast (0) or slow (1). Speed for slow
+	 * roation is 8192/440 units / deg/s and for fast rotation 8192/2000
+	 * units / deg/s. To get a linear scale for fast rotation we multiply
+	 * by 2000/440 = ~4.5454 and scale both fast and slow by 9 to match the
+	 * previous scale reported by this driver.
+	 * This leaves a linear scale with 8192*9/440 (~167.564) units / deg/s.
+	 * If the wiimote is not rotating the sensor reports 2^13 = 8192.
+	 * Ext specifies whether an extension is connected to the motionp.
+	 * which is parsed by wiimote-core.
+	 */
+
+	x = ext[0];
+	y = ext[1];
+	z = ext[2];
+
+	x |= (((__u16)ext[3]) << 6) & 0xff00;
+	y |= (((__u16)ext[4]) << 6) & 0xff00;
+	z |= (((__u16)ext[5]) << 6) & 0xff00;
+
+	x -= 8192;
+	y -= 8192;
+	z -= 8192;
+
+	if (!(ext[3] & 0x02))
+		x = (x * 2000 * 9) / 440;
+	else
+		x *= 9;
+	if (!(ext[4] & 0x02))
+		y = (y * 2000 * 9) / 440;
+	else
+		y *= 9;
+	if (!(ext[3] & 0x01))
+		z = (z * 2000 * 9) / 440;
+	else
+		z *= 9;
+
+	input_report_abs(wdata->mp, ABS_RX, x);
+	input_report_abs(wdata->mp, ABS_RY, y);
+	input_report_abs(wdata->mp, ABS_RZ, z);
+	input_sync(wdata->mp);
+}
+
+static int wiimod_mp_open(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags |= WIIPROTO_FLAG_MP_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	__wiimote_schedule(wdata);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+	return 0;
+}
+
+static void wiimod_mp_close(struct input_dev *dev)
+{
+	struct wiimote_data *wdata = input_get_drvdata(dev);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdata->state.lock, flags);
+	wdata->state.flags &= ~WIIPROTO_FLAG_MP_USED;
+	wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+	__wiimote_schedule(wdata);
+	spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_mp_probe(const struct wiimod_ops *ops,
+			   struct wiimote_data *wdata)
+{
+	int ret;
+
+	wdata->mp = input_allocate_device();
+	if (!wdata->mp)
+		return -ENOMEM;
+
+	input_set_drvdata(wdata->mp, wdata);
+	wdata->mp->open = wiimod_mp_open;
+	wdata->mp->close = wiimod_mp_close;
+	wdata->mp->dev.parent = &wdata->hdev->dev;
+	wdata->mp->id.bustype = wdata->hdev->bus;
+	wdata->mp->id.vendor = wdata->hdev->vendor;
+	wdata->mp->id.product = wdata->hdev->product;
+	wdata->mp->id.version = wdata->hdev->version;
+	wdata->mp->name = WIIMOTE_NAME " Motion Plus";
+
+	set_bit(EV_ABS, wdata->mp->evbit);
+	set_bit(ABS_RX, wdata->mp->absbit);
+	set_bit(ABS_RY, wdata->mp->absbit);
+	set_bit(ABS_RZ, wdata->mp->absbit);
+	input_set_abs_params(wdata->mp,
+			     ABS_RX, -16000, 16000, 4, 8);
+	input_set_abs_params(wdata->mp,
+			     ABS_RY, -16000, 16000, 4, 8);
+	input_set_abs_params(wdata->mp,
+			     ABS_RZ, -16000, 16000, 4, 8);
+
+	ret = input_register_device(wdata->mp);
+	if (ret)
+		goto err_free;
+
+	return 0;
+
+err_free:
+	input_free_device(wdata->mp);
+	wdata->mp = NULL;
+	return ret;
+}
+
+static void wiimod_mp_remove(const struct wiimod_ops *ops,
+			     struct wiimote_data *wdata)
+{
+	if (!wdata->mp)
+		return;
+
+	input_unregister_device(wdata->mp);
+	wdata->mp = NULL;
+}
+
+const struct wiimod_ops wiimod_mp = {
+	.flags = 0,
+	.arg = 0,
+	.probe = wiimod_mp_probe,
+	.remove = wiimod_mp_remove,
+	.in_mp = wiimod_mp_in_mp,
+};
+
+/* module table */
+
+static const struct wiimod_ops wiimod_dummy;
+
+const struct wiimod_ops *wiimod_table[WIIMOD_NUM] = {
+	[WIIMOD_KEYS] = &wiimod_keys,
+	[WIIMOD_RUMBLE] = &wiimod_rumble,
+	[WIIMOD_BATTERY] = &wiimod_battery,
+	[WIIMOD_LED1] = &wiimod_leds[0],
+	[WIIMOD_LED2] = &wiimod_leds[1],
+	[WIIMOD_LED3] = &wiimod_leds[2],
+	[WIIMOD_LED4] = &wiimod_leds[3],
+	[WIIMOD_ACCEL] = &wiimod_accel,
+	[WIIMOD_IR] = &wiimod_ir,
+	[WIIMOD_BUILTIN_MP] = &wiimod_builtin_mp,
+	[WIIMOD_NO_MP] = &wiimod_no_mp,
+};
+
+const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = {
+	[WIIMOTE_EXT_NONE] = &wiimod_dummy,
+	[WIIMOTE_EXT_UNKNOWN] = &wiimod_dummy,
+	[WIIMOTE_EXT_NUNCHUK] = &wiimod_nunchuk,
+	[WIIMOTE_EXT_CLASSIC_CONTROLLER] = &wiimod_classic,
+	[WIIMOTE_EXT_BALANCE_BOARD] = &wiimod_bboard,
+	[WIIMOTE_EXT_PRO_CONTROLLER] = &wiimod_pro,
+	[WIIMOTE_EXT_DRUMS] = &wiimod_drums,
+	[WIIMOTE_EXT_GUITAR] = &wiimod_guitar,
+};
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
new file mode 100644
index 0000000..3bf3d3c
--- /dev/null
+++ b/drivers/hid/hid-wiimote.h
@@ -0,0 +1,378 @@
+#ifndef __HID_WIIMOTE_H
+#define __HID_WIIMOTE_H
+
+/*
+ * HID driver for Nintendo Wii / Wii U peripherals
+ * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#define WIIMOTE_NAME "Nintendo Wii Remote"
+#define WIIMOTE_BUFSIZE 32
+
+#define WIIPROTO_FLAG_LED1		0x01
+#define WIIPROTO_FLAG_LED2		0x02
+#define WIIPROTO_FLAG_LED3		0x04
+#define WIIPROTO_FLAG_LED4		0x08
+#define WIIPROTO_FLAG_RUMBLE		0x10
+#define WIIPROTO_FLAG_ACCEL		0x20
+#define WIIPROTO_FLAG_IR_BASIC		0x40
+#define WIIPROTO_FLAG_IR_EXT		0x80
+#define WIIPROTO_FLAG_IR_FULL		0xc0 /* IR_BASIC | IR_EXT */
+#define WIIPROTO_FLAG_EXT_PLUGGED	0x0100
+#define WIIPROTO_FLAG_EXT_USED		0x0200
+#define WIIPROTO_FLAG_EXT_ACTIVE	0x0400
+#define WIIPROTO_FLAG_MP_PLUGGED	0x0800
+#define WIIPROTO_FLAG_MP_USED		0x1000
+#define WIIPROTO_FLAG_MP_ACTIVE		0x2000
+#define WIIPROTO_FLAG_EXITING		0x4000
+#define WIIPROTO_FLAG_DRM_LOCKED	0x8000
+#define WIIPROTO_FLAG_BUILTIN_MP	0x010000
+#define WIIPROTO_FLAG_NO_MP		0x020000
+#define WIIPROTO_FLAG_PRO_CALIB_DONE	0x040000
+
+#define WIIPROTO_FLAGS_LEDS (WIIPROTO_FLAG_LED1 | WIIPROTO_FLAG_LED2 | \
+					WIIPROTO_FLAG_LED3 | WIIPROTO_FLAG_LED4)
+#define WIIPROTO_FLAGS_IR (WIIPROTO_FLAG_IR_BASIC | WIIPROTO_FLAG_IR_EXT | \
+							WIIPROTO_FLAG_IR_FULL)
+
+/* return flag for led \num */
+#define WIIPROTO_FLAG_LED(num) (WIIPROTO_FLAG_LED1 << (num - 1))
+
+enum wiiproto_keys {
+	WIIPROTO_KEY_LEFT,
+	WIIPROTO_KEY_RIGHT,
+	WIIPROTO_KEY_UP,
+	WIIPROTO_KEY_DOWN,
+	WIIPROTO_KEY_PLUS,
+	WIIPROTO_KEY_MINUS,
+	WIIPROTO_KEY_ONE,
+	WIIPROTO_KEY_TWO,
+	WIIPROTO_KEY_A,
+	WIIPROTO_KEY_B,
+	WIIPROTO_KEY_HOME,
+	WIIPROTO_KEY_COUNT
+};
+
+enum wiimote_devtype {
+	WIIMOTE_DEV_PENDING,
+	WIIMOTE_DEV_UNKNOWN,
+	WIIMOTE_DEV_GENERIC,
+	WIIMOTE_DEV_GEN10,
+	WIIMOTE_DEV_GEN20,
+	WIIMOTE_DEV_BALANCE_BOARD,
+	WIIMOTE_DEV_PRO_CONTROLLER,
+	WIIMOTE_DEV_NUM,
+};
+
+enum wiimote_exttype {
+	WIIMOTE_EXT_NONE,
+	WIIMOTE_EXT_UNKNOWN,
+	WIIMOTE_EXT_NUNCHUK,
+	WIIMOTE_EXT_CLASSIC_CONTROLLER,
+	WIIMOTE_EXT_BALANCE_BOARD,
+	WIIMOTE_EXT_PRO_CONTROLLER,
+	WIIMOTE_EXT_DRUMS,
+	WIIMOTE_EXT_GUITAR,
+	WIIMOTE_EXT_NUM,
+};
+
+enum wiimote_mptype {
+	WIIMOTE_MP_NONE,
+	WIIMOTE_MP_UNKNOWN,
+	WIIMOTE_MP_SINGLE,
+	WIIMOTE_MP_PASSTHROUGH_NUNCHUK,
+	WIIMOTE_MP_PASSTHROUGH_CLASSIC,
+};
+
+struct wiimote_buf {
+	__u8 data[HID_MAX_BUFFER_SIZE];
+	size_t size;
+};
+
+struct wiimote_queue {
+	spinlock_t lock;
+	struct work_struct worker;
+	__u8 head;
+	__u8 tail;
+	struct wiimote_buf outq[WIIMOTE_BUFSIZE];
+};
+
+struct wiimote_state {
+	spinlock_t lock;
+	__u32 flags;
+	__u8 accel_split[2];
+	__u8 drm;
+	__u8 devtype;
+	__u8 exttype;
+	__u8 mp;
+
+	/* synchronous cmd requests */
+	struct mutex sync;
+	struct completion ready;
+	int cmd;
+	__u32 opt;
+
+	/* results of synchronous requests */
+	__u8 cmd_battery;
+	__u8 cmd_err;
+	__u8 *cmd_read_buf;
+	__u8 cmd_read_size;
+
+	/* calibration/cache data */
+	__u16 calib_bboard[4][3];
+	__s16 calib_pro_sticks[4];
+	__u8 pressure_drums[7];
+	__u8 cache_rumble;
+};
+
+struct wiimote_data {
+	struct hid_device *hdev;
+	struct input_dev *input;
+	struct work_struct rumble_worker;
+	struct led_classdev *leds[4];
+	struct input_dev *accel;
+	struct input_dev *ir;
+	struct power_supply *battery;
+	struct power_supply_desc battery_desc;
+	struct input_dev *mp;
+	struct timer_list timer;
+	struct wiimote_debug *debug;
+
+	union {
+		struct input_dev *input;
+	} extension;
+
+	struct wiimote_queue queue;
+	struct wiimote_state state;
+	struct work_struct init_worker;
+};
+
+/* wiimote modules */
+
+enum wiimod_module {
+	WIIMOD_KEYS,
+	WIIMOD_RUMBLE,
+	WIIMOD_BATTERY,
+	WIIMOD_LED1,
+	WIIMOD_LED2,
+	WIIMOD_LED3,
+	WIIMOD_LED4,
+	WIIMOD_ACCEL,
+	WIIMOD_IR,
+	WIIMOD_BUILTIN_MP,
+	WIIMOD_NO_MP,
+	WIIMOD_NUM,
+	WIIMOD_NULL = WIIMOD_NUM,
+};
+
+#define WIIMOD_FLAG_INPUT		0x0001
+#define WIIMOD_FLAG_EXT8		0x0002
+#define WIIMOD_FLAG_EXT16		0x0004
+
+struct wiimod_ops {
+	__u16 flags;
+	unsigned long arg;
+	int (*probe) (const struct wiimod_ops *ops,
+		      struct wiimote_data *wdata);
+	void (*remove) (const struct wiimod_ops *ops,
+			struct wiimote_data *wdata);
+
+	void (*in_keys) (struct wiimote_data *wdata, const __u8 *keys);
+	void (*in_accel) (struct wiimote_data *wdata, const __u8 *accel);
+	void (*in_ir) (struct wiimote_data *wdata, const __u8 *ir, bool packed,
+		       unsigned int id);
+	void (*in_mp) (struct wiimote_data *wdata, const __u8 *mp);
+	void (*in_ext) (struct wiimote_data *wdata, const __u8 *ext);
+};
+
+extern const struct wiimod_ops *wiimod_table[WIIMOD_NUM];
+extern const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM];
+extern const struct wiimod_ops wiimod_mp;
+
+/* wiimote requests */
+
+enum wiiproto_reqs {
+	WIIPROTO_REQ_NULL = 0x0,
+	WIIPROTO_REQ_RUMBLE = 0x10,
+	WIIPROTO_REQ_LED = 0x11,
+	WIIPROTO_REQ_DRM = 0x12,
+	WIIPROTO_REQ_IR1 = 0x13,
+	WIIPROTO_REQ_SREQ = 0x15,
+	WIIPROTO_REQ_WMEM = 0x16,
+	WIIPROTO_REQ_RMEM = 0x17,
+	WIIPROTO_REQ_IR2 = 0x1a,
+	WIIPROTO_REQ_STATUS = 0x20,
+	WIIPROTO_REQ_DATA = 0x21,
+	WIIPROTO_REQ_RETURN = 0x22,
+
+	/* DRM_K: BB*2 */
+	WIIPROTO_REQ_DRM_K = 0x30,
+
+	/* DRM_KA: BB*2 AA*3 */
+	WIIPROTO_REQ_DRM_KA = 0x31,
+
+	/* DRM_KE: BB*2 EE*8 */
+	WIIPROTO_REQ_DRM_KE = 0x32,
+
+	/* DRM_KAI: BB*2 AA*3 II*12 */
+	WIIPROTO_REQ_DRM_KAI = 0x33,
+
+	/* DRM_KEE: BB*2 EE*19 */
+	WIIPROTO_REQ_DRM_KEE = 0x34,
+
+	/* DRM_KAE: BB*2 AA*3 EE*16 */
+	WIIPROTO_REQ_DRM_KAE = 0x35,
+
+	/* DRM_KIE: BB*2 II*10 EE*9 */
+	WIIPROTO_REQ_DRM_KIE = 0x36,
+
+	/* DRM_KAIE: BB*2 AA*3 II*10 EE*6 */
+	WIIPROTO_REQ_DRM_KAIE = 0x37,
+
+	/* DRM_E: EE*21 */
+	WIIPROTO_REQ_DRM_E = 0x3d,
+
+	/* DRM_SKAI1: BB*2 AA*1 II*18 */
+	WIIPROTO_REQ_DRM_SKAI1 = 0x3e,
+
+	/* DRM_SKAI2: BB*2 AA*1 II*18 */
+	WIIPROTO_REQ_DRM_SKAI2 = 0x3f,
+
+	WIIPROTO_REQ_MAX
+};
+
+#define dev_to_wii(pdev) hid_get_drvdata(to_hid_device(pdev))
+
+void __wiimote_schedule(struct wiimote_data *wdata);
+
+extern void wiiproto_req_drm(struct wiimote_data *wdata, __u8 drm);
+extern void wiiproto_req_rumble(struct wiimote_data *wdata, __u8 rumble);
+extern void wiiproto_req_leds(struct wiimote_data *wdata, int leds);
+extern void wiiproto_req_status(struct wiimote_data *wdata);
+extern void wiiproto_req_accel(struct wiimote_data *wdata, __u8 accel);
+extern void wiiproto_req_ir1(struct wiimote_data *wdata, __u8 flags);
+extern void wiiproto_req_ir2(struct wiimote_data *wdata, __u8 flags);
+extern int wiimote_cmd_write(struct wiimote_data *wdata, __u32 offset,
+						const __u8 *wmem, __u8 size);
+extern ssize_t wiimote_cmd_read(struct wiimote_data *wdata, __u32 offset,
+							__u8 *rmem, __u8 size);
+
+#define wiiproto_req_rreg(wdata, os, sz) \
+				wiiproto_req_rmem((wdata), false, (os), (sz))
+#define wiiproto_req_reeprom(wdata, os, sz) \
+				wiiproto_req_rmem((wdata), true, (os), (sz))
+extern void wiiproto_req_rmem(struct wiimote_data *wdata, bool eeprom,
+						__u32 offset, __u16 size);
+
+#ifdef CONFIG_DEBUG_FS
+
+extern int wiidebug_init(struct wiimote_data *wdata);
+extern void wiidebug_deinit(struct wiimote_data *wdata);
+
+#else
+
+static inline int wiidebug_init(void *u) { return 0; }
+static inline void wiidebug_deinit(void *u) { }
+
+#endif
+
+/* requires the state.lock spinlock to be held */
+static inline bool wiimote_cmd_pending(struct wiimote_data *wdata, int cmd,
+								__u32 opt)
+{
+	return wdata->state.cmd == cmd && wdata->state.opt == opt;
+}
+
+/* requires the state.lock spinlock to be held */
+static inline void wiimote_cmd_complete(struct wiimote_data *wdata)
+{
+	wdata->state.cmd = WIIPROTO_REQ_NULL;
+	complete(&wdata->state.ready);
+}
+
+/* requires the state.lock spinlock to be held */
+static inline void wiimote_cmd_abort(struct wiimote_data *wdata)
+{
+	/* Abort synchronous request by waking up the sleeping caller. But
+	 * reset the state.cmd field to an invalid value so no further event
+	 * handlers will work with it. */
+	wdata->state.cmd = WIIPROTO_REQ_MAX;
+	complete(&wdata->state.ready);
+}
+
+static inline int wiimote_cmd_acquire(struct wiimote_data *wdata)
+{
+	return mutex_lock_interruptible(&wdata->state.sync) ? -ERESTARTSYS : 0;
+}
+
+static inline void wiimote_cmd_acquire_noint(struct wiimote_data *wdata)
+{
+	mutex_lock(&wdata->state.sync);
+}
+
+/* requires the state.lock spinlock to be held */
+static inline void wiimote_cmd_set(struct wiimote_data *wdata, int cmd,
+								__u32 opt)
+{
+	reinit_completion(&wdata->state.ready);
+	wdata->state.cmd = cmd;
+	wdata->state.opt = opt;
+}
+
+static inline void wiimote_cmd_release(struct wiimote_data *wdata)
+{
+	mutex_unlock(&wdata->state.sync);
+}
+
+static inline int wiimote_cmd_wait(struct wiimote_data *wdata)
+{
+	int ret;
+
+	/* The completion acts as implicit memory barrier so we can safely
+	 * assume that state.cmd is set on success/failure and isn't accessed
+	 * by any other thread, anymore. */
+
+	ret = wait_for_completion_interruptible_timeout(&wdata->state.ready, HZ);
+	if (ret < 0)
+		return -ERESTARTSYS;
+	else if (ret == 0)
+		return -EIO;
+	else if (wdata->state.cmd != WIIPROTO_REQ_NULL)
+		return -EIO;
+	else
+		return 0;
+}
+
+static inline int wiimote_cmd_wait_noint(struct wiimote_data *wdata)
+{
+	unsigned long ret;
+
+	/* no locking needed; see wiimote_cmd_wait() */
+	ret = wait_for_completion_timeout(&wdata->state.ready, HZ);
+	if (!ret)
+		return -EIO;
+	else if (wdata->state.cmd != WIIPROTO_REQ_NULL)
+		return -EIO;
+	else
+		return 0;
+}
+
+#endif
diff --git a/drivers/hid/hid-xinmo.c b/drivers/hid/hid-xinmo.c
new file mode 100644
index 0000000..9ad7731
--- /dev/null
+++ b/drivers/hid/hid-xinmo.c
@@ -0,0 +1,62 @@
+/*
+ *  HID driver for Xin-Mo devices, currently only the Dual Arcade controller.
+ *  Fixes the negative axis event values (the devices sends -2) to match the
+ *  logical axis minimum of the HID report descriptor (the report announces
+ *  -1). It is needed because hid-input discards out of bounds values.
+ *  (This module is based on "hid-saitek" and "hid-lg".)
+ *
+ *  Copyright (c) 2013 Olivier Scherler
+ */
+
+/*
+ * 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/kernel.h>
+
+#include "hid-ids.h"
+
+/*
+ * Fix negative events that are out of bounds.
+ */
+static int xinmo_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	switch (usage->code) {
+	case ABS_X:
+	case ABS_Y:
+	case ABS_Z:
+	case ABS_RX:
+		if (value < -1) {
+			input_event(field->hidinput->input, usage->type,
+				usage->code, -1);
+			return 1;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id xinmo_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_THT_2P_ARCADE) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, xinmo_devices);
+
+static struct hid_driver xinmo_driver = {
+	.name = "xinmo",
+	.id_table = xinmo_devices,
+	.event = xinmo_event
+};
+
+module_hid_driver(xinmo_driver);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c
new file mode 100644
index 0000000..a29756c
--- /dev/null
+++ b/drivers/hid/hid-zpff.c
@@ -0,0 +1,147 @@
+/*
+ *  Force feedback support for Zeroplus based devices
+ *
+ *  Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+
+#include "hid-ids.h"
+
+#ifdef CONFIG_ZEROPLUS_FF
+
+struct zpff_device {
+	struct hid_report *report;
+};
+
+static int zpff_play(struct input_dev *dev, void *data,
+			 struct ff_effect *effect)
+{
+	struct hid_device *hid = input_get_drvdata(dev);
+	struct zpff_device *zpff = data;
+	int left, right;
+
+	/*
+	 * The following is specified the other way around in the Zeroplus
+	 * datasheet but the order below is correct for the XFX Executioner;
+	 * however it is possible that the XFX Executioner is an exception
+	 */
+
+	left = effect->u.rumble.strong_magnitude;
+	right = effect->u.rumble.weak_magnitude;
+	dbg_hid("called with 0x%04x 0x%04x\n", left, right);
+
+	left = left * 0x7f / 0xffff;
+	right = right * 0x7f / 0xffff;
+
+	zpff->report->field[2]->value[0] = left;
+	zpff->report->field[3]->value[0] = right;
+	dbg_hid("running with 0x%02x 0x%02x\n", left, right);
+	hid_hw_request(hid, zpff->report, HID_REQ_SET_REPORT);
+
+	return 0;
+}
+
+static int zpff_init(struct hid_device *hid)
+{
+	struct zpff_device *zpff;
+	struct hid_report *report;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	int i, error;
+
+	for (i = 0; i < 4; i++) {
+		report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1);
+		if (!report)
+			return -ENODEV;
+	}
+
+	zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL);
+	if (!zpff)
+		return -ENOMEM;
+
+	set_bit(FF_RUMBLE, dev->ffbit);
+
+	error = input_ff_create_memless(dev, zpff, zpff_play);
+	if (error) {
+		kfree(zpff);
+		return error;
+	}
+
+	zpff->report = report;
+	zpff->report->field[0]->value[0] = 0x00;
+	zpff->report->field[1]->value[0] = 0x02;
+	zpff->report->field[2]->value[0] = 0x00;
+	zpff->report->field[3]->value[0] = 0x00;
+	hid_hw_request(hid, zpff->report, HID_REQ_SET_REPORT);
+
+	hid_info(hid, "force feedback for Zeroplus based devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	return 0;
+}
+#else
+static inline int zpff_init(struct hid_device *hid)
+{
+	return 0;
+}
+#endif
+
+static int zp_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		goto err;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT & ~HID_CONNECT_FF);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		goto err;
+	}
+
+	zpff_init(hdev);
+
+	return 0;
+err:
+	return ret;
+}
+
+static const struct hid_device_id zp_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0005) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZEROPLUS, 0x0030) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, zp_devices);
+
+static struct hid_driver zp_driver = {
+	.name = "zeroplus",
+	.id_table = zp_devices,
+	.probe = zp_probe,
+};
+module_hid_driver(zp_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hid-zydacron.c b/drivers/hid/hid-zydacron.c
new file mode 100644
index 0000000..1a660bd
--- /dev/null
+++ b/drivers/hid/hid-zydacron.c
@@ -0,0 +1,211 @@
+/*
+*  HID driver for zydacron remote control
+*
+*  Copyright (c) 2010 Don Prince <dhprince.devel@yahoo.co.uk>
+*/
+
+/*
+* 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 "hid-ids.h"
+
+struct zc_device {
+	struct input_dev	*input_ep81;
+	unsigned short		last_key[4];
+};
+
+
+/*
+* Zydacron remote control has an invalid HID report descriptor,
+* that needs fixing before we can parse it.
+*/
+static __u8 *zc_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+	unsigned int *rsize)
+{
+	if (*rsize >= 253 &&
+		rdesc[0x96] == 0xbc && rdesc[0x97] == 0xff &&
+		rdesc[0xca] == 0xbc && rdesc[0xcb] == 0xff &&
+		rdesc[0xe1] == 0xbc && rdesc[0xe2] == 0xff) {
+			hid_info(hdev,
+				"fixing up zydacron remote control report descriptor\n");
+			rdesc[0x96] = rdesc[0xca] = rdesc[0xe1] = 0x0c;
+			rdesc[0x97] = rdesc[0xcb] = rdesc[0xe2] = 0x00;
+		}
+	return rdesc;
+}
+
+#define zc_map_key_clear(c) \
+	hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static int zc_input_mapping(struct hid_device *hdev, struct hid_input *hi,
+	struct hid_field *field, struct hid_usage *usage,
+	unsigned long **bit, int *max)
+{
+	int i;
+	struct zc_device *zc = hid_get_drvdata(hdev);
+	zc->input_ep81 = hi->input;
+
+	if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
+		return 0;
+
+	dbg_hid("zynacron input mapping event [0x%x]\n",
+		usage->hid & HID_USAGE);
+
+	switch (usage->hid & HID_USAGE) {
+	/* report 2 */
+	case 0x10:
+		zc_map_key_clear(KEY_MODE);
+		break;
+	case 0x30:
+		zc_map_key_clear(KEY_SCREEN);
+		break;
+	case 0x70:
+		zc_map_key_clear(KEY_INFO);
+		break;
+	/* report 3 */
+	case 0x04:
+		zc_map_key_clear(KEY_RADIO);
+		break;
+	/* report 4 */
+	case 0x0d:
+		zc_map_key_clear(KEY_PVR);
+		break;
+	case 0x25:
+		zc_map_key_clear(KEY_TV);
+		break;
+	case 0x47:
+		zc_map_key_clear(KEY_AUDIO);
+		break;
+	case 0x49:
+		zc_map_key_clear(KEY_AUX);
+		break;
+	case 0x4a:
+		zc_map_key_clear(KEY_VIDEO);
+		break;
+	case 0x48:
+		zc_map_key_clear(KEY_DVD);
+		break;
+	case 0x24:
+		zc_map_key_clear(KEY_MENU);
+		break;
+	case 0x32:
+		zc_map_key_clear(KEY_TEXT);
+		break;
+	default:
+		return 0;
+	}
+
+	for (i = 0; i < 4; i++)
+		zc->last_key[i] = 0;
+
+	return 1;
+}
+
+static int zc_raw_event(struct hid_device *hdev, struct hid_report *report,
+	 u8 *data, int size)
+{
+	struct zc_device *zc = hid_get_drvdata(hdev);
+	int ret = 0;
+	unsigned key;
+	unsigned short index;
+
+	if (report->id == data[0]) {
+
+		/* break keys */
+		for (index = 0; index < 4; index++) {
+			key = zc->last_key[index];
+			if (key) {
+				input_event(zc->input_ep81, EV_KEY, key, 0);
+				zc->last_key[index] = 0;
+			}
+		}
+
+		key = 0;
+		switch (report->id) {
+		case 0x02:
+		case 0x03:
+			switch (data[1]) {
+			case 0x10:
+				key = KEY_MODE;
+				index = 0;
+				break;
+			case 0x30:
+				key = KEY_SCREEN;
+				index = 1;
+				break;
+			case 0x70:
+				key = KEY_INFO;
+				index = 2;
+				break;
+			case 0x04:
+				key = KEY_RADIO;
+				index = 3;
+				break;
+			}
+
+			if (key) {
+				input_event(zc->input_ep81, EV_KEY, key, 1);
+				zc->last_key[index] = key;
+			}
+
+			ret = 1;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static int zc_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+	int ret;
+	struct zc_device *zc;
+
+	zc = devm_kzalloc(&hdev->dev, sizeof(*zc), GFP_KERNEL);
+	if (zc == NULL) {
+		hid_err(hdev, "can't alloc descriptor\n");
+		return -ENOMEM;
+	}
+
+	hid_set_drvdata(hdev, zc);
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "parse failed\n");
+		return ret;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hw start failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct hid_device_id zc_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_ZYDACRON, USB_DEVICE_ID_ZYDACRON_REMOTE_CONTROL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, zc_devices);
+
+static struct hid_driver zc_driver = {
+	.name = "zydacron",
+	.id_table = zc_devices,
+	.report_fixup = zc_report_fixup,
+	.input_mapping = zc_input_mapping,
+	.raw_event = zc_raw_event,
+	.probe = zc_probe,
+};
+module_hid_driver(zc_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
new file mode 100644
index 0000000..4a44e48
--- /dev/null
+++ b/drivers/hid/hidraw.c
@@ -0,0 +1,627 @@
+/*
+ * HID raw devices, giving access to raw HID events.
+ *
+ * In comparison to hiddev, this device does not process the
+ * hid events at all (no parsing, no lookups). This lets applications
+ * to work on raw hid events as they want to, and avoids a need to
+ * use a transport-specific userspace libhid/libusb libraries.
+ *
+ *  Copyright (c) 2007-2014 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/cdev.h>
+#include <linux/poll.h>
+#include <linux/device.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/hid.h>
+#include <linux/mutex.h>
+#include <linux/sched/signal.h>
+#include <linux/string.h>
+
+#include <linux/hidraw.h>
+
+static int hidraw_major;
+static struct cdev hidraw_cdev;
+static struct class *hidraw_class;
+static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
+static DEFINE_MUTEX(minors_lock);
+
+static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
+{
+	struct hidraw_list *list = file->private_data;
+	int ret = 0, len;
+	DECLARE_WAITQUEUE(wait, current);
+
+	mutex_lock(&list->read_mutex);
+
+	while (ret == 0) {
+		if (list->head == list->tail) {
+			add_wait_queue(&list->hidraw->wait, &wait);
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			while (list->head == list->tail) {
+				if (signal_pending(current)) {
+					ret = -ERESTARTSYS;
+					break;
+				}
+				if (!list->hidraw->exist) {
+					ret = -EIO;
+					break;
+				}
+				if (file->f_flags & O_NONBLOCK) {
+					ret = -EAGAIN;
+					break;
+				}
+
+				/* allow O_NONBLOCK to work well from other threads */
+				mutex_unlock(&list->read_mutex);
+				schedule();
+				mutex_lock(&list->read_mutex);
+				set_current_state(TASK_INTERRUPTIBLE);
+			}
+
+			set_current_state(TASK_RUNNING);
+			remove_wait_queue(&list->hidraw->wait, &wait);
+		}
+
+		if (ret)
+			goto out;
+
+		len = list->buffer[list->tail].len > count ?
+			count : list->buffer[list->tail].len;
+
+		if (list->buffer[list->tail].value) {
+			if (copy_to_user(buffer, list->buffer[list->tail].value, len)) {
+				ret = -EFAULT;
+				goto out;
+			}
+			ret = len;
+		}
+
+		kfree(list->buffer[list->tail].value);
+		list->buffer[list->tail].value = NULL;
+		list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
+	}
+out:
+	mutex_unlock(&list->read_mutex);
+	return ret;
+}
+
+/*
+ * The first byte of the report buffer is expected to be a report number.
+ *
+ * This function is to be called with the minors_lock mutex held.
+ */
+static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)
+{
+	unsigned int minor = iminor(file_inode(file));
+	struct hid_device *dev;
+	__u8 *buf;
+	int ret = 0;
+
+	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	dev = hidraw_table[minor]->hid;
+
+	if (count > HID_MAX_BUFFER_SIZE) {
+		hid_warn(dev, "pid %d passed too large report\n",
+			 task_pid_nr(current));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (count < 2) {
+		hid_warn(dev, "pid %d passed too short report\n",
+			 task_pid_nr(current));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	buf = memdup_user(buffer, count);
+	if (IS_ERR(buf)) {
+		ret = PTR_ERR(buf);
+		goto out;
+	}
+
+	if ((report_type == HID_OUTPUT_REPORT) &&
+	    !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
+		ret = hid_hw_output_report(dev, buf, count);
+		/*
+		 * compatibility with old implementation of USB-HID and I2C-HID:
+		 * if the device does not support receiving output reports,
+		 * on an interrupt endpoint, fallback to SET_REPORT HID command.
+		 */
+		if (ret != -ENOSYS)
+			goto out_free;
+	}
+
+	ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type,
+				HID_REQ_SET_REPORT);
+
+out_free:
+	kfree(buf);
+out:
+	return ret;
+}
+
+static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
+{
+	ssize_t ret;
+	mutex_lock(&minors_lock);
+	ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
+	mutex_unlock(&minors_lock);
+	return ret;
+}
+
+
+/*
+ * This function performs a Get_Report transfer over the control endpoint
+ * per section 7.2.1 of the HID specification, version 1.1.  The first byte
+ * of buffer is the report number to request, or 0x0 if the defice does not
+ * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT
+ * or HID_INPUT_REPORT.
+ *
+ * This function is to be called with the minors_lock mutex held.
+ */
+static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type)
+{
+	unsigned int minor = iminor(file_inode(file));
+	struct hid_device *dev;
+	__u8 *buf;
+	int ret = 0, len;
+	unsigned char report_number;
+
+	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	dev = hidraw_table[minor]->hid;
+
+	if (!dev->ll_driver->raw_request) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	if (count > HID_MAX_BUFFER_SIZE) {
+		printk(KERN_WARNING "hidraw: pid %d passed too large report\n",
+				task_pid_nr(current));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (count < 2) {
+		printk(KERN_WARNING "hidraw: pid %d passed too short report\n",
+				task_pid_nr(current));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	/*
+	 * Read the first byte from the user. This is the report number,
+	 * which is passed to hid_hw_raw_request().
+	 */
+	if (copy_from_user(&report_number, buffer, 1)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	ret = hid_hw_raw_request(dev, report_number, buf, count, report_type,
+				 HID_REQ_GET_REPORT);
+
+	if (ret < 0)
+		goto out_free;
+
+	len = (ret < count) ? ret : count;
+
+	if (copy_to_user(buffer, buf, len)) {
+		ret = -EFAULT;
+		goto out_free;
+	}
+
+	ret = len;
+
+out_free:
+	kfree(buf);
+out:
+	return ret;
+}
+
+static __poll_t hidraw_poll(struct file *file, poll_table *wait)
+{
+	struct hidraw_list *list = file->private_data;
+
+	poll_wait(file, &list->hidraw->wait, wait);
+	if (list->head != list->tail)
+		return EPOLLIN | EPOLLRDNORM;
+	if (!list->hidraw->exist)
+		return EPOLLERR | EPOLLHUP;
+	return 0;
+}
+
+static int hidraw_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+	struct hidraw *dev;
+	struct hidraw_list *list;
+	unsigned long flags;
+	int err = 0;
+
+	if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	mutex_lock(&minors_lock);
+	if (!hidraw_table[minor] || !hidraw_table[minor]->exist) {
+		err = -ENODEV;
+		goto out_unlock;
+	}
+
+	dev = hidraw_table[minor];
+	if (!dev->open++) {
+		err = hid_hw_power(dev->hid, PM_HINT_FULLON);
+		if (err < 0) {
+			dev->open--;
+			goto out_unlock;
+		}
+
+		err = hid_hw_open(dev->hid);
+		if (err < 0) {
+			hid_hw_power(dev->hid, PM_HINT_NORMAL);
+			dev->open--;
+			goto out_unlock;
+		}
+	}
+
+	list->hidraw = hidraw_table[minor];
+	mutex_init(&list->read_mutex);
+	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
+	list_add_tail(&list->node, &hidraw_table[minor]->list);
+	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
+	file->private_data = list;
+out_unlock:
+	mutex_unlock(&minors_lock);
+out:
+	if (err < 0)
+		kfree(list);
+	return err;
+
+}
+
+static int hidraw_fasync(int fd, struct file *file, int on)
+{
+	struct hidraw_list *list = file->private_data;
+
+	return fasync_helper(fd, file, on, &list->fasync);
+}
+
+static void drop_ref(struct hidraw *hidraw, int exists_bit)
+{
+	if (exists_bit) {
+		hidraw->exist = 0;
+		if (hidraw->open) {
+			hid_hw_close(hidraw->hid);
+			wake_up_interruptible(&hidraw->wait);
+		}
+		device_destroy(hidraw_class,
+			       MKDEV(hidraw_major, hidraw->minor));
+	} else {
+		--hidraw->open;
+	}
+	if (!hidraw->open) {
+		if (!hidraw->exist) {
+			hidraw_table[hidraw->minor] = NULL;
+			kfree(hidraw);
+		} else {
+			/* close device for last reader */
+			hid_hw_close(hidraw->hid);
+			hid_hw_power(hidraw->hid, PM_HINT_NORMAL);
+		}
+	}
+}
+
+static int hidraw_release(struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+	struct hidraw_list *list = file->private_data;
+	unsigned long flags;
+
+	mutex_lock(&minors_lock);
+
+	spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags);
+	list_del(&list->node);
+	spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags);
+	kfree(list);
+
+	drop_ref(hidraw_table[minor], 0);
+
+	mutex_unlock(&minors_lock);
+	return 0;
+}
+
+static long hidraw_ioctl(struct file *file, unsigned int cmd,
+							unsigned long arg)
+{
+	struct inode *inode = file_inode(file);
+	unsigned int minor = iminor(inode);
+	long ret = 0;
+	struct hidraw *dev;
+	void __user *user_arg = (void __user*) arg;
+
+	mutex_lock(&minors_lock);
+	dev = hidraw_table[minor];
+	if (!dev) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	switch (cmd) {
+		case HIDIOCGRDESCSIZE:
+			if (put_user(dev->hid->rsize, (int __user *)arg))
+				ret = -EFAULT;
+			break;
+
+		case HIDIOCGRDESC:
+			{
+				__u32 len;
+
+				if (get_user(len, (int __user *)arg))
+					ret = -EFAULT;
+				else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
+					ret = -EINVAL;
+				else if (copy_to_user(user_arg + offsetof(
+					struct hidraw_report_descriptor,
+					value[0]),
+					dev->hid->rdesc,
+					min(dev->hid->rsize, len)))
+					ret = -EFAULT;
+				break;
+			}
+		case HIDIOCGRAWINFO:
+			{
+				struct hidraw_devinfo dinfo;
+
+				dinfo.bustype = dev->hid->bus;
+				dinfo.vendor = dev->hid->vendor;
+				dinfo.product = dev->hid->product;
+				if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
+					ret = -EFAULT;
+				break;
+			}
+		default:
+			{
+				struct hid_device *hid = dev->hid;
+				if (_IOC_TYPE(cmd) != 'H') {
+					ret = -EINVAL;
+					break;
+				}
+
+				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
+					int len = _IOC_SIZE(cmd);
+					ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
+					break;
+				}
+				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
+					int len = _IOC_SIZE(cmd);
+					ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
+					break;
+				}
+
+				/* Begin Read-only ioctls. */
+				if (_IOC_DIR(cmd) != _IOC_READ) {
+					ret = -EINVAL;
+					break;
+				}
+
+				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
+					int len = strlen(hid->name) + 1;
+					if (len > _IOC_SIZE(cmd))
+						len = _IOC_SIZE(cmd);
+					ret = copy_to_user(user_arg, hid->name, len) ?
+						-EFAULT : len;
+					break;
+				}
+
+				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
+					int len = strlen(hid->phys) + 1;
+					if (len > _IOC_SIZE(cmd))
+						len = _IOC_SIZE(cmd);
+					ret = copy_to_user(user_arg, hid->phys, len) ?
+						-EFAULT : len;
+					break;
+				}
+			}
+
+		ret = -ENOTTY;
+	}
+out:
+	mutex_unlock(&minors_lock);
+	return ret;
+}
+
+static const struct file_operations hidraw_ops = {
+	.owner =        THIS_MODULE,
+	.read =         hidraw_read,
+	.write =        hidraw_write,
+	.poll =         hidraw_poll,
+	.open =         hidraw_open,
+	.release =      hidraw_release,
+	.unlocked_ioctl = hidraw_ioctl,
+	.fasync =	hidraw_fasync,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl   = hidraw_ioctl,
+#endif
+	.llseek =	noop_llseek,
+};
+
+int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
+{
+	struct hidraw *dev = hid->hidraw;
+	struct hidraw_list *list;
+	int ret = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->list_lock, flags);
+	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 = new_head;
+		kill_fasync(&list->fasync, SIGIO, POLL_IN);
+	}
+	spin_unlock_irqrestore(&dev->list_lock, flags);
+
+	wake_up_interruptible(&dev->wait);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(hidraw_report_event);
+
+int hidraw_connect(struct hid_device *hid)
+{
+	int minor, result;
+	struct hidraw *dev;
+
+	/* we accept any HID device, all applications */
+
+	dev = kzalloc(sizeof(struct hidraw), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	result = -EINVAL;
+
+	mutex_lock(&minors_lock);
+
+	for (minor = 0; minor < HIDRAW_MAX_DEVICES; minor++) {
+		if (hidraw_table[minor])
+			continue;
+		hidraw_table[minor] = dev;
+		result = 0;
+		break;
+	}
+
+	if (result) {
+		mutex_unlock(&minors_lock);
+		kfree(dev);
+		goto out;
+	}
+
+	dev->dev = device_create(hidraw_class, &hid->dev, MKDEV(hidraw_major, minor),
+				 NULL, "%s%d", "hidraw", minor);
+
+	if (IS_ERR(dev->dev)) {
+		hidraw_table[minor] = NULL;
+		mutex_unlock(&minors_lock);
+		result = PTR_ERR(dev->dev);
+		kfree(dev);
+		goto out;
+	}
+
+	init_waitqueue_head(&dev->wait);
+	spin_lock_init(&dev->list_lock);
+	INIT_LIST_HEAD(&dev->list);
+
+	dev->hid = hid;
+	dev->minor = minor;
+
+	dev->exist = 1;
+	hid->hidraw = dev;
+
+	mutex_unlock(&minors_lock);
+out:
+	return result;
+
+}
+EXPORT_SYMBOL_GPL(hidraw_connect);
+
+void hidraw_disconnect(struct hid_device *hid)
+{
+	struct hidraw *hidraw = hid->hidraw;
+
+	mutex_lock(&minors_lock);
+
+	drop_ref(hidraw, 1);
+
+	mutex_unlock(&minors_lock);
+}
+EXPORT_SYMBOL_GPL(hidraw_disconnect);
+
+int __init hidraw_init(void)
+{
+	int result;
+	dev_t dev_id;
+
+	result = alloc_chrdev_region(&dev_id, HIDRAW_FIRST_MINOR,
+			HIDRAW_MAX_DEVICES, "hidraw");
+	if (result < 0) {
+		pr_warn("can't get major number\n");
+		goto out;
+	}
+
+	hidraw_major = MAJOR(dev_id);
+
+	hidraw_class = class_create(THIS_MODULE, "hidraw");
+	if (IS_ERR(hidraw_class)) {
+		result = PTR_ERR(hidraw_class);
+		goto error_cdev;
+	}
+
+        cdev_init(&hidraw_cdev, &hidraw_ops);
+	result = cdev_add(&hidraw_cdev, dev_id, HIDRAW_MAX_DEVICES);
+	if (result < 0)
+		goto error_class;
+
+	printk(KERN_INFO "hidraw: raw HID events driver (C) Jiri Kosina\n");
+out:
+	return result;
+
+error_class:
+	class_destroy(hidraw_class);
+error_cdev:
+	unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
+	goto out;
+}
+
+void hidraw_exit(void)
+{
+	dev_t dev_id = MKDEV(hidraw_major, 0);
+
+	cdev_del(&hidraw_cdev);
+	class_destroy(hidraw_class);
+	unregister_chrdev_region(dev_id, HIDRAW_MAX_DEVICES);
+
+}
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
new file mode 100644
index 0000000..b66617a
--- /dev/null
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -0,0 +1,18 @@
+menu "I2C HID support"
+	depends on I2C
+
+config I2C_HID
+	tristate "HID over I2C transport layer"
+	default n
+	depends on I2C && INPUT
+	select HID
+	---help---
+	  Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
+	  other HID based devices which is connected to your computer via I2C.
+
+	  If unsure, say N.
+
+	  This support is also available as a module.  If so, the module
+	  will be called i2c-hid.
+
+endmenu
diff --git a/drivers/hid/i2c-hid/Makefile b/drivers/hid/i2c-hid/Makefile
new file mode 100644
index 0000000..832d8f9
--- /dev/null
+++ b/drivers/hid/i2c-hid/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the I2C input drivers
+#
+
+obj-$(CONFIG_I2C_HID)				+= i2c-hid.o
diff --git a/drivers/hid/i2c-hid/i2c-hid.c b/drivers/hid/i2c-hid/i2c-hid.c
new file mode 100644
index 0000000..88daa38
--- /dev/null
+++ b/drivers/hid/i2c-hid/i2c-hid.c
@@ -0,0 +1,1328 @@
+/*
+ * HID over I2C protocol implementation
+ *
+ * Copyright (c) 2012 Benjamin Tissoires <benjamin.tissoires@gmail.com>
+ * Copyright (c) 2012 Ecole Nationale de l'Aviation Civile, France
+ * Copyright (c) 2012 Red Hat, Inc
+ *
+ * This code is partly based on "USB HID support for Linux":
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2007-2008 Oliver Neukum
+ *  Copyright (c) 2006-2010 Jiri Kosina
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/hid.h>
+#include <linux/mutex.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/platform_data/i2c-hid.h>
+
+#include "../hid-ids.h"
+
+/* quirks to control the device */
+#define I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV	BIT(0)
+#define I2C_HID_QUIRK_NO_IRQ_AFTER_RESET	BIT(1)
+#define I2C_HID_QUIRK_NO_RUNTIME_PM		BIT(2)
+#define I2C_HID_QUIRK_DELAY_AFTER_SLEEP		BIT(3)
+
+/* flags */
+#define I2C_HID_STARTED		0
+#define I2C_HID_RESET_PENDING	1
+#define I2C_HID_READ_PENDING	2
+
+#define I2C_HID_PWR_ON		0x00
+#define I2C_HID_PWR_SLEEP	0x01
+
+/* debug option */
+static bool debug;
+module_param(debug, bool, 0444);
+MODULE_PARM_DESC(debug, "print a lot of debug information");
+
+#define i2c_hid_dbg(ihid, fmt, arg...)					  \
+do {									  \
+	if (debug)							  \
+		dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg); \
+} while (0)
+
+struct i2c_hid_desc {
+	__le16 wHIDDescLength;
+	__le16 bcdVersion;
+	__le16 wReportDescLength;
+	__le16 wReportDescRegister;
+	__le16 wInputRegister;
+	__le16 wMaxInputLength;
+	__le16 wOutputRegister;
+	__le16 wMaxOutputLength;
+	__le16 wCommandRegister;
+	__le16 wDataRegister;
+	__le16 wVendorID;
+	__le16 wProductID;
+	__le16 wVersionID;
+	__le32 reserved;
+} __packed;
+
+struct i2c_hid_cmd {
+	unsigned int registerIndex;
+	__u8 opcode;
+	unsigned int length;
+	bool wait;
+};
+
+union command {
+	u8 data[0];
+	struct cmd {
+		__le16 reg;
+		__u8 reportTypeID;
+		__u8 opcode;
+	} __packed c;
+};
+
+#define I2C_HID_CMD(opcode_) \
+	.opcode = opcode_, .length = 4, \
+	.registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister)
+
+/* fetch HID descriptor */
+static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 };
+/* fetch report descriptors */
+static const struct i2c_hid_cmd hid_report_descr_cmd = {
+		.registerIndex = offsetof(struct i2c_hid_desc,
+			wReportDescRegister),
+		.opcode = 0x00,
+		.length = 2 };
+/* commands */
+static const struct i2c_hid_cmd hid_reset_cmd =		{ I2C_HID_CMD(0x01),
+							  .wait = true };
+static const struct i2c_hid_cmd hid_get_report_cmd =	{ I2C_HID_CMD(0x02) };
+static const struct i2c_hid_cmd hid_set_report_cmd =	{ I2C_HID_CMD(0x03) };
+static const struct i2c_hid_cmd hid_set_power_cmd =	{ I2C_HID_CMD(0x08) };
+static const struct i2c_hid_cmd hid_no_cmd =		{ .length = 0 };
+
+/*
+ * These definitions are not used here, but are defined by the spec.
+ * Keeping them here for documentation purposes.
+ *
+ * static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) };
+ * static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) };
+ * static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) };
+ * static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) };
+ */
+
+/* The main device structure */
+struct i2c_hid {
+	struct i2c_client	*client;	/* i2c client */
+	struct hid_device	*hid;	/* pointer to corresponding HID dev */
+	union {
+		__u8 hdesc_buffer[sizeof(struct i2c_hid_desc)];
+		struct i2c_hid_desc hdesc;	/* the HID Descriptor */
+	};
+	__le16			wHIDDescRegister; /* location of the i2c
+						   * register of the HID
+						   * descriptor. */
+	unsigned int		bufsize;	/* i2c buffer size */
+	u8			*inbuf;		/* Input buffer */
+	u8			*rawbuf;	/* Raw Input buffer */
+	u8			*cmdbuf;	/* Command buffer */
+	u8			*argsbuf;	/* Command arguments buffer */
+
+	unsigned long		flags;		/* device flags */
+	unsigned long		quirks;		/* Various quirks */
+
+	wait_queue_head_t	wait;		/* For waiting the interrupt */
+
+	struct i2c_hid_platform_data pdata;
+
+	bool			irq_wake_enabled;
+	struct mutex		reset_lock;
+
+	unsigned long		sleep_delay;
+};
+
+static const struct i2c_hid_quirks {
+	__u16 idVendor;
+	__u16 idProduct;
+	__u32 quirks;
+} i2c_hid_quirks[] = {
+	{ USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8752,
+		I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+	{ USB_VENDOR_ID_WEIDA, USB_DEVICE_ID_WEIDA_8755,
+		I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV },
+	{ I2C_VENDOR_ID_HANTICK, I2C_PRODUCT_ID_HANTICK_5288,
+		I2C_HID_QUIRK_NO_IRQ_AFTER_RESET |
+		I2C_HID_QUIRK_NO_RUNTIME_PM },
+	{ I2C_VENDOR_ID_RAYDIUM, I2C_PRODUCT_ID_RAYDIUM_4B33,
+		I2C_HID_QUIRK_DELAY_AFTER_SLEEP },
+	{ 0, 0 }
+};
+
+/*
+ * i2c_hid_lookup_quirk: return any quirks associated with a I2C HID device
+ * @idVendor: the 16-bit vendor ID
+ * @idProduct: the 16-bit product ID
+ *
+ * Returns: a u32 quirks value.
+ */
+static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
+{
+	u32 quirks = 0;
+	int n;
+
+	for (n = 0; i2c_hid_quirks[n].idVendor; n++)
+		if (i2c_hid_quirks[n].idVendor == idVendor &&
+		    (i2c_hid_quirks[n].idProduct == (__u16)HID_ANY_ID ||
+		     i2c_hid_quirks[n].idProduct == idProduct))
+			quirks = i2c_hid_quirks[n].quirks;
+
+	return quirks;
+}
+
+static int __i2c_hid_command(struct i2c_client *client,
+		const struct i2c_hid_cmd *command, u8 reportID,
+		u8 reportType, u8 *args, int args_len,
+		unsigned char *buf_recv, int data_len)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	union command *cmd = (union command *)ihid->cmdbuf;
+	int ret;
+	struct i2c_msg msg[2];
+	int msg_num = 1;
+
+	int length = command->length;
+	bool wait = command->wait;
+	unsigned int registerIndex = command->registerIndex;
+
+	/* special case for hid_descr_cmd */
+	if (command == &hid_descr_cmd) {
+		cmd->c.reg = ihid->wHIDDescRegister;
+	} else {
+		cmd->data[0] = ihid->hdesc_buffer[registerIndex];
+		cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1];
+	}
+
+	if (length > 2) {
+		cmd->c.opcode = command->opcode;
+		cmd->c.reportTypeID = reportID | reportType << 4;
+	}
+
+	memcpy(cmd->data + length, args, args_len);
+	length += args_len;
+
+	i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data);
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags & I2C_M_TEN;
+	msg[0].len = length;
+	msg[0].buf = cmd->data;
+	if (data_len > 0) {
+		msg[1].addr = client->addr;
+		msg[1].flags = client->flags & I2C_M_TEN;
+		msg[1].flags |= I2C_M_RD;
+		msg[1].len = data_len;
+		msg[1].buf = buf_recv;
+		msg_num = 2;
+		set_bit(I2C_HID_READ_PENDING, &ihid->flags);
+	}
+
+	if (wait)
+		set_bit(I2C_HID_RESET_PENDING, &ihid->flags);
+
+	ret = i2c_transfer(client->adapter, msg, msg_num);
+
+	if (data_len > 0)
+		clear_bit(I2C_HID_READ_PENDING, &ihid->flags);
+
+	if (ret != msg_num)
+		return ret < 0 ? ret : -EIO;
+
+	ret = 0;
+
+	if (wait && (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET)) {
+		msleep(100);
+	} else if (wait) {
+		i2c_hid_dbg(ihid, "%s: waiting...\n", __func__);
+		if (!wait_event_timeout(ihid->wait,
+				!test_bit(I2C_HID_RESET_PENDING, &ihid->flags),
+				msecs_to_jiffies(5000)))
+			ret = -ENODATA;
+		i2c_hid_dbg(ihid, "%s: finished.\n", __func__);
+	}
+
+	return ret;
+}
+
+static int i2c_hid_command(struct i2c_client *client,
+		const struct i2c_hid_cmd *command,
+		unsigned char *buf_recv, int data_len)
+{
+	return __i2c_hid_command(client, command, 0, 0, NULL, 0,
+				buf_recv, data_len);
+}
+
+static int i2c_hid_get_report(struct i2c_client *client, u8 reportType,
+		u8 reportID, unsigned char *buf_recv, int data_len)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	u8 args[3];
+	int ret;
+	int args_len = 0;
+	u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
+
+	i2c_hid_dbg(ihid, "%s\n", __func__);
+
+	if (reportID >= 0x0F) {
+		args[args_len++] = reportID;
+		reportID = 0x0F;
+	}
+
+	args[args_len++] = readRegister & 0xFF;
+	args[args_len++] = readRegister >> 8;
+
+	ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID,
+		reportType, args, args_len, buf_recv, data_len);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to retrieve report from device.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/**
+ * i2c_hid_set_or_send_report: forward an incoming report to the device
+ * @client: the i2c_client of the device
+ * @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT
+ * @reportID: the report ID
+ * @buf: the actual data to transfer, without the report ID
+ * @len: size of buf
+ * @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report
+ */
+static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType,
+		u8 reportID, unsigned char *buf, size_t data_len, bool use_data)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	u8 *args = ihid->argsbuf;
+	const struct i2c_hid_cmd *hidcmd;
+	int ret;
+	u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister);
+	u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister);
+	u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength);
+	u16 size;
+	int args_len;
+	int index = 0;
+
+	i2c_hid_dbg(ihid, "%s\n", __func__);
+
+	if (data_len > ihid->bufsize)
+		return -EINVAL;
+
+	size =		2			/* size */ +
+			(reportID ? 1 : 0)	/* reportID */ +
+			data_len		/* buf */;
+	args_len =	(reportID >= 0x0F ? 1 : 0) /* optional third byte */ +
+			2			/* dataRegister */ +
+			size			/* args */;
+
+	if (!use_data && maxOutputLength == 0)
+		return -ENOSYS;
+
+	if (reportID >= 0x0F) {
+		args[index++] = reportID;
+		reportID = 0x0F;
+	}
+
+	/*
+	 * use the data register for feature reports or if the device does not
+	 * support the output register
+	 */
+	if (use_data) {
+		args[index++] = dataRegister & 0xFF;
+		args[index++] = dataRegister >> 8;
+		hidcmd = &hid_set_report_cmd;
+	} else {
+		args[index++] = outputRegister & 0xFF;
+		args[index++] = outputRegister >> 8;
+		hidcmd = &hid_no_cmd;
+	}
+
+	args[index++] = size & 0xFF;
+	args[index++] = size >> 8;
+
+	if (reportID)
+		args[index++] = reportID;
+
+	memcpy(&args[index], buf, data_len);
+
+	ret = __i2c_hid_command(client, hidcmd, reportID,
+		reportType, args, args_len, NULL, 0);
+	if (ret) {
+		dev_err(&client->dev, "failed to set a report to device.\n");
+		return ret;
+	}
+
+	return data_len;
+}
+
+static int i2c_hid_set_power(struct i2c_client *client, int power_state)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	int ret;
+	unsigned long now, delay;
+
+	i2c_hid_dbg(ihid, "%s\n", __func__);
+
+	/*
+	 * Some devices require to send a command to wakeup before power on.
+	 * The call will get a return value (EREMOTEIO) but device will be
+	 * triggered and activated. After that, it goes like a normal device.
+	 */
+	if (power_state == I2C_HID_PWR_ON &&
+	    ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) {
+		ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0);
+
+		/* Device was already activated */
+		if (!ret)
+			goto set_pwr_exit;
+	}
+
+	if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
+	    power_state == I2C_HID_PWR_ON) {
+		now = jiffies;
+		if (time_after(ihid->sleep_delay, now)) {
+			delay = jiffies_to_usecs(ihid->sleep_delay - now);
+			usleep_range(delay, delay + 1);
+		}
+	}
+
+	ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state,
+		0, NULL, 0, NULL, 0);
+
+	if (ihid->quirks & I2C_HID_QUIRK_DELAY_AFTER_SLEEP &&
+	    power_state == I2C_HID_PWR_SLEEP)
+		ihid->sleep_delay = jiffies + msecs_to_jiffies(20);
+
+	if (ret)
+		dev_err(&client->dev, "failed to change power setting.\n");
+
+set_pwr_exit:
+	return ret;
+}
+
+static int i2c_hid_hwreset(struct i2c_client *client)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	int ret;
+
+	i2c_hid_dbg(ihid, "%s\n", __func__);
+
+	/*
+	 * This prevents sending feature reports while the device is
+	 * being reset. Otherwise we may lose the reset complete
+	 * interrupt.
+	 */
+	mutex_lock(&ihid->reset_lock);
+
+	ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+	if (ret)
+		goto out_unlock;
+
+	/*
+	 * The HID over I2C specification states that if a DEVICE needs time
+	 * after the PWR_ON request, it should utilise CLOCK stretching.
+	 * However, it has been observered that the Windows driver provides a
+	 * 1ms sleep between the PWR_ON and RESET requests and that some devices
+	 * rely on this.
+	 */
+	usleep_range(1000, 5000);
+
+	i2c_hid_dbg(ihid, "resetting...\n");
+
+	ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0);
+	if (ret) {
+		dev_err(&client->dev, "failed to reset device.\n");
+		i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+	}
+
+out_unlock:
+	mutex_unlock(&ihid->reset_lock);
+	return ret;
+}
+
+static void i2c_hid_get_input(struct i2c_hid *ihid)
+{
+	int ret;
+	u32 ret_size;
+	int size = le16_to_cpu(ihid->hdesc.wMaxInputLength);
+
+	if (size > ihid->bufsize)
+		size = ihid->bufsize;
+
+	ret = i2c_master_recv(ihid->client, ihid->inbuf, size);
+	if (ret != size) {
+		if (ret < 0)
+			return;
+
+		dev_err(&ihid->client->dev, "%s: got %d data instead of %d\n",
+			__func__, ret, size);
+		return;
+	}
+
+	ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8;
+
+	if (!ret_size) {
+		/* host or device initiated RESET completed */
+		if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags))
+			wake_up(&ihid->wait);
+		return;
+	}
+
+	if ((ret_size > size) || (ret_size < 2)) {
+		dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n",
+			__func__, size, ret_size);
+		return;
+	}
+
+	i2c_hid_dbg(ihid, "input: %*ph\n", ret_size, ihid->inbuf);
+
+	if (test_bit(I2C_HID_STARTED, &ihid->flags))
+		hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2,
+				ret_size - 2, 1);
+
+	return;
+}
+
+static irqreturn_t i2c_hid_irq(int irq, void *dev_id)
+{
+	struct i2c_hid *ihid = dev_id;
+
+	if (test_bit(I2C_HID_READ_PENDING, &ihid->flags))
+		return IRQ_HANDLED;
+
+	i2c_hid_get_input(ihid);
+
+	return IRQ_HANDLED;
+}
+
+static int i2c_hid_get_report_length(struct hid_report *report)
+{
+	return ((report->size - 1) >> 3) + 1 +
+		report->device->report_enum[report->type].numbered + 2;
+}
+
+/*
+ * Traverse the supplied list of reports and find the longest
+ */
+static void i2c_hid_find_max_report(struct hid_device *hid, unsigned int type,
+		unsigned int *max)
+{
+	struct hid_report *report;
+	unsigned int size;
+
+	/* We should not rely on wMaxInputLength, as some devices may set it to
+	 * a wrong length. */
+	list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+		size = i2c_hid_get_report_length(report);
+		if (*max < size)
+			*max = size;
+	}
+}
+
+static void i2c_hid_free_buffers(struct i2c_hid *ihid)
+{
+	kfree(ihid->inbuf);
+	kfree(ihid->rawbuf);
+	kfree(ihid->argsbuf);
+	kfree(ihid->cmdbuf);
+	ihid->inbuf = NULL;
+	ihid->rawbuf = NULL;
+	ihid->cmdbuf = NULL;
+	ihid->argsbuf = NULL;
+	ihid->bufsize = 0;
+}
+
+static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size)
+{
+	/* the worst case is computed from the set_report command with a
+	 * reportID > 15 and the maximum report length */
+	int args_len = sizeof(__u8) + /* ReportID */
+		       sizeof(__u8) + /* optional ReportID byte */
+		       sizeof(__u16) + /* data register */
+		       sizeof(__u16) + /* size of the report */
+		       report_size; /* report */
+
+	ihid->inbuf = kzalloc(report_size, GFP_KERNEL);
+	ihid->rawbuf = kzalloc(report_size, GFP_KERNEL);
+	ihid->argsbuf = kzalloc(args_len, GFP_KERNEL);
+	ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL);
+
+	if (!ihid->inbuf || !ihid->rawbuf || !ihid->argsbuf || !ihid->cmdbuf) {
+		i2c_hid_free_buffers(ihid);
+		return -ENOMEM;
+	}
+
+	ihid->bufsize = report_size;
+
+	return 0;
+}
+
+static int i2c_hid_get_raw_report(struct hid_device *hid,
+		unsigned char report_number, __u8 *buf, size_t count,
+		unsigned char report_type)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	size_t ret_count, ask_count;
+	int ret;
+
+	if (report_type == HID_OUTPUT_REPORT)
+		return -EINVAL;
+
+	/* +2 bytes to include the size of the reply in the query buffer */
+	ask_count = min(count + 2, (size_t)ihid->bufsize);
+
+	ret = i2c_hid_get_report(client,
+			report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
+			report_number, ihid->rawbuf, ask_count);
+
+	if (ret < 0)
+		return ret;
+
+	ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8);
+
+	if (ret_count <= 2)
+		return 0;
+
+	ret_count = min(ret_count, ask_count);
+
+	/* The query buffer contains the size, dropping it in the reply */
+	count = min(count, ret_count - 2);
+	memcpy(buf, ihid->rawbuf + 2, count);
+
+	return count;
+}
+
+static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf,
+		size_t count, unsigned char report_type, bool use_data)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	int report_id = buf[0];
+	int ret;
+
+	if (report_type == HID_INPUT_REPORT)
+		return -EINVAL;
+
+	mutex_lock(&ihid->reset_lock);
+
+	if (report_id) {
+		buf++;
+		count--;
+	}
+
+	ret = i2c_hid_set_or_send_report(client,
+				report_type == HID_FEATURE_REPORT ? 0x03 : 0x02,
+				report_id, buf, count, use_data);
+
+	if (report_id && ret >= 0)
+		ret++; /* add report_id to the number of transfered bytes */
+
+	mutex_unlock(&ihid->reset_lock);
+
+	return ret;
+}
+
+static int i2c_hid_output_report(struct hid_device *hid, __u8 *buf,
+		size_t count)
+{
+	return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT,
+			false);
+}
+
+static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+			       __u8 *buf, size_t len, unsigned char rtype,
+			       int reqtype)
+{
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return i2c_hid_get_raw_report(hid, reportnum, buf, len, rtype);
+	case HID_REQ_SET_REPORT:
+		if (buf[0] != reportnum)
+			return -EINVAL;
+		return i2c_hid_output_raw_report(hid, buf, len, rtype, true);
+	default:
+		return -EIO;
+	}
+}
+
+static int i2c_hid_parse(struct hid_device *hid)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	struct i2c_hid_desc *hdesc = &ihid->hdesc;
+	unsigned int rsize;
+	char *rdesc;
+	int ret;
+	int tries = 3;
+
+	i2c_hid_dbg(ihid, "entering %s\n", __func__);
+
+	rsize = le16_to_cpu(hdesc->wReportDescLength);
+	if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+		dbg_hid("weird size of report descriptor (%u)\n", rsize);
+		return -EINVAL;
+	}
+
+	do {
+		ret = i2c_hid_hwreset(client);
+		if (ret)
+			msleep(1000);
+	} while (tries-- > 0 && ret);
+
+	if (ret)
+		return ret;
+
+	rdesc = kzalloc(rsize, GFP_KERNEL);
+
+	if (!rdesc) {
+		dbg_hid("couldn't allocate rdesc memory\n");
+		return -ENOMEM;
+	}
+
+	i2c_hid_dbg(ihid, "asking HID report descriptor\n");
+
+	ret = i2c_hid_command(client, &hid_report_descr_cmd, rdesc, rsize);
+	if (ret) {
+		hid_err(hid, "reading report descriptor failed\n");
+		kfree(rdesc);
+		return -EIO;
+	}
+
+	i2c_hid_dbg(ihid, "Report Descriptor: %*ph\n", rsize, rdesc);
+
+	ret = hid_parse_report(hid, rdesc, rsize);
+	kfree(rdesc);
+	if (ret) {
+		dbg_hid("parsing report descriptor failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int i2c_hid_start(struct hid_device *hid)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	int ret;
+	unsigned int bufsize = HID_MIN_BUFFER_SIZE;
+
+	i2c_hid_find_max_report(hid, HID_INPUT_REPORT, &bufsize);
+	i2c_hid_find_max_report(hid, HID_OUTPUT_REPORT, &bufsize);
+	i2c_hid_find_max_report(hid, HID_FEATURE_REPORT, &bufsize);
+
+	if (bufsize > ihid->bufsize) {
+		disable_irq(client->irq);
+		i2c_hid_free_buffers(ihid);
+
+		ret = i2c_hid_alloc_buffers(ihid, bufsize);
+		enable_irq(client->irq);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void i2c_hid_stop(struct hid_device *hid)
+{
+	hid->claimed = 0;
+}
+
+static int i2c_hid_open(struct hid_device *hid)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	int ret = 0;
+
+	ret = pm_runtime_get_sync(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	set_bit(I2C_HID_STARTED, &ihid->flags);
+	return 0;
+}
+
+static void i2c_hid_close(struct hid_device *hid)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+	clear_bit(I2C_HID_STARTED, &ihid->flags);
+
+	/* Save some power */
+	pm_runtime_put(&client->dev);
+}
+
+static int i2c_hid_power(struct hid_device *hid, int lvl)
+{
+	struct i2c_client *client = hid->driver_data;
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+	i2c_hid_dbg(ihid, "%s lvl:%d\n", __func__, lvl);
+
+	switch (lvl) {
+	case PM_HINT_FULLON:
+		pm_runtime_get_sync(&client->dev);
+		break;
+	case PM_HINT_NORMAL:
+		pm_runtime_put(&client->dev);
+		break;
+	}
+	return 0;
+}
+
+struct hid_ll_driver i2c_hid_ll_driver = {
+	.parse = i2c_hid_parse,
+	.start = i2c_hid_start,
+	.stop = i2c_hid_stop,
+	.open = i2c_hid_open,
+	.close = i2c_hid_close,
+	.power = i2c_hid_power,
+	.output_report = i2c_hid_output_report,
+	.raw_request = i2c_hid_raw_request,
+};
+EXPORT_SYMBOL_GPL(i2c_hid_ll_driver);
+
+static int i2c_hid_init_irq(struct i2c_client *client)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	unsigned long irqflags = 0;
+	int ret;
+
+	dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
+
+	if (!irq_get_trigger_type(client->irq))
+		irqflags = IRQF_TRIGGER_LOW;
+
+	ret = request_threaded_irq(client->irq, NULL, i2c_hid_irq,
+				   irqflags | IRQF_ONESHOT, client->name, ihid);
+	if (ret < 0) {
+		dev_warn(&client->dev,
+			"Could not register for %s interrupt, irq = %d,"
+			" ret = %d\n",
+			client->name, client->irq, ret);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid)
+{
+	struct i2c_client *client = ihid->client;
+	struct i2c_hid_desc *hdesc = &ihid->hdesc;
+	unsigned int dsize;
+	int ret;
+
+	/* i2c hid fetch using a fixed descriptor size (30 bytes) */
+	i2c_hid_dbg(ihid, "Fetching the HID descriptor\n");
+	ret = i2c_hid_command(client, &hid_descr_cmd, ihid->hdesc_buffer,
+				sizeof(struct i2c_hid_desc));
+	if (ret) {
+		dev_err(&client->dev, "hid_descr_cmd failed\n");
+		return -ENODEV;
+	}
+
+	/* Validate the length of HID descriptor, the 4 first bytes:
+	 * bytes 0-1 -> length
+	 * bytes 2-3 -> bcdVersion (has to be 1.00) */
+	/* check bcdVersion == 1.0 */
+	if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) {
+		dev_err(&client->dev,
+			"unexpected HID descriptor bcdVersion (0x%04hx)\n",
+			le16_to_cpu(hdesc->bcdVersion));
+		return -ENODEV;
+	}
+
+	/* Descriptor length should be 30 bytes as per the specification */
+	dsize = le16_to_cpu(hdesc->wHIDDescLength);
+	if (dsize != sizeof(struct i2c_hid_desc)) {
+		dev_err(&client->dev, "weird size of HID descriptor (%u)\n",
+			dsize);
+		return -ENODEV;
+	}
+	i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer);
+	return 0;
+}
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
+	/*
+	 * The CHPN0001 ACPI device, which is used to describe the Chipone
+	 * ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
+	 */
+	{"CHPN0001", 0 },
+	{ },
+};
+
+static int i2c_hid_acpi_pdata(struct i2c_client *client,
+		struct i2c_hid_platform_data *pdata)
+{
+	static guid_t i2c_hid_guid =
+		GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
+			  0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
+	union acpi_object *obj;
+	struct acpi_device *adev;
+	acpi_handle handle;
+
+	handle = ACPI_HANDLE(&client->dev);
+	if (!handle || acpi_bus_get_device(handle, &adev)) {
+		dev_err(&client->dev, "Error could not get ACPI device\n");
+		return -ENODEV;
+	}
+
+	if (acpi_match_device_ids(adev, i2c_hid_acpi_blacklist) == 0)
+		return -ENODEV;
+
+	obj = acpi_evaluate_dsm_typed(handle, &i2c_hid_guid, 1, 1, NULL,
+				      ACPI_TYPE_INTEGER);
+	if (!obj) {
+		dev_err(&client->dev, "Error _DSM call to get HID descriptor address failed\n");
+		return -ENODEV;
+	}
+
+	pdata->hid_descriptor_address = obj->integer.value;
+	ACPI_FREE(obj);
+
+	return 0;
+}
+
+static void i2c_hid_acpi_fix_up_power(struct device *dev)
+{
+	struct acpi_device *adev;
+
+	adev = ACPI_COMPANION(dev);
+	if (adev)
+		acpi_device_fix_up_power(adev);
+}
+
+static const struct acpi_device_id i2c_hid_acpi_match[] = {
+	{"ACPI0C50", 0 },
+	{"PNP0C50", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
+#else
+static inline int i2c_hid_acpi_pdata(struct i2c_client *client,
+		struct i2c_hid_platform_data *pdata)
+{
+	return -ENODEV;
+}
+
+static inline void i2c_hid_acpi_fix_up_power(struct device *dev) {}
+#endif
+
+#ifdef CONFIG_OF
+static int i2c_hid_of_probe(struct i2c_client *client,
+		struct i2c_hid_platform_data *pdata)
+{
+	struct device *dev = &client->dev;
+	u32 val;
+	int ret;
+
+	ret = of_property_read_u32(dev->of_node, "hid-descr-addr", &val);
+	if (ret) {
+		dev_err(&client->dev, "HID register address not provided\n");
+		return -ENODEV;
+	}
+	if (val >> 16) {
+		dev_err(&client->dev, "Bad HID register address: 0x%08x\n",
+			val);
+		return -EINVAL;
+	}
+	pdata->hid_descriptor_address = val;
+
+	return 0;
+}
+
+static const struct of_device_id i2c_hid_of_match[] = {
+	{ .compatible = "hid-over-i2c" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
+#else
+static inline int i2c_hid_of_probe(struct i2c_client *client,
+		struct i2c_hid_platform_data *pdata)
+{
+	return -ENODEV;
+}
+#endif
+
+static void i2c_hid_fwnode_probe(struct i2c_client *client,
+				 struct i2c_hid_platform_data *pdata)
+{
+	u32 val;
+
+	if (!device_property_read_u32(&client->dev, "post-power-on-delay-ms",
+				      &val))
+		pdata->post_power_delay_ms = val;
+}
+
+static int i2c_hid_probe(struct i2c_client *client,
+			 const struct i2c_device_id *dev_id)
+{
+	int ret;
+	struct i2c_hid *ihid;
+	struct hid_device *hid;
+	__u16 hidRegister;
+	struct i2c_hid_platform_data *platform_data = client->dev.platform_data;
+
+	dbg_hid("HID probe called for i2c 0x%02x\n", client->addr);
+
+	if (!client->irq) {
+		dev_err(&client->dev,
+			"HID over i2c has not been provided an Int IRQ\n");
+		return -EINVAL;
+	}
+
+	if (client->irq < 0) {
+		if (client->irq != -EPROBE_DEFER)
+			dev_err(&client->dev,
+				"HID over i2c doesn't have a valid IRQ\n");
+		return client->irq;
+	}
+
+	ihid = devm_kzalloc(&client->dev, sizeof(*ihid), GFP_KERNEL);
+	if (!ihid)
+		return -ENOMEM;
+
+	if (client->dev.of_node) {
+		ret = i2c_hid_of_probe(client, &ihid->pdata);
+		if (ret)
+			return ret;
+	} else if (!platform_data) {
+		ret = i2c_hid_acpi_pdata(client, &ihid->pdata);
+		if (ret)
+			return ret;
+	} else {
+		ihid->pdata = *platform_data;
+	}
+
+	/* Parse platform agnostic common properties from ACPI / device tree */
+	i2c_hid_fwnode_probe(client, &ihid->pdata);
+
+	ihid->pdata.supplies[0].supply = "vdd";
+	ihid->pdata.supplies[1].supply = "vddl";
+
+	ret = devm_regulator_bulk_get(&client->dev,
+				      ARRAY_SIZE(ihid->pdata.supplies),
+				      ihid->pdata.supplies);
+	if (ret)
+		return ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(ihid->pdata.supplies),
+				    ihid->pdata.supplies);
+	if (ret < 0)
+		return ret;
+
+	if (ihid->pdata.post_power_delay_ms)
+		msleep(ihid->pdata.post_power_delay_ms);
+
+	i2c_set_clientdata(client, ihid);
+
+	ihid->client = client;
+
+	hidRegister = ihid->pdata.hid_descriptor_address;
+	ihid->wHIDDescRegister = cpu_to_le16(hidRegister);
+
+	init_waitqueue_head(&ihid->wait);
+	mutex_init(&ihid->reset_lock);
+
+	/* we need to allocate the command buffer without knowing the maximum
+	 * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
+	 * real computation later. */
+	ret = i2c_hid_alloc_buffers(ihid, HID_MIN_BUFFER_SIZE);
+	if (ret < 0)
+		goto err_regulator;
+
+	i2c_hid_acpi_fix_up_power(&client->dev);
+
+	pm_runtime_get_noresume(&client->dev);
+	pm_runtime_set_active(&client->dev);
+	pm_runtime_enable(&client->dev);
+	device_enable_async_suspend(&client->dev);
+
+	/* Make sure there is something at this address */
+	ret = i2c_smbus_read_byte(client);
+	if (ret < 0) {
+		dev_dbg(&client->dev, "nothing at this address: %d\n", ret);
+		ret = -ENXIO;
+		goto err_pm;
+	}
+
+	ret = i2c_hid_fetch_hid_descriptor(ihid);
+	if (ret < 0)
+		goto err_pm;
+
+	ret = i2c_hid_init_irq(client);
+	if (ret < 0)
+		goto err_pm;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		ret = PTR_ERR(hid);
+		goto err_irq;
+	}
+
+	ihid->hid = hid;
+
+	hid->driver_data = client;
+	hid->ll_driver = &i2c_hid_ll_driver;
+	hid->dev.parent = &client->dev;
+	hid->bus = BUS_I2C;
+	hid->version = le16_to_cpu(ihid->hdesc.bcdVersion);
+	hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
+	hid->product = le16_to_cpu(ihid->hdesc.wProductID);
+
+	snprintf(hid->name, sizeof(hid->name), "%s %04hX:%04hX",
+		 client->name, hid->vendor, hid->product);
+	strlcpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
+
+	ihid->quirks = i2c_hid_lookup_quirk(hid->vendor, hid->product);
+
+	ret = hid_add_device(hid);
+	if (ret) {
+		if (ret != -ENODEV)
+			hid_err(client, "can't add hid device: %d\n", ret);
+		goto err_mem_free;
+	}
+
+	if (!(ihid->quirks & I2C_HID_QUIRK_NO_RUNTIME_PM))
+		pm_runtime_put(&client->dev);
+
+	return 0;
+
+err_mem_free:
+	hid_destroy_device(hid);
+
+err_irq:
+	free_irq(client->irq, ihid);
+
+err_pm:
+	pm_runtime_put_noidle(&client->dev);
+	pm_runtime_disable(&client->dev);
+
+err_regulator:
+	regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
+			       ihid->pdata.supplies);
+	i2c_hid_free_buffers(ihid);
+	return ret;
+}
+
+static int i2c_hid_remove(struct i2c_client *client)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	struct hid_device *hid;
+
+	if (!(ihid->quirks & I2C_HID_QUIRK_NO_RUNTIME_PM))
+		pm_runtime_get_sync(&client->dev);
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+
+	hid = ihid->hid;
+	hid_destroy_device(hid);
+
+	free_irq(client->irq, ihid);
+
+	if (ihid->bufsize)
+		i2c_hid_free_buffers(ihid);
+
+	regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
+			       ihid->pdata.supplies);
+
+	return 0;
+}
+
+static void i2c_hid_shutdown(struct i2c_client *client)
+{
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+
+	i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+	free_irq(client->irq, ihid);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int i2c_hid_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	struct hid_device *hid = ihid->hid;
+	int ret;
+	int wake_status;
+
+	if (hid->driver && hid->driver->suspend) {
+		/*
+		 * Wake up the device so that IO issues in
+		 * HID driver's suspend code can succeed.
+		 */
+		ret = pm_runtime_resume(dev);
+		if (ret < 0)
+			return ret;
+
+		ret = hid->driver->suspend(hid, PMSG_SUSPEND);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (!pm_runtime_suspended(dev)) {
+		/* Save some power */
+		i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+
+		disable_irq(client->irq);
+	}
+
+	if (device_may_wakeup(&client->dev)) {
+		wake_status = enable_irq_wake(client->irq);
+		if (!wake_status)
+			ihid->irq_wake_enabled = true;
+		else
+			hid_warn(hid, "Failed to enable irq wake: %d\n",
+				wake_status);
+	} else {
+		regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
+				       ihid->pdata.supplies);
+	}
+
+	return 0;
+}
+
+static int i2c_hid_resume(struct device *dev)
+{
+	int ret;
+	struct i2c_client *client = to_i2c_client(dev);
+	struct i2c_hid *ihid = i2c_get_clientdata(client);
+	struct hid_device *hid = ihid->hid;
+	int wake_status;
+
+	if (!device_may_wakeup(&client->dev)) {
+		ret = regulator_bulk_enable(ARRAY_SIZE(ihid->pdata.supplies),
+					    ihid->pdata.supplies);
+		if (ret)
+			hid_warn(hid, "Failed to enable supplies: %d\n", ret);
+
+		if (ihid->pdata.post_power_delay_ms)
+			msleep(ihid->pdata.post_power_delay_ms);
+	} else if (ihid->irq_wake_enabled) {
+		wake_status = disable_irq_wake(client->irq);
+		if (!wake_status)
+			ihid->irq_wake_enabled = false;
+		else
+			hid_warn(hid, "Failed to disable irq wake: %d\n",
+				wake_status);
+	}
+
+	/* We'll resume to full power */
+	pm_runtime_disable(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+
+	enable_irq(client->irq);
+
+	/* Instead of resetting device, simply powers the device on. This
+	 * solves "incomplete reports" on Raydium devices 2386:3118 and
+	 * 2386:4B33 and fixes various SIS touchscreens no longer sending
+	 * data after a suspend/resume.
+	 */
+	ret = i2c_hid_set_power(client, I2C_HID_PWR_ON);
+	if (ret)
+		return ret;
+
+	if (hid->driver && hid->driver->reset_resume) {
+		ret = hid->driver->reset_resume(hid);
+		return ret;
+	}
+
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static int i2c_hid_runtime_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
+	disable_irq(client->irq);
+	return 0;
+}
+
+static int i2c_hid_runtime_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	enable_irq(client->irq);
+	i2c_hid_set_power(client, I2C_HID_PWR_ON);
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops i2c_hid_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(i2c_hid_suspend, i2c_hid_resume)
+	SET_RUNTIME_PM_OPS(i2c_hid_runtime_suspend, i2c_hid_runtime_resume,
+			   NULL)
+};
+
+static const struct i2c_device_id i2c_hid_id_table[] = {
+	{ "hid", 0 },
+	{ "hid-over-i2c", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, i2c_hid_id_table);
+
+
+static struct i2c_driver i2c_hid_driver = {
+	.driver = {
+		.name	= "i2c_hid",
+		.pm	= &i2c_hid_pm,
+		.acpi_match_table = ACPI_PTR(i2c_hid_acpi_match),
+		.of_match_table = of_match_ptr(i2c_hid_of_match),
+	},
+
+	.probe		= i2c_hid_probe,
+	.remove		= i2c_hid_remove,
+	.shutdown	= i2c_hid_shutdown,
+	.id_table	= i2c_hid_id_table,
+};
+
+module_i2c_driver(i2c_hid_driver);
+
+MODULE_DESCRIPTION("HID over I2C core driver");
+MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-ish-hid/Kconfig
new file mode 100644
index 0000000..519e4c8
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/Kconfig
@@ -0,0 +1,17 @@
+menu "Intel ISH HID support"
+	depends on (X86_64 || COMPILE_TEST) && PCI
+
+config INTEL_ISH_HID
+	tristate "Intel Integrated Sensor Hub"
+	default n
+	select HID
+	help
+	  The Integrated Sensor Hub (ISH) enables the ability to offload
+	  sensor polling and algorithm processing to a dedicated low power
+	  processor in the chipset. This allows the core processor to go into
+	  low power modes more often, resulting in the increased battery life.
+	  The current processors that support ISH are: Cherrytrail, Skylake,
+	  Broxton and Kaby Lake.
+
+	  Say Y here if you want to support Intel ISH. If unsure, say N.
+endmenu
diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile
new file mode 100644
index 0000000..825b70a
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/Makefile
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile - Intel ISH HID drivers
+# Copyright (c) 2014-2016, Intel Corporation.
+#
+#
+obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp.o
+intel-ishtp-objs := ishtp/init.o
+intel-ishtp-objs += ishtp/hbm.o
+intel-ishtp-objs += ishtp/client.o
+intel-ishtp-objs += ishtp/bus.o
+intel-ishtp-objs += ishtp/dma-if.o
+intel-ishtp-objs += ishtp/client-buffers.o
+
+obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
+intel-ish-ipc-objs := ipc/ipc.o
+intel-ish-ipc-objs += ipc/pci-ish.o
+
+obj-$(CONFIG_INTEL_ISH_HID) += intel-ishtp-hid.o
+intel-ishtp-hid-objs := ishtp-hid.o
+intel-ishtp-hid-objs += ishtp-hid-client.o
+
+ccflags-y += -Idrivers/hid/intel-ish-hid/ishtp
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h b/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h
new file mode 100644
index 0000000..a5897b9
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish-regs.h
@@ -0,0 +1,228 @@
+/*
+ * ISH registers definitions
+ *
+ * Copyright (c) 2012-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef _ISHTP_ISH_REGS_H_
+#define _ISHTP_ISH_REGS_H_
+
+
+/*** IPC PCI Offsets and sizes ***/
+/* ISH IPC Base Address */
+#define IPC_REG_BASE		0x0000
+/* Peripheral Interrupt Status Register */
+#define IPC_REG_PISR_CHV_AB      (IPC_REG_BASE + 0x00)
+/* Peripheral Interrupt Mask Register */
+#define IPC_REG_PIMR_CHV_AB      (IPC_REG_BASE + 0x04)
+/*BXT, CHV_K0*/
+/*Peripheral Interrupt Status Register */
+#define IPC_REG_PISR_BXT	 (IPC_REG_BASE + 0x0C)
+/*Peripheral Interrupt Mask Register */
+#define IPC_REG_PIMR_BXT	 (IPC_REG_BASE + 0x08)
+/***********************************/
+/* ISH Host Firmware status Register */
+#define IPC_REG_ISH_HOST_FWSTS	(IPC_REG_BASE + 0x34)
+/* Host Communication Register */
+#define IPC_REG_HOST_COMM	(IPC_REG_BASE + 0x38)
+/* Reset register */
+#define IPC_REG_ISH_RST		(IPC_REG_BASE + 0x44)
+
+/* Inbound doorbell register Host to ISH */
+#define IPC_REG_HOST2ISH_DRBL	(IPC_REG_BASE + 0x48)
+/* Outbound doorbell register ISH to Host */
+#define IPC_REG_ISH2HOST_DRBL	(IPC_REG_BASE + 0x54)
+/* ISH to HOST message registers */
+#define IPC_REG_ISH2HOST_MSG	(IPC_REG_BASE + 0x60)
+/* HOST to ISH message registers */
+#define IPC_REG_HOST2ISH_MSG	(IPC_REG_BASE + 0xE0)
+/* REMAP2 to enable DMA (D3 RCR) */
+#define	IPC_REG_ISH_RMP2	(IPC_REG_BASE + 0x368)
+
+#define	IPC_REG_MAX		(IPC_REG_BASE + 0x400)
+
+/*** register bits - HISR ***/
+/* bit corresponds HOST2ISH interrupt in PISR and PIMR registers */
+#define IPC_INT_HOST2ISH_BIT            (1<<0)
+/***********************************/
+/*CHV_A0, CHV_B0*/
+/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
+#define IPC_INT_ISH2HOST_BIT_CHV_AB	(1<<3)
+/*BXT, CHV_K0*/
+/* bit corresponds ISH2HOST interrupt in PISR and PIMR registers */
+#define IPC_INT_ISH2HOST_BIT_BXT	(1<<0)
+/***********************************/
+
+/* bit corresponds ISH2HOST busy clear interrupt in PIMR register */
+#define IPC_INT_ISH2HOST_CLR_MASK_BIT	(1<<11)
+
+/* offset of ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
+#define IPC_INT_ISH2HOST_CLR_OFFS	(0)
+
+/* bit corresponds ISH2HOST busy clear interrupt in IPC_BUSY_CLR register */
+#define IPC_INT_ISH2HOST_CLR_BIT	(1<<IPC_INT_ISH2HOST_CLR_OFFS)
+
+/* bit corresponds busy bit in doorbell registers */
+#define IPC_DRBL_BUSY_OFFS		(31)
+#define IPC_DRBL_BUSY_BIT		(1<<IPC_DRBL_BUSY_OFFS)
+
+#define	IPC_HOST_OWNS_MSG_OFFS		(30)
+
+/*
+ * A0: bit means that host owns MSGnn registers and is reading them.
+ * ISH FW may not write to them
+ */
+#define	IPC_HOST_OWNS_MSG_BIT		(1<<IPC_HOST_OWNS_MSG_OFFS)
+
+/*
+ * Host status bits (HOSTCOMM)
+ */
+/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
+#define IPC_HOSTCOMM_READY_OFFS		(7)
+#define IPC_HOSTCOMM_READY_BIT		(1<<IPC_HOSTCOMM_READY_OFFS)
+
+/***********************************/
+/*CHV_A0, CHV_B0*/
+#define	IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB	(31)
+#define	IPC_HOSTCOMM_INT_EN_BIT_CHV_AB		\
+	(1<<IPC_HOSTCOMM_INT_EN_OFFS_CHV_AB)
+/*BXT, CHV_K0*/
+#define IPC_PIMR_INT_EN_OFFS_BXT	(0)
+#define IPC_PIMR_INT_EN_BIT_BXT		(1<<IPC_PIMR_INT_EN_OFFS_BXT)
+
+#define IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT	(8)
+#define IPC_HOST2ISH_BUSYCLEAR_MASK_BIT		\
+	(1<<IPC_HOST2ISH_BUSYCLEAR_MASK_OFFS_BXT)
+/***********************************/
+/*
+ * both Host and ISH have ILUP at bit 0
+ * bit corresponds host ready bit in both status registers
+ */
+#define IPC_ILUP_OFFS			(0)
+#define IPC_ILUP_BIT			(1<<IPC_ILUP_OFFS)
+
+/*
+ * ISH FW status bits in ISH FW Status Register
+ */
+#define IPC_ISH_FWSTS_SHIFT		12
+#define IPC_ISH_FWSTS_MASK		GENMASK(15, 12)
+#define IPC_GET_ISH_FWSTS(status)	\
+	(((status) & IPC_ISH_FWSTS_MASK) >> IPC_ISH_FWSTS_SHIFT)
+
+/*
+ * FW status bits (relevant)
+ */
+#define	IPC_FWSTS_ILUP		0x1
+#define	IPC_FWSTS_ISHTP_UP	(1<<1)
+#define	IPC_FWSTS_DMA0		(1<<16)
+#define	IPC_FWSTS_DMA1		(1<<17)
+#define	IPC_FWSTS_DMA2		(1<<18)
+#define	IPC_FWSTS_DMA3		(1<<19)
+
+#define	IPC_ISH_IN_DMA		\
+	(IPC_FWSTS_DMA0 | IPC_FWSTS_DMA1 | IPC_FWSTS_DMA2 | IPC_FWSTS_DMA3)
+
+/* bit corresponds host ready bit in ISH FW Status Register */
+#define IPC_ISH_ISHTP_READY_OFFS		(1)
+#define IPC_ISH_ISHTP_READY_BIT		(1<<IPC_ISH_ISHTP_READY_OFFS)
+
+#define	IPC_RMP2_DMA_ENABLED	0x1	/* Value to enable DMA, per D3 RCR */
+
+#define IPC_MSG_MAX_SIZE	0x80
+
+
+#define IPC_HEADER_LENGTH_MASK		0x03FF
+#define IPC_HEADER_PROTOCOL_MASK	0x0F
+#define IPC_HEADER_MNG_CMD_MASK		0x0F
+
+#define IPC_HEADER_LENGTH_OFFSET	0
+#define IPC_HEADER_PROTOCOL_OFFSET	10
+#define IPC_HEADER_MNG_CMD_OFFSET	16
+
+#define IPC_HEADER_GET_LENGTH(drbl_reg)		\
+	(((drbl_reg) >> IPC_HEADER_LENGTH_OFFSET)&IPC_HEADER_LENGTH_MASK)
+#define IPC_HEADER_GET_PROTOCOL(drbl_reg)	\
+	(((drbl_reg) >> IPC_HEADER_PROTOCOL_OFFSET)&IPC_HEADER_PROTOCOL_MASK)
+#define IPC_HEADER_GET_MNG_CMD(drbl_reg)	\
+	(((drbl_reg) >> IPC_HEADER_MNG_CMD_OFFSET)&IPC_HEADER_MNG_CMD_MASK)
+
+#define IPC_IS_BUSY(drbl_reg)			\
+	(((drbl_reg)&IPC_DRBL_BUSY_BIT) == ((uint32_t)IPC_DRBL_BUSY_BIT))
+
+/***********************************/
+/*CHV_A0, CHV_B0*/
+#define IPC_INT_FROM_ISH_TO_HOST_CHV_AB(drbl_reg) \
+	(((drbl_reg)&IPC_INT_ISH2HOST_BIT_CHV_AB) == \
+	((u32)IPC_INT_ISH2HOST_BIT_CHV_AB))
+/*BXT, CHV_K0*/
+#define IPC_INT_FROM_ISH_TO_HOST_BXT(drbl_reg) \
+	(((drbl_reg)&IPC_INT_ISH2HOST_BIT_BXT) == \
+	((u32)IPC_INT_ISH2HOST_BIT_BXT))
+/***********************************/
+
+#define IPC_BUILD_HEADER(length, protocol, busy)		\
+	(((busy)<<IPC_DRBL_BUSY_OFFS) |				\
+	((protocol) << IPC_HEADER_PROTOCOL_OFFSET) |		\
+	((length)<<IPC_HEADER_LENGTH_OFFSET))
+
+#define IPC_BUILD_MNG_MSG(cmd, length)				\
+	(((1)<<IPC_DRBL_BUSY_OFFS)|				\
+	((IPC_PROTOCOL_MNG)<<IPC_HEADER_PROTOCOL_OFFSET)|	\
+	((cmd)<<IPC_HEADER_MNG_CMD_OFFSET)|			\
+	 ((length)<<IPC_HEADER_LENGTH_OFFSET))
+
+
+#define IPC_SET_HOST_READY(host_status)		\
+				((host_status) |= (IPC_HOSTCOMM_READY_BIT))
+
+#define IPC_SET_HOST_ILUP(host_status)		\
+				((host_status) |= (IPC_ILUP_BIT))
+
+#define IPC_CLEAR_HOST_READY(host_status)	\
+				((host_status) ^= (IPC_HOSTCOMM_READY_BIT))
+
+#define IPC_CLEAR_HOST_ILUP(host_status)	\
+				((host_status) ^= (IPC_ILUP_BIT))
+
+/* todo - temp until PIMR HW ready */
+#define IPC_HOST_BUSY_READING_OFFS	6
+
+/* bit corresponds host ready bit in Host Status Register (HOST_COMM) */
+#define IPC_HOST_BUSY_READING_BIT	(1<<IPC_HOST_BUSY_READING_OFFS)
+
+#define IPC_SET_HOST_BUSY_READING(host_status)	\
+				((host_status) |= (IPC_HOST_BUSY_READING_BIT))
+
+#define IPC_CLEAR_HOST_BUSY_READING(host_status)\
+				((host_status) ^= (IPC_HOST_BUSY_READING_BIT))
+
+
+#define IPC_IS_ISH_ISHTP_READY(ish_status)	\
+		(((ish_status) & IPC_ISH_ISHTP_READY_BIT) ==	\
+			((uint32_t)IPC_ISH_ISHTP_READY_BIT))
+
+#define IPC_IS_ISH_ILUP(ish_status)		\
+		(((ish_status) & IPC_ILUP_BIT) == ((uint32_t)IPC_ILUP_BIT))
+
+
+#define IPC_PROTOCOL_ISHTP		1
+#define IPC_PROTOCOL_MNG		3
+
+#define MNG_RX_CMPL_ENABLE		0
+#define MNG_RX_CMPL_DISABLE		1
+#define MNG_RX_CMPL_INDICATION		2
+#define MNG_RESET_NOTIFY		3
+#define MNG_RESET_NOTIFY_ACK		4
+#define MNG_SYNC_FW_CLOCK		5
+#define MNG_ILLEGAL_CMD			0xFF
+
+#endif /* _ISHTP_ISH_REGS_H_ */
diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
new file mode 100644
index 0000000..08a8327
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h
@@ -0,0 +1,88 @@
+/*
+ * H/W layer of ISHTP provider device (ISH)
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef _ISHTP_HW_ISH_H_
+#define _ISHTP_HW_ISH_H_
+
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include "hw-ish-regs.h"
+#include "ishtp-dev.h"
+
+#define CHV_DEVICE_ID		0x22D8
+#define BXT_Ax_DEVICE_ID	0x0AA2
+#define BXT_Bx_DEVICE_ID	0x1AA2
+#define APL_Ax_DEVICE_ID	0x5AA2
+#define SPT_Ax_DEVICE_ID	0x9D35
+#define CNL_Ax_DEVICE_ID	0x9DFC
+#define GLK_Ax_DEVICE_ID	0x31A2
+#define CNL_H_DEVICE_ID		0xA37C
+#define ICL_MOBILE_DEVICE_ID	0x34FC
+#define SPT_H_DEVICE_ID		0xA135
+
+#define	REVISION_ID_CHT_A0	0x6
+#define	REVISION_ID_CHT_Ax_SI	0x0
+#define	REVISION_ID_CHT_Bx_SI	0x10
+#define	REVISION_ID_CHT_Kx_SI	0x20
+#define	REVISION_ID_CHT_Dx_SI	0x30
+#define	REVISION_ID_CHT_B0	0xB0
+#define	REVISION_ID_SI_MASK	0x70
+
+struct ipc_rst_payload_type {
+	uint16_t	reset_id;
+	uint16_t	reserved;
+};
+
+struct time_sync_format {
+	uint8_t ts1_source;
+	uint8_t ts2_source;
+	uint16_t reserved;
+} __packed;
+
+struct ipc_time_update_msg {
+	uint64_t primary_host_time;
+	struct time_sync_format sync_info;
+	uint64_t secondary_host_time;
+} __packed;
+
+enum {
+	HOST_UTC_TIME_USEC = 0,
+	HOST_SYSTEM_TIME_USEC = 1
+};
+
+struct ish_hw {
+	void __iomem *mem_addr;
+};
+
+/*
+ * ISH FW status type
+ */
+enum {
+	FWSTS_AFTER_RESET		= 0,
+	FWSTS_WAIT_FOR_HOST		= 4,
+	FWSTS_START_KERNEL_DMA		= 5,
+	FWSTS_FW_IS_RUNNING		= 7,
+	FWSTS_SENSOR_APP_LOADED		= 8,
+	FWSTS_SENSOR_APP_RUNNING	= 15
+};
+
+#define to_ish_hw(dev) (struct ish_hw *)((dev)->hw)
+
+irqreturn_t ish_irq_handler(int irq, void *dev_id);
+struct ishtp_device *ish_dev_init(struct pci_dev *pdev);
+int ish_hw_start(struct ishtp_device *dev);
+void ish_device_disable(struct ishtp_device *dev);
+
+#endif /* _ISHTP_HW_ISH_H_ */
diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c
new file mode 100644
index 0000000..bfbca7e
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ipc/ipc.c
@@ -0,0 +1,976 @@
+/*
+ * H/W layer of ISHTP provider device (ISH)
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include "client.h"
+#include "hw-ish.h"
+#include "hbm.h"
+
+/* For FW reset flow */
+static struct work_struct fw_reset_work;
+static struct ishtp_device *ishtp_dev;
+
+/**
+ * ish_reg_read() - Read register
+ * @dev: ISHTP device pointer
+ * @offset: Register offset
+ *
+ * Read 32 bit register at a given offset
+ *
+ * Return: Read register value
+ */
+static inline uint32_t ish_reg_read(const struct ishtp_device *dev,
+	unsigned long offset)
+{
+	struct ish_hw *hw = to_ish_hw(dev);
+
+	return readl(hw->mem_addr + offset);
+}
+
+/**
+ * ish_reg_write() - Write register
+ * @dev: ISHTP device pointer
+ * @offset: Register offset
+ * @value: Value to write
+ *
+ * Writes 32 bit register at a give offset
+ */
+static inline void ish_reg_write(struct ishtp_device *dev,
+				 unsigned long offset,
+				 uint32_t value)
+{
+	struct ish_hw *hw = to_ish_hw(dev);
+
+	writel(value, hw->mem_addr + offset);
+}
+
+/**
+ * _ish_read_fw_sts_reg() - Read FW status register
+ * @dev: ISHTP device pointer
+ *
+ * Read FW status register
+ *
+ * Return: Read register value
+ */
+static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev)
+{
+	return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+}
+
+/**
+ * check_generated_interrupt() - Check if ISH interrupt
+ * @dev: ISHTP device pointer
+ *
+ * Check if an interrupt was generated for ISH
+ *
+ * Return: Read true or false
+ */
+static bool check_generated_interrupt(struct ishtp_device *dev)
+{
+	bool interrupt_generated = true;
+	uint32_t pisr_val = 0;
+
+	if (dev->pdev->device == CHV_DEVICE_ID) {
+		pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
+		interrupt_generated =
+			IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
+	} else {
+		pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT);
+		interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val);
+	}
+
+	return interrupt_generated;
+}
+
+/**
+ * ish_is_input_ready() - Check if FW ready for RX
+ * @dev: ISHTP device pointer
+ *
+ * Check if ISH FW is ready for receiving data
+ *
+ * Return: Read true or false
+ */
+static bool ish_is_input_ready(struct ishtp_device *dev)
+{
+	uint32_t doorbell_val;
+
+	doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL);
+	return !IPC_IS_BUSY(doorbell_val);
+}
+
+/**
+ * set_host_ready() - Indicate host ready
+ * @dev: ISHTP device pointer
+ *
+ * Set host ready indication to FW
+ */
+static void set_host_ready(struct ishtp_device *dev)
+{
+	if (dev->pdev->device == CHV_DEVICE_ID) {
+		if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
+				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
+				REVISION_ID_CHT_Ax_SI)
+			ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81);
+		else if (dev->pdev->revision == REVISION_ID_CHT_B0 ||
+				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
+				REVISION_ID_CHT_Bx_SI ||
+				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
+				REVISION_ID_CHT_Kx_SI ||
+				(dev->pdev->revision & REVISION_ID_SI_MASK) ==
+				REVISION_ID_CHT_Dx_SI) {
+			uint32_t host_comm_val;
+
+			host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM);
+			host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81;
+			ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val);
+		}
+	} else {
+			uint32_t host_pimr_val;
+
+			host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT);
+			host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT;
+			/*
+			 * disable interrupt generated instead of
+			 * RX_complete_msg
+			 */
+			host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT;
+
+			ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val);
+	}
+}
+
+/**
+ * ishtp_fw_is_ready() - Check if FW ready
+ * @dev: ISHTP device pointer
+ *
+ * Check if ISH FW is ready
+ *
+ * Return: Read true or false
+ */
+static bool ishtp_fw_is_ready(struct ishtp_device *dev)
+{
+	uint32_t ish_status = _ish_read_fw_sts_reg(dev);
+
+	return IPC_IS_ISH_ILUP(ish_status) &&
+		IPC_IS_ISH_ISHTP_READY(ish_status);
+}
+
+/**
+ * ish_set_host_rdy() - Indicate host ready
+ * @dev: ISHTP device pointer
+ *
+ * Set host ready indication to FW
+ */
+static void ish_set_host_rdy(struct ishtp_device *dev)
+{
+	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
+
+	IPC_SET_HOST_READY(host_status);
+	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
+}
+
+/**
+ * ish_clr_host_rdy() - Indicate host not ready
+ * @dev: ISHTP device pointer
+ *
+ * Send host not ready indication to FW
+ */
+static void ish_clr_host_rdy(struct ishtp_device *dev)
+{
+	uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM);
+
+	IPC_CLEAR_HOST_READY(host_status);
+	ish_reg_write(dev, IPC_REG_HOST_COMM, host_status);
+}
+
+/**
+ * _ishtp_read_hdr() - Read message header
+ * @dev: ISHTP device pointer
+ *
+ * Read header of 32bit length
+ *
+ * Return: Read register value
+ */
+static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev)
+{
+	return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG);
+}
+
+/**
+ * _ishtp_read - Read message
+ * @dev: ISHTP device pointer
+ * @buffer: message buffer
+ * @buffer_length: length of message buffer
+ *
+ * Read message from FW
+ *
+ * Return: Always 0
+ */
+static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer,
+	unsigned long buffer_length)
+{
+	uint32_t	i;
+	uint32_t	*r_buf = (uint32_t *)buffer;
+	uint32_t	msg_offs;
+
+	msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr);
+	for (i = 0; i < buffer_length; i += sizeof(uint32_t))
+		*r_buf++ = ish_reg_read(dev, msg_offs + i);
+
+	return 0;
+}
+
+/**
+ * write_ipc_from_queue() - try to write ipc msg from Tx queue to device
+ * @dev: ishtp device pointer
+ *
+ * Check if DRBL is cleared. if it is - write the first IPC msg,  then call
+ * the callback function (unless it's NULL)
+ *
+ * Return: 0 for success else failure code
+ */
+static int write_ipc_from_queue(struct ishtp_device *dev)
+{
+	struct wr_msg_ctl_info	*ipc_link;
+	unsigned long	length;
+	unsigned long	rem;
+	unsigned long	flags;
+	uint32_t	doorbell_val;
+	uint32_t	*r_buf;
+	uint32_t	reg_addr;
+	int	i;
+	void	(*ipc_send_compl)(void *);
+	void	*ipc_send_compl_prm;
+	static int	out_ipc_locked;
+	unsigned long	out_ipc_flags;
+
+	if (dev->dev_state == ISHTP_DEV_DISABLED)
+		return	-EINVAL;
+
+	spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags);
+	if (out_ipc_locked) {
+		spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+		return -EBUSY;
+	}
+	out_ipc_locked = 1;
+	if (!ish_is_input_ready(dev)) {
+		out_ipc_locked = 0;
+		spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+		return -EBUSY;
+	}
+	spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags);
+
+	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+	/*
+	 * if tx send list is empty - return 0;
+	 * may happen, as RX_COMPLETE handler doesn't check list emptiness.
+	 */
+	if (list_empty(&dev->wr_processing_list_head.link)) {
+		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+		out_ipc_locked = 0;
+		return	0;
+	}
+
+	ipc_link = list_entry(dev->wr_processing_list_head.link.next,
+			      struct wr_msg_ctl_info, link);
+	/* first 4 bytes of the data is the doorbell value (IPC header) */
+	length = ipc_link->length - sizeof(uint32_t);
+	doorbell_val = *(uint32_t *)ipc_link->inline_data;
+	r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t));
+
+	/* If sending MNG_SYNC_FW_CLOCK, update clock again */
+	if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG &&
+		IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) {
+		uint64_t usec_system, usec_utc;
+		struct ipc_time_update_msg time_update;
+		struct time_sync_format ts_format;
+
+		usec_system = ktime_to_us(ktime_get_boottime());
+		usec_utc = ktime_to_us(ktime_get_real());
+		ts_format.ts1_source = HOST_SYSTEM_TIME_USEC;
+		ts_format.ts2_source = HOST_UTC_TIME_USEC;
+		ts_format.reserved = 0;
+
+		time_update.primary_host_time = usec_system;
+		time_update.secondary_host_time = usec_utc;
+		time_update.sync_info = ts_format;
+
+		memcpy(r_buf, &time_update,
+		       sizeof(struct ipc_time_update_msg));
+	}
+
+	for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++,
+			reg_addr += 4)
+		ish_reg_write(dev, reg_addr, r_buf[i]);
+
+	rem = length & 0x3;
+	if (rem > 0) {
+		uint32_t reg = 0;
+
+		memcpy(&reg, &r_buf[length >> 2], rem);
+		ish_reg_write(dev, reg_addr, reg);
+	}
+	/* Flush writes to msg registers and doorbell */
+	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+	/* Update IPC counters */
+	++dev->ipc_tx_cnt;
+	dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
+
+	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val);
+	out_ipc_locked = 0;
+
+	ipc_send_compl = ipc_link->ipc_send_compl;
+	ipc_send_compl_prm = ipc_link->ipc_send_compl_prm;
+	list_del_init(&ipc_link->link);
+	list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link);
+	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+	/*
+	 * callback will be called out of spinlock,
+	 * after ipc_link returned to free list
+	 */
+	if (ipc_send_compl)
+		ipc_send_compl(ipc_send_compl_prm);
+
+	return 0;
+}
+
+/**
+ * write_ipc_to_queue() - write ipc msg to Tx queue
+ * @dev: ishtp device instance
+ * @ipc_send_compl: Send complete callback
+ * @ipc_send_compl_prm:	Parameter to send in complete callback
+ * @msg: Pointer to message
+ * @length: Length of message
+ *
+ * Recived msg with IPC (and upper protocol) header  and add it to the device
+ *  Tx-to-write list then try to send the first IPC waiting msg
+ *  (if DRBL is cleared)
+ * This function returns negative value for failure (means free list
+ *  is empty, or msg too long) and 0 for success.
+ *
+ * Return: 0 for success else failure code
+ */
+static int write_ipc_to_queue(struct ishtp_device *dev,
+	void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
+	unsigned char *msg, int length)
+{
+	struct wr_msg_ctl_info *ipc_link;
+	unsigned long	flags;
+
+	if (length > IPC_FULL_MSG_SIZE)
+		return -EMSGSIZE;
+
+	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+	if (list_empty(&dev->wr_free_list_head.link)) {
+		spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+		return -ENOMEM;
+	}
+	ipc_link = list_entry(dev->wr_free_list_head.link.next,
+		struct wr_msg_ctl_info, link);
+	list_del_init(&ipc_link->link);
+
+	ipc_link->ipc_send_compl = ipc_send_compl;
+	ipc_link->ipc_send_compl_prm = ipc_send_compl_prm;
+	ipc_link->length = length;
+	memcpy(ipc_link->inline_data, msg, length);
+
+	list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link);
+	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+	write_ipc_from_queue(dev);
+
+	return 0;
+}
+
+/**
+ * ipc_send_mng_msg() - Send management message
+ * @dev: ishtp device instance
+ * @msg_code: Message code
+ * @msg: Pointer to message
+ * @size: Length of message
+ *
+ * Send management message to FW
+ *
+ * Return: 0 for success else failure code
+ */
+static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code,
+	void *msg, size_t size)
+{
+	unsigned char	ipc_msg[IPC_FULL_MSG_SIZE];
+	uint32_t	drbl_val = IPC_BUILD_MNG_MSG(msg_code, size);
+
+	memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
+	memcpy(ipc_msg + sizeof(uint32_t), msg, size);
+	return	write_ipc_to_queue(dev, NULL, NULL, ipc_msg,
+		sizeof(uint32_t) + size);
+}
+
+#define WAIT_FOR_FW_RDY			0x1
+#define WAIT_FOR_INPUT_RDY		0x2
+
+/**
+ * timed_wait_for_timeout() - wait special event with timeout
+ * @dev: ISHTP device pointer
+ * @condition: indicate the condition for waiting
+ * @timeinc: time slice for every wait cycle, in ms
+ * @timeout: time in ms for timeout
+ *
+ * This function will check special event to be ready in a loop, the loop
+ * period is specificd in timeinc. Wait timeout will causes failure.
+ *
+ * Return: 0 for success else failure code
+ */
+static int timed_wait_for_timeout(struct ishtp_device *dev, int condition,
+				unsigned int timeinc, unsigned int timeout)
+{
+	bool complete = false;
+	int ret;
+
+	do {
+		if (condition == WAIT_FOR_FW_RDY) {
+			complete = ishtp_fw_is_ready(dev);
+		} else if (condition == WAIT_FOR_INPUT_RDY) {
+			complete = ish_is_input_ready(dev);
+		} else {
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (!complete) {
+			unsigned long left_time;
+
+			left_time = msleep_interruptible(timeinc);
+			timeout -= (timeinc - left_time);
+		}
+	} while (!complete && timeout > 0);
+
+	if (complete)
+		ret = 0;
+	else
+		ret = -EBUSY;
+
+out:
+	return ret;
+}
+
+#define TIME_SLICE_FOR_FW_RDY_MS		100
+#define TIME_SLICE_FOR_INPUT_RDY_MS		100
+#define TIMEOUT_FOR_FW_RDY_MS			2000
+#define TIMEOUT_FOR_INPUT_RDY_MS		2000
+
+/**
+ * ish_fw_reset_handler() - FW reset handler
+ * @dev: ishtp device pointer
+ *
+ * Handle FW reset
+ *
+ * Return: 0 for success else failure code
+ */
+static int ish_fw_reset_handler(struct ishtp_device *dev)
+{
+	uint32_t	reset_id;
+	unsigned long	flags;
+	struct wr_msg_ctl_info *processing, *next;
+
+	/* Read reset ID */
+	reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF;
+
+	/* Clear IPC output queue */
+	spin_lock_irqsave(&dev->wr_processing_spinlock, flags);
+	list_for_each_entry_safe(processing, next,
+			&dev->wr_processing_list_head.link, link) {
+		list_move_tail(&processing->link, &dev->wr_free_list_head.link);
+	}
+	spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags);
+
+	/* ISHTP notification in IPC_RESET */
+	ishtp_reset_handler(dev);
+
+	if (!ish_is_input_ready(dev))
+		timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY,
+			TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS);
+
+	/* ISH FW is dead */
+	if (!ish_is_input_ready(dev))
+		return	-EPIPE;
+	/*
+	 * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending
+	 * RESET_NOTIFY_ACK - FW will be checking for it
+	 */
+	ish_set_host_rdy(dev);
+	/* Send RESET_NOTIFY_ACK (with reset_id) */
+	ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id,
+			 sizeof(uint32_t));
+
+	/* Wait for ISH FW'es ILUP and ISHTP_READY */
+	timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY,
+			TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS);
+	if (!ishtp_fw_is_ready(dev)) {
+		/* ISH FW is dead */
+		uint32_t	ish_status;
+
+		ish_status = _ish_read_fw_sts_reg(dev);
+		dev_err(dev->devc,
+			"[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n",
+			ish_status);
+		return -ENODEV;
+	}
+	return	0;
+}
+
+#define TIMEOUT_FOR_HW_RDY_MS			300
+
+/**
+ * ish_fw_reset_work_fn() - FW reset worker function
+ * @unused: not used
+ *
+ * Call ish_fw_reset_handler to complete FW reset
+ */
+static void fw_reset_work_fn(struct work_struct *unused)
+{
+	int	rv;
+
+	rv = ish_fw_reset_handler(ishtp_dev);
+	if (!rv) {
+		/* ISH is ILUP & ISHTP-ready. Restart ISHTP */
+		msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS);
+		ishtp_dev->recvd_hw_ready = 1;
+		wake_up_interruptible(&ishtp_dev->wait_hw_ready);
+
+		/* ISHTP notification in IPC_RESET sequence completion */
+		ishtp_reset_compl_handler(ishtp_dev);
+	} else
+		dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
+			rv);
+}
+
+/**
+ * _ish_sync_fw_clock() -Sync FW clock with the OS clock
+ * @dev: ishtp device pointer
+ *
+ * Sync FW and OS time
+ */
+static void _ish_sync_fw_clock(struct ishtp_device *dev)
+{
+	static unsigned long	prev_sync;
+	uint64_t	usec;
+
+	if (prev_sync && jiffies - prev_sync < 20 * HZ)
+		return;
+
+	prev_sync = jiffies;
+	usec = ktime_to_us(ktime_get_boottime());
+	ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t));
+}
+
+/**
+ * recv_ipc() - Receive and process IPC management messages
+ * @dev: ishtp device instance
+ * @doorbell_val: doorbell value
+ *
+ * This function runs in ISR context.
+ * NOTE: Any other mng command than reset_notify and reset_notify_ack
+ * won't wake BH handler
+ */
+static void	recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val)
+{
+	uint32_t	mng_cmd;
+
+	mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val);
+
+	switch (mng_cmd) {
+	default:
+		break;
+
+	case MNG_RX_CMPL_INDICATION:
+		if (dev->suspend_flag) {
+			dev->suspend_flag = 0;
+			wake_up_interruptible(&dev->suspend_wait);
+		}
+		if (dev->resume_flag) {
+			dev->resume_flag = 0;
+			wake_up_interruptible(&dev->resume_wait);
+		}
+
+		write_ipc_from_queue(dev);
+		break;
+
+	case MNG_RESET_NOTIFY:
+		if (!ishtp_dev) {
+			ishtp_dev = dev;
+			INIT_WORK(&fw_reset_work, fw_reset_work_fn);
+		}
+		schedule_work(&fw_reset_work);
+		break;
+
+	case MNG_RESET_NOTIFY_ACK:
+		dev->recvd_hw_ready = 1;
+		wake_up_interruptible(&dev->wait_hw_ready);
+		break;
+	}
+}
+
+/**
+ * ish_irq_handler() - ISH IRQ handler
+ * @irq: irq number
+ * @dev_id: ishtp device pointer
+ *
+ * ISH IRQ handler. If interrupt is generated and is for ISH it will process
+ * the interrupt.
+ */
+irqreturn_t ish_irq_handler(int irq, void *dev_id)
+{
+	struct ishtp_device	*dev = dev_id;
+	uint32_t	doorbell_val;
+	bool	interrupt_generated;
+
+	/* Check that it's interrupt from ISH (may be shared) */
+	interrupt_generated = check_generated_interrupt(dev);
+
+	if (!interrupt_generated)
+		return IRQ_NONE;
+
+	doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL);
+	if (!IPC_IS_BUSY(doorbell_val))
+		return IRQ_HANDLED;
+
+	if (dev->dev_state == ISHTP_DEV_DISABLED)
+		return	IRQ_HANDLED;
+
+	/* Sanity check: IPC dgram length in header */
+	if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) {
+		dev_err(dev->devc,
+			"IPC hdr - bad length: %u; dropped\n",
+			(unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val));
+		goto	eoi;
+	}
+
+	switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) {
+	default:
+		break;
+	case IPC_PROTOCOL_MNG:
+		recv_ipc(dev, doorbell_val);
+		break;
+	case IPC_PROTOCOL_ISHTP:
+		ishtp_recv(dev);
+		break;
+	}
+
+eoi:
+	/* Update IPC counters */
+	++dev->ipc_rx_cnt;
+	dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val);
+
+	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
+	/* Flush write to doorbell */
+	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+	return	IRQ_HANDLED;
+}
+
+/**
+ * ish_disable_dma() - disable dma communication between host and ISHFW
+ * @dev: ishtp device pointer
+ *
+ * Clear the dma enable bit and wait for dma inactive.
+ *
+ * Return: 0 for success else error code.
+ */
+static int ish_disable_dma(struct ishtp_device *dev)
+{
+	unsigned int	dma_delay;
+
+	/* Clear the dma enable bit */
+	ish_reg_write(dev, IPC_REG_ISH_RMP2, 0);
+
+	/* wait for dma inactive */
+	for (dma_delay = 0; dma_delay < MAX_DMA_DELAY &&
+		_ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA);
+		dma_delay += 5)
+		mdelay(5);
+
+	if (dma_delay >= MAX_DMA_DELAY) {
+		dev_err(dev->devc,
+			"Wait for DMA inactive timeout\n");
+		return	-EBUSY;
+	}
+
+	return 0;
+}
+
+/**
+ * ish_wakeup() - wakeup ishfw from waiting-for-host state
+ * @dev: ishtp device pointer
+ *
+ * Set the dma enable bit and send a void message to FW,
+ * it wil wakeup FW from waiting-for-host state.
+ */
+static void ish_wakeup(struct ishtp_device *dev)
+{
+	/* Set dma enable bit */
+	ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED);
+
+	/*
+	 * Send 0 IPC message so that ISH FW wakes up if it was already
+	 * asleep.
+	 */
+	ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT);
+
+	/* Flush writes to doorbell and REMAP2 */
+	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+}
+
+/**
+ * _ish_hw_reset() - HW reset
+ * @dev: ishtp device pointer
+ *
+ * Reset ISH HW to recover if any error
+ *
+ * Return: 0 for success else error fault code
+ */
+static int _ish_hw_reset(struct ishtp_device *dev)
+{
+	struct pci_dev *pdev = dev->pdev;
+	int	rv;
+	uint16_t csr;
+
+	if (!pdev)
+		return	-ENODEV;
+
+	rv = pci_reset_function(pdev);
+	if (!rv)
+		dev->dev_state = ISHTP_DEV_RESETTING;
+
+	if (!pdev->pm_cap) {
+		dev_err(&pdev->dev, "Can't reset - no PM caps\n");
+		return	-EINVAL;
+	}
+
+	/* Disable dma communication between FW and host */
+	if (ish_disable_dma(dev)) {
+		dev_err(&pdev->dev,
+			"Can't reset - stuck with DMA in-progress\n");
+		return	-EBUSY;
+	}
+
+	pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr);
+
+	csr &= ~PCI_PM_CTRL_STATE_MASK;
+	csr |= PCI_D3hot;
+	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
+
+	mdelay(pdev->d3_delay);
+
+	csr &= ~PCI_PM_CTRL_STATE_MASK;
+	csr |= PCI_D0;
+	pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr);
+
+	/* Now we can enable ISH DMA operation and wakeup ISHFW */
+	ish_wakeup(dev);
+
+	return	0;
+}
+
+/**
+ * _ish_ipc_reset() - IPC reset
+ * @dev: ishtp device pointer
+ *
+ * Resets host and fw IPC and upper layers
+ *
+ * Return: 0 for success else error fault code
+ */
+static int _ish_ipc_reset(struct ishtp_device *dev)
+{
+	struct ipc_rst_payload_type ipc_mng_msg;
+	int	rv = 0;
+
+	ipc_mng_msg.reset_id = 1;
+	ipc_mng_msg.reserved = 0;
+
+	set_host_ready(dev);
+
+	/* Clear the incoming doorbell */
+	ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0);
+	/* Flush write to doorbell */
+	ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS);
+
+	dev->recvd_hw_ready = 0;
+
+	/* send message */
+	rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg,
+		sizeof(struct ipc_rst_payload_type));
+	if (rv) {
+		dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n");
+		return	rv;
+	}
+
+	wait_event_interruptible_timeout(dev->wait_hw_ready,
+					 dev->recvd_hw_ready, 2 * HZ);
+	if (!dev->recvd_hw_ready) {
+		dev_err(dev->devc, "Timed out waiting for HW ready\n");
+		rv = -ENODEV;
+	}
+
+	return rv;
+}
+
+/**
+ * ish_hw_start() -Start ISH HW
+ * @dev: ishtp device pointer
+ *
+ * Set host to ready state and wait for FW reset
+ *
+ * Return: 0 for success else error fault code
+ */
+int ish_hw_start(struct ishtp_device *dev)
+{
+	ish_set_host_rdy(dev);
+
+	/* After that we can enable ISH DMA operation and wakeup ISHFW */
+	ish_wakeup(dev);
+
+	set_host_ready(dev);
+
+	/* wait for FW-initiated reset flow */
+	if (!dev->recvd_hw_ready)
+		wait_event_interruptible_timeout(dev->wait_hw_ready,
+						 dev->recvd_hw_ready,
+						 10 * HZ);
+
+	if (!dev->recvd_hw_ready) {
+		dev_err(dev->devc,
+			"[ishtp-ish]: Timed out waiting for FW-initiated reset\n");
+		return	-ENODEV;
+	}
+
+	return 0;
+}
+
+/**
+ * ish_ipc_get_header() -Get doorbell value
+ * @dev: ishtp device pointer
+ * @length: length of message
+ * @busy: busy status
+ *
+ * Get door bell value from message header
+ *
+ * Return: door bell value
+ */
+static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
+				   int busy)
+{
+	uint32_t drbl_val;
+
+	drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy);
+
+	return drbl_val;
+}
+
+static const struct ishtp_hw_ops ish_hw_ops = {
+	.hw_reset = _ish_hw_reset,
+	.ipc_reset = _ish_ipc_reset,
+	.ipc_get_header = ish_ipc_get_header,
+	.ishtp_read = _ishtp_read,
+	.write = write_ipc_to_queue,
+	.get_fw_status = _ish_read_fw_sts_reg,
+	.sync_fw_clock = _ish_sync_fw_clock,
+	.ishtp_read_hdr = _ishtp_read_hdr
+};
+
+/**
+ * ish_dev_init() -Initialize ISH devoce
+ * @pdev: PCI device
+ *
+ * Allocate ISHTP device and initialize IPC processing
+ *
+ * Return: ISHTP device instance on success else NULL
+ */
+struct ishtp_device *ish_dev_init(struct pci_dev *pdev)
+{
+	struct ishtp_device *dev;
+	int	i;
+
+	dev = devm_kzalloc(&pdev->dev,
+			   sizeof(struct ishtp_device) + sizeof(struct ish_hw),
+			   GFP_KERNEL);
+	if (!dev)
+		return NULL;
+
+	ishtp_device_init(dev);
+
+	init_waitqueue_head(&dev->wait_hw_ready);
+
+	spin_lock_init(&dev->wr_processing_spinlock);
+	spin_lock_init(&dev->out_ipc_spinlock);
+
+	/* Init IPC processing and free lists */
+	INIT_LIST_HEAD(&dev->wr_processing_list_head.link);
+	INIT_LIST_HEAD(&dev->wr_free_list_head.link);
+	for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) {
+		struct wr_msg_ctl_info	*tx_buf;
+
+		tx_buf = devm_kzalloc(&pdev->dev,
+				      sizeof(struct wr_msg_ctl_info),
+				      GFP_KERNEL);
+		if (!tx_buf) {
+			/*
+			 * IPC buffers may be limited or not available
+			 * at all - although this shouldn't happen
+			 */
+			dev_err(dev->devc,
+				"[ishtp-ish]: failure in Tx FIFO allocations (%d)\n",
+				i);
+			break;
+		}
+		list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link);
+	}
+
+	dev->ops = &ish_hw_ops;
+	dev->devc = &pdev->dev;
+	dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr);
+	return dev;
+}
+
+/**
+ * ish_device_disable() - Disable ISH device
+ * @dev: ISHTP device pointer
+ *
+ * Disable ISH by clearing host ready to inform firmware.
+ */
+void	ish_device_disable(struct ishtp_device *dev)
+{
+	struct pci_dev *pdev = dev->pdev;
+
+	if (!pdev)
+		return;
+
+	/* Disable dma communication between FW and host */
+	if (ish_disable_dma(dev)) {
+		dev_err(&pdev->dev,
+			"Can't reset - stuck with DMA in-progress\n");
+		return;
+	}
+
+	/* Put ISH to D3hot state for power saving */
+	pci_set_power_state(pdev, PCI_D3hot);
+
+	dev->dev_state = ISHTP_DEV_DISABLED;
+	ish_clr_host_rdy(dev);
+}
diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
new file mode 100644
index 0000000..256b301
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c
@@ -0,0 +1,342 @@
+/*
+ * PCI glue for ISHTP provider device (ISH) driver
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#define CREATE_TRACE_POINTS
+#include <trace/events/intel_ish.h>
+#include "ishtp-dev.h"
+#include "hw-ish.h"
+
+static const struct pci_device_id ish_pci_tbl[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
+	{0, }
+};
+MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
+
+/**
+ * ish_event_tracer() - Callback function to dump trace messages
+ * @dev:	ishtp device
+ * @format:	printf style format
+ *
+ * Callback to direct log messages to Linux trace buffers
+ */
+static __printf(2, 3)
+void ish_event_tracer(struct ishtp_device *dev, const char *format, ...)
+{
+	if (trace_ishtp_dump_enabled()) {
+		va_list args;
+		char tmp_buf[100];
+
+		va_start(args, format);
+		vsnprintf(tmp_buf, sizeof(tmp_buf), format, args);
+		va_end(args);
+
+		trace_ishtp_dump(tmp_buf);
+	}
+}
+
+/**
+ * ish_init() - Init function
+ * @dev:	ishtp device
+ *
+ * This function initialize wait queues for suspend/resume and call
+ * calls hadware initialization function. This will initiate
+ * startup sequence
+ *
+ * Return: 0 for success or error code for failure
+ */
+static int ish_init(struct ishtp_device *dev)
+{
+	int ret;
+
+	/* Set the state of ISH HW to start */
+	ret = ish_hw_start(dev);
+	if (ret) {
+		dev_err(dev->devc, "ISH: hw start failed.\n");
+		return ret;
+	}
+
+	/* Start the inter process communication to ISH processor */
+	ret = ishtp_start(dev);
+	if (ret) {
+		dev_err(dev->devc, "ISHTP: Protocol init failed.\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct pci_device_id ish_invalid_pci_ids[] = {
+	/* Mehlow platform special pci ids */
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
+	{}
+};
+
+/**
+ * ish_probe() - PCI driver probe callback
+ * @pdev:	pci device
+ * @ent:	pci device id
+ *
+ * Initialize PCI function, setup interrupt and call for ISH initialization
+ *
+ * Return: 0 for success or error code for failure
+ */
+static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+	struct ishtp_device *dev;
+	struct ish_hw *hw;
+	int	ret;
+
+	/* Check for invalid platforms for ISH support */
+	if (pci_dev_present(ish_invalid_pci_ids))
+		return -ENODEV;
+
+	/* enable pci dev */
+	ret = pci_enable_device(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "ISH: Failed to enable PCI device\n");
+		return ret;
+	}
+
+	/* set PCI host mastering */
+	pci_set_master(pdev);
+
+	/* pci request regions for ISH driver */
+	ret = pci_request_regions(pdev, KBUILD_MODNAME);
+	if (ret) {
+		dev_err(&pdev->dev, "ISH: Failed to get PCI regions\n");
+		goto disable_device;
+	}
+
+	/* allocates and initializes the ISH dev structure */
+	dev = ish_dev_init(pdev);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto release_regions;
+	}
+	hw = to_ish_hw(dev);
+	dev->print_log = ish_event_tracer;
+
+	/* mapping IO device memory */
+	hw->mem_addr = pci_iomap(pdev, 0, 0);
+	if (!hw->mem_addr) {
+		dev_err(&pdev->dev, "ISH: mapping I/O range failure\n");
+		ret = -ENOMEM;
+		goto free_device;
+	}
+
+	dev->pdev = pdev;
+
+	pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3;
+
+	/* request and enable interrupt */
+	ret = request_irq(pdev->irq, ish_irq_handler, IRQF_SHARED,
+			  KBUILD_MODNAME, dev);
+	if (ret) {
+		dev_err(&pdev->dev, "ISH: request IRQ failure (%d)\n",
+			pdev->irq);
+		goto free_device;
+	}
+
+	dev_set_drvdata(dev->devc, dev);
+
+	init_waitqueue_head(&dev->suspend_wait);
+	init_waitqueue_head(&dev->resume_wait);
+
+	ret = ish_init(dev);
+	if (ret)
+		goto free_irq;
+
+	return 0;
+
+free_irq:
+	free_irq(pdev->irq, dev);
+free_device:
+	pci_iounmap(pdev, hw->mem_addr);
+release_regions:
+	pci_release_regions(pdev);
+disable_device:
+	pci_clear_master(pdev);
+	pci_disable_device(pdev);
+	dev_err(&pdev->dev, "ISH: PCI driver initialization failed.\n");
+
+	return ret;
+}
+
+/**
+ * ish_remove() - PCI driver remove callback
+ * @pdev:	pci device
+ *
+ * This function does cleanup of ISH on pci remove callback
+ */
+static void ish_remove(struct pci_dev *pdev)
+{
+	struct ishtp_device *ishtp_dev = pci_get_drvdata(pdev);
+	struct ish_hw *hw = to_ish_hw(ishtp_dev);
+
+	ishtp_bus_remove_all_clients(ishtp_dev, false);
+	ish_device_disable(ishtp_dev);
+
+	free_irq(pdev->irq, ishtp_dev);
+	pci_iounmap(pdev, hw->mem_addr);
+	pci_release_regions(pdev);
+	pci_clear_master(pdev);
+	pci_disable_device(pdev);
+}
+
+static struct device __maybe_unused *ish_resume_device;
+
+/* 50ms to get resume response */
+#define WAIT_FOR_RESUME_ACK_MS		50
+
+/**
+ * ish_resume_handler() - Work function to complete resume
+ * @work:	work struct
+ *
+ * The resume work function to complete resume function asynchronously.
+ * There are two resume paths, one where ISH is not powered off,
+ * in that case a simple resume message is enough, others we need
+ * a reset sequence.
+ */
+static void __maybe_unused ish_resume_handler(struct work_struct *work)
+{
+	struct pci_dev *pdev = to_pci_dev(ish_resume_device);
+	struct ishtp_device *dev = pci_get_drvdata(pdev);
+	uint32_t fwsts;
+	int ret;
+
+	/* Get ISH FW status */
+	fwsts = IPC_GET_ISH_FWSTS(dev->ops->get_fw_status(dev));
+
+	/*
+	 * If currently, in ISH FW, sensor app is loaded or beyond that,
+	 * it means ISH isn't powered off, in this case, send a resume message.
+	 */
+	if (fwsts >= FWSTS_SENSOR_APP_LOADED) {
+		ishtp_send_resume(dev);
+
+		/* Waiting to get resume response */
+		if (dev->resume_flag)
+			ret = wait_event_interruptible_timeout(dev->resume_wait,
+				!dev->resume_flag,
+				msecs_to_jiffies(WAIT_FOR_RESUME_ACK_MS));
+	}
+
+	/*
+	 * If in ISH FW, sensor app isn't loaded yet, or no resume response.
+	 * That means this platform is not S0ix compatible, or something is
+	 * wrong with ISH FW. So on resume, full reboot of ISH processor will
+	 * happen, so need to go through init sequence again.
+	 */
+	if (dev->resume_flag)
+		ish_init(dev);
+}
+
+/**
+ * ish_suspend() - ISH suspend callback
+ * @device:	device pointer
+ *
+ * ISH suspend callback
+ *
+ * Return: 0 to the pm core
+ */
+static int __maybe_unused ish_suspend(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct ishtp_device *dev = pci_get_drvdata(pdev);
+
+	enable_irq_wake(pdev->irq);
+	/*
+	 * If previous suspend hasn't been asnwered then ISH is likely dead,
+	 * don't attempt nested notification
+	 */
+	if (dev->suspend_flag)
+		return	0;
+
+	dev->resume_flag = 0;
+	dev->suspend_flag = 1;
+	ishtp_send_suspend(dev);
+
+	/* 25 ms should be enough for live ISH to flush all IPC buf */
+	if (dev->suspend_flag)
+		wait_event_interruptible_timeout(dev->suspend_wait,
+						 !dev->suspend_flag,
+						  msecs_to_jiffies(25));
+
+	return 0;
+}
+
+static __maybe_unused DECLARE_WORK(resume_work, ish_resume_handler);
+/**
+ * ish_resume() - ISH resume callback
+ * @device:	device pointer
+ *
+ * ISH resume callback
+ *
+ * Return: 0 to the pm core
+ */
+static int __maybe_unused ish_resume(struct device *device)
+{
+	struct pci_dev *pdev = to_pci_dev(device);
+	struct ishtp_device *dev = pci_get_drvdata(pdev);
+
+	ish_resume_device = device;
+	dev->resume_flag = 1;
+
+	disable_irq_wake(pdev->irq);
+	schedule_work(&resume_work);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
+
+static struct pci_driver ish_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = ish_pci_tbl,
+	.probe = ish_probe,
+	.remove = ish_remove,
+	.driver.pm = &ish_pm_ops,
+};
+
+module_pci_driver(ish_driver);
+
+/* Original author */
+MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
+/* Adoption to upstream Linux kernel */
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+
+MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid-client.c b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
new file mode 100644
index 0000000..2d28cff
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp-hid-client.c
@@ -0,0 +1,973 @@
+/*
+ * ISHTP client driver for HID (ISH)
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/hid.h>
+#include <linux/sched.h>
+#include "ishtp/ishtp-dev.h"
+#include "ishtp/client.h"
+#include "ishtp-hid.h"
+
+/* Rx ring buffer pool size */
+#define HID_CL_RX_RING_SIZE	32
+#define HID_CL_TX_RING_SIZE	16
+
+/**
+ * report_bad_packets() - Report bad packets
+ * @hid_ishtp_cl:	Client instance to get stats
+ * @recv_buf:		Raw received host interface message
+ * @cur_pos:		Current position index in payload
+ * @payload_len:	Length of payload expected
+ *
+ * Dumps error in case bad packet is received
+ */
+static void report_bad_packet(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
+			      size_t cur_pos,  size_t payload_len)
+{
+	struct hostif_msg *recv_msg = recv_buf;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+
+	dev_err(&client_data->cl_device->dev, "[hid-ish]: BAD packet %02X\n"
+		"total_bad=%u cur_pos=%u\n"
+		"[%02X %02X %02X %02X]\n"
+		"payload_len=%u\n"
+		"multi_packet_cnt=%u\n"
+		"is_response=%02X\n",
+		recv_msg->hdr.command, client_data->bad_recv_cnt,
+		(unsigned int)cur_pos,
+		((unsigned char *)recv_msg)[0], ((unsigned char *)recv_msg)[1],
+		((unsigned char *)recv_msg)[2], ((unsigned char *)recv_msg)[3],
+		(unsigned int)payload_len, client_data->multi_packet_cnt,
+		recv_msg->hdr.command & ~CMD_MASK);
+}
+
+/**
+ * process_recv() - Received and parse incoming packet
+ * @hid_ishtp_cl:	Client instance to get stats
+ * @recv_buf:		Raw received host interface message
+ * @data_len:		length of the message
+ *
+ * Parse the incoming packet. If it is a response packet then it will update
+ * per instance flags and wake up the caller waiting to for the response.
+ */
+static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
+			 size_t data_len)
+{
+	struct hostif_msg *recv_msg;
+	unsigned char *payload;
+	struct device_info *dev_info;
+	int i, j;
+	size_t	payload_len, total_len, cur_pos;
+	int report_type;
+	struct report_list *reports_list;
+	char *reports;
+	size_t report_len;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+	int curr_hid_dev = client_data->cur_hid_dev;
+
+	payload = recv_buf + sizeof(struct hostif_msg_hdr);
+	total_len = data_len;
+	cur_pos = 0;
+
+	do {
+		if (cur_pos + sizeof(struct hostif_msg) > total_len) {
+			dev_err(&client_data->cl_device->dev,
+				"[hid-ish]: error, received %u which is less than data header %u\n",
+				(unsigned int)data_len,
+				(unsigned int)sizeof(struct hostif_msg_hdr));
+			++client_data->bad_recv_cnt;
+			ish_hw_reset(hid_ishtp_cl->dev);
+			break;
+		}
+
+		recv_msg = (struct hostif_msg *)(recv_buf + cur_pos);
+		payload_len = recv_msg->hdr.size;
+
+		/* Sanity checks */
+		if (cur_pos + payload_len + sizeof(struct hostif_msg) >
+				total_len) {
+			++client_data->bad_recv_cnt;
+			report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
+					  payload_len);
+			ish_hw_reset(hid_ishtp_cl->dev);
+			break;
+		}
+
+		hid_ishtp_trace(client_data,  "%s %d\n",
+				__func__, recv_msg->hdr.command & CMD_MASK);
+
+		switch (recv_msg->hdr.command & CMD_MASK) {
+		case HOSTIF_DM_ENUM_DEVICES:
+			if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
+					client_data->init_done)) {
+				++client_data->bad_recv_cnt;
+				report_bad_packet(hid_ishtp_cl, recv_msg,
+						  cur_pos,
+						  payload_len);
+				ish_hw_reset(hid_ishtp_cl->dev);
+				break;
+			}
+			client_data->hid_dev_count = (unsigned int)*payload;
+			if (!client_data->hid_devices)
+				client_data->hid_devices = devm_kcalloc(
+						&client_data->cl_device->dev,
+						client_data->hid_dev_count,
+						sizeof(struct device_info),
+						GFP_KERNEL);
+			if (!client_data->hid_devices) {
+				dev_err(&client_data->cl_device->dev,
+				"Mem alloc failed for hid device info\n");
+				wake_up_interruptible(&client_data->init_wait);
+				break;
+			}
+			for (i = 0; i < client_data->hid_dev_count; ++i) {
+				if (1 + sizeof(struct device_info) * i >=
+						payload_len) {
+					dev_err(&client_data->cl_device->dev,
+						"[hid-ish]: [ENUM_DEVICES]: content size %zu is bigger than payload_len %zu\n",
+						1 + sizeof(struct device_info)
+						* i, payload_len);
+				}
+
+				if (1 + sizeof(struct device_info) * i >=
+						data_len)
+					break;
+
+				dev_info = (struct device_info *)(payload + 1 +
+					sizeof(struct device_info) * i);
+				if (client_data->hid_devices)
+					memcpy(client_data->hid_devices + i,
+					       dev_info,
+					       sizeof(struct device_info));
+			}
+
+			client_data->enum_devices_done = true;
+			wake_up_interruptible(&client_data->init_wait);
+
+			break;
+
+		case HOSTIF_GET_HID_DESCRIPTOR:
+			if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
+					client_data->init_done)) {
+				++client_data->bad_recv_cnt;
+				report_bad_packet(hid_ishtp_cl, recv_msg,
+						  cur_pos,
+						  payload_len);
+				ish_hw_reset(hid_ishtp_cl->dev);
+				break;
+			}
+			if (!client_data->hid_descr[curr_hid_dev])
+				client_data->hid_descr[curr_hid_dev] =
+				devm_kmalloc(&client_data->cl_device->dev,
+					     payload_len, GFP_KERNEL);
+			if (client_data->hid_descr[curr_hid_dev]) {
+				memcpy(client_data->hid_descr[curr_hid_dev],
+				       payload, payload_len);
+				client_data->hid_descr_size[curr_hid_dev] =
+					payload_len;
+				client_data->hid_descr_done = true;
+			}
+			wake_up_interruptible(&client_data->init_wait);
+
+			break;
+
+		case HOSTIF_GET_REPORT_DESCRIPTOR:
+			if ((!(recv_msg->hdr.command & ~CMD_MASK) ||
+					client_data->init_done)) {
+				++client_data->bad_recv_cnt;
+				report_bad_packet(hid_ishtp_cl, recv_msg,
+						  cur_pos,
+						  payload_len);
+				ish_hw_reset(hid_ishtp_cl->dev);
+				break;
+			}
+			if (!client_data->report_descr[curr_hid_dev])
+				client_data->report_descr[curr_hid_dev] =
+				devm_kmalloc(&client_data->cl_device->dev,
+					     payload_len, GFP_KERNEL);
+			if (client_data->report_descr[curr_hid_dev])  {
+				memcpy(client_data->report_descr[curr_hid_dev],
+				       payload,
+				       payload_len);
+				client_data->report_descr_size[curr_hid_dev] =
+					payload_len;
+				client_data->report_descr_done = true;
+			}
+			wake_up_interruptible(&client_data->init_wait);
+
+			break;
+
+		case HOSTIF_GET_FEATURE_REPORT:
+			report_type = HID_FEATURE_REPORT;
+			goto	do_get_report;
+
+		case HOSTIF_GET_INPUT_REPORT:
+			report_type = HID_INPUT_REPORT;
+do_get_report:
+			/* Get index of device that matches this id */
+			for (i = 0; i < client_data->num_hid_devices; ++i) {
+				if (recv_msg->hdr.device_id ==
+					client_data->hid_devices[i].dev_id)
+					if (client_data->hid_sensor_hubs[i]) {
+						hid_input_report(
+						client_data->hid_sensor_hubs[
+									i],
+						report_type, payload,
+						payload_len, 0);
+						ishtp_hid_wakeup(
+						client_data->hid_sensor_hubs[
+							i]);
+						break;
+					}
+			}
+			break;
+
+		case HOSTIF_SET_FEATURE_REPORT:
+			/* Get index of device that matches this id */
+			for (i = 0; i < client_data->num_hid_devices; ++i) {
+				if (recv_msg->hdr.device_id ==
+					client_data->hid_devices[i].dev_id)
+					if (client_data->hid_sensor_hubs[i]) {
+						ishtp_hid_wakeup(
+						client_data->hid_sensor_hubs[
+							i]);
+						break;
+					}
+			}
+			break;
+
+		case HOSTIF_PUBLISH_INPUT_REPORT:
+			report_type = HID_INPUT_REPORT;
+			for (i = 0; i < client_data->num_hid_devices; ++i)
+				if (recv_msg->hdr.device_id ==
+					client_data->hid_devices[i].dev_id)
+					if (client_data->hid_sensor_hubs[i])
+						hid_input_report(
+						client_data->hid_sensor_hubs[
+									i],
+						report_type, payload,
+						payload_len, 0);
+			break;
+
+		case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
+			report_type = HID_INPUT_REPORT;
+			reports_list = (struct report_list *)payload;
+			reports = (char *)reports_list->reports;
+
+			for (j = 0; j < reports_list->num_of_reports; j++) {
+				recv_msg = (struct hostif_msg *)(reports +
+					sizeof(uint16_t));
+				report_len = *(uint16_t *)reports;
+				payload = reports + sizeof(uint16_t) +
+					sizeof(struct hostif_msg_hdr);
+				payload_len = report_len -
+					sizeof(struct hostif_msg_hdr);
+
+				for (i = 0; i < client_data->num_hid_devices;
+				     ++i)
+					if (recv_msg->hdr.device_id ==
+					client_data->hid_devices[i].dev_id &&
+					client_data->hid_sensor_hubs[i]) {
+						hid_input_report(
+						client_data->hid_sensor_hubs[
+									i],
+						report_type,
+						payload, payload_len,
+						0);
+					}
+
+				reports += sizeof(uint16_t) + report_len;
+			}
+			break;
+		default:
+			++client_data->bad_recv_cnt;
+			report_bad_packet(hid_ishtp_cl, recv_msg, cur_pos,
+					  payload_len);
+			ish_hw_reset(hid_ishtp_cl->dev);
+			break;
+
+		}
+
+		if (!cur_pos && cur_pos + payload_len +
+				sizeof(struct hostif_msg) < total_len)
+			++client_data->multi_packet_cnt;
+
+		cur_pos += payload_len + sizeof(struct hostif_msg);
+		payload += payload_len + sizeof(struct hostif_msg);
+
+	} while (cur_pos < total_len);
+}
+
+/**
+ * ish_cl_event_cb() - bus driver callback for incoming message/packet
+ * @device:	Pointer to the the ishtp client device for which this message
+ *		is targeted
+ *
+ * Remove the packet from the list and process the message by calling
+ * process_recv
+ */
+static void ish_cl_event_cb(struct ishtp_cl_device *device)
+{
+	struct ishtp_cl	*hid_ishtp_cl = device->driver_data;
+	struct ishtp_cl_rb *rb_in_proc;
+	size_t r_length;
+	unsigned long flags;
+
+	if (!hid_ishtp_cl)
+		return;
+
+	spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags);
+	while (!list_empty(&hid_ishtp_cl->in_process_list.list)) {
+		rb_in_proc = list_entry(
+			hid_ishtp_cl->in_process_list.list.next,
+			struct ishtp_cl_rb, list);
+		list_del_init(&rb_in_proc->list);
+		spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock,
+			flags);
+
+		if (!rb_in_proc->buffer.data)
+			return;
+
+		r_length = rb_in_proc->buf_idx;
+
+		/* decide what to do with received data */
+		process_recv(hid_ishtp_cl, rb_in_proc->buffer.data, r_length);
+
+		ishtp_cl_io_rb_recycle(rb_in_proc);
+		spin_lock_irqsave(&hid_ishtp_cl->in_process_spinlock, flags);
+	}
+	spin_unlock_irqrestore(&hid_ishtp_cl->in_process_spinlock, flags);
+}
+
+/**
+ * hid_ishtp_set_feature() - send request to ISH FW to set a feature request
+ * @hid:	hid device instance for this request
+ * @buf:	feature buffer
+ * @len:	Length of feature buffer
+ * @report_id:	Report id for the feature set request
+ *
+ * This is called from hid core .request() callback. This function doesn't wait
+ * for response.
+ */
+void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len,
+			   int report_id)
+{
+	struct ishtp_hid_data *hid_data =  hid->driver_data;
+	struct ishtp_cl_data *client_data = hid_data->client_data;
+	struct hostif_msg *msg = (struct hostif_msg *)buf;
+	int	rv;
+	int	i;
+
+	hid_ishtp_trace(client_data,  "%s hid %p\n", __func__, hid);
+
+	rv = ishtp_hid_link_ready_wait(client_data);
+	if (rv) {
+		hid_ishtp_trace(client_data,  "%s hid %p link not ready\n",
+				__func__, hid);
+		return;
+	}
+
+	memset(msg, 0, sizeof(struct hostif_msg));
+	msg->hdr.command = HOSTIF_SET_FEATURE_REPORT;
+	for (i = 0; i < client_data->num_hid_devices; ++i) {
+		if (hid == client_data->hid_sensor_hubs[i]) {
+			msg->hdr.device_id =
+				client_data->hid_devices[i].dev_id;
+			break;
+		}
+	}
+
+	if (i == client_data->num_hid_devices)
+		return;
+
+	rv = ishtp_cl_send(client_data->hid_ishtp_cl, buf, len);
+	if (rv)
+		hid_ishtp_trace(client_data,  "%s hid %p send failed\n",
+				__func__, hid);
+}
+
+/**
+ * hid_ishtp_get_report() - request to get feature/input report
+ * @hid:	hid device instance for this request
+ * @report_id:	Report id for the get request
+ * @report_type:	Report type for the this request
+ *
+ * This is called from hid core .request() callback. This function will send
+ * request to FW and return without waiting for response.
+ */
+void hid_ishtp_get_report(struct hid_device *hid, int report_id,
+			  int report_type)
+{
+	struct ishtp_hid_data *hid_data =  hid->driver_data;
+	struct ishtp_cl_data *client_data = hid_data->client_data;
+	struct hostif_msg_to_sensor msg = {};
+	int	rv;
+	int	i;
+
+	hid_ishtp_trace(client_data,  "%s hid %p\n", __func__, hid);
+	rv = ishtp_hid_link_ready_wait(client_data);
+	if (rv) {
+		hid_ishtp_trace(client_data,  "%s hid %p link not ready\n",
+				__func__, hid);
+		return;
+	}
+
+	msg.hdr.command = (report_type == HID_FEATURE_REPORT) ?
+		HOSTIF_GET_FEATURE_REPORT : HOSTIF_GET_INPUT_REPORT;
+	for (i = 0; i < client_data->num_hid_devices; ++i) {
+		if (hid == client_data->hid_sensor_hubs[i]) {
+			msg.hdr.device_id =
+				client_data->hid_devices[i].dev_id;
+			break;
+		}
+	}
+
+	if (i == client_data->num_hid_devices)
+		return;
+
+	msg.report_id = report_id;
+	rv = ishtp_cl_send(client_data->hid_ishtp_cl, (uint8_t *)&msg,
+			    sizeof(msg));
+	if (rv)
+		hid_ishtp_trace(client_data,  "%s hid %p send failed\n",
+				__func__, hid);
+}
+
+/**
+ * ishtp_hid_link_ready_wait() - Wait for link ready
+ * @client_data:	client data instance
+ *
+ * If the transport link started suspend process, then wait, till either
+ * resumed or timeout
+ *
+ * Return: 0 on success, non zero on error
+ */
+int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data)
+{
+	int rc;
+
+	if (client_data->suspended) {
+		hid_ishtp_trace(client_data,  "wait for link ready\n");
+		rc = wait_event_interruptible_timeout(
+					client_data->ishtp_resume_wait,
+					!client_data->suspended,
+					5 * HZ);
+
+		if (rc == 0) {
+			hid_ishtp_trace(client_data,  "link not ready\n");
+			return -EIO;
+		}
+		hid_ishtp_trace(client_data,  "link ready\n");
+	}
+
+	return 0;
+}
+
+/**
+ * ishtp_enum_enum_devices() - Enumerate hid devices
+ * @hid_ishtp_cl:	client instance
+ *
+ * Helper function to send request to firmware to enumerate HID devices
+ *
+ * Return: 0 on success, non zero on error
+ */
+static int ishtp_enum_enum_devices(struct ishtp_cl *hid_ishtp_cl)
+{
+	struct hostif_msg msg;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+	int retry_count;
+	int rv;
+
+	/* Send HOSTIF_DM_ENUM_DEVICES */
+	memset(&msg, 0, sizeof(struct hostif_msg));
+	msg.hdr.command = HOSTIF_DM_ENUM_DEVICES;
+	rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *)&msg,
+			   sizeof(struct hostif_msg));
+	if (rv)
+		return rv;
+
+	retry_count = 0;
+	while (!client_data->enum_devices_done &&
+	       retry_count < 10) {
+		wait_event_interruptible_timeout(client_data->init_wait,
+					 client_data->enum_devices_done,
+					 3 * HZ);
+		++retry_count;
+		if (!client_data->enum_devices_done)
+			/* Send HOSTIF_DM_ENUM_DEVICES */
+			rv = ishtp_cl_send(hid_ishtp_cl,
+					   (unsigned char *) &msg,
+					   sizeof(struct hostif_msg));
+	}
+	if (!client_data->enum_devices_done) {
+		dev_err(&client_data->cl_device->dev,
+			"[hid-ish]: timed out waiting for enum_devices\n");
+		return -ETIMEDOUT;
+	}
+	if (!client_data->hid_devices) {
+		dev_err(&client_data->cl_device->dev,
+			"[hid-ish]: failed to allocate HID dev structures\n");
+		return -ENOMEM;
+	}
+
+	client_data->num_hid_devices = client_data->hid_dev_count;
+	dev_info(&hid_ishtp_cl->device->dev,
+		"[hid-ish]: enum_devices_done OK, num_hid_devices=%d\n",
+		client_data->num_hid_devices);
+
+	return	0;
+}
+
+/**
+ * ishtp_get_hid_descriptor() - Get hid descriptor
+ * @hid_ishtp_cl:	client instance
+ * @index:		Index into the hid_descr array
+ *
+ * Helper function to send request to firmware get HID descriptor of a device
+ *
+ * Return: 0 on success, non zero on error
+ */
+static int ishtp_get_hid_descriptor(struct ishtp_cl *hid_ishtp_cl, int index)
+{
+	struct hostif_msg msg;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+	int rv;
+
+	/* Get HID descriptor */
+	client_data->hid_descr_done = false;
+	memset(&msg, 0, sizeof(struct hostif_msg));
+	msg.hdr.command = HOSTIF_GET_HID_DESCRIPTOR;
+	msg.hdr.device_id = client_data->hid_devices[index].dev_id;
+	rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg,
+			   sizeof(struct hostif_msg));
+	if (rv)
+		return rv;
+
+	if (!client_data->hid_descr_done) {
+		wait_event_interruptible_timeout(client_data->init_wait,
+						 client_data->hid_descr_done,
+						 3 * HZ);
+		if (!client_data->hid_descr_done) {
+			dev_err(&client_data->cl_device->dev,
+				"[hid-ish]: timed out for hid_descr_done\n");
+			return -EIO;
+		}
+
+		if (!client_data->hid_descr[index]) {
+			dev_err(&client_data->cl_device->dev,
+				"[hid-ish]: allocation HID desc fail\n");
+			return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * ishtp_get_report_descriptor() - Get report descriptor
+ * @hid_ishtp_cl:	client instance
+ * @index:		Index into the hid_descr array
+ *
+ * Helper function to send request to firmware get HID report descriptor of
+ * a device
+ *
+ * Return: 0 on success, non zero on error
+ */
+static int ishtp_get_report_descriptor(struct ishtp_cl *hid_ishtp_cl,
+				       int index)
+{
+	struct hostif_msg msg;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+	int rv;
+
+	/* Get report descriptor */
+	client_data->report_descr_done = false;
+	memset(&msg, 0, sizeof(struct hostif_msg));
+	msg.hdr.command = HOSTIF_GET_REPORT_DESCRIPTOR;
+	msg.hdr.device_id = client_data->hid_devices[index].dev_id;
+	rv = ishtp_cl_send(hid_ishtp_cl, (unsigned char *) &msg,
+			   sizeof(struct hostif_msg));
+	if (rv)
+		return rv;
+
+	if (!client_data->report_descr_done)
+		wait_event_interruptible_timeout(client_data->init_wait,
+					 client_data->report_descr_done,
+					 3 * HZ);
+	if (!client_data->report_descr_done) {
+		dev_err(&client_data->cl_device->dev,
+				"[hid-ish]: timed out for report descr\n");
+		return -EIO;
+	}
+	if (!client_data->report_descr[index]) {
+		dev_err(&client_data->cl_device->dev,
+			"[hid-ish]: failed to alloc report descr\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ * hid_ishtp_cl_init() - Init function for ISHTP client
+ * @hid_ishtp_cl:	ISHTP client instance
+ * @reset:		true if called for init after reset
+ *
+ * This function complete the initializtion of the client. The summary of
+ * processing:
+ * - Send request to enumerate the hid clients
+ *	Get the HID descriptor for each enumearated device
+ *	Get report description of each device
+ *	Register each device wik hid core by calling ishtp_hid_probe
+ *
+ * Return: 0 on success, non zero on error
+ */
+static int hid_ishtp_cl_init(struct ishtp_cl *hid_ishtp_cl, int reset)
+{
+	struct ishtp_device *dev;
+	unsigned long flags;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+	int i;
+	int rv;
+
+	dev_dbg(&client_data->cl_device->dev, "%s\n", __func__);
+	hid_ishtp_trace(client_data,  "%s reset flag: %d\n", __func__, reset);
+
+	rv = ishtp_cl_link(hid_ishtp_cl, ISHTP_HOST_CLIENT_ID_ANY);
+	if (rv) {
+		dev_err(&client_data->cl_device->dev,
+			"ishtp_cl_link failed\n");
+		return	-ENOMEM;
+	}
+
+	client_data->init_done = 0;
+
+	dev = hid_ishtp_cl->dev;
+
+	/* Connect to FW client */
+	hid_ishtp_cl->rx_ring_size = HID_CL_RX_RING_SIZE;
+	hid_ishtp_cl->tx_ring_size = HID_CL_TX_RING_SIZE;
+
+	spin_lock_irqsave(&dev->fw_clients_lock, flags);
+	i = ishtp_fw_cl_by_uuid(dev, &hid_ishtp_guid);
+	if (i < 0) {
+		spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
+		dev_err(&client_data->cl_device->dev,
+			"ish client uuid not found\n");
+		return i;
+	}
+	hid_ishtp_cl->fw_client_id = dev->fw_clients[i].client_id;
+	spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
+	hid_ishtp_cl->state = ISHTP_CL_CONNECTING;
+
+	rv = ishtp_cl_connect(hid_ishtp_cl);
+	if (rv) {
+		dev_err(&client_data->cl_device->dev,
+			"client connect fail\n");
+		goto err_cl_unlink;
+	}
+
+	hid_ishtp_trace(client_data,  "%s client connected\n", __func__);
+
+	/* Register read callback */
+	ishtp_register_event_cb(hid_ishtp_cl->device, ish_cl_event_cb);
+
+	rv = ishtp_enum_enum_devices(hid_ishtp_cl);
+	if (rv)
+		goto err_cl_disconnect;
+
+	hid_ishtp_trace(client_data,  "%s enumerated device count %d\n",
+			__func__, client_data->num_hid_devices);
+
+	for (i = 0; i < client_data->num_hid_devices; ++i) {
+		client_data->cur_hid_dev = i;
+
+		rv = ishtp_get_hid_descriptor(hid_ishtp_cl, i);
+		if (rv)
+			goto err_cl_disconnect;
+
+		rv = ishtp_get_report_descriptor(hid_ishtp_cl, i);
+		if (rv)
+			goto err_cl_disconnect;
+
+		if (!reset) {
+			rv = ishtp_hid_probe(i, client_data);
+			if (rv) {
+				dev_err(&client_data->cl_device->dev,
+				"[hid-ish]: HID probe for #%u failed: %d\n",
+				i, rv);
+				goto err_cl_disconnect;
+			}
+		}
+	} /* for() on all hid devices */
+
+	client_data->init_done = 1;
+	client_data->suspended = false;
+	wake_up_interruptible(&client_data->ishtp_resume_wait);
+	hid_ishtp_trace(client_data,  "%s successful init\n", __func__);
+	return 0;
+
+err_cl_disconnect:
+	hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING;
+	ishtp_cl_disconnect(hid_ishtp_cl);
+err_cl_unlink:
+	ishtp_cl_unlink(hid_ishtp_cl);
+	return rv;
+}
+
+/**
+ * hid_ishtp_cl_deinit() - Deinit function for ISHTP client
+ * @hid_ishtp_cl:	ISHTP client instance
+ *
+ * Unlink and free hid client
+ */
+static void hid_ishtp_cl_deinit(struct ishtp_cl *hid_ishtp_cl)
+{
+	ishtp_cl_unlink(hid_ishtp_cl);
+	ishtp_cl_flush_queues(hid_ishtp_cl);
+
+	/* disband and free all Tx and Rx client-level rings */
+	ishtp_cl_free(hid_ishtp_cl);
+}
+
+static void hid_ishtp_cl_reset_handler(struct work_struct *work)
+{
+	struct ishtp_cl_data *client_data;
+	struct ishtp_cl *hid_ishtp_cl;
+	struct ishtp_cl_device *cl_device;
+	int retry;
+	int rv;
+
+	client_data = container_of(work, struct ishtp_cl_data, work);
+
+	hid_ishtp_cl = client_data->hid_ishtp_cl;
+	cl_device = client_data->cl_device;
+
+	hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
+			hid_ishtp_cl);
+	dev_dbg(&cl_device->dev, "%s\n", __func__);
+
+	hid_ishtp_cl_deinit(hid_ishtp_cl);
+
+	hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev);
+	if (!hid_ishtp_cl)
+		return;
+
+	cl_device->driver_data = hid_ishtp_cl;
+	hid_ishtp_cl->client_data = client_data;
+	client_data->hid_ishtp_cl = hid_ishtp_cl;
+
+	client_data->num_hid_devices = 0;
+
+	for (retry = 0; retry < 3; ++retry) {
+		rv = hid_ishtp_cl_init(hid_ishtp_cl, 1);
+		if (!rv)
+			break;
+		dev_err(&client_data->cl_device->dev, "Retry reset init\n");
+	}
+	if (rv) {
+		dev_err(&client_data->cl_device->dev, "Reset Failed\n");
+		hid_ishtp_trace(client_data, "%s Failed hid_ishtp_cl %p\n",
+				__func__, hid_ishtp_cl);
+	}
+}
+
+/**
+ * hid_ishtp_cl_probe() - ISHTP client driver probe
+ * @cl_device:		ISHTP client device instance
+ *
+ * This function gets called on device create on ISHTP bus
+ *
+ * Return: 0 on success, non zero on error
+ */
+static int hid_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
+{
+	struct ishtp_cl *hid_ishtp_cl;
+	struct ishtp_cl_data *client_data;
+	int rv;
+
+	if (!cl_device)
+		return	-ENODEV;
+
+	if (uuid_le_cmp(hid_ishtp_guid,
+			cl_device->fw_client->props.protocol_name) != 0)
+		return	-ENODEV;
+
+	client_data = devm_kzalloc(&cl_device->dev, sizeof(*client_data),
+				   GFP_KERNEL);
+	if (!client_data)
+		return -ENOMEM;
+
+	hid_ishtp_cl = ishtp_cl_allocate(cl_device->ishtp_dev);
+	if (!hid_ishtp_cl)
+		return -ENOMEM;
+
+	cl_device->driver_data = hid_ishtp_cl;
+	hid_ishtp_cl->client_data = client_data;
+	client_data->hid_ishtp_cl = hid_ishtp_cl;
+	client_data->cl_device = cl_device;
+
+	init_waitqueue_head(&client_data->init_wait);
+	init_waitqueue_head(&client_data->ishtp_resume_wait);
+
+	INIT_WORK(&client_data->work, hid_ishtp_cl_reset_handler);
+
+	rv = hid_ishtp_cl_init(hid_ishtp_cl, 0);
+	if (rv) {
+		ishtp_cl_free(hid_ishtp_cl);
+		return rv;
+	}
+	ishtp_get_device(cl_device);
+
+	return 0;
+}
+
+/**
+ * hid_ishtp_cl_remove() - ISHTP client driver remove
+ * @cl_device:		ISHTP client device instance
+ *
+ * This function gets called on device remove on ISHTP bus
+ *
+ * Return: 0
+ */
+static int hid_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
+{
+	struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+
+	hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
+			hid_ishtp_cl);
+
+	dev_dbg(&cl_device->dev, "%s\n", __func__);
+	hid_ishtp_cl->state = ISHTP_CL_DISCONNECTING;
+	ishtp_cl_disconnect(hid_ishtp_cl);
+	ishtp_put_device(cl_device);
+	ishtp_hid_remove(client_data);
+	hid_ishtp_cl_deinit(hid_ishtp_cl);
+
+	hid_ishtp_cl = NULL;
+
+	client_data->num_hid_devices = 0;
+
+	return 0;
+}
+
+/**
+ * hid_ishtp_cl_reset() - ISHTP client driver reset
+ * @cl_device:		ISHTP client device instance
+ *
+ * This function gets called on device reset on ISHTP bus
+ *
+ * Return: 0
+ */
+static int hid_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
+{
+	struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+
+	hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
+			hid_ishtp_cl);
+
+	schedule_work(&client_data->work);
+
+	return 0;
+}
+
+#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev)
+
+/**
+ * hid_ishtp_cl_suspend() - ISHTP client driver suspend
+ * @device:	device instance
+ *
+ * This function gets called on system suspend
+ *
+ * Return: 0
+ */
+static int hid_ishtp_cl_suspend(struct device *device)
+{
+	struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device);
+	struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+
+	hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
+			hid_ishtp_cl);
+	client_data->suspended = true;
+
+	return 0;
+}
+
+/**
+ * hid_ishtp_cl_resume() - ISHTP client driver resume
+ * @device:	device instance
+ *
+ * This function gets called on system resume
+ *
+ * Return: 0
+ */
+static int hid_ishtp_cl_resume(struct device *device)
+{
+	struct ishtp_cl_device *cl_device = to_ishtp_cl_device(device);
+	struct ishtp_cl *hid_ishtp_cl = cl_device->driver_data;
+	struct ishtp_cl_data *client_data = hid_ishtp_cl->client_data;
+
+	hid_ishtp_trace(client_data, "%s hid_ishtp_cl %p\n", __func__,
+			hid_ishtp_cl);
+	client_data->suspended = false;
+	return 0;
+}
+
+static const struct dev_pm_ops hid_ishtp_pm_ops = {
+	.suspend = hid_ishtp_cl_suspend,
+	.resume = hid_ishtp_cl_resume,
+};
+
+static struct ishtp_cl_driver	hid_ishtp_cl_driver = {
+	.name = "ish-hid",
+	.probe = hid_ishtp_cl_probe,
+	.remove = hid_ishtp_cl_remove,
+	.reset = hid_ishtp_cl_reset,
+	.driver.pm = &hid_ishtp_pm_ops,
+};
+
+static int __init ish_hid_init(void)
+{
+	int	rv;
+
+	/* Register ISHTP client device driver with ISHTP Bus */
+	rv = ishtp_cl_driver_register(&hid_ishtp_cl_driver);
+
+	return rv;
+
+}
+
+static void __exit ish_hid_exit(void)
+{
+	ishtp_cl_driver_unregister(&hid_ishtp_cl_driver);
+}
+
+late_initcall(ish_hid_init);
+module_exit(ish_hid_exit);
+
+MODULE_DESCRIPTION("ISH ISHTP HID client driver");
+/* Primary author */
+MODULE_AUTHOR("Daniel Drubin <daniel.drubin@intel.com>");
+/*
+ * Several modification for multi instance support
+ * suspend/resume and clean up
+ */
+MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ishtp:*");
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c
new file mode 100644
index 0000000..cd23903
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.c
@@ -0,0 +1,246 @@
+/*
+ * ISHTP-HID glue driver.
+ *
+ * Copyright (c) 2012-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/hid.h>
+#include <uapi/linux/input.h>
+#include "ishtp/client.h"
+#include "ishtp-hid.h"
+
+/**
+ * ishtp_hid_parse() - hid-core .parse() callback
+ * @hid:	hid device instance
+ *
+ * This function gets called during call to hid_add_device
+ *
+ * Return: 0 on success and non zero on error
+ */
+static int ishtp_hid_parse(struct hid_device *hid)
+{
+	struct ishtp_hid_data *hid_data =  hid->driver_data;
+	struct ishtp_cl_data *client_data = hid_data->client_data;
+	int rv;
+
+	rv = hid_parse_report(hid, client_data->report_descr[hid_data->index],
+			      client_data->report_descr_size[hid_data->index]);
+	if (rv)
+		return	rv;
+
+	return 0;
+}
+
+/* Empty callbacks with success return code */
+static int ishtp_hid_start(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void ishtp_hid_stop(struct hid_device *hid)
+{
+}
+
+static int ishtp_hid_open(struct hid_device *hid)
+{
+	return 0;
+}
+
+static void ishtp_hid_close(struct hid_device *hid)
+{
+}
+
+static int ishtp_raw_request(struct hid_device *hdev, unsigned char reportnum,
+	__u8 *buf, size_t len, unsigned char rtype, int reqtype)
+{
+	return 0;
+}
+
+/**
+ * ishtp_hid_request() - hid-core .request() callback
+ * @hid:	hid device instance
+ * @rep:	pointer to hid_report
+ * @reqtype:	type of req. [GET|SET]_REPORT
+ *
+ * This function is used to set/get feaure/input report.
+ */
+static void ishtp_hid_request(struct hid_device *hid, struct hid_report *rep,
+	int reqtype)
+{
+	struct ishtp_hid_data *hid_data =  hid->driver_data;
+	/* the specific report length, just HID part of it */
+	unsigned int len = ((rep->size - 1) >> 3) + 1 + (rep->id > 0);
+	char *buf;
+	unsigned int header_size = sizeof(struct hostif_msg);
+
+	len += header_size;
+
+	hid_data->request_done = false;
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		hid_ishtp_get_report(hid, rep->id, rep->type);
+		break;
+	case HID_REQ_SET_REPORT:
+		/*
+		 * Spare 7 bytes for 64b accesses through
+		 * get/put_unaligned_le64()
+		 */
+		buf = kzalloc(len + 7, GFP_KERNEL);
+		if (!buf)
+			return;
+
+		hid_output_report(rep, buf + header_size);
+		hid_ishtp_set_feature(hid, buf, len, rep->id);
+		kfree(buf);
+		break;
+	}
+}
+
+/**
+ * ishtp_wait_for_response() - hid-core .wait() callback
+ * @hid:	hid device instance
+ *
+ * This function is used to wait after get feaure/input report.
+ *
+ * Return: 0 on success and non zero on error
+ */
+static int ishtp_wait_for_response(struct hid_device *hid)
+{
+	struct ishtp_hid_data *hid_data =  hid->driver_data;
+	struct ishtp_cl_data *client_data = hid_data->client_data;
+	int rv;
+
+	hid_ishtp_trace(client_data,  "%s hid %p\n", __func__, hid);
+
+	rv = ishtp_hid_link_ready_wait(hid_data->client_data);
+	if (rv)
+		return rv;
+
+	if (!hid_data->request_done)
+		wait_event_interruptible_timeout(hid_data->hid_wait,
+					hid_data->request_done, 3 * HZ);
+
+	if (!hid_data->request_done) {
+		hid_err(hid,
+			"timeout waiting for response from ISHTP device\n");
+		return -ETIMEDOUT;
+	}
+	hid_ishtp_trace(client_data,  "%s hid %p done\n", __func__, hid);
+
+	hid_data->request_done = false;
+
+	return 0;
+}
+
+/**
+ * ishtp_hid_wakeup() - Wakeup caller
+ * @hid:	hid device instance
+ *
+ * This function will wakeup caller waiting for Get/Set feature report
+ */
+void ishtp_hid_wakeup(struct hid_device *hid)
+{
+	struct ishtp_hid_data *hid_data = hid->driver_data;
+
+	hid_data->request_done = true;
+	wake_up_interruptible(&hid_data->hid_wait);
+}
+
+static struct hid_ll_driver ishtp_hid_ll_driver = {
+	.parse = ishtp_hid_parse,
+	.start = ishtp_hid_start,
+	.stop = ishtp_hid_stop,
+	.open = ishtp_hid_open,
+	.close = ishtp_hid_close,
+	.request = ishtp_hid_request,
+	.wait = ishtp_wait_for_response,
+	.raw_request = ishtp_raw_request
+};
+
+/**
+ * ishtp_hid_probe() - hid register ll driver
+ * @cur_hid_dev:	Index of hid device calling to register
+ * @client_data:	Client data pointer
+ *
+ * This function is used to allocate and add HID device.
+ *
+ * Return: 0 on success, non zero on error
+ */
+int ishtp_hid_probe(unsigned int cur_hid_dev,
+		    struct ishtp_cl_data *client_data)
+{
+	int rv;
+	struct hid_device *hid;
+	struct ishtp_hid_data *hid_data;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		rv = PTR_ERR(hid);
+		return	-ENOMEM;
+	}
+
+	hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL);
+	if (!hid_data) {
+		rv = -ENOMEM;
+		goto err_hid_data;
+	}
+
+	hid_data->index = cur_hid_dev;
+	hid_data->client_data = client_data;
+	init_waitqueue_head(&hid_data->hid_wait);
+
+	hid->driver_data = hid_data;
+
+	client_data->hid_sensor_hubs[cur_hid_dev] = hid;
+
+	hid->ll_driver = &ishtp_hid_ll_driver;
+	hid->bus = BUS_INTEL_ISHTP;
+	hid->dev.parent = &client_data->cl_device->dev;
+	hid->version = le16_to_cpu(ISH_HID_VERSION);
+	hid->vendor = le16_to_cpu(ISH_HID_VENDOR);
+	hid->product = le16_to_cpu(ISH_HID_PRODUCT);
+	snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X", "hid-ishtp",
+		hid->vendor, hid->product);
+
+	rv = hid_add_device(hid);
+	if (rv)
+		goto err_hid_device;
+
+	hid_ishtp_trace(client_data,  "%s allocated hid %p\n", __func__, hid);
+
+	return 0;
+
+err_hid_device:
+	kfree(hid_data);
+err_hid_data:
+	kfree(hid);
+	return rv;
+}
+
+/**
+ * ishtp_hid_probe() - Remove registered hid device
+ * @client_data:	client data pointer
+ *
+ * This function is used to destroy allocatd HID device.
+ */
+void ishtp_hid_remove(struct ishtp_cl_data *client_data)
+{
+	int i;
+
+	for (i = 0; i < client_data->num_hid_devices; ++i) {
+		if (client_data->hid_sensor_hubs[i]) {
+			kfree(client_data->hid_sensor_hubs[i]->driver_data);
+			hid_destroy_device(client_data->hid_sensor_hubs[i]);
+			client_data->hid_sensor_hubs[i] = NULL;
+		}
+	}
+}
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.h b/drivers/hid/intel-ish-hid/ishtp-hid.h
new file mode 100644
index 0000000..f5c7eb7
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.h
@@ -0,0 +1,182 @@
+/*
+ * ISHTP-HID glue driver's definitions.
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+#ifndef ISHTP_HID__H
+#define	ISHTP_HID__H
+
+/* The fixed ISH product and vendor id */
+#define	ISH_HID_VENDOR	0x8086
+#define	ISH_HID_PRODUCT	0x22D8
+#define	ISH_HID_VERSION	0x0200
+
+#define	CMD_MASK	0x7F
+#define	IS_RESPONSE	0x80
+
+/* Used to dump to Linux trace buffer, if enabled */
+#define hid_ishtp_trace(client, ...)	\
+	client->cl_device->ishtp_dev->print_log(\
+		client->cl_device->ishtp_dev, __VA_ARGS__)
+
+/* ISH Transport protocol (ISHTP in short) GUID */
+static const uuid_le hid_ishtp_guid = UUID_LE(0x33AECD58, 0xB679, 0x4E54,
+					      0x9B, 0xD9, 0xA0, 0x4D, 0x34,
+					      0xF0, 0xC2, 0x26);
+
+/* ISH HID message structure */
+struct hostif_msg_hdr {
+	uint8_t	command; /* Bit 7: is_response */
+	uint8_t	device_id;
+	uint8_t	status;
+	uint8_t	flags;
+	uint16_t size;
+} __packed;
+
+struct hostif_msg {
+	struct hostif_msg_hdr	hdr;
+} __packed;
+
+struct hostif_msg_to_sensor {
+	struct hostif_msg_hdr	hdr;
+	uint8_t	report_id;
+} __packed;
+
+struct device_info {
+	uint32_t dev_id;
+	uint8_t dev_class;
+	uint16_t pid;
+	uint16_t vid;
+} __packed;
+
+struct ishtp_version {
+	uint8_t	major;
+	uint8_t	minor;
+	uint8_t	hotfix;
+	uint16_t build;
+} __packed;
+
+/* struct for ISHTP aggregated input data */
+struct report_list {
+	uint16_t total_size;
+	uint8_t	num_of_reports;
+	uint8_t	flags;
+	struct {
+		uint16_t	size_of_report;
+		uint8_t report[1];
+	} __packed reports[1];
+} __packed;
+
+/* HOSTIF commands */
+#define	HOSTIF_HID_COMMAND_BASE			0
+#define	HOSTIF_GET_HID_DESCRIPTOR		0
+#define	HOSTIF_GET_REPORT_DESCRIPTOR		1
+#define HOSTIF_GET_FEATURE_REPORT		2
+#define	HOSTIF_SET_FEATURE_REPORT		3
+#define	HOSTIF_GET_INPUT_REPORT			4
+#define	HOSTIF_PUBLISH_INPUT_REPORT		5
+#define	HOSTIF_PUBLISH_INPUT_REPORT_LIST	6
+#define	HOSTIF_DM_COMMAND_BASE			32
+#define	HOSTIF_DM_ENUM_DEVICES			33
+#define	HOSTIF_DM_ADD_DEVICE			34
+
+#define	MAX_HID_DEVICES				32
+
+/**
+ * struct ishtp_cl_data - Encapsulate per ISH TP HID Client
+ * @enum_device_done:	Enum devices response complete flag
+ * @hid_descr_done:	HID descriptor complete flag
+ * @report_descr_done:	Get report descriptor complete flag
+ * @init_done:		Init process completed successfully
+ * @suspended:		System is under suspend state or in progress
+ * @num_hid_devices:	Number of HID devices enumerated in this client
+ * @cur_hid_dev:	This keeps track of the device index for which
+ *			initialization and registration with HID core
+ *			in progress.
+ * @hid_devices:	Store vid/pid/devid for each enumerated HID device
+ * @report_descr:	Stores the raw report descriptors for each HID device
+ * @report_descr_size:	Report description of size of above repo_descr[]
+ * @hid_sensor_hubs:	Pointer to hid_device for all HID device, so that
+ *			when clients are removed, they can be freed
+ * @hid_descr:		Pointer to hid descriptor for each enumerated hid
+ *			device
+ * @hid_descr_size:	Size of each above report descriptor
+ * @init_wait:		Wait queue to wait during initialization, where the
+ *			client send message to ISH FW and wait for response
+ * @ishtp_hid_wait:	The wait for get report during wait callback from hid
+ *			core
+ * @bad_recv_cnt:	Running count of packets received with error
+ * @multi_packet_cnt:	Count of fragmented packet count
+ *
+ * This structure is used to store completion flags and per client data like
+ * like report description, number of HID devices etc.
+ */
+struct ishtp_cl_data {
+	/* completion flags */
+	bool enum_devices_done;
+	bool hid_descr_done;
+	bool report_descr_done;
+	bool init_done;
+	bool suspended;
+
+	unsigned int num_hid_devices;
+	unsigned int cur_hid_dev;
+	unsigned int hid_dev_count;
+
+	struct device_info *hid_devices;
+	unsigned char *report_descr[MAX_HID_DEVICES];
+	int report_descr_size[MAX_HID_DEVICES];
+	struct hid_device *hid_sensor_hubs[MAX_HID_DEVICES];
+	unsigned char *hid_descr[MAX_HID_DEVICES];
+	int hid_descr_size[MAX_HID_DEVICES];
+
+	wait_queue_head_t init_wait;
+	wait_queue_head_t ishtp_resume_wait;
+	struct ishtp_cl *hid_ishtp_cl;
+
+	/* Statistics */
+	unsigned int bad_recv_cnt;
+	int multi_packet_cnt;
+
+	struct work_struct work;
+	struct ishtp_cl_device *cl_device;
+};
+
+/**
+ * struct ishtp_hid_data - Per instance HID data
+ * @index:		Device index in the order of enumeration
+ * @request_done:	Get Feature/Input report complete flag
+ *			used during get/set request from hid core
+ * @client_data:	Link to the client instance
+ * @hid_wait:		Completion waitq
+ *
+ * Used to tie hid hid->driver data to driver client instance
+ */
+struct ishtp_hid_data {
+	int index;
+	bool request_done;
+	struct ishtp_cl_data *client_data;
+	wait_queue_head_t hid_wait;
+};
+
+/* Interface functions between HID LL driver and ISH TP client */
+void hid_ishtp_set_feature(struct hid_device *hid, char *buf, unsigned int len,
+			   int report_id);
+void hid_ishtp_get_report(struct hid_device *hid, int report_id,
+			  int report_type);
+int ishtp_hid_probe(unsigned int cur_hid_dev,
+		    struct ishtp_cl_data *client_data);
+void ishtp_hid_remove(struct ishtp_cl_data *client_data);
+int ishtp_hid_link_ready_wait(struct ishtp_cl_data *client_data);
+void ishtp_hid_wakeup(struct hid_device *hid);
+
+#endif	/* ISHTP_HID__H */
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.c b/drivers/hid/intel-ish-hid/ishtp/bus.c
new file mode 100644
index 0000000..2623a56
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.c
@@ -0,0 +1,783 @@
+/*
+ * ISHTP bus driver
+ *
+ * Copyright (c) 2012-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include "bus.h"
+#include "ishtp-dev.h"
+#include "client.h"
+#include "hbm.h"
+
+static int ishtp_use_dma;
+module_param_named(ishtp_use_dma, ishtp_use_dma, int, 0600);
+MODULE_PARM_DESC(ishtp_use_dma, "Use DMA to send messages");
+
+#define to_ishtp_cl_driver(d) container_of(d, struct ishtp_cl_driver, driver)
+#define to_ishtp_cl_device(d) container_of(d, struct ishtp_cl_device, dev)
+static bool ishtp_device_ready;
+
+/**
+ * ishtp_recv() - process ishtp message
+ * @dev: ishtp device
+ *
+ * If a message with valid header and size is received, then
+ * this function calls appropriate handler. The host or firmware
+ * address is zero, then they are host bus management message,
+ * otherwise they are message fo clients.
+ */
+void ishtp_recv(struct ishtp_device *dev)
+{
+	uint32_t	msg_hdr;
+	struct ishtp_msg_hdr	*ishtp_hdr;
+
+	/* Read ISHTP header dword */
+	msg_hdr = dev->ops->ishtp_read_hdr(dev);
+	if (!msg_hdr)
+		return;
+
+	dev->ops->sync_fw_clock(dev);
+
+	ishtp_hdr = (struct ishtp_msg_hdr *)&msg_hdr;
+	dev->ishtp_msg_hdr = msg_hdr;
+
+	/* Sanity check: ISHTP frag. length in header */
+	if (ishtp_hdr->length > dev->mtu) {
+		dev_err(dev->devc,
+			"ISHTP hdr - bad length: %u; dropped [%08X]\n",
+			(unsigned int)ishtp_hdr->length, msg_hdr);
+		return;
+	}
+
+	/* ISHTP bus message */
+	if (!ishtp_hdr->host_addr && !ishtp_hdr->fw_addr)
+		recv_hbm(dev, ishtp_hdr);
+	/* ISHTP fixed-client message */
+	else if (!ishtp_hdr->host_addr)
+		recv_fixed_cl_msg(dev, ishtp_hdr);
+	else
+		/* ISHTP client message */
+		recv_ishtp_cl_msg(dev, ishtp_hdr);
+}
+EXPORT_SYMBOL(ishtp_recv);
+
+/**
+ * ishtp_send_msg() - Send ishtp message
+ * @dev: ishtp device
+ * @hdr: Message header
+ * @msg: Message contents
+ * @ipc_send_compl: completion callback
+ * @ipc_send_compl_prm: completion callback parameter
+ *
+ * Send a multi fragment message via IPC. After sending the first fragment
+ * the completion callback is called to schedule transmit of next fragment.
+ *
+ * Return: This returns IPC send message status.
+ */
+int ishtp_send_msg(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
+		       void *msg, void(*ipc_send_compl)(void *),
+		       void *ipc_send_compl_prm)
+{
+	unsigned char	ipc_msg[IPC_FULL_MSG_SIZE];
+	uint32_t	drbl_val;
+
+	drbl_val = dev->ops->ipc_get_header(dev, hdr->length +
+					    sizeof(struct ishtp_msg_hdr),
+					    1);
+
+	memcpy(ipc_msg, &drbl_val, sizeof(uint32_t));
+	memcpy(ipc_msg + sizeof(uint32_t), hdr, sizeof(uint32_t));
+	memcpy(ipc_msg + 2 * sizeof(uint32_t), msg, hdr->length);
+	return	dev->ops->write(dev, ipc_send_compl, ipc_send_compl_prm,
+				ipc_msg, 2 * sizeof(uint32_t) + hdr->length);
+}
+
+/**
+ * ishtp_write_message() - Send ishtp single fragment message
+ * @dev: ishtp device
+ * @hdr: Message header
+ * @buf: message data
+ *
+ * Send a single fragment message via IPC.  This returns IPC send message
+ * status.
+ *
+ * Return: This returns IPC send message status.
+ */
+int ishtp_write_message(struct ishtp_device *dev, struct ishtp_msg_hdr *hdr,
+			unsigned char *buf)
+{
+	return ishtp_send_msg(dev, hdr, buf, NULL, NULL);
+}
+
+/**
+ * ishtp_fw_cl_by_uuid() - locate index of fw client
+ * @dev: ishtp device
+ * @uuid: uuid of the client to search
+ *
+ * Search firmware client using UUID.
+ *
+ * Return: fw client index or -ENOENT if not found
+ */
+int ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *uuid)
+{
+	int i, res = -ENOENT;
+
+	for (i = 0; i < dev->fw_clients_num; ++i) {
+		if (uuid_le_cmp(*uuid, dev->fw_clients[i].props.protocol_name)
+				== 0) {
+			res = i;
+			break;
+		}
+	}
+	return res;
+}
+EXPORT_SYMBOL(ishtp_fw_cl_by_uuid);
+
+/**
+ * ishtp_fw_cl_by_id() - return index to fw_clients for client_id
+ * @dev: the ishtp device structure
+ * @client_id: fw client id to search
+ *
+ * Search firmware client using client id.
+ *
+ * Return: index on success, -ENOENT on failure.
+ */
+int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id)
+{
+	int i, res = -ENOENT;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->fw_clients_lock, flags);
+	for (i = 0; i < dev->fw_clients_num; i++) {
+		if (dev->fw_clients[i].client_id == client_id) {
+			res = i;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->fw_clients_lock, flags);
+
+	return res;
+}
+
+/**
+ * ishtp_cl_device_probe() - Bus probe() callback
+ * @dev: the device structure
+ *
+ * This is a bus probe callback and calls the drive probe function.
+ *
+ * Return: Return value from driver probe() call.
+ */
+static int ishtp_cl_device_probe(struct device *dev)
+{
+	struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
+	struct ishtp_cl_driver *driver;
+
+	if (!device)
+		return 0;
+
+	driver = to_ishtp_cl_driver(dev->driver);
+	if (!driver || !driver->probe)
+		return -ENODEV;
+
+	return driver->probe(device);
+}
+
+/**
+ * ishtp_cl_device_remove() - Bus remove() callback
+ * @dev: the device structure
+ *
+ * This is a bus remove callback and calls the drive remove function.
+ * Since the ISH driver model supports only built in, this is
+ * primarily can be called during pci driver init failure.
+ *
+ * Return: Return value from driver remove() call.
+ */
+static int ishtp_cl_device_remove(struct device *dev)
+{
+	struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
+	struct ishtp_cl_driver *driver;
+
+	if (!device || !dev->driver)
+		return 0;
+
+	if (device->event_cb) {
+		device->event_cb = NULL;
+		cancel_work_sync(&device->event_work);
+	}
+
+	driver = to_ishtp_cl_driver(dev->driver);
+	if (!driver->remove) {
+		dev->driver = NULL;
+
+		return 0;
+	}
+
+	return driver->remove(device);
+}
+
+/**
+ * ishtp_cl_device_suspend() - Bus suspend callback
+ * @dev:	device
+ *
+ * Called during device suspend process.
+ *
+ * Return: Return value from driver suspend() call.
+ */
+static int ishtp_cl_device_suspend(struct device *dev)
+{
+	struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
+	struct ishtp_cl_driver *driver;
+	int ret = 0;
+
+	if (!device)
+		return 0;
+
+	driver = to_ishtp_cl_driver(dev->driver);
+	if (driver && driver->driver.pm) {
+		if (driver->driver.pm->suspend)
+			ret = driver->driver.pm->suspend(dev);
+	}
+
+	return ret;
+}
+
+/**
+ * ishtp_cl_device_resume() - Bus resume callback
+ * @dev:	device
+ *
+ * Called during device resume process.
+ *
+ * Return: Return value from driver resume() call.
+ */
+static int ishtp_cl_device_resume(struct device *dev)
+{
+	struct ishtp_cl_device *device = to_ishtp_cl_device(dev);
+	struct ishtp_cl_driver *driver;
+	int ret = 0;
+
+	if (!device)
+		return 0;
+
+	/*
+	 * When ISH needs hard reset, it is done asynchrnously, hence bus
+	 * resume will  be called before full ISH resume
+	 */
+	if (device->ishtp_dev->resume_flag)
+		return 0;
+
+	driver = to_ishtp_cl_driver(dev->driver);
+	if (driver && driver->driver.pm) {
+		if (driver->driver.pm->resume)
+			ret = driver->driver.pm->resume(dev);
+	}
+
+	return ret;
+}
+
+/**
+ * ishtp_cl_device_reset() - Reset callback
+ * @device:	ishtp client device instance
+ *
+ * This is a callback when HW reset is done and the device need
+ * reinit.
+ *
+ * Return: Return value from driver reset() call.
+ */
+static int ishtp_cl_device_reset(struct ishtp_cl_device *device)
+{
+	struct ishtp_cl_driver *driver;
+	int ret = 0;
+
+	device->event_cb = NULL;
+	cancel_work_sync(&device->event_work);
+
+	driver = to_ishtp_cl_driver(device->dev.driver);
+	if (driver && driver->reset)
+		ret = driver->reset(device);
+
+	return ret;
+}
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *a,
+	char *buf)
+{
+	int len;
+
+	len = snprintf(buf, PAGE_SIZE, "ishtp:%s\n", dev_name(dev));
+	return (len >= PAGE_SIZE) ? (PAGE_SIZE - 1) : len;
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *ishtp_cl_dev_attrs[] = {
+	&dev_attr_modalias.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(ishtp_cl_dev);
+
+static int ishtp_cl_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	if (add_uevent_var(env, "MODALIAS=ishtp:%s", dev_name(dev)))
+		return -ENOMEM;
+	return 0;
+}
+
+static const struct dev_pm_ops ishtp_cl_bus_dev_pm_ops = {
+	/* Suspend callbacks */
+	.suspend = ishtp_cl_device_suspend,
+	.resume = ishtp_cl_device_resume,
+	/* Hibernate callbacks */
+	.freeze = ishtp_cl_device_suspend,
+	.thaw = ishtp_cl_device_resume,
+	.restore = ishtp_cl_device_resume,
+};
+
+static struct bus_type ishtp_cl_bus_type = {
+	.name		= "ishtp",
+	.dev_groups	= ishtp_cl_dev_groups,
+	.probe		= ishtp_cl_device_probe,
+	.remove		= ishtp_cl_device_remove,
+	.pm		= &ishtp_cl_bus_dev_pm_ops,
+	.uevent		= ishtp_cl_uevent,
+};
+
+static void ishtp_cl_dev_release(struct device *dev)
+{
+	kfree(to_ishtp_cl_device(dev));
+}
+
+static const struct device_type ishtp_cl_device_type = {
+	.release	= ishtp_cl_dev_release,
+};
+
+/**
+ * ishtp_bus_add_device() - Function to create device on bus
+ * @dev:	ishtp device
+ * @uuid:	uuid of the client
+ * @name:	Name of the client
+ *
+ * Allocate ISHTP bus client device, attach it to uuid
+ * and register with ISHTP bus.
+ *
+ * Return: ishtp_cl_device pointer or NULL on failure
+ */
+static struct ishtp_cl_device *ishtp_bus_add_device(struct ishtp_device *dev,
+						    uuid_le uuid, char *name)
+{
+	struct ishtp_cl_device *device;
+	int status;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->device_list_lock, flags);
+	list_for_each_entry(device, &dev->device_list, device_link) {
+		if (!strcmp(name, dev_name(&device->dev))) {
+			device->fw_client = &dev->fw_clients[
+				dev->fw_client_presentation_num - 1];
+			spin_unlock_irqrestore(&dev->device_list_lock, flags);
+			ishtp_cl_device_reset(device);
+			return device;
+		}
+	}
+	spin_unlock_irqrestore(&dev->device_list_lock, flags);
+
+	device = kzalloc(sizeof(struct ishtp_cl_device), GFP_KERNEL);
+	if (!device)
+		return NULL;
+
+	device->dev.parent = dev->devc;
+	device->dev.bus = &ishtp_cl_bus_type;
+	device->dev.type = &ishtp_cl_device_type;
+	device->ishtp_dev = dev;
+
+	device->fw_client =
+		&dev->fw_clients[dev->fw_client_presentation_num - 1];
+
+	dev_set_name(&device->dev, "%s", name);
+
+	spin_lock_irqsave(&dev->device_list_lock, flags);
+	list_add_tail(&device->device_link, &dev->device_list);
+	spin_unlock_irqrestore(&dev->device_list_lock, flags);
+
+	status = device_register(&device->dev);
+	if (status) {
+		spin_lock_irqsave(&dev->device_list_lock, flags);
+		list_del(&device->device_link);
+		spin_unlock_irqrestore(&dev->device_list_lock, flags);
+		dev_err(dev->devc, "Failed to register ISHTP client device\n");
+		put_device(&device->dev);
+		return NULL;
+	}
+
+	ishtp_device_ready = true;
+
+	return device;
+}
+
+/**
+ * ishtp_bus_remove_device() - Function to relase device on bus
+ * @device:	client device instance
+ *
+ * This is a counterpart of ishtp_bus_add_device.
+ * Device is unregistered.
+ * the device structure is freed in 'ishtp_cl_dev_release' function
+ * Called only during error in pci driver init path.
+ */
+static void ishtp_bus_remove_device(struct ishtp_cl_device *device)
+{
+	device_unregister(&device->dev);
+}
+
+/**
+ * __ishtp_cl_driver_register() - Client driver register
+ * @driver:	the client driver instance
+ * @owner:	Owner of this driver module
+ *
+ * Once a client driver is probed, it created a client
+ * instance and registers with the bus.
+ *
+ * Return: Return value of driver_register or -ENODEV if not ready
+ */
+int __ishtp_cl_driver_register(struct ishtp_cl_driver *driver,
+			       struct module *owner)
+{
+	int err;
+
+	if (!ishtp_device_ready)
+		return -ENODEV;
+
+	driver->driver.name = driver->name;
+	driver->driver.owner = owner;
+	driver->driver.bus = &ishtp_cl_bus_type;
+
+	err = driver_register(&driver->driver);
+	if (err)
+		return err;
+
+	return 0;
+}
+EXPORT_SYMBOL(__ishtp_cl_driver_register);
+
+/**
+ * ishtp_cl_driver_unregister() - Client driver unregister
+ * @driver:	the client driver instance
+ *
+ * Unregister client during device removal process.
+ */
+void ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver)
+{
+	driver_unregister(&driver->driver);
+}
+EXPORT_SYMBOL(ishtp_cl_driver_unregister);
+
+/**
+ * ishtp_bus_event_work() - event work function
+ * @work:	work struct pointer
+ *
+ * Once an event is received for a client this work
+ * function is called. If the device has registered a
+ * callback then the callback is called.
+ */
+static void ishtp_bus_event_work(struct work_struct *work)
+{
+	struct ishtp_cl_device *device;
+
+	device = container_of(work, struct ishtp_cl_device, event_work);
+
+	if (device->event_cb)
+		device->event_cb(device);
+}
+
+/**
+ * ishtp_cl_bus_rx_event() - schedule event work
+ * @device:	client device instance
+ *
+ * Once an event is received for a client this schedules
+ * a work function to process.
+ */
+void ishtp_cl_bus_rx_event(struct ishtp_cl_device *device)
+{
+	if (!device || !device->event_cb)
+		return;
+
+	if (device->event_cb)
+		schedule_work(&device->event_work);
+}
+
+/**
+ * ishtp_register_event_cb() - Register callback
+ * @device:	client device instance
+ * @event_cb:	Event processor for an client
+ *
+ * Register a callback for events, called from client driver
+ *
+ * Return: Return 0 or -EALREADY if already registered
+ */
+int ishtp_register_event_cb(struct ishtp_cl_device *device,
+	void (*event_cb)(struct ishtp_cl_device *))
+{
+	if (device->event_cb)
+		return -EALREADY;
+
+	device->event_cb = event_cb;
+	INIT_WORK(&device->event_work, ishtp_bus_event_work);
+
+	return 0;
+}
+EXPORT_SYMBOL(ishtp_register_event_cb);
+
+/**
+ * ishtp_get_device() - update usage count for the device
+ * @cl_device:	client device instance
+ *
+ * Increment the usage count. The device can't be deleted
+ */
+void ishtp_get_device(struct ishtp_cl_device *cl_device)
+{
+	cl_device->reference_count++;
+}
+EXPORT_SYMBOL(ishtp_get_device);
+
+/**
+ * ishtp_put_device() - decrement usage count for the device
+ * @cl_device:	client device instance
+ *
+ * Decrement the usage count. The device can be deleted is count = 0
+ */
+void ishtp_put_device(struct ishtp_cl_device *cl_device)
+{
+	cl_device->reference_count--;
+}
+EXPORT_SYMBOL(ishtp_put_device);
+
+/**
+ * ishtp_bus_new_client() - Create a new client
+ * @dev:	ISHTP device instance
+ *
+ * Once bus protocol enumerates a client, this is called
+ * to add a device for the client.
+ *
+ * Return: 0 on success or error code on failure
+ */
+int ishtp_bus_new_client(struct ishtp_device *dev)
+{
+	int	i;
+	char	*dev_name;
+	struct ishtp_cl_device	*cl_device;
+	uuid_le	device_uuid;
+
+	/*
+	 * For all reported clients, create an unconnected client and add its
+	 * device to ISHTP bus.
+	 * If appropriate driver has loaded, this will trigger its probe().
+	 * Otherwise, probe() will be called when driver is loaded
+	 */
+	i = dev->fw_client_presentation_num - 1;
+	device_uuid = dev->fw_clients[i].props.protocol_name;
+	dev_name = kasprintf(GFP_KERNEL, "{%pUL}", device_uuid.b);
+	if (!dev_name)
+		return	-ENOMEM;
+
+	cl_device = ishtp_bus_add_device(dev, device_uuid, dev_name);
+	if (!cl_device) {
+		kfree(dev_name);
+		return	-ENOENT;
+	}
+
+	kfree(dev_name);
+
+	return	0;
+}
+
+/**
+ * ishtp_cl_device_bind() - bind a device
+ * @cl:		ishtp client device
+ *
+ * Binds connected ishtp_cl to ISHTP bus device
+ *
+ * Return: 0 on success or fault code
+ */
+int ishtp_cl_device_bind(struct ishtp_cl *cl)
+{
+	struct ishtp_cl_device	*cl_device;
+	unsigned long flags;
+	int	rv;
+
+	if (!cl->fw_client_id || cl->state != ISHTP_CL_CONNECTED)
+		return	-EFAULT;
+
+	rv = -ENOENT;
+	spin_lock_irqsave(&cl->dev->device_list_lock, flags);
+	list_for_each_entry(cl_device, &cl->dev->device_list,
+			device_link) {
+		if (cl_device->fw_client->client_id == cl->fw_client_id) {
+			cl->device = cl_device;
+			rv = 0;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&cl->dev->device_list_lock, flags);
+	return	rv;
+}
+
+/**
+ * ishtp_bus_remove_all_clients() - Remove all clients
+ * @ishtp_dev:		ishtp device
+ * @warm_reset:		Reset due to FW reset dure to errors or S3 suspend
+ *
+ * This is part of reset/remove flow. This function the main processing
+ * only targets error processing, if the FW has forced reset or
+ * error to remove connected clients. When warm reset the client devices are
+ * not removed.
+ */
+void ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
+				  bool warm_reset)
+{
+	struct ishtp_cl_device	*cl_device, *n;
+	struct ishtp_cl	*cl;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&ishtp_dev->cl_list_lock, flags);
+	list_for_each_entry(cl, &ishtp_dev->cl_list, link) {
+		cl->state = ISHTP_CL_DISCONNECTED;
+
+		/*
+		 * Wake any pending process. The waiter would check dev->state
+		 * and determine that it's not enabled already,
+		 * and will return error to its caller
+		 */
+		wake_up_interruptible(&cl->wait_ctrl_res);
+
+		/* Disband any pending read/write requests and free rb */
+		ishtp_cl_flush_queues(cl);
+
+		/* Remove all free and in_process rings, both Rx and Tx */
+		ishtp_cl_free_rx_ring(cl);
+		ishtp_cl_free_tx_ring(cl);
+
+		/*
+		 * Free client and ISHTP bus client device structures
+		 * don't free host client because it is part of the OS fd
+		 * structure
+		 */
+	}
+	spin_unlock_irqrestore(&ishtp_dev->cl_list_lock, flags);
+
+	/* Release DMA buffers for client messages */
+	ishtp_cl_free_dma_buf(ishtp_dev);
+
+	/* remove bus clients */
+	spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
+	list_for_each_entry_safe(cl_device, n, &ishtp_dev->device_list,
+				 device_link) {
+		if (warm_reset && cl_device->reference_count)
+			continue;
+
+		list_del(&cl_device->device_link);
+		spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
+		ishtp_bus_remove_device(cl_device);
+		spin_lock_irqsave(&ishtp_dev->device_list_lock, flags);
+	}
+	spin_unlock_irqrestore(&ishtp_dev->device_list_lock, flags);
+
+	/* Free all client structures */
+	spin_lock_irqsave(&ishtp_dev->fw_clients_lock, flags);
+	kfree(ishtp_dev->fw_clients);
+	ishtp_dev->fw_clients = NULL;
+	ishtp_dev->fw_clients_num = 0;
+	ishtp_dev->fw_client_presentation_num = 0;
+	ishtp_dev->fw_client_index = 0;
+	bitmap_zero(ishtp_dev->fw_clients_map, ISHTP_CLIENTS_MAX);
+	spin_unlock_irqrestore(&ishtp_dev->fw_clients_lock, flags);
+}
+EXPORT_SYMBOL(ishtp_bus_remove_all_clients);
+
+/**
+ * ishtp_reset_handler() - IPC reset handler
+ * @dev:	ishtp device
+ *
+ * ISHTP Handler for IPC_RESET notification
+ */
+void ishtp_reset_handler(struct ishtp_device *dev)
+{
+	unsigned long	flags;
+
+	/* Handle FW-initiated reset */
+	dev->dev_state = ISHTP_DEV_RESETTING;
+
+	/* Clear BH processing queue - no further HBMs */
+	spin_lock_irqsave(&dev->rd_msg_spinlock, flags);
+	dev->rd_msg_fifo_head = dev->rd_msg_fifo_tail = 0;
+	spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
+
+	/* Handle ISH FW reset against upper layers */
+	ishtp_bus_remove_all_clients(dev, true);
+}
+EXPORT_SYMBOL(ishtp_reset_handler);
+
+/**
+ * ishtp_reset_compl_handler() - Reset completion handler
+ * @dev:	ishtp device
+ *
+ * ISHTP handler for IPC_RESET sequence completion to start
+ * host message bus start protocol sequence.
+ */
+void ishtp_reset_compl_handler(struct ishtp_device *dev)
+{
+	dev->dev_state = ISHTP_DEV_INIT_CLIENTS;
+	dev->hbm_state = ISHTP_HBM_START;
+	ishtp_hbm_start_req(dev);
+}
+EXPORT_SYMBOL(ishtp_reset_compl_handler);
+
+/**
+ * ishtp_use_dma_transfer() - Function to use DMA
+ *
+ * This interface is used to enable usage of DMA
+ *
+ * Return non zero if DMA can be enabled
+ */
+int ishtp_use_dma_transfer(void)
+{
+	return ishtp_use_dma;
+}
+
+/**
+ * ishtp_bus_register() - Function to register bus
+ *
+ * This register ishtp bus
+ *
+ * Return: Return output of bus_register
+ */
+static int  __init ishtp_bus_register(void)
+{
+	return bus_register(&ishtp_cl_bus_type);
+}
+
+/**
+ * ishtp_bus_unregister() - Function to unregister bus
+ *
+ * This unregister ishtp bus
+ */
+static void __exit ishtp_bus_unregister(void)
+{
+	bus_unregister(&ishtp_cl_bus_type);
+}
+
+module_init(ishtp_bus_register);
+module_exit(ishtp_bus_unregister);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/intel-ish-hid/ishtp/bus.h b/drivers/hid/intel-ish-hid/ishtp/bus.h
new file mode 100644
index 0000000..a1ffae7
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/bus.h
@@ -0,0 +1,114 @@
+/*
+ * ISHTP bus definitions
+ *
+ * Copyright (c) 2014-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+#ifndef _LINUX_ISHTP_CL_BUS_H
+#define _LINUX_ISHTP_CL_BUS_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+struct ishtp_cl;
+struct ishtp_cl_device;
+struct ishtp_device;
+struct ishtp_msg_hdr;
+
+/**
+ * struct ishtp_cl_device - ISHTP device handle
+ * @dev:	device pointer
+ * @ishtp_dev:	pointer to ishtp device structure to primarily to access
+ *		hw device operation callbacks and properties
+ * @fw_client:	fw_client pointer to get fw information like protocol name
+ *		max message length etc.
+ * @device_link: Link to next client in the list on a bus
+ * @event_work:	Used to schedule rx event for client
+ * @driver_data: Storage driver private data
+ * @reference_count:	Used for get/put device
+ * @event_cb:	Callback to driver to send events
+ *
+ * An ishtp_cl_device pointer is returned from ishtp_add_device()
+ * and links ISHTP bus clients to their actual host client pointer.
+ * Drivers for ISHTP devices will get an ishtp_cl_device pointer
+ * when being probed and shall use it for doing bus I/O.
+ */
+struct ishtp_cl_device {
+	struct device		dev;
+	struct ishtp_device	*ishtp_dev;
+	struct ishtp_fw_client	*fw_client;
+	struct list_head	device_link;
+	struct work_struct	event_work;
+	void			*driver_data;
+	int			reference_count;
+	void (*event_cb)(struct ishtp_cl_device *device);
+};
+
+/**
+ * struct ishtp_cl_device - ISHTP device handle
+ * @driver:	driver instance on a bus
+ * @name:	Name of the device for probe
+ * @probe:	driver callback for device probe
+ * @remove:	driver callback on device removal
+ *
+ * Client drivers defines to get probed/removed for ISHTP client device.
+ */
+struct ishtp_cl_driver {
+	struct device_driver driver;
+	const char *name;
+	int (*probe)(struct ishtp_cl_device *dev);
+	int (*remove)(struct ishtp_cl_device *dev);
+	int (*reset)(struct ishtp_cl_device *dev);
+	const struct dev_pm_ops *pm;
+};
+
+
+int	ishtp_bus_new_client(struct ishtp_device *dev);
+void	ishtp_remove_all_clients(struct ishtp_device *dev);
+int	ishtp_cl_device_bind(struct ishtp_cl *cl);
+void	ishtp_cl_bus_rx_event(struct ishtp_cl_device *device);
+
+/* Write a multi-fragment message */
+int	ishtp_send_msg(struct ishtp_device *dev,
+		       struct ishtp_msg_hdr *hdr, void *msg,
+		       void (*ipc_send_compl)(void *),
+		       void *ipc_send_compl_prm);
+
+/* Write a single-fragment message */
+int	ishtp_write_message(struct ishtp_device *dev,
+			    struct ishtp_msg_hdr *hdr,
+			    unsigned char *buf);
+
+/* Use DMA to send/receive messages */
+int ishtp_use_dma_transfer(void);
+
+/* Exported functions */
+void	ishtp_bus_remove_all_clients(struct ishtp_device *ishtp_dev,
+				     bool warm_reset);
+
+void	ishtp_recv(struct ishtp_device *dev);
+void	ishtp_reset_handler(struct ishtp_device *dev);
+void	ishtp_reset_compl_handler(struct ishtp_device *dev);
+
+void	ishtp_put_device(struct ishtp_cl_device *);
+void	ishtp_get_device(struct ishtp_cl_device *);
+
+int	__ishtp_cl_driver_register(struct ishtp_cl_driver *driver,
+				   struct module *owner);
+#define ishtp_cl_driver_register(driver)		\
+	__ishtp_cl_driver_register(driver, THIS_MODULE)
+void	ishtp_cl_driver_unregister(struct ishtp_cl_driver *driver);
+
+int	ishtp_register_event_cb(struct ishtp_cl_device *device,
+				void (*read_cb)(struct ishtp_cl_device *));
+int	ishtp_fw_cl_by_uuid(struct ishtp_device *dev, const uuid_le *cuuid);
+
+#endif /* _LINUX_ISHTP_CL_BUS_H */
diff --git a/drivers/hid/intel-ish-hid/ishtp/client-buffers.c b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
new file mode 100644
index 0000000..b9b917d
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/client-buffers.c
@@ -0,0 +1,257 @@
+/*
+ * ISHTP Ring Buffers
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include "client.h"
+
+/**
+ * ishtp_cl_alloc_rx_ring() - Allocate RX ring buffers
+ * @cl: client device instance
+ *
+ * Allocate and initialize RX ring buffers
+ *
+ * Return: 0 on success else -ENOMEM
+ */
+int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl)
+{
+	size_t	len = cl->device->fw_client->props.max_msg_length;
+	int	j;
+	struct ishtp_cl_rb *rb;
+	int	ret = 0;
+	unsigned long	flags;
+
+	for (j = 0; j < cl->rx_ring_size; ++j) {
+		rb = ishtp_io_rb_init(cl);
+		if (!rb) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		ret = ishtp_io_rb_alloc_buf(rb, len);
+		if (ret)
+			goto out;
+		spin_lock_irqsave(&cl->free_list_spinlock, flags);
+		list_add_tail(&rb->list, &cl->free_rb_list.list);
+		spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+	}
+
+	return	0;
+
+out:
+	dev_err(&cl->device->dev, "error in allocating Rx buffers\n");
+	ishtp_cl_free_rx_ring(cl);
+	return	ret;
+}
+
+/**
+ * ishtp_cl_alloc_tx_ring() - Allocate TX ring buffers
+ * @cl: client device instance
+ *
+ * Allocate and initialize TX ring buffers
+ *
+ * Return: 0 on success else -ENOMEM
+ */
+int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl)
+{
+	size_t	len = cl->device->fw_client->props.max_msg_length;
+	int	j;
+	unsigned long	flags;
+
+	/* Allocate pool to free Tx bufs */
+	for (j = 0; j < cl->tx_ring_size; ++j) {
+		struct ishtp_cl_tx_ring	*tx_buf;
+
+		tx_buf = kzalloc(sizeof(struct ishtp_cl_tx_ring), GFP_KERNEL);
+		if (!tx_buf)
+			goto	out;
+
+		tx_buf->send_buf.data = kmalloc(len, GFP_KERNEL);
+		if (!tx_buf->send_buf.data) {
+			kfree(tx_buf);
+			goto	out;
+		}
+
+		spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
+		list_add_tail(&tx_buf->list, &cl->tx_free_list.list);
+		spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
+	}
+	return	0;
+out:
+	dev_err(&cl->device->dev, "error in allocating Tx pool\n");
+	ishtp_cl_free_rx_ring(cl);
+	return	-ENOMEM;
+}
+
+/**
+ * ishtp_cl_free_rx_ring() - Free RX ring buffers
+ * @cl: client device instance
+ *
+ * Free RX ring buffers
+ */
+void ishtp_cl_free_rx_ring(struct ishtp_cl *cl)
+{
+	struct ishtp_cl_rb *rb;
+	unsigned long	flags;
+
+	/* release allocated memory - pass over free_rb_list */
+	spin_lock_irqsave(&cl->free_list_spinlock, flags);
+	while (!list_empty(&cl->free_rb_list.list)) {
+		rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb,
+				list);
+		list_del(&rb->list);
+		kfree(rb->buffer.data);
+		kfree(rb);
+	}
+	spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+	/* release allocated memory - pass over in_process_list */
+	spin_lock_irqsave(&cl->in_process_spinlock, flags);
+	while (!list_empty(&cl->in_process_list.list)) {
+		rb = list_entry(cl->in_process_list.list.next,
+				struct ishtp_cl_rb, list);
+		list_del(&rb->list);
+		kfree(rb->buffer.data);
+		kfree(rb);
+	}
+	spin_unlock_irqrestore(&cl->in_process_spinlock, flags);
+}
+
+/**
+ * ishtp_cl_free_tx_ring() - Free TX ring buffers
+ * @cl: client device instance
+ *
+ * Free TX ring buffers
+ */
+void ishtp_cl_free_tx_ring(struct ishtp_cl *cl)
+{
+	struct ishtp_cl_tx_ring	*tx_buf;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&cl->tx_free_list_spinlock, flags);
+	/* release allocated memory - pass over tx_free_list */
+	while (!list_empty(&cl->tx_free_list.list)) {
+		tx_buf = list_entry(cl->tx_free_list.list.next,
+				    struct ishtp_cl_tx_ring, list);
+		list_del(&tx_buf->list);
+		kfree(tx_buf->send_buf.data);
+		kfree(tx_buf);
+	}
+	spin_unlock_irqrestore(&cl->tx_free_list_spinlock, flags);
+
+	spin_lock_irqsave(&cl->tx_list_spinlock, flags);
+	/* release allocated memory - pass over tx_list */
+	while (!list_empty(&cl->tx_list.list)) {
+		tx_buf = list_entry(cl->tx_list.list.next,
+				    struct ishtp_cl_tx_ring, list);
+		list_del(&tx_buf->list);
+		kfree(tx_buf->send_buf.data);
+		kfree(tx_buf);
+	}
+	spin_unlock_irqrestore(&cl->tx_list_spinlock, flags);
+}
+
+/**
+ * ishtp_io_rb_free() - Free IO request block
+ * @rb: IO request block
+ *
+ * Free io request block memory
+ */
+void ishtp_io_rb_free(struct ishtp_cl_rb *rb)
+{
+	if (rb == NULL)
+		return;
+
+	kfree(rb->buffer.data);
+	kfree(rb);
+}
+
+/**
+ * ishtp_io_rb_init() - Allocate and init IO request block
+ * @cl: client device instance
+ *
+ * Allocate and initialize request block
+ *
+ * Return: Allocted IO request block pointer
+ */
+struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl)
+{
+	struct ishtp_cl_rb *rb;
+
+	rb = kzalloc(sizeof(struct ishtp_cl_rb), GFP_KERNEL);
+	if (!rb)
+		return NULL;
+
+	INIT_LIST_HEAD(&rb->list);
+	rb->cl = cl;
+	rb->buf_idx = 0;
+	return rb;
+}
+
+/**
+ * ishtp_io_rb_alloc_buf() - Allocate and init response buffer
+ * @rb: IO request block
+ * @length: length of response buffer
+ *
+ * Allocate respose buffer
+ *
+ * Return: 0 on success else -ENOMEM
+ */
+int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length)
+{
+	if (!rb)
+		return -EINVAL;
+
+	if (length == 0)
+		return 0;
+
+	rb->buffer.data = kmalloc(length, GFP_KERNEL);
+	if (!rb->buffer.data)
+		return -ENOMEM;
+
+	rb->buffer.size = length;
+	return 0;
+}
+
+/**
+ * ishtp_cl_io_rb_recycle() - Recycle IO request blocks
+ * @rb: IO request block
+ *
+ * Re-append rb to its client's free list and send flow control if needed
+ *
+ * Return: 0 on success else -EFAULT
+ */
+int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb)
+{
+	struct ishtp_cl *cl;
+	int	rets = 0;
+	unsigned long	flags;
+
+	if (!rb || !rb->cl)
+		return	-EFAULT;
+
+	cl = rb->cl;
+	spin_lock_irqsave(&cl->free_list_spinlock, flags);
+	list_add_tail(&rb->list, &cl->free_rb_list.list);
+	spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+
+	/*
+	 * If we returned the first buffer to empty 'free' list,
+	 * send flow control
+	 */
+	if (!cl->out_flow_ctrl_creds)
+		rets = ishtp_cl_read_start(cl);
+
+	return	rets;
+}
+EXPORT_SYMBOL(ishtp_cl_io_rb_recycle);
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c
new file mode 100644
index 0000000..007443e
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/client.c
@@ -0,0 +1,1047 @@
+/*
+ * ISHTP client logic
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include "hbm.h"
+#include "client.h"
+
+/**
+ * ishtp_read_list_flush() - Flush read queue
+ * @cl: ishtp client instance
+ *
+ * Used to remove all entries from read queue for a client
+ */
+static void ishtp_read_list_flush(struct ishtp_cl *cl)
+{
+	struct ishtp_cl_rb *rb;
+	struct ishtp_cl_rb *next;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&cl->dev->read_list_spinlock, flags);
+	list_for_each_entry_safe(rb, next, &cl->dev->read_list.list, list)
+		if (rb->cl && ishtp_cl_cmp_id(cl, rb->cl)) {
+			list_del(&rb->list);
+			ishtp_io_rb_free(rb);
+		}
+	spin_unlock_irqrestore(&cl->dev->read_list_spinlock, flags);
+}
+
+/**
+ * ishtp_cl_flush_queues() - Flush all queues for a client
+ * @cl: ishtp client instance
+ *
+ * Used to remove all queues for a client. This is called when a client device
+ * needs reset due to error, S3 resume or during module removal
+ *
+ * Return: 0 on success else -EINVAL if device is NULL
+ */
+int ishtp_cl_flush_queues(struct ishtp_cl *cl)
+{
+	if (WARN_ON(!cl || !cl->dev))
+		return -EINVAL;
+
+	ishtp_read_list_flush(cl);
+
+	return 0;
+}
+EXPORT_SYMBOL(ishtp_cl_flush_queues);
+
+/**
+ * ishtp_cl_init() - Initialize all fields of a client device
+ * @cl: ishtp client instance
+ * @dev: ishtp device
+ *
+ * Initializes a client device fields: Init spinlocks, init queues etc.
+ * This function is called during new client creation
+ */
+static void ishtp_cl_init(struct ishtp_cl *cl, struct ishtp_device *dev)
+{
+	memset(cl, 0, sizeof(struct ishtp_cl));
+	init_waitqueue_head(&cl->wait_ctrl_res);
+	spin_lock_init(&cl->free_list_spinlock);
+	spin_lock_init(&cl->in_process_spinlock);
+	spin_lock_init(&cl->tx_list_spinlock);
+	spin_lock_init(&cl->tx_free_list_spinlock);
+	spin_lock_init(&cl->fc_spinlock);
+	INIT_LIST_HEAD(&cl->link);
+	cl->dev = dev;
+
+	INIT_LIST_HEAD(&cl->free_rb_list.list);
+	INIT_LIST_HEAD(&cl->tx_list.list);
+	INIT_LIST_HEAD(&cl->tx_free_list.list);
+	INIT_LIST_HEAD(&cl->in_process_list.list);
+
+	cl->rx_ring_size = CL_DEF_RX_RING_SIZE;
+	cl->tx_ring_size = CL_DEF_TX_RING_SIZE;
+
+	/* dma */
+	cl->last_tx_path = CL_TX_PATH_IPC;
+	cl->last_dma_acked = 1;
+	cl->last_dma_addr = NULL;
+	cl->last_ipc_acked = 1;
+}
+
+/**
+ * ishtp_cl_allocate() - allocates client structure and sets it up.
+ * @dev: ishtp device
+ *
+ * Allocate memory for new client device and call to initialize each field.
+ *
+ * Return: The allocated client instance or NULL on failure
+ */
+struct ishtp_cl *ishtp_cl_allocate(struct ishtp_device *dev)
+{
+	struct ishtp_cl *cl;
+
+	cl = kmalloc(sizeof(struct ishtp_cl), GFP_KERNEL);
+	if (!cl)
+		return NULL;
+
+	ishtp_cl_init(cl, dev);
+	return cl;
+}
+EXPORT_SYMBOL(ishtp_cl_allocate);
+
+/**
+ * ishtp_cl_free() - Frees a client device
+ * @cl: client device instance
+ *
+ * Frees a client device
+ */
+void	ishtp_cl_free(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	unsigned long flags;
+
+	if (!cl)
+		return;
+
+	dev = cl->dev;
+	if (!dev)
+		return;
+
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	ishtp_cl_free_rx_ring(cl);
+	ishtp_cl_free_tx_ring(cl);
+	kfree(cl);
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+}
+EXPORT_SYMBOL(ishtp_cl_free);
+
+/**
+ * ishtp_cl_link() - Reserve a host id and link the client instance
+ * @cl: client device instance
+ * @id: host client id to use. It can be ISHTP_HOST_CLIENT_ID_ANY if any
+ *	id from the available can be used
+ *
+ *
+ * This allocates a single bit in the hostmap. This function will make sure
+ * that not many client sessions are opened at the same time. Once allocated
+ * the client device instance is added to the ishtp device in the current
+ * client list
+ *
+ * Return: 0 or error code on failure
+ */
+int ishtp_cl_link(struct ishtp_cl *cl, int id)
+{
+	struct ishtp_device *dev;
+	unsigned long	flags, flags_cl;
+	int	ret = 0;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return -EINVAL;
+
+	dev = cl->dev;
+
+	spin_lock_irqsave(&dev->device_lock, flags);
+
+	if (dev->open_handle_count >= ISHTP_MAX_OPEN_HANDLE_COUNT) {
+		ret = -EMFILE;
+		goto unlock_dev;
+	}
+
+	/* If Id is not assigned get one*/
+	if (id == ISHTP_HOST_CLIENT_ID_ANY)
+		id = find_first_zero_bit(dev->host_clients_map,
+			ISHTP_CLIENTS_MAX);
+
+	if (id >= ISHTP_CLIENTS_MAX) {
+		spin_unlock_irqrestore(&dev->device_lock, flags);
+		dev_err(&cl->device->dev, "id exceeded %d", ISHTP_CLIENTS_MAX);
+		return -ENOENT;
+	}
+
+	dev->open_handle_count++;
+	cl->host_client_id = id;
+	spin_lock_irqsave(&dev->cl_list_lock, flags_cl);
+	if (dev->dev_state != ISHTP_DEV_ENABLED) {
+		ret = -ENODEV;
+		goto unlock_cl;
+	}
+	list_add_tail(&cl->link, &dev->cl_list);
+	set_bit(id, dev->host_clients_map);
+	cl->state = ISHTP_CL_INITIALIZING;
+
+unlock_cl:
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags_cl);
+unlock_dev:
+	spin_unlock_irqrestore(&dev->device_lock, flags);
+	return ret;
+}
+EXPORT_SYMBOL(ishtp_cl_link);
+
+/**
+ * ishtp_cl_unlink() - remove fw_cl from the client device list
+ * @cl: client device instance
+ *
+ * Remove a previously linked device to a ishtp device
+ */
+void ishtp_cl_unlink(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	struct ishtp_cl *pos;
+	unsigned long	flags;
+
+	/* don't shout on error exit path */
+	if (!cl || !cl->dev)
+		return;
+
+	dev = cl->dev;
+
+	spin_lock_irqsave(&dev->device_lock, flags);
+	if (dev->open_handle_count > 0) {
+		clear_bit(cl->host_client_id, dev->host_clients_map);
+		dev->open_handle_count--;
+	}
+	spin_unlock_irqrestore(&dev->device_lock, flags);
+
+	/*
+	 * This checks that 'cl' is actually linked into device's structure,
+	 * before attempting 'list_del'
+	 */
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	list_for_each_entry(pos, &dev->cl_list, link)
+		if (cl->host_client_id == pos->host_client_id) {
+			list_del_init(&pos->link);
+			break;
+		}
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+}
+EXPORT_SYMBOL(ishtp_cl_unlink);
+
+/**
+ * ishtp_cl_disconnect() - Send disconnect request to firmware
+ * @cl: client device instance
+ *
+ * Send a disconnect request for a client to firmware.
+ *
+ * Return: 0 if successful disconnect response from the firmware or error
+ * code on failure
+ */
+int ishtp_cl_disconnect(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	int err;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return -ENODEV;
+
+	dev = cl->dev;
+
+	dev->print_log(dev, "%s() state %d\n", __func__, cl->state);
+
+	if (cl->state != ISHTP_CL_DISCONNECTING) {
+		dev->print_log(dev, "%s() Disconnect in progress\n", __func__);
+		return 0;
+	}
+
+	if (ishtp_hbm_cl_disconnect_req(dev, cl)) {
+		dev->print_log(dev, "%s() Failed to disconnect\n", __func__);
+		dev_err(&cl->device->dev, "failed to disconnect.\n");
+		return -ENODEV;
+	}
+
+	err = wait_event_interruptible_timeout(cl->wait_ctrl_res,
+			(dev->dev_state != ISHTP_DEV_ENABLED ||
+			cl->state == ISHTP_CL_DISCONNECTED),
+			ishtp_secs_to_jiffies(ISHTP_CL_CONNECT_TIMEOUT));
+
+	/*
+	 * If FW reset arrived, this will happen. Don't check cl->,
+	 * as 'cl' may be freed already
+	 */
+	if (dev->dev_state != ISHTP_DEV_ENABLED) {
+		dev->print_log(dev, "%s() dev_state != ISHTP_DEV_ENABLED\n",
+			       __func__);
+		return -ENODEV;
+	}
+
+	if (cl->state == ISHTP_CL_DISCONNECTED) {
+		dev->print_log(dev, "%s() successful\n", __func__);
+		return 0;
+	}
+
+	return -ENODEV;
+}
+EXPORT_SYMBOL(ishtp_cl_disconnect);
+
+/**
+ * ishtp_cl_is_other_connecting() - Check other client is connecting
+ * @cl: client device instance
+ *
+ * Checks if other client with the same fw client id is connecting
+ *
+ * Return: true if other client is connected else false
+ */
+static bool ishtp_cl_is_other_connecting(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	struct ishtp_cl *pos;
+	unsigned long	flags;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return false;
+
+	dev = cl->dev;
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	list_for_each_entry(pos, &dev->cl_list, link) {
+		if ((pos->state == ISHTP_CL_CONNECTING) && (pos != cl) &&
+				cl->fw_client_id == pos->fw_client_id) {
+			spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+			return true;
+		}
+	}
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+
+	return false;
+}
+
+/**
+ * ishtp_cl_connect() - Send connect request to firmware
+ * @cl: client device instance
+ *
+ * Send a connect request for a client to firmware. If successful it will
+ * RX and TX ring buffers
+ *
+ * Return: 0 if successful connect response from the firmware and able
+ * to bind and allocate ring buffers or error code on failure
+ */
+int ishtp_cl_connect(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	int rets;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return -ENODEV;
+
+	dev = cl->dev;
+
+	dev->print_log(dev, "%s() current_state = %d\n", __func__, cl->state);
+
+	if (ishtp_cl_is_other_connecting(cl)) {
+		dev->print_log(dev, "%s() Busy\n", __func__);
+		return	-EBUSY;
+	}
+
+	if (ishtp_hbm_cl_connect_req(dev, cl)) {
+		dev->print_log(dev, "%s() HBM connect req fail\n", __func__);
+		return -ENODEV;
+	}
+
+	rets = wait_event_interruptible_timeout(cl->wait_ctrl_res,
+				(dev->dev_state == ISHTP_DEV_ENABLED &&
+				(cl->state == ISHTP_CL_CONNECTED ||
+				 cl->state == ISHTP_CL_DISCONNECTED)),
+				ishtp_secs_to_jiffies(
+					ISHTP_CL_CONNECT_TIMEOUT));
+	/*
+	 * If FW reset arrived, this will happen. Don't check cl->,
+	 * as 'cl' may be freed already
+	 */
+	if (dev->dev_state != ISHTP_DEV_ENABLED) {
+		dev->print_log(dev, "%s() dev_state != ISHTP_DEV_ENABLED\n",
+			       __func__);
+		return -EFAULT;
+	}
+
+	if (cl->state != ISHTP_CL_CONNECTED) {
+		dev->print_log(dev, "%s() state != ISHTP_CL_CONNECTED\n",
+			       __func__);
+		return -EFAULT;
+	}
+
+	rets = cl->status;
+	if (rets) {
+		dev->print_log(dev, "%s() Invalid status\n", __func__);
+		return rets;
+	}
+
+	rets = ishtp_cl_device_bind(cl);
+	if (rets) {
+		dev->print_log(dev, "%s() Bind error\n", __func__);
+		ishtp_cl_disconnect(cl);
+		return rets;
+	}
+
+	rets = ishtp_cl_alloc_rx_ring(cl);
+	if (rets) {
+		dev->print_log(dev, "%s() Alloc RX ring failed\n", __func__);
+		/* if failed allocation, disconnect */
+		ishtp_cl_disconnect(cl);
+		return rets;
+	}
+
+	rets = ishtp_cl_alloc_tx_ring(cl);
+	if (rets) {
+		dev->print_log(dev, "%s() Alloc TX ring failed\n", __func__);
+		/* if failed allocation, disconnect */
+		ishtp_cl_free_rx_ring(cl);
+		ishtp_cl_disconnect(cl);
+		return rets;
+	}
+
+	/* Upon successful connection and allocation, emit flow-control */
+	rets = ishtp_cl_read_start(cl);
+
+	dev->print_log(dev, "%s() successful\n", __func__);
+
+	return rets;
+}
+EXPORT_SYMBOL(ishtp_cl_connect);
+
+/**
+ * ishtp_cl_read_start() - Prepare to read client message
+ * @cl: client device instance
+ *
+ * Get a free buffer from pool of free read buffers and add to read buffer
+ * pool to add contents. Send a flow control request to firmware to be able
+ * send next message.
+ *
+ * Return: 0 if successful or error code on failure
+ */
+int ishtp_cl_read_start(struct ishtp_cl *cl)
+{
+	struct ishtp_device *dev;
+	struct ishtp_cl_rb *rb;
+	int rets;
+	int i;
+	unsigned long	flags;
+	unsigned long	dev_flags;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return -ENODEV;
+
+	dev = cl->dev;
+
+	if (cl->state != ISHTP_CL_CONNECTED)
+		return -ENODEV;
+
+	if (dev->dev_state != ISHTP_DEV_ENABLED)
+		return -ENODEV;
+
+	i = ishtp_fw_cl_by_id(dev, cl->fw_client_id);
+	if (i < 0) {
+		dev_err(&cl->device->dev, "no such fw client %d\n",
+			cl->fw_client_id);
+		return -ENODEV;
+	}
+
+	/* The current rb is the head of the free rb list */
+	spin_lock_irqsave(&cl->free_list_spinlock, flags);
+	if (list_empty(&cl->free_rb_list.list)) {
+		dev_warn(&cl->device->dev,
+			 "[ishtp-ish] Rx buffers pool is empty\n");
+		rets = -ENOMEM;
+		rb = NULL;
+		spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+		goto out;
+	}
+	rb = list_entry(cl->free_rb_list.list.next, struct ishtp_cl_rb, list);
+	list_del_init(&rb->list);
+	spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+
+	rb->cl = cl;
+	rb->buf_idx = 0;
+
+	INIT_LIST_HEAD(&rb->list);
+	rets = 0;
+
+	/*
+	 * This must be BEFORE sending flow control -
+	 * response in ISR may come too fast...
+	 */
+	spin_lock_irqsave(&dev->read_list_spinlock, dev_flags);
+	list_add_tail(&rb->list, &dev->read_list.list);
+	spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags);
+	if (ishtp_hbm_cl_flow_control_req(dev, cl)) {
+		rets = -ENODEV;
+		goto out;
+	}
+out:
+	/* if ishtp_hbm_cl_flow_control_req failed, return rb to free list */
+	if (rets && rb) {
+		spin_lock_irqsave(&dev->read_list_spinlock, dev_flags);
+		list_del(&rb->list);
+		spin_unlock_irqrestore(&dev->read_list_spinlock, dev_flags);
+
+		spin_lock_irqsave(&cl->free_list_spinlock, flags);
+		list_add_tail(&rb->list, &cl->free_rb_list.list);
+		spin_unlock_irqrestore(&cl->free_list_spinlock, flags);
+	}
+	return rets;
+}
+
+/**
+ * ishtp_cl_send() - Send a message to firmware
+ * @cl: client device instance
+ * @buf: message buffer
+ * @length: length of message
+ *
+ * If the client is correct state to send message, this function gets a buffer
+ * from tx ring buffers, copy the message data and call to send the message
+ * using ishtp_cl_send_msg()
+ *
+ * Return: 0 if successful or error code on failure
+ */
+int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length)
+{
+	struct ishtp_device	*dev;
+	int	id;
+	struct ishtp_cl_tx_ring	*cl_msg;
+	int	have_msg_to_send = 0;
+	unsigned long	tx_flags, tx_free_flags;
+
+	if (WARN_ON(!cl || !cl->dev))
+		return -ENODEV;
+
+	dev = cl->dev;
+
+	if (cl->state != ISHTP_CL_CONNECTED) {
+		++cl->err_send_msg;
+		return -EPIPE;
+	}
+
+	if (dev->dev_state != ISHTP_DEV_ENABLED) {
+		++cl->err_send_msg;
+		return -ENODEV;
+	}
+
+	/* Check if we have fw client device */
+	id = ishtp_fw_cl_by_id(dev, cl->fw_client_id);
+	if (id < 0) {
+		++cl->err_send_msg;
+		return -ENOENT;
+	}
+
+	if (length > dev->fw_clients[id].props.max_msg_length) {
+		++cl->err_send_msg;
+		return -EMSGSIZE;
+	}
+
+	/* No free bufs */
+	spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags);
+	if (list_empty(&cl->tx_free_list.list)) {
+		spin_unlock_irqrestore(&cl->tx_free_list_spinlock,
+			tx_free_flags);
+		++cl->err_send_msg;
+		return	-ENOMEM;
+	}
+
+	cl_msg = list_first_entry(&cl->tx_free_list.list,
+		struct ishtp_cl_tx_ring, list);
+	if (!cl_msg->send_buf.data) {
+		spin_unlock_irqrestore(&cl->tx_free_list_spinlock,
+			tx_free_flags);
+		return	-EIO;
+		/* Should not happen, as free list is pre-allocated */
+	}
+	/*
+	 * This is safe, as 'length' is already checked for not exceeding
+	 * max ISHTP message size per client
+	 */
+	list_del_init(&cl_msg->list);
+	spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags);
+	memcpy(cl_msg->send_buf.data, buf, length);
+	cl_msg->send_buf.size = length;
+	spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags);
+	have_msg_to_send = !list_empty(&cl->tx_list.list);
+	list_add_tail(&cl_msg->list, &cl->tx_list.list);
+	spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+
+	if (!have_msg_to_send && cl->ishtp_flow_ctrl_creds > 0)
+		ishtp_cl_send_msg(dev, cl);
+
+	return	0;
+}
+EXPORT_SYMBOL(ishtp_cl_send);
+
+/**
+ * ishtp_cl_read_complete() - read complete
+ * @rb: Pointer to client request block
+ *
+ * If the message is completely received call ishtp_cl_bus_rx_event()
+ * to process message
+ */
+static void ishtp_cl_read_complete(struct ishtp_cl_rb *rb)
+{
+	unsigned long	flags;
+	int	schedule_work_flag = 0;
+	struct ishtp_cl	*cl = rb->cl;
+
+	spin_lock_irqsave(&cl->in_process_spinlock, flags);
+	/*
+	 * if in-process list is empty, then need to schedule
+	 * the processing thread
+	 */
+	schedule_work_flag = list_empty(&cl->in_process_list.list);
+	list_add_tail(&rb->list, &cl->in_process_list.list);
+	spin_unlock_irqrestore(&cl->in_process_spinlock, flags);
+
+	if (schedule_work_flag)
+		ishtp_cl_bus_rx_event(cl->device);
+}
+
+/**
+ * ipc_tx_callback() - IPC tx callback function
+ * @prm: Pointer to client device instance
+ *
+ * Send message over IPC either first time or on callback on previous message
+ * completion
+ */
+static void ipc_tx_callback(void *prm)
+{
+	struct ishtp_cl	*cl = prm;
+	struct ishtp_cl_tx_ring	*cl_msg;
+	size_t	rem;
+	struct ishtp_device	*dev = (cl ? cl->dev : NULL);
+	struct ishtp_msg_hdr	ishtp_hdr;
+	unsigned long	tx_flags, tx_free_flags;
+	unsigned char	*pmsg;
+
+	if (!dev)
+		return;
+
+	/*
+	 * Other conditions if some critical error has
+	 * occurred before this callback is called
+	 */
+	if (dev->dev_state != ISHTP_DEV_ENABLED)
+		return;
+
+	if (cl->state != ISHTP_CL_CONNECTED)
+		return;
+
+	spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags);
+	if (list_empty(&cl->tx_list.list)) {
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		return;
+	}
+
+	if (cl->ishtp_flow_ctrl_creds != 1 && !cl->sending) {
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		return;
+	}
+
+	if (!cl->sending) {
+		--cl->ishtp_flow_ctrl_creds;
+		cl->last_ipc_acked = 0;
+		cl->last_tx_path = CL_TX_PATH_IPC;
+		cl->sending = 1;
+	}
+
+	cl_msg = list_entry(cl->tx_list.list.next, struct ishtp_cl_tx_ring,
+			    list);
+	rem = cl_msg->send_buf.size - cl->tx_offs;
+
+	ishtp_hdr.host_addr = cl->host_client_id;
+	ishtp_hdr.fw_addr = cl->fw_client_id;
+	ishtp_hdr.reserved = 0;
+	pmsg = cl_msg->send_buf.data + cl->tx_offs;
+
+	if (rem <= dev->mtu) {
+		ishtp_hdr.length = rem;
+		ishtp_hdr.msg_complete = 1;
+		cl->sending = 0;
+		list_del_init(&cl_msg->list);	/* Must be before write */
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		/* Submit to IPC queue with no callback */
+		ishtp_write_message(dev, &ishtp_hdr, pmsg);
+		spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags);
+		list_add_tail(&cl_msg->list, &cl->tx_free_list.list);
+		spin_unlock_irqrestore(&cl->tx_free_list_spinlock,
+			tx_free_flags);
+	} else {
+		/* Send IPC fragment */
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		cl->tx_offs += dev->mtu;
+		ishtp_hdr.length = dev->mtu;
+		ishtp_hdr.msg_complete = 0;
+		ishtp_send_msg(dev, &ishtp_hdr, pmsg, ipc_tx_callback, cl);
+	}
+}
+
+/**
+ * ishtp_cl_send_msg_ipc() -Send message using IPC
+ * @dev: ISHTP device instance
+ * @cl: Pointer to client device instance
+ *
+ * Send message over IPC not using DMA
+ */
+static void ishtp_cl_send_msg_ipc(struct ishtp_device *dev,
+				  struct ishtp_cl *cl)
+{
+	/* If last DMA message wasn't acked yet, leave this one in Tx queue */
+	if (cl->last_tx_path == CL_TX_PATH_DMA && cl->last_dma_acked == 0)
+		return;
+
+	cl->tx_offs = 0;
+	ipc_tx_callback(cl);
+	++cl->send_msg_cnt_ipc;
+}
+
+/**
+ * ishtp_cl_send_msg_dma() -Send message using DMA
+ * @dev: ISHTP device instance
+ * @cl: Pointer to client device instance
+ *
+ * Send message using DMA
+ */
+static void ishtp_cl_send_msg_dma(struct ishtp_device *dev,
+	struct ishtp_cl *cl)
+{
+	struct ishtp_msg_hdr	hdr;
+	struct dma_xfer_hbm	dma_xfer;
+	unsigned char	*msg_addr;
+	int off;
+	struct ishtp_cl_tx_ring	*cl_msg;
+	unsigned long tx_flags, tx_free_flags;
+
+	/* If last IPC message wasn't acked yet, leave this one in Tx queue */
+	if (cl->last_tx_path == CL_TX_PATH_IPC && cl->last_ipc_acked == 0)
+		return;
+
+	spin_lock_irqsave(&cl->tx_list_spinlock, tx_flags);
+	if (list_empty(&cl->tx_list.list)) {
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		return;
+	}
+
+	cl_msg = list_entry(cl->tx_list.list.next, struct ishtp_cl_tx_ring,
+		list);
+
+	msg_addr = ishtp_cl_get_dma_send_buf(dev, cl_msg->send_buf.size);
+	if (!msg_addr) {
+		spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+		if (dev->transfer_path == CL_TX_PATH_DEFAULT)
+			ishtp_cl_send_msg_ipc(dev, cl);
+		return;
+	}
+
+	list_del_init(&cl_msg->list);	/* Must be before write */
+	spin_unlock_irqrestore(&cl->tx_list_spinlock, tx_flags);
+
+	--cl->ishtp_flow_ctrl_creds;
+	cl->last_dma_acked = 0;
+	cl->last_dma_addr = msg_addr;
+	cl->last_tx_path = CL_TX_PATH_DMA;
+
+	/* write msg to dma buf */
+	memcpy(msg_addr, cl_msg->send_buf.data, cl_msg->send_buf.size);
+
+	/* send dma_xfer hbm msg */
+	off = msg_addr - (unsigned char *)dev->ishtp_host_dma_tx_buf;
+	ishtp_hbm_hdr(&hdr, sizeof(struct dma_xfer_hbm));
+	dma_xfer.hbm = DMA_XFER;
+	dma_xfer.fw_client_id = cl->fw_client_id;
+	dma_xfer.host_client_id = cl->host_client_id;
+	dma_xfer.reserved = 0;
+	dma_xfer.msg_addr = dev->ishtp_host_dma_tx_buf_phys + off;
+	dma_xfer.msg_length = cl_msg->send_buf.size;
+	dma_xfer.reserved2 = 0;
+	ishtp_write_message(dev, &hdr, (unsigned char *)&dma_xfer);
+	spin_lock_irqsave(&cl->tx_free_list_spinlock, tx_free_flags);
+	list_add_tail(&cl_msg->list, &cl->tx_free_list.list);
+	spin_unlock_irqrestore(&cl->tx_free_list_spinlock, tx_free_flags);
+	++cl->send_msg_cnt_dma;
+}
+
+/**
+ * ishtp_cl_send_msg() -Send message using DMA or IPC
+ * @dev: ISHTP device instance
+ * @cl: Pointer to client device instance
+ *
+ * Send message using DMA or IPC based on transfer_path
+ */
+void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl)
+{
+	if (dev->transfer_path == CL_TX_PATH_DMA)
+		ishtp_cl_send_msg_dma(dev, cl);
+	else
+		ishtp_cl_send_msg_ipc(dev, cl);
+}
+
+/**
+ * recv_ishtp_cl_msg() -Receive client message
+ * @dev: ISHTP device instance
+ * @ishtp_hdr: Pointer to message header
+ *
+ * Receive and dispatch ISHTP client messages. This function executes in ISR
+ * or work queue context
+ */
+void recv_ishtp_cl_msg(struct ishtp_device *dev,
+		       struct ishtp_msg_hdr *ishtp_hdr)
+{
+	struct ishtp_cl *cl;
+	struct ishtp_cl_rb *rb;
+	struct ishtp_cl_rb *new_rb;
+	unsigned char *buffer = NULL;
+	struct ishtp_cl_rb *complete_rb = NULL;
+	unsigned long	flags;
+	int	rb_count;
+
+	if (ishtp_hdr->reserved) {
+		dev_err(dev->devc, "corrupted message header.\n");
+		goto	eoi;
+	}
+
+	if (ishtp_hdr->length > IPC_PAYLOAD_SIZE) {
+		dev_err(dev->devc,
+			"ISHTP message length in hdr exceeds IPC MTU\n");
+		goto	eoi;
+	}
+
+	spin_lock_irqsave(&dev->read_list_spinlock, flags);
+	rb_count = -1;
+	list_for_each_entry(rb, &dev->read_list.list, list) {
+		++rb_count;
+		cl = rb->cl;
+		if (!cl || !(cl->host_client_id == ishtp_hdr->host_addr &&
+				cl->fw_client_id == ishtp_hdr->fw_addr) ||
+				!(cl->state == ISHTP_CL_CONNECTED))
+			continue;
+
+		 /* If no Rx buffer is allocated, disband the rb */
+		if (rb->buffer.size == 0 || rb->buffer.data == NULL) {
+			spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+			dev_err(&cl->device->dev,
+				"Rx buffer is not allocated.\n");
+			list_del(&rb->list);
+			ishtp_io_rb_free(rb);
+			cl->status = -ENOMEM;
+			goto	eoi;
+		}
+
+		/*
+		 * If message buffer overflown (exceeds max. client msg
+		 * size, drop message and return to free buffer.
+		 * Do we need to disconnect such a client? (We don't send
+		 * back FC, so communication will be stuck anyway)
+		 */
+		if (rb->buffer.size < ishtp_hdr->length + rb->buf_idx) {
+			spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+			dev_err(&cl->device->dev,
+				"message overflow. size %d len %d idx %ld\n",
+				rb->buffer.size, ishtp_hdr->length,
+				rb->buf_idx);
+			list_del(&rb->list);
+			ishtp_cl_io_rb_recycle(rb);
+			cl->status = -EIO;
+			goto	eoi;
+		}
+
+		buffer = rb->buffer.data + rb->buf_idx;
+		dev->ops->ishtp_read(dev, buffer, ishtp_hdr->length);
+
+		rb->buf_idx += ishtp_hdr->length;
+		if (ishtp_hdr->msg_complete) {
+			/* Last fragment in message - it's complete */
+			cl->status = 0;
+			list_del(&rb->list);
+			complete_rb = rb;
+
+			--cl->out_flow_ctrl_creds;
+			/*
+			 * the whole msg arrived, send a new FC, and add a new
+			 * rb buffer for the next coming msg
+			 */
+			spin_lock(&cl->free_list_spinlock);
+
+			if (!list_empty(&cl->free_rb_list.list)) {
+				new_rb = list_entry(cl->free_rb_list.list.next,
+					struct ishtp_cl_rb, list);
+				list_del_init(&new_rb->list);
+				spin_unlock(&cl->free_list_spinlock);
+				new_rb->cl = cl;
+				new_rb->buf_idx = 0;
+				INIT_LIST_HEAD(&new_rb->list);
+				list_add_tail(&new_rb->list,
+					&dev->read_list.list);
+
+				ishtp_hbm_cl_flow_control_req(dev, cl);
+			} else {
+				spin_unlock(&cl->free_list_spinlock);
+			}
+		}
+		/* One more fragment in message (even if this was last) */
+		++cl->recv_msg_num_frags;
+
+		/*
+		 * We can safely break here (and in BH too),
+		 * a single input message can go only to a single request!
+		 */
+		break;
+	}
+
+	spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+	/* If it's nobody's message, just read and discard it */
+	if (!buffer) {
+		uint8_t	rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE];
+
+		dev_err(dev->devc, "Dropped Rx msg - no request\n");
+		dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length);
+		goto	eoi;
+	}
+
+	if (complete_rb) {
+		cl = complete_rb->cl;
+		cl->ts_rx = ktime_get();
+		++cl->recv_msg_cnt_ipc;
+		ishtp_cl_read_complete(complete_rb);
+	}
+eoi:
+	return;
+}
+
+/**
+ * recv_ishtp_cl_msg_dma() -Receive client message
+ * @dev: ISHTP device instance
+ * @msg: message pointer
+ * @hbm: hbm buffer
+ *
+ * Receive and dispatch ISHTP client messages using DMA. This function executes
+ * in ISR or work queue context
+ */
+void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg,
+			   struct dma_xfer_hbm *hbm)
+{
+	struct ishtp_cl *cl;
+	struct ishtp_cl_rb *rb;
+	struct ishtp_cl_rb *new_rb;
+	unsigned char *buffer = NULL;
+	struct ishtp_cl_rb *complete_rb = NULL;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->read_list_spinlock, flags);
+
+	list_for_each_entry(rb, &dev->read_list.list, list) {
+		cl = rb->cl;
+		if (!cl || !(cl->host_client_id == hbm->host_client_id &&
+				cl->fw_client_id == hbm->fw_client_id) ||
+				!(cl->state == ISHTP_CL_CONNECTED))
+			continue;
+
+		/*
+		 * If no Rx buffer is allocated, disband the rb
+		 */
+		if (rb->buffer.size == 0 || rb->buffer.data == NULL) {
+			spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+			dev_err(&cl->device->dev,
+				"response buffer is not allocated.\n");
+			list_del(&rb->list);
+			ishtp_io_rb_free(rb);
+			cl->status = -ENOMEM;
+			goto	eoi;
+		}
+
+		/*
+		 * If message buffer overflown (exceeds max. client msg
+		 * size, drop message and return to free buffer.
+		 * Do we need to disconnect such a client? (We don't send
+		 * back FC, so communication will be stuck anyway)
+		 */
+		if (rb->buffer.size < hbm->msg_length) {
+			spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+			dev_err(&cl->device->dev,
+				"message overflow. size %d len %d idx %ld\n",
+				rb->buffer.size, hbm->msg_length, rb->buf_idx);
+			list_del(&rb->list);
+			ishtp_cl_io_rb_recycle(rb);
+			cl->status = -EIO;
+			goto	eoi;
+		}
+
+		buffer = rb->buffer.data;
+		memcpy(buffer, msg, hbm->msg_length);
+		rb->buf_idx = hbm->msg_length;
+
+		/* Last fragment in message - it's complete */
+		cl->status = 0;
+		list_del(&rb->list);
+		complete_rb = rb;
+
+		--cl->out_flow_ctrl_creds;
+		/*
+		 * the whole msg arrived, send a new FC, and add a new
+		 * rb buffer for the next coming msg
+		 */
+		spin_lock(&cl->free_list_spinlock);
+
+		if (!list_empty(&cl->free_rb_list.list)) {
+			new_rb = list_entry(cl->free_rb_list.list.next,
+				struct ishtp_cl_rb, list);
+			list_del_init(&new_rb->list);
+			spin_unlock(&cl->free_list_spinlock);
+			new_rb->cl = cl;
+			new_rb->buf_idx = 0;
+			INIT_LIST_HEAD(&new_rb->list);
+			list_add_tail(&new_rb->list,
+				&dev->read_list.list);
+
+			ishtp_hbm_cl_flow_control_req(dev, cl);
+		} else {
+			spin_unlock(&cl->free_list_spinlock);
+		}
+
+		/* One more fragment in message (this is always last) */
+		++cl->recv_msg_num_frags;
+
+		/*
+		 * We can safely break here (and in BH too),
+		 * a single input message can go only to a single request!
+		 */
+		break;
+	}
+
+	spin_unlock_irqrestore(&dev->read_list_spinlock, flags);
+	/* If it's nobody's message, just read and discard it */
+	if (!buffer) {
+		dev_err(dev->devc, "Dropped Rx (DMA) msg - no request\n");
+		goto	eoi;
+	}
+
+	if (complete_rb) {
+		cl = complete_rb->cl;
+		cl->ts_rx = ktime_get();
+		++cl->recv_msg_cnt_dma;
+		ishtp_cl_read_complete(complete_rb);
+	}
+eoi:
+	return;
+}
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.h b/drivers/hid/intel-ish-hid/ishtp/client.h
new file mode 100644
index 0000000..79eade5
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/client.h
@@ -0,0 +1,182 @@
+/*
+ * ISHTP client logic
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef _ISHTP_CLIENT_H_
+#define _ISHTP_CLIENT_H_
+
+#include <linux/types.h>
+#include "ishtp-dev.h"
+
+/* Client state */
+enum cl_state {
+	ISHTP_CL_INITIALIZING = 0,
+	ISHTP_CL_CONNECTING,
+	ISHTP_CL_CONNECTED,
+	ISHTP_CL_DISCONNECTING,
+	ISHTP_CL_DISCONNECTED
+};
+
+/* Tx and Rx ring size */
+#define	CL_DEF_RX_RING_SIZE	2
+#define	CL_DEF_TX_RING_SIZE	2
+#define	CL_MAX_RX_RING_SIZE	32
+#define	CL_MAX_TX_RING_SIZE	32
+
+#define DMA_SLOT_SIZE		4096
+/* Number of IPC fragments after which it's worth sending via DMA */
+#define	DMA_WORTH_THRESHOLD	3
+
+/* DMA/IPC Tx paths. Other the default means enforcement */
+#define	CL_TX_PATH_DEFAULT	0
+#define	CL_TX_PATH_IPC		1
+#define	CL_TX_PATH_DMA		2
+
+/* Client Tx buffer list entry */
+struct ishtp_cl_tx_ring {
+	struct list_head	list;
+	struct ishtp_msg_data	send_buf;
+};
+
+/* ISHTP client instance */
+struct ishtp_cl {
+	struct list_head	link;
+	struct ishtp_device	*dev;
+	enum cl_state		state;
+	int			status;
+
+	/* Link to ISHTP bus device */
+	struct ishtp_cl_device	*device;
+
+	/* ID of client connected */
+	uint8_t	host_client_id;
+	uint8_t	fw_client_id;
+	uint8_t	ishtp_flow_ctrl_creds;
+	uint8_t	out_flow_ctrl_creds;
+
+	/* dma */
+	int	last_tx_path;
+	/* 0: ack wasn't received,1:ack was received */
+	int	last_dma_acked;
+	unsigned char	*last_dma_addr;
+	/* 0: ack wasn't received,1:ack was received */
+	int	last_ipc_acked;
+
+	/* Rx ring buffer pool */
+	unsigned int	rx_ring_size;
+	struct ishtp_cl_rb	free_rb_list;
+	spinlock_t	free_list_spinlock;
+	/* Rx in-process list */
+	struct ishtp_cl_rb	in_process_list;
+	spinlock_t	in_process_spinlock;
+
+	/* Client Tx buffers list */
+	unsigned int	tx_ring_size;
+	struct ishtp_cl_tx_ring	tx_list, tx_free_list;
+	spinlock_t	tx_list_spinlock;
+	spinlock_t	tx_free_list_spinlock;
+	size_t	tx_offs;	/* Offset in buffer at head of 'tx_list' */
+
+	/**
+	 * if we get a FC, and the list is not empty, we must know whether we
+	 * are at the middle of sending.
+	 * if so -need to increase FC counter, otherwise, need to start sending
+	 * the first msg in list
+	 * (!)This is for counting-FC implementation only. Within single-FC the
+	 * other party may NOT send FC until it receives complete message
+	 */
+	int	sending;
+
+	/* Send FC spinlock */
+	spinlock_t	fc_spinlock;
+
+	/* wait queue for connect and disconnect response from FW */
+	wait_queue_head_t	wait_ctrl_res;
+
+	/* Error stats */
+	unsigned int	err_send_msg;
+	unsigned int	err_send_fc;
+
+	/* Send/recv stats */
+	unsigned int	send_msg_cnt_ipc;
+	unsigned int	send_msg_cnt_dma;
+	unsigned int	recv_msg_cnt_ipc;
+	unsigned int	recv_msg_cnt_dma;
+	unsigned int	recv_msg_num_frags;
+	unsigned int	ishtp_flow_ctrl_cnt;
+	unsigned int	out_flow_ctrl_cnt;
+
+	/* Rx msg ... out FC timing */
+	ktime_t ts_rx;
+	ktime_t ts_out_fc;
+	ktime_t ts_max_fc_delay;
+	void *client_data;
+};
+
+/* Client connection managenment internal functions */
+int ishtp_can_client_connect(struct ishtp_device *ishtp_dev, uuid_le *uuid);
+int ishtp_fw_cl_by_id(struct ishtp_device *dev, uint8_t client_id);
+void ishtp_cl_send_msg(struct ishtp_device *dev, struct ishtp_cl *cl);
+void recv_ishtp_cl_msg(struct ishtp_device *dev,
+		       struct ishtp_msg_hdr *ishtp_hdr);
+int ishtp_cl_read_start(struct ishtp_cl *cl);
+
+/* Ring Buffer I/F */
+int ishtp_cl_alloc_rx_ring(struct ishtp_cl *cl);
+int ishtp_cl_alloc_tx_ring(struct ishtp_cl *cl);
+void ishtp_cl_free_rx_ring(struct ishtp_cl *cl);
+void ishtp_cl_free_tx_ring(struct ishtp_cl *cl);
+
+/* DMA I/F functions */
+void recv_ishtp_cl_msg_dma(struct ishtp_device *dev, void *msg,
+			   struct dma_xfer_hbm *hbm);
+void ishtp_cl_alloc_dma_buf(struct ishtp_device *dev);
+void ishtp_cl_free_dma_buf(struct ishtp_device *dev);
+void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
+				uint32_t size);
+void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
+				    void *msg_addr,
+				    uint8_t size);
+
+/* Request blocks alloc/free I/F */
+struct ishtp_cl_rb *ishtp_io_rb_init(struct ishtp_cl *cl);
+void ishtp_io_rb_free(struct ishtp_cl_rb *priv_rb);
+int ishtp_io_rb_alloc_buf(struct ishtp_cl_rb *rb, size_t length);
+
+/**
+ * ishtp_cl_cmp_id - tells if file private data have same id
+ * returns true  - if ids are the same and not NULL
+ */
+static inline bool ishtp_cl_cmp_id(const struct ishtp_cl *cl1,
+				   const struct ishtp_cl *cl2)
+{
+	return cl1 && cl2 &&
+		(cl1->host_client_id == cl2->host_client_id) &&
+		(cl1->fw_client_id == cl2->fw_client_id);
+}
+
+/* exported functions from ISHTP under client management scope */
+struct ishtp_cl	*ishtp_cl_allocate(struct ishtp_device *dev);
+void ishtp_cl_free(struct ishtp_cl *cl);
+int ishtp_cl_link(struct ishtp_cl *cl, int id);
+void ishtp_cl_unlink(struct ishtp_cl *cl);
+int ishtp_cl_disconnect(struct ishtp_cl *cl);
+int ishtp_cl_connect(struct ishtp_cl *cl);
+int ishtp_cl_send(struct ishtp_cl *cl, uint8_t *buf, size_t length);
+int ishtp_cl_flush_queues(struct ishtp_cl *cl);
+
+/* exported functions from ISHTP client buffer management scope */
+int ishtp_cl_io_rb_recycle(struct ishtp_cl_rb *rb);
+
+#endif /* _ISHTP_CLIENT_H_ */
diff --git a/drivers/hid/intel-ish-hid/ishtp/dma-if.c b/drivers/hid/intel-ish-hid/ishtp/dma-if.c
new file mode 100644
index 0000000..2783f36
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/dma-if.c
@@ -0,0 +1,175 @@
+/*
+ * ISHTP DMA I/F functions
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include "ishtp-dev.h"
+#include "client.h"
+
+/**
+ * ishtp_cl_alloc_dma_buf() - Allocate DMA RX and TX buffer
+ * @dev: ishtp device
+ *
+ * Allocate RX and TX DMA buffer once during bus setup.
+ * It allocates 1MB, RX and TX DMA buffer, which are divided
+ * into slots.
+ */
+void	ishtp_cl_alloc_dma_buf(struct ishtp_device *dev)
+{
+	dma_addr_t	h;
+
+	if (dev->ishtp_host_dma_tx_buf)
+		return;
+
+	dev->ishtp_host_dma_tx_buf_size = 1024*1024;
+	dev->ishtp_host_dma_rx_buf_size = 1024*1024;
+
+	/* Allocate Tx buffer and init usage bitmap */
+	dev->ishtp_host_dma_tx_buf = dma_alloc_coherent(dev->devc,
+					dev->ishtp_host_dma_tx_buf_size,
+					&h, GFP_KERNEL);
+	if (dev->ishtp_host_dma_tx_buf)
+		dev->ishtp_host_dma_tx_buf_phys = h;
+
+	dev->ishtp_dma_num_slots = dev->ishtp_host_dma_tx_buf_size /
+						DMA_SLOT_SIZE;
+
+	dev->ishtp_dma_tx_map = kcalloc(dev->ishtp_dma_num_slots,
+					sizeof(uint8_t),
+					GFP_KERNEL);
+	spin_lock_init(&dev->ishtp_dma_tx_lock);
+
+	/* Allocate Rx buffer */
+	dev->ishtp_host_dma_rx_buf = dma_alloc_coherent(dev->devc,
+					dev->ishtp_host_dma_rx_buf_size,
+					 &h, GFP_KERNEL);
+
+	if (dev->ishtp_host_dma_rx_buf)
+		dev->ishtp_host_dma_rx_buf_phys = h;
+}
+
+/**
+ * ishtp_cl_free_dma_buf() - Free DMA RX and TX buffer
+ * @dev: ishtp device
+ *
+ * Free DMA buffer when all clients are released. This is
+ * only happens during error path in ISH built in driver
+ * model
+ */
+void	ishtp_cl_free_dma_buf(struct ishtp_device *dev)
+{
+	dma_addr_t	h;
+
+	if (dev->ishtp_host_dma_tx_buf) {
+		h = dev->ishtp_host_dma_tx_buf_phys;
+		dma_free_coherent(dev->devc, dev->ishtp_host_dma_tx_buf_size,
+				  dev->ishtp_host_dma_tx_buf, h);
+	}
+
+	if (dev->ishtp_host_dma_rx_buf) {
+		h = dev->ishtp_host_dma_rx_buf_phys;
+		dma_free_coherent(dev->devc, dev->ishtp_host_dma_rx_buf_size,
+				  dev->ishtp_host_dma_rx_buf, h);
+	}
+
+	kfree(dev->ishtp_dma_tx_map);
+	dev->ishtp_host_dma_tx_buf = NULL;
+	dev->ishtp_host_dma_rx_buf = NULL;
+	dev->ishtp_dma_tx_map = NULL;
+}
+
+/*
+ * ishtp_cl_get_dma_send_buf() - Get a DMA memory slot
+ * @dev:	ishtp device
+ * @size:	Size of memory to get
+ *
+ * Find and return free address of "size" bytes in dma tx buffer.
+ * the function will mark this address as "in-used" memory.
+ *
+ * Return: NULL when no free buffer else a buffer to copy
+ */
+void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
+				uint32_t size)
+{
+	unsigned long	flags;
+	int i, j, free;
+	/* additional slot is needed if there is rem */
+	int required_slots = (size / DMA_SLOT_SIZE)
+		+ 1 * (size % DMA_SLOT_SIZE != 0);
+
+	spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
+	for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) {
+		free = 1;
+		for (j = 0; j < required_slots; j++)
+			if (dev->ishtp_dma_tx_map[i+j]) {
+				free = 0;
+				i += j;
+				break;
+			}
+		if (free) {
+			/* mark memory as "caught" */
+			for (j = 0; j < required_slots; j++)
+				dev->ishtp_dma_tx_map[i+j] = 1;
+			spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
+			return (i * DMA_SLOT_SIZE) +
+				(unsigned char *)dev->ishtp_host_dma_tx_buf;
+		}
+	}
+	spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
+	dev_err(dev->devc, "No free DMA buffer to send msg\n");
+	return NULL;
+}
+
+/*
+ * ishtp_cl_release_dma_acked_mem() - Release DMA memory slot
+ * @dev:	ishtp device
+ * @msg_addr:	message address of slot
+ * @size:	Size of memory to get
+ *
+ * Release_dma_acked_mem - returnes the acked memory to free list.
+ * (from msg_addr, size bytes long)
+ */
+void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
+				    void *msg_addr,
+				    uint8_t size)
+{
+	unsigned long	flags;
+	int acked_slots = (size / DMA_SLOT_SIZE)
+		+ 1 * (size % DMA_SLOT_SIZE != 0);
+	int i, j;
+
+	if ((msg_addr - dev->ishtp_host_dma_tx_buf) % DMA_SLOT_SIZE) {
+		dev_err(dev->devc, "Bad DMA Tx ack address\n");
+		return;
+	}
+
+	i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE;
+	spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
+	for (j = 0; j < acked_slots; j++) {
+		if ((i + j) >= dev->ishtp_dma_num_slots ||
+					!dev->ishtp_dma_tx_map[i+j]) {
+			/* no such slot, or memory is already free */
+			spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
+			dev_err(dev->devc, "Bad DMA Tx ack address\n");
+			return;
+		}
+		dev->ishtp_dma_tx_map[i+j] = 0;
+	}
+	spin_unlock_irqrestore(&dev->ishtp_dma_tx_lock, flags);
+}
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c
new file mode 100644
index 0000000..8b5dd58
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c
@@ -0,0 +1,1024 @@
+/*
+ * ISHTP bus layer messages handling
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ */
+
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include "ishtp-dev.h"
+#include "hbm.h"
+#include "client.h"
+
+/**
+ * ishtp_hbm_fw_cl_allocate() - Allocate FW clients
+ * @dev: ISHTP device instance
+ *
+ * Allocates storage for fw clients
+ */
+static void ishtp_hbm_fw_cl_allocate(struct ishtp_device *dev)
+{
+	struct ishtp_fw_client *clients;
+	int b;
+
+	/* count how many ISH clients we have */
+	for_each_set_bit(b, dev->fw_clients_map, ISHTP_CLIENTS_MAX)
+		dev->fw_clients_num++;
+
+	if (dev->fw_clients_num <= 0)
+		return;
+
+	/* allocate storage for fw clients representation */
+	clients = kcalloc(dev->fw_clients_num, sizeof(struct ishtp_fw_client),
+			  GFP_KERNEL);
+	if (!clients) {
+		dev->dev_state = ISHTP_DEV_RESETTING;
+		ish_hw_reset(dev);
+		return;
+	}
+	dev->fw_clients = clients;
+}
+
+/**
+ * ishtp_hbm_cl_hdr() - construct client hbm header
+ * @cl: client
+ * @hbm_cmd: host bus message command
+ * @buf: buffer for cl header
+ * @len: buffer length
+ *
+ * Initialize HBM buffer
+ */
+static inline void ishtp_hbm_cl_hdr(struct ishtp_cl *cl, uint8_t hbm_cmd,
+	void *buf, size_t len)
+{
+	struct ishtp_hbm_cl_cmd *cmd = buf;
+
+	memset(cmd, 0, len);
+
+	cmd->hbm_cmd = hbm_cmd;
+	cmd->host_addr = cl->host_client_id;
+	cmd->fw_addr = cl->fw_client_id;
+}
+
+/**
+ * ishtp_hbm_cl_addr_equal() - Compare client address
+ * @cl: client
+ * @buf: Client command buffer
+ *
+ * Compare client address with the address in command buffer
+ *
+ * Return: True if they have the same address
+ */
+static inline bool ishtp_hbm_cl_addr_equal(struct ishtp_cl *cl, void *buf)
+{
+	struct ishtp_hbm_cl_cmd *cmd = buf;
+
+	return cl->host_client_id == cmd->host_addr &&
+		cl->fw_client_id == cmd->fw_addr;
+}
+
+/**
+ * ishtp_hbm_start_wait() - Wait for HBM start message
+ * @dev: ISHTP device instance
+ *
+ * Wait for HBM start message from firmware
+ *
+ * Return: 0 if HBM start is/was received else timeout error
+ */
+int ishtp_hbm_start_wait(struct ishtp_device *dev)
+{
+	int ret;
+
+	if (dev->hbm_state > ISHTP_HBM_START)
+		return 0;
+
+	dev_dbg(dev->devc, "Going to wait for ishtp start. hbm_state=%08X\n",
+		dev->hbm_state);
+	ret = wait_event_interruptible_timeout(dev->wait_hbm_recvd_msg,
+					dev->hbm_state >= ISHTP_HBM_STARTED,
+					(ISHTP_INTEROP_TIMEOUT * HZ));
+
+	dev_dbg(dev->devc,
+		"Woke up from waiting for ishtp start. hbm_state=%08X\n",
+		dev->hbm_state);
+
+	if (ret <= 0 && (dev->hbm_state <= ISHTP_HBM_START)) {
+		dev->hbm_state = ISHTP_HBM_IDLE;
+		dev_err(dev->devc,
+		"waiting for ishtp start failed. ret=%d hbm_state=%08X\n",
+			ret, dev->hbm_state);
+		return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+/**
+ * ishtp_hbm_start_req() - Send HBM start message
+ * @dev: ISHTP device instance
+ *
+ * Send HBM start message to firmware
+ *
+ * Return: 0 if success else error code
+ */
+int ishtp_hbm_start_req(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	struct hbm_host_version_request *start_req;
+	const size_t len = sizeof(struct hbm_host_version_request);
+
+	ishtp_hbm_hdr(ishtp_hdr, len);
+
+	/* host start message */
+	start_req = (struct hbm_host_version_request *)data;
+	memset(start_req, 0, len);
+	start_req->hbm_cmd = HOST_START_REQ_CMD;
+	start_req->host_version.major_version = HBM_MAJOR_VERSION;
+	start_req->host_version.minor_version = HBM_MINOR_VERSION;
+
+	/*
+	 * (!) Response to HBM start may be so quick that this thread would get
+	 * preempted BEFORE managing to set hbm_state = ISHTP_HBM_START.
+	 * So set it at first, change back to ISHTP_HBM_IDLE upon failure
+	 */
+	dev->hbm_state = ISHTP_HBM_START;
+	if (ishtp_write_message(dev, ishtp_hdr, data)) {
+		dev_err(dev->devc, "version message send failed\n");
+		dev->dev_state = ISHTP_DEV_RESETTING;
+		dev->hbm_state = ISHTP_HBM_IDLE;
+		ish_hw_reset(dev);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/**
+ * ishtp_hbm_enum_clients_req() - Send client enum req
+ * @dev: ISHTP device instance
+ *
+ * Send enumeration client request message
+ *
+ * Return: 0 if success else error code
+ */
+void ishtp_hbm_enum_clients_req(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	struct hbm_host_enum_request *enum_req;
+	const size_t len = sizeof(struct hbm_host_enum_request);
+
+	/* enumerate clients */
+	ishtp_hbm_hdr(ishtp_hdr, len);
+
+	enum_req = (struct hbm_host_enum_request *)data;
+	memset(enum_req, 0, len);
+	enum_req->hbm_cmd = HOST_ENUM_REQ_CMD;
+
+	if (ishtp_write_message(dev, ishtp_hdr, data)) {
+		dev->dev_state = ISHTP_DEV_RESETTING;
+		dev_err(dev->devc, "enumeration request send failed\n");
+		ish_hw_reset(dev);
+	}
+	dev->hbm_state = ISHTP_HBM_ENUM_CLIENTS;
+}
+
+/**
+ * ishtp_hbm_prop_req() - Request property
+ * @dev: ISHTP device instance
+ *
+ * Request property for a single client
+ *
+ * Return: 0 if success else error code
+ */
+static int ishtp_hbm_prop_req(struct ishtp_device *dev)
+{
+
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	struct hbm_props_request *prop_req;
+	const size_t len = sizeof(struct hbm_props_request);
+	unsigned long next_client_index;
+	uint8_t client_num;
+
+	client_num = dev->fw_client_presentation_num;
+
+	next_client_index = find_next_bit(dev->fw_clients_map,
+		ISHTP_CLIENTS_MAX, dev->fw_client_index);
+
+	/* We got all client properties */
+	if (next_client_index == ISHTP_CLIENTS_MAX) {
+		dev->hbm_state = ISHTP_HBM_WORKING;
+		dev->dev_state = ISHTP_DEV_ENABLED;
+
+		for (dev->fw_client_presentation_num = 1;
+			dev->fw_client_presentation_num < client_num + 1;
+				++dev->fw_client_presentation_num)
+			/* Add new client device */
+			ishtp_bus_new_client(dev);
+		return 0;
+	}
+
+	dev->fw_clients[client_num].client_id = next_client_index;
+
+	ishtp_hbm_hdr(ishtp_hdr, len);
+	prop_req = (struct hbm_props_request *)data;
+
+	memset(prop_req, 0, sizeof(struct hbm_props_request));
+
+	prop_req->hbm_cmd = HOST_CLIENT_PROPERTIES_REQ_CMD;
+	prop_req->address = next_client_index;
+
+	if (ishtp_write_message(dev, ishtp_hdr, data)) {
+		dev->dev_state = ISHTP_DEV_RESETTING;
+		dev_err(dev->devc, "properties request send failed\n");
+		ish_hw_reset(dev);
+		return -EIO;
+	}
+
+	dev->fw_client_index = next_client_index;
+
+	return 0;
+}
+
+/**
+ * ishtp_hbm_stop_req() - Send HBM stop
+ * @dev: ISHTP device instance
+ *
+ * Send stop request message
+ */
+static void ishtp_hbm_stop_req(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	struct hbm_host_stop_request *req;
+	const size_t len = sizeof(struct hbm_host_stop_request);
+
+	ishtp_hbm_hdr(ishtp_hdr, len);
+	req = (struct hbm_host_stop_request *)data;
+
+	memset(req, 0, sizeof(struct hbm_host_stop_request));
+	req->hbm_cmd = HOST_STOP_REQ_CMD;
+	req->reason = DRIVER_STOP_REQUEST;
+
+	ishtp_write_message(dev, ishtp_hdr, data);
+}
+
+/**
+ * ishtp_hbm_cl_flow_control_req() - Send flow control request
+ * @dev: ISHTP device instance
+ * @cl: ISHTP client instance
+ *
+ * Send flow control request
+ *
+ * Return: 0 if success else error code
+ */
+int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
+				  struct ishtp_cl *cl)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	const size_t len = sizeof(struct hbm_flow_control);
+	int	rv;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&cl->fc_spinlock, flags);
+	ishtp_hbm_hdr(ishtp_hdr, len);
+	ishtp_hbm_cl_hdr(cl, ISHTP_FLOW_CONTROL_CMD, data, len);
+
+	/*
+	 * Sync possible race when RB recycle and packet receive paths
+	 * both try to send an out FC
+	 */
+	if (cl->out_flow_ctrl_creds) {
+		spin_unlock_irqrestore(&cl->fc_spinlock, flags);
+		return	0;
+	}
+
+	cl->recv_msg_num_frags = 0;
+
+	rv = ishtp_write_message(dev, ishtp_hdr, data);
+	if (!rv) {
+		++cl->out_flow_ctrl_creds;
+		++cl->out_flow_ctrl_cnt;
+		cl->ts_out_fc = ktime_get();
+		if (cl->ts_rx) {
+			ktime_t ts_diff = ktime_sub(cl->ts_out_fc, cl->ts_rx);
+			if (ktime_after(ts_diff, cl->ts_max_fc_delay))
+				cl->ts_max_fc_delay = ts_diff;
+		}
+	} else {
+		++cl->err_send_fc;
+	}
+
+	spin_unlock_irqrestore(&cl->fc_spinlock, flags);
+	return	rv;
+}
+
+/**
+ * ishtp_hbm_cl_disconnect_req() - Send disconnect request
+ * @dev: ISHTP device instance
+ * @cl: ISHTP client instance
+ *
+ * Send disconnect message to fw
+ *
+ * Return: 0 if success else error code
+ */
+int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	const size_t len = sizeof(struct hbm_client_connect_request);
+
+	ishtp_hbm_hdr(ishtp_hdr, len);
+	ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_REQ_CMD, data, len);
+
+	return ishtp_write_message(dev, ishtp_hdr, data);
+}
+
+/**
+ * ishtp_hbm_cl_disconnect_res() - Get disconnect response
+ * @dev: ISHTP device instance
+ * @rs: Response message
+ *
+ * Received disconnect response from fw
+ */
+static void ishtp_hbm_cl_disconnect_res(struct ishtp_device *dev,
+	struct hbm_client_connect_response *rs)
+{
+	struct ishtp_cl *cl = NULL;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	list_for_each_entry(cl, &dev->cl_list, link) {
+		if (!rs->status && ishtp_hbm_cl_addr_equal(cl, rs)) {
+			cl->state = ISHTP_CL_DISCONNECTED;
+			wake_up_interruptible(&cl->wait_ctrl_res);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+}
+
+/**
+ * ishtp_hbm_cl_connect_req() - Send connect request
+ * @dev: ISHTP device instance
+ * @cl: client device instance
+ *
+ * Send connection request to specific fw client
+ *
+ * Return: 0 if success else error code
+ */
+int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl)
+{
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[128];
+	struct ishtp_msg_hdr *ishtp_hdr = &hdr;
+	const size_t len = sizeof(struct hbm_client_connect_request);
+
+	ishtp_hbm_hdr(ishtp_hdr, len);
+	ishtp_hbm_cl_hdr(cl, CLIENT_CONNECT_REQ_CMD, data, len);
+
+	return ishtp_write_message(dev, ishtp_hdr, data);
+}
+
+/**
+ * ishtp_hbm_cl_connect_res() - Get connect response
+ * @dev: ISHTP device instance
+ * @rs: Response message
+ *
+ * Received connect response from fw
+ */
+static void ishtp_hbm_cl_connect_res(struct ishtp_device *dev,
+	struct hbm_client_connect_response *rs)
+{
+	struct ishtp_cl *cl = NULL;
+	unsigned long	flags;
+
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	list_for_each_entry(cl, &dev->cl_list, link) {
+		if (ishtp_hbm_cl_addr_equal(cl, rs)) {
+			if (!rs->status) {
+				cl->state = ISHTP_CL_CONNECTED;
+				cl->status = 0;
+			} else {
+				cl->state = ISHTP_CL_DISCONNECTED;
+				cl->status = -ENODEV;
+			}
+			wake_up_interruptible(&cl->wait_ctrl_res);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+}
+
+/**
+ * ishtp_client_disconnect_request() - Receive disconnect request
+ * @dev: ISHTP device instance
+ * @disconnect_req: disconnect request structure
+ *
+ * Disconnect request bus message from the fw. Send diconnect response.
+ */
+static void ishtp_hbm_fw_disconnect_req(struct ishtp_device *dev,
+	struct hbm_client_connect_request *disconnect_req)
+{
+	struct ishtp_cl *cl;
+	const size_t len = sizeof(struct hbm_client_connect_response);
+	unsigned long	flags;
+	struct ishtp_msg_hdr hdr;
+	unsigned char data[4];	/* All HBM messages are 4 bytes */
+
+	spin_lock_irqsave(&dev->cl_list_lock, flags);
+	list_for_each_entry(cl, &dev->cl_list, link) {
+		if (ishtp_hbm_cl_addr_equal(cl, disconnect_req)) {
+			cl->state = ISHTP_CL_DISCONNECTED;
+
+			/* send disconnect response */
+			ishtp_hbm_hdr(&hdr, len);
+			ishtp_hbm_cl_hdr(cl, CLIENT_DISCONNECT_RES_CMD, data,
+				len);
+			ishtp_write_message(dev, &hdr, data);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+}
+
+/**
+ * ishtp_hbm_dma_xfer_ack(() - Receive transfer ACK
+ * @dev: ISHTP device instance
+ * @dma_xfer: HBM transfer message
+ *
+ * Receive ack for ISHTP-over-DMA client message
+ */
+static void ishtp_hbm_dma_xfer_ack(struct ishtp_device *dev,
+				   struct dma_xfer_hbm *dma_xfer)
+{
+	void	*msg;
+	uint64_t	offs;
+	struct ishtp_msg_hdr	*ishtp_hdr =
+		(struct ishtp_msg_hdr *)&dev->ishtp_msg_hdr;
+	unsigned int	msg_offs;
+	struct ishtp_cl *cl;
+
+	for (msg_offs = 0; msg_offs < ishtp_hdr->length;
+		msg_offs += sizeof(struct dma_xfer_hbm)) {
+		offs = dma_xfer->msg_addr - dev->ishtp_host_dma_tx_buf_phys;
+		if (offs > dev->ishtp_host_dma_tx_buf_size) {
+			dev_err(dev->devc, "Bad DMA Tx ack message address\n");
+			return;
+		}
+		if (dma_xfer->msg_length >
+				dev->ishtp_host_dma_tx_buf_size - offs) {
+			dev_err(dev->devc, "Bad DMA Tx ack message size\n");
+			return;
+		}
+
+		/* logical address of the acked mem */
+		msg = (unsigned char *)dev->ishtp_host_dma_tx_buf + offs;
+		ishtp_cl_release_dma_acked_mem(dev, msg, dma_xfer->msg_length);
+
+		list_for_each_entry(cl, &dev->cl_list, link) {
+			if (cl->fw_client_id == dma_xfer->fw_client_id &&
+			    cl->host_client_id == dma_xfer->host_client_id)
+				/*
+				 * in case that a single ack may be sent
+				 * over several dma transfers, and the last msg
+				 * addr was inside the acked memory, but not in
+				 * its start
+				 */
+				if (cl->last_dma_addr >=
+							(unsigned char *)msg &&
+						cl->last_dma_addr <
+						(unsigned char *)msg +
+						dma_xfer->msg_length) {
+					cl->last_dma_acked = 1;
+
+					if (!list_empty(&cl->tx_list.list) &&
+						cl->ishtp_flow_ctrl_creds) {
+						/*
+						 * start sending the first msg
+						 */
+						ishtp_cl_send_msg(dev, cl);
+					}
+				}
+		}
+		++dma_xfer;
+	}
+}
+
+/**
+ * ishtp_hbm_dma_xfer() - Receive DMA transfer message
+ * @dev: ISHTP device instance
+ * @dma_xfer: HBM transfer message
+ *
+ * Receive ISHTP-over-DMA client message
+ */
+static void ishtp_hbm_dma_xfer(struct ishtp_device *dev,
+			       struct dma_xfer_hbm *dma_xfer)
+{
+	void	*msg;
+	uint64_t	offs;
+	struct ishtp_msg_hdr	hdr;
+	struct ishtp_msg_hdr	*ishtp_hdr =
+		(struct ishtp_msg_hdr *) &dev->ishtp_msg_hdr;
+	struct dma_xfer_hbm	*prm = dma_xfer;
+	unsigned int	msg_offs;
+
+	for (msg_offs = 0; msg_offs < ishtp_hdr->length;
+		msg_offs += sizeof(struct dma_xfer_hbm)) {
+
+		offs = dma_xfer->msg_addr - dev->ishtp_host_dma_rx_buf_phys;
+		if (offs > dev->ishtp_host_dma_rx_buf_size) {
+			dev_err(dev->devc, "Bad DMA Rx message address\n");
+			return;
+		}
+		if (dma_xfer->msg_length >
+				dev->ishtp_host_dma_rx_buf_size - offs) {
+			dev_err(dev->devc, "Bad DMA Rx message size\n");
+			return;
+		}
+		msg = dev->ishtp_host_dma_rx_buf + offs;
+		recv_ishtp_cl_msg_dma(dev, msg, dma_xfer);
+		dma_xfer->hbm = DMA_XFER_ACK;	/* Prepare for response */
+		++dma_xfer;
+	}
+
+	/* Send DMA_XFER_ACK [...] */
+	ishtp_hbm_hdr(&hdr, ishtp_hdr->length);
+	ishtp_write_message(dev, &hdr, (unsigned char *)prm);
+}
+
+/**
+ * ishtp_hbm_dispatch() - HBM dispatch function
+ * @dev: ISHTP device instance
+ * @hdr: bus message
+ *
+ * Bottom half read routine after ISR to handle the read bus message cmd
+ * processing
+ */
+void ishtp_hbm_dispatch(struct ishtp_device *dev,
+			struct ishtp_bus_message *hdr)
+{
+	struct ishtp_bus_message *ishtp_msg;
+	struct ishtp_fw_client *fw_client;
+	struct hbm_host_version_response *version_res;
+	struct hbm_client_connect_response *connect_res;
+	struct hbm_client_connect_response *disconnect_res;
+	struct hbm_client_connect_request *disconnect_req;
+	struct hbm_props_response *props_res;
+	struct hbm_host_enum_response *enum_res;
+	struct ishtp_msg_hdr ishtp_hdr;
+	struct dma_alloc_notify	dma_alloc_notify;
+	struct dma_xfer_hbm	*dma_xfer;
+
+	ishtp_msg = hdr;
+
+	switch (ishtp_msg->hbm_cmd) {
+	case HOST_START_RES_CMD:
+		version_res = (struct hbm_host_version_response *)ishtp_msg;
+		if (!version_res->host_version_supported) {
+			dev->version = version_res->fw_max_version;
+
+			dev->hbm_state = ISHTP_HBM_STOPPED;
+			ishtp_hbm_stop_req(dev);
+			return;
+		}
+
+		dev->version.major_version = HBM_MAJOR_VERSION;
+		dev->version.minor_version = HBM_MINOR_VERSION;
+		if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
+				dev->hbm_state == ISHTP_HBM_START) {
+			dev->hbm_state = ISHTP_HBM_STARTED;
+			ishtp_hbm_enum_clients_req(dev);
+		} else {
+			dev_err(dev->devc,
+				"reset: wrong host start response\n");
+			/* BUG: why do we arrive here? */
+			ish_hw_reset(dev);
+			return;
+		}
+
+		wake_up_interruptible(&dev->wait_hbm_recvd_msg);
+		break;
+
+	case CLIENT_CONNECT_RES_CMD:
+		connect_res = (struct hbm_client_connect_response *)ishtp_msg;
+		ishtp_hbm_cl_connect_res(dev, connect_res);
+		break;
+
+	case CLIENT_DISCONNECT_RES_CMD:
+		disconnect_res =
+			(struct hbm_client_connect_response *)ishtp_msg;
+		ishtp_hbm_cl_disconnect_res(dev, disconnect_res);
+		break;
+
+	case HOST_CLIENT_PROPERTIES_RES_CMD:
+		props_res = (struct hbm_props_response *)ishtp_msg;
+		fw_client = &dev->fw_clients[dev->fw_client_presentation_num];
+
+		if (props_res->status || !dev->fw_clients) {
+			dev_err(dev->devc,
+			"reset: properties response hbm wrong status\n");
+			ish_hw_reset(dev);
+			return;
+		}
+
+		if (fw_client->client_id != props_res->address) {
+			dev_err(dev->devc,
+				"reset: host properties response address mismatch [%02X %02X]\n",
+				fw_client->client_id, props_res->address);
+			ish_hw_reset(dev);
+			return;
+		}
+
+		if (dev->dev_state != ISHTP_DEV_INIT_CLIENTS ||
+			dev->hbm_state != ISHTP_HBM_CLIENT_PROPERTIES) {
+			dev_err(dev->devc,
+				"reset: unexpected properties response\n");
+			ish_hw_reset(dev);
+			return;
+		}
+
+		fw_client->props = props_res->client_properties;
+		dev->fw_client_index++;
+		dev->fw_client_presentation_num++;
+
+		/* request property for the next client */
+		ishtp_hbm_prop_req(dev);
+
+		if (dev->dev_state != ISHTP_DEV_ENABLED)
+			break;
+
+		if (!ishtp_use_dma_transfer())
+			break;
+
+		dev_dbg(dev->devc, "Requesting to use DMA\n");
+		ishtp_cl_alloc_dma_buf(dev);
+		if (dev->ishtp_host_dma_rx_buf) {
+			const size_t len = sizeof(dma_alloc_notify);
+
+			memset(&dma_alloc_notify, 0, sizeof(dma_alloc_notify));
+			dma_alloc_notify.hbm = DMA_BUFFER_ALLOC_NOTIFY;
+			dma_alloc_notify.buf_size =
+					dev->ishtp_host_dma_rx_buf_size;
+			dma_alloc_notify.buf_address =
+					dev->ishtp_host_dma_rx_buf_phys;
+			ishtp_hbm_hdr(&ishtp_hdr, len);
+			ishtp_write_message(dev, &ishtp_hdr,
+				(unsigned char *)&dma_alloc_notify);
+		}
+
+		break;
+
+	case HOST_ENUM_RES_CMD:
+		enum_res = (struct hbm_host_enum_response *) ishtp_msg;
+		memcpy(dev->fw_clients_map, enum_res->valid_addresses, 32);
+		if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
+			dev->hbm_state == ISHTP_HBM_ENUM_CLIENTS) {
+			dev->fw_client_presentation_num = 0;
+			dev->fw_client_index = 0;
+
+			ishtp_hbm_fw_cl_allocate(dev);
+			dev->hbm_state = ISHTP_HBM_CLIENT_PROPERTIES;
+
+			/* first property request */
+			ishtp_hbm_prop_req(dev);
+		} else {
+			dev_err(dev->devc,
+			      "reset: unexpected enumeration response hbm\n");
+			ish_hw_reset(dev);
+			return;
+		}
+		break;
+
+	case HOST_STOP_RES_CMD:
+		if (dev->hbm_state != ISHTP_HBM_STOPPED)
+			dev_err(dev->devc, "unexpected stop response\n");
+
+		dev->dev_state = ISHTP_DEV_DISABLED;
+		dev_info(dev->devc, "reset: FW stop response\n");
+		ish_hw_reset(dev);
+		break;
+
+	case CLIENT_DISCONNECT_REQ_CMD:
+		/* search for client */
+		disconnect_req =
+			(struct hbm_client_connect_request *)ishtp_msg;
+		ishtp_hbm_fw_disconnect_req(dev, disconnect_req);
+		break;
+
+	case FW_STOP_REQ_CMD:
+		dev->hbm_state = ISHTP_HBM_STOPPED;
+		break;
+
+	case DMA_BUFFER_ALLOC_RESPONSE:
+		dev->ishtp_host_dma_enabled = 1;
+		break;
+
+	case DMA_XFER:
+		dma_xfer = (struct dma_xfer_hbm *)ishtp_msg;
+		if (!dev->ishtp_host_dma_enabled) {
+			dev_err(dev->devc,
+				"DMA XFER requested but DMA is not enabled\n");
+			break;
+		}
+		ishtp_hbm_dma_xfer(dev, dma_xfer);
+		break;
+
+	case DMA_XFER_ACK:
+		dma_xfer = (struct dma_xfer_hbm *)ishtp_msg;
+		if (!dev->ishtp_host_dma_enabled ||
+		    !dev->ishtp_host_dma_tx_buf) {
+			dev_err(dev->devc,
+				"DMA XFER acked but DMA Tx is not enabled\n");
+			break;
+		}
+		ishtp_hbm_dma_xfer_ack(dev, dma_xfer);
+		break;
+
+	default:
+		dev_err(dev->devc, "unknown HBM: %u\n",
+			(unsigned int)ishtp_msg->hbm_cmd);
+
+		break;
+	}
+}
+
+/**
+ * bh_hbm_work_fn() - HBM work function
+ * @work: work struct
+ *
+ * Bottom half processing work function (instead of thread handler)
+ * for processing hbm messages
+ */
+void	bh_hbm_work_fn(struct work_struct *work)
+{
+	unsigned long	flags;
+	struct ishtp_device	*dev;
+	unsigned char	hbm[IPC_PAYLOAD_SIZE];
+
+	dev = container_of(work, struct ishtp_device, bh_hbm_work);
+	spin_lock_irqsave(&dev->rd_msg_spinlock, flags);
+	if (dev->rd_msg_fifo_head != dev->rd_msg_fifo_tail) {
+		memcpy(hbm, dev->rd_msg_fifo + dev->rd_msg_fifo_head,
+			IPC_PAYLOAD_SIZE);
+		dev->rd_msg_fifo_head =
+			(dev->rd_msg_fifo_head + IPC_PAYLOAD_SIZE) %
+			(RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE);
+		spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
+		ishtp_hbm_dispatch(dev, (struct ishtp_bus_message *)hbm);
+	} else {
+		spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
+	}
+}
+
+/**
+ * recv_hbm() - Receive HBM message
+ * @dev: ISHTP device instance
+ * @ishtp_hdr: received bus message
+ *
+ * Receive and process ISHTP bus messages in ISR context. This will schedule
+ * work function to process message
+ */
+void	recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr)
+{
+	uint8_t	rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE];
+	struct ishtp_bus_message	*ishtp_msg =
+		(struct ishtp_bus_message *)rd_msg_buf;
+	unsigned long	flags;
+
+	dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length);
+
+	/* Flow control - handle in place */
+	if (ishtp_msg->hbm_cmd == ISHTP_FLOW_CONTROL_CMD) {
+		struct hbm_flow_control *flow_control =
+			(struct hbm_flow_control *)ishtp_msg;
+		struct ishtp_cl *cl = NULL;
+		unsigned long	flags, tx_flags;
+
+		spin_lock_irqsave(&dev->cl_list_lock, flags);
+		list_for_each_entry(cl, &dev->cl_list, link) {
+			if (cl->host_client_id == flow_control->host_addr &&
+					cl->fw_client_id ==
+					flow_control->fw_addr) {
+				/*
+				 * NOTE: It's valid only for counting
+				 * flow-control implementation to receive a
+				 * FC in the middle of sending. Meanwhile not
+				 * supported
+				 */
+				if (cl->ishtp_flow_ctrl_creds)
+					dev_err(dev->devc,
+					 "recv extra FC from FW client %u (host client %u) (FC count was %d)\n",
+					 (unsigned int)cl->fw_client_id,
+					 (unsigned int)cl->host_client_id,
+					 cl->ishtp_flow_ctrl_creds);
+				else {
+					++cl->ishtp_flow_ctrl_creds;
+					++cl->ishtp_flow_ctrl_cnt;
+					cl->last_ipc_acked = 1;
+					spin_lock_irqsave(
+							&cl->tx_list_spinlock,
+							tx_flags);
+					if (!list_empty(&cl->tx_list.list)) {
+						/*
+						 * start sending the first msg
+						 *	= the callback function
+						 */
+						spin_unlock_irqrestore(
+							&cl->tx_list_spinlock,
+							tx_flags);
+						ishtp_cl_send_msg(dev, cl);
+					} else {
+						spin_unlock_irqrestore(
+							&cl->tx_list_spinlock,
+							tx_flags);
+					}
+				}
+				break;
+			}
+		}
+		spin_unlock_irqrestore(&dev->cl_list_lock, flags);
+		goto	eoi;
+	}
+
+	/*
+	 * Some messages that are safe for ISR processing and important
+	 * to be done "quickly" and in-order, go here
+	 */
+	if (ishtp_msg->hbm_cmd == CLIENT_CONNECT_RES_CMD ||
+			ishtp_msg->hbm_cmd == CLIENT_DISCONNECT_RES_CMD ||
+			ishtp_msg->hbm_cmd == CLIENT_DISCONNECT_REQ_CMD ||
+			ishtp_msg->hbm_cmd == DMA_XFER) {
+		ishtp_hbm_dispatch(dev, ishtp_msg);
+		goto	eoi;
+	}
+
+	/*
+	 * All other HBMs go here.
+	 * We schedule HBMs for processing serially by using system wq,
+	 * possibly there will be multiple HBMs scheduled at the same time.
+	 */
+	spin_lock_irqsave(&dev->rd_msg_spinlock, flags);
+	if ((dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) %
+			(RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE) ==
+			dev->rd_msg_fifo_head) {
+		spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
+		dev_err(dev->devc, "BH buffer overflow, dropping HBM %u\n",
+			(unsigned int)ishtp_msg->hbm_cmd);
+		goto	eoi;
+	}
+	memcpy(dev->rd_msg_fifo + dev->rd_msg_fifo_tail, ishtp_msg,
+		ishtp_hdr->length);
+	dev->rd_msg_fifo_tail = (dev->rd_msg_fifo_tail + IPC_PAYLOAD_SIZE) %
+		(RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE);
+	spin_unlock_irqrestore(&dev->rd_msg_spinlock, flags);
+	schedule_work(&dev->bh_hbm_work);
+eoi:
+	return;
+}
+
+/**
+ * recv_fixed_cl_msg() - Receive fixed client message
+ * @dev: ISHTP device instance
+ * @ishtp_hdr: received bus message
+ *
+ * Receive and process ISHTP fixed client messages (address == 0)
+ * in ISR context
+ */
+void recv_fixed_cl_msg(struct ishtp_device *dev,
+	struct ishtp_msg_hdr *ishtp_hdr)
+{
+	uint8_t rd_msg_buf[ISHTP_RD_MSG_BUF_SIZE];
+
+	dev->print_log(dev,
+		"%s() got fixed client msg from client #%d\n",
+		__func__, ishtp_hdr->fw_addr);
+	dev->ops->ishtp_read(dev, rd_msg_buf, ishtp_hdr->length);
+	if (ishtp_hdr->fw_addr == ISHTP_SYSTEM_STATE_CLIENT_ADDR) {
+		struct ish_system_states_header *msg_hdr =
+			(struct ish_system_states_header *)rd_msg_buf;
+		if (msg_hdr->cmd == SYSTEM_STATE_SUBSCRIBE)
+			ishtp_send_resume(dev);
+		/* if FW request arrived here, the system is not suspended */
+		else
+			dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
+				msg_hdr->cmd);
+	}
+}
+
+/**
+ * fix_cl_hdr() - Initialize fixed client header
+ * @hdr: message header
+ * @length: length of message
+ * @cl_addr: Client address
+ *
+ * Initialize message header for fixed client
+ */
+static inline void fix_cl_hdr(struct ishtp_msg_hdr *hdr, size_t length,
+	uint8_t cl_addr)
+{
+	hdr->host_addr = 0;
+	hdr->fw_addr = cl_addr;
+	hdr->length = length;
+	hdr->msg_complete = 1;
+	hdr->reserved = 0;
+}
+
+/*** Suspend and resume notification ***/
+
+static uint32_t current_state;
+static uint32_t supported_states = 0 | SUSPEND_STATE_BIT;
+
+/**
+ * ishtp_send_suspend() - Send suspend message to FW
+ * @dev: ISHTP device instance
+ *
+ * Send suspend message to FW. This is useful for system freeze (non S3) case
+ */
+void ishtp_send_suspend(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr	ishtp_hdr;
+	struct ish_system_states_status state_status_msg;
+	const size_t len = sizeof(struct ish_system_states_status);
+
+	fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR);
+
+	memset(&state_status_msg, 0, len);
+	state_status_msg.hdr.cmd = SYSTEM_STATE_STATUS;
+	state_status_msg.supported_states = supported_states;
+	current_state |= SUSPEND_STATE_BIT;
+	dev->print_log(dev, "%s() sends SUSPEND notification\n", __func__);
+	state_status_msg.states_status = current_state;
+
+	ishtp_write_message(dev, &ishtp_hdr,
+		(unsigned char *)&state_status_msg);
+}
+EXPORT_SYMBOL(ishtp_send_suspend);
+
+/**
+ * ishtp_send_resume() - Send resume message to FW
+ * @dev: ISHTP device instance
+ *
+ * Send resume message to FW. This is useful for system freeze (non S3) case
+ */
+void ishtp_send_resume(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr	ishtp_hdr;
+	struct ish_system_states_status state_status_msg;
+	const size_t len = sizeof(struct ish_system_states_status);
+
+	fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR);
+
+	memset(&state_status_msg, 0, len);
+	state_status_msg.hdr.cmd = SYSTEM_STATE_STATUS;
+	state_status_msg.supported_states = supported_states;
+	current_state &= ~SUSPEND_STATE_BIT;
+	dev->print_log(dev, "%s() sends RESUME notification\n", __func__);
+	state_status_msg.states_status = current_state;
+
+	ishtp_write_message(dev, &ishtp_hdr,
+		(unsigned char *)&state_status_msg);
+}
+EXPORT_SYMBOL(ishtp_send_resume);
+
+/**
+ * ishtp_query_subscribers() - Send query subscribers message
+ * @dev: ISHTP device instance
+ *
+ * Send message to query subscribers
+ */
+void ishtp_query_subscribers(struct ishtp_device *dev)
+{
+	struct ishtp_msg_hdr	ishtp_hdr;
+	struct ish_system_states_query_subscribers query_subscribers_msg;
+	const size_t len = sizeof(struct ish_system_states_query_subscribers);
+
+	fix_cl_hdr(&ishtp_hdr, len, ISHTP_SYSTEM_STATE_CLIENT_ADDR);
+
+	memset(&query_subscribers_msg, 0, len);
+	query_subscribers_msg.hdr.cmd = SYSTEM_STATE_QUERY_SUBSCRIBERS;
+
+	ishtp_write_message(dev, &ishtp_hdr,
+		(unsigned char *)&query_subscribers_msg);
+}
diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.h b/drivers/hid/intel-ish-hid/ishtp/hbm.h
new file mode 100644
index 0000000..d96111c
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/hbm.h
@@ -0,0 +1,321 @@
+/*
+ * ISHTP bus layer messages handling
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef _ISHTP_HBM_H_
+#define _ISHTP_HBM_H_
+
+#include <linux/uuid.h>
+
+struct ishtp_device;
+struct ishtp_msg_hdr;
+struct ishtp_cl;
+
+/*
+ * Timeouts in Seconds
+ */
+#define ISHTP_INTEROP_TIMEOUT		7 /* Timeout on ready message */
+
+#define ISHTP_CL_CONNECT_TIMEOUT	15 /* HPS: Client Connect Timeout */
+
+/*
+ * ISHTP Version
+ */
+#define HBM_MINOR_VERSION		0
+#define HBM_MAJOR_VERSION		1
+
+/* Host bus message command opcode */
+#define ISHTP_HBM_CMD_OP_MSK		0x7f
+/* Host bus message command RESPONSE */
+#define ISHTP_HBM_CMD_RES_MSK		0x80
+
+/*
+ * ISHTP Bus Message Command IDs
+ */
+#define HOST_START_REQ_CMD		0x01
+#define HOST_START_RES_CMD		0x81
+
+#define HOST_STOP_REQ_CMD		0x02
+#define HOST_STOP_RES_CMD		0x82
+
+#define FW_STOP_REQ_CMD			0x03
+
+#define HOST_ENUM_REQ_CMD		0x04
+#define HOST_ENUM_RES_CMD		0x84
+
+#define HOST_CLIENT_PROPERTIES_REQ_CMD	0x05
+#define HOST_CLIENT_PROPERTIES_RES_CMD	0x85
+
+#define CLIENT_CONNECT_REQ_CMD		0x06
+#define CLIENT_CONNECT_RES_CMD		0x86
+
+#define CLIENT_DISCONNECT_REQ_CMD	0x07
+#define CLIENT_DISCONNECT_RES_CMD	0x87
+
+#define ISHTP_FLOW_CONTROL_CMD		0x08
+
+#define DMA_BUFFER_ALLOC_NOTIFY		0x11
+#define DMA_BUFFER_ALLOC_RESPONSE	0x91
+
+#define DMA_XFER			0x12
+#define DMA_XFER_ACK			0x92
+
+/*
+ * ISHTP Stop Reason
+ * used by hbm_host_stop_request.reason
+ */
+#define	DRIVER_STOP_REQUEST		0x00
+
+/*
+ * ISHTP BUS Interface Section
+ */
+struct ishtp_msg_hdr {
+	uint32_t fw_addr:8;
+	uint32_t host_addr:8;
+	uint32_t length:9;
+	uint32_t reserved:6;
+	uint32_t msg_complete:1;
+} __packed;
+
+struct ishtp_bus_message {
+	uint8_t hbm_cmd;
+	uint8_t data[0];
+} __packed;
+
+/**
+ * struct hbm_cl_cmd - client specific host bus command
+ *	CONNECT, DISCONNECT, and FlOW CONTROL
+ *
+ * @hbm_cmd - bus message command header
+ * @fw_addr - address of the fw client
+ * @host_addr - address of the client in the driver
+ * @data
+ */
+struct ishtp_hbm_cl_cmd {
+	uint8_t hbm_cmd;
+	uint8_t fw_addr;
+	uint8_t host_addr;
+	uint8_t data;
+};
+
+struct hbm_version {
+	uint8_t minor_version;
+	uint8_t major_version;
+} __packed;
+
+struct hbm_host_version_request {
+	uint8_t hbm_cmd;
+	uint8_t reserved;
+	struct hbm_version host_version;
+} __packed;
+
+struct hbm_host_version_response {
+	uint8_t hbm_cmd;
+	uint8_t host_version_supported;
+	struct hbm_version fw_max_version;
+} __packed;
+
+struct hbm_host_stop_request {
+	uint8_t hbm_cmd;
+	uint8_t reason;
+	uint8_t reserved[2];
+} __packed;
+
+struct hbm_host_stop_response {
+	uint8_t hbm_cmd;
+	uint8_t reserved[3];
+} __packed;
+
+struct hbm_host_enum_request {
+	uint8_t hbm_cmd;
+	uint8_t reserved[3];
+} __packed;
+
+struct hbm_host_enum_response {
+	uint8_t hbm_cmd;
+	uint8_t reserved[3];
+	uint8_t valid_addresses[32];
+} __packed;
+
+struct ishtp_client_properties {
+	uuid_le protocol_name;
+	uint8_t protocol_version;
+	uint8_t max_number_of_connections;
+	uint8_t fixed_address;
+	uint8_t single_recv_buf;
+	uint32_t max_msg_length;
+	uint8_t dma_hdr_len;
+#define	ISHTP_CLIENT_DMA_ENABLED	0x80
+	uint8_t reserved4;
+	uint8_t reserved5;
+	uint8_t reserved6;
+} __packed;
+
+struct hbm_props_request {
+	uint8_t hbm_cmd;
+	uint8_t address;
+	uint8_t reserved[2];
+} __packed;
+
+struct hbm_props_response {
+	uint8_t hbm_cmd;
+	uint8_t address;
+	uint8_t status;
+	uint8_t reserved[1];
+	struct ishtp_client_properties client_properties;
+} __packed;
+
+/**
+ * struct hbm_client_connect_request - connect/disconnect request
+ *
+ * @hbm_cmd - bus message command header
+ * @fw_addr - address of the fw client
+ * @host_addr - address of the client in the driver
+ * @reserved
+ */
+struct hbm_client_connect_request {
+	uint8_t hbm_cmd;
+	uint8_t fw_addr;
+	uint8_t host_addr;
+	uint8_t reserved;
+} __packed;
+
+/**
+ * struct hbm_client_connect_response - connect/disconnect response
+ *
+ * @hbm_cmd - bus message command header
+ * @fw_addr - address of the fw client
+ * @host_addr - address of the client in the driver
+ * @status - status of the request
+ */
+struct hbm_client_connect_response {
+	uint8_t hbm_cmd;
+	uint8_t fw_addr;
+	uint8_t host_addr;
+	uint8_t status;
+} __packed;
+
+
+#define ISHTP_FC_MESSAGE_RESERVED_LENGTH		5
+
+struct hbm_flow_control {
+	uint8_t hbm_cmd;
+	uint8_t fw_addr;
+	uint8_t host_addr;
+	uint8_t reserved[ISHTP_FC_MESSAGE_RESERVED_LENGTH];
+} __packed;
+
+struct dma_alloc_notify {
+	uint8_t hbm;
+	uint8_t status;
+	uint8_t reserved[2];
+	uint32_t buf_size;
+	uint64_t buf_address;
+	/* [...] May come more size/address pairs */
+} __packed;
+
+struct dma_xfer_hbm {
+	uint8_t hbm;
+	uint8_t fw_client_id;
+	uint8_t host_client_id;
+	uint8_t reserved;
+	uint64_t msg_addr;
+	uint32_t msg_length;
+	uint32_t reserved2;
+} __packed;
+
+/* System state */
+#define ISHTP_SYSTEM_STATE_CLIENT_ADDR		13
+
+#define SYSTEM_STATE_SUBSCRIBE			0x1
+#define SYSTEM_STATE_STATUS			0x2
+#define SYSTEM_STATE_QUERY_SUBSCRIBERS		0x3
+#define SYSTEM_STATE_STATE_CHANGE_REQ		0x4
+/*indicates suspend and resume states*/
+#define SUSPEND_STATE_BIT			(1<<1)
+
+struct ish_system_states_header {
+	uint32_t cmd;
+	uint32_t cmd_status;	/*responses will have this set*/
+} __packed;
+
+struct ish_system_states_subscribe {
+	struct ish_system_states_header hdr;
+	uint32_t states;
+} __packed;
+
+struct ish_system_states_status {
+	struct ish_system_states_header hdr;
+	uint32_t supported_states;
+	uint32_t states_status;
+} __packed;
+
+struct ish_system_states_query_subscribers {
+	struct ish_system_states_header hdr;
+} __packed;
+
+struct ish_system_states_state_change_req {
+	struct ish_system_states_header hdr;
+	uint32_t requested_states;
+	uint32_t states_status;
+} __packed;
+
+/**
+ * enum ishtp_hbm_state - host bus message protocol state
+ *
+ * @ISHTP_HBM_IDLE : protocol not started
+ * @ISHTP_HBM_START : start request message was sent
+ * @ISHTP_HBM_ENUM_CLIENTS : enumeration request was sent
+ * @ISHTP_HBM_CLIENT_PROPERTIES : acquiring clients properties
+ */
+enum ishtp_hbm_state {
+	ISHTP_HBM_IDLE = 0,
+	ISHTP_HBM_START,
+	ISHTP_HBM_STARTED,
+	ISHTP_HBM_ENUM_CLIENTS,
+	ISHTP_HBM_CLIENT_PROPERTIES,
+	ISHTP_HBM_WORKING,
+	ISHTP_HBM_STOPPED,
+};
+
+static inline void ishtp_hbm_hdr(struct ishtp_msg_hdr *hdr, size_t length)
+{
+	hdr->host_addr = 0;
+	hdr->fw_addr = 0;
+	hdr->length = length;
+	hdr->msg_complete = 1;
+	hdr->reserved = 0;
+}
+
+int ishtp_hbm_start_req(struct ishtp_device *dev);
+int ishtp_hbm_start_wait(struct ishtp_device *dev);
+int ishtp_hbm_cl_flow_control_req(struct ishtp_device *dev,
+				  struct ishtp_cl *cl);
+int ishtp_hbm_cl_disconnect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
+int ishtp_hbm_cl_connect_req(struct ishtp_device *dev, struct ishtp_cl *cl);
+void ishtp_hbm_enum_clients_req(struct ishtp_device *dev);
+void bh_hbm_work_fn(struct work_struct *work);
+void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr);
+void recv_fixed_cl_msg(struct ishtp_device *dev,
+	struct ishtp_msg_hdr *ishtp_hdr);
+void ishtp_hbm_dispatch(struct ishtp_device *dev,
+	struct ishtp_bus_message *hdr);
+
+void ishtp_query_subscribers(struct ishtp_device *dev);
+
+/* Exported I/F */
+void ishtp_send_suspend(struct ishtp_device *dev);
+void ishtp_send_resume(struct ishtp_device *dev);
+
+#endif /* _ISHTP_HBM_H_ */
diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c
new file mode 100644
index 0000000..d27e035
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/init.c
@@ -0,0 +1,114 @@
+/*
+ * Initialization protocol for ISHTP driver
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include "ishtp-dev.h"
+#include "hbm.h"
+#include "client.h"
+
+/**
+ * ishtp_dev_state_str() -Convert to string format
+ * @state: state to convert
+ *
+ * Convert state to string for prints
+ *
+ * Return: character pointer to converted string
+ */
+const char *ishtp_dev_state_str(int state)
+{
+	switch (state) {
+	case ISHTP_DEV_INITIALIZING:
+		return	"INITIALIZING";
+	case ISHTP_DEV_INIT_CLIENTS:
+		return	"INIT_CLIENTS";
+	case ISHTP_DEV_ENABLED:
+		return	"ENABLED";
+	case ISHTP_DEV_RESETTING:
+		return	"RESETTING";
+	case ISHTP_DEV_DISABLED:
+		return	"DISABLED";
+	case ISHTP_DEV_POWER_DOWN:
+		return	"POWER_DOWN";
+	case ISHTP_DEV_POWER_UP:
+		return	"POWER_UP";
+	default:
+		return "unknown";
+	}
+}
+
+/**
+ * ishtp_device_init() - ishtp device init
+ * @dev: ISHTP device instance
+ *
+ * After ISHTP device is alloacted, this function is used to initialize
+ * each field which includes spin lock, work struct and lists
+ */
+void ishtp_device_init(struct ishtp_device *dev)
+{
+	dev->dev_state = ISHTP_DEV_INITIALIZING;
+	INIT_LIST_HEAD(&dev->cl_list);
+	INIT_LIST_HEAD(&dev->device_list);
+	dev->rd_msg_fifo_head = 0;
+	dev->rd_msg_fifo_tail = 0;
+	spin_lock_init(&dev->rd_msg_spinlock);
+
+	init_waitqueue_head(&dev->wait_hbm_recvd_msg);
+	spin_lock_init(&dev->read_list_spinlock);
+	spin_lock_init(&dev->device_lock);
+	spin_lock_init(&dev->device_list_lock);
+	spin_lock_init(&dev->cl_list_lock);
+	spin_lock_init(&dev->fw_clients_lock);
+	INIT_WORK(&dev->bh_hbm_work, bh_hbm_work_fn);
+
+	bitmap_zero(dev->host_clients_map, ISHTP_CLIENTS_MAX);
+	dev->open_handle_count = 0;
+
+	/*
+	 * Reserving client ID 0 for ISHTP Bus Message communications
+	 */
+	bitmap_set(dev->host_clients_map, 0, 1);
+
+	INIT_LIST_HEAD(&dev->read_list.list);
+
+}
+EXPORT_SYMBOL(ishtp_device_init);
+
+/**
+ * ishtp_start() - Start ISH processing
+ * @dev: ISHTP device instance
+ *
+ * Start ISHTP processing by sending query subscriber message
+ *
+ * Return: 0 on success else -ENODEV
+ */
+int ishtp_start(struct ishtp_device *dev)
+{
+	if (ishtp_hbm_start_wait(dev)) {
+		dev_err(dev->devc, "HBM haven't started");
+		goto err;
+	}
+
+	/* suspend & resume notification - send QUERY_SUBSCRIBERS msg */
+	ishtp_query_subscribers(dev);
+
+	return 0;
+err:
+	dev_err(dev->devc, "link layer initialization failed.\n");
+	dev->dev_state = ISHTP_DEV_DISABLED;
+	return -ENODEV;
+}
+EXPORT_SYMBOL(ishtp_start);
diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
new file mode 100644
index 0000000..6a6d927
--- /dev/null
+++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h
@@ -0,0 +1,278 @@
+/*
+ * Most ISHTP provider device and ISHTP logic declarations
+ *
+ * Copyright (c) 2003-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ */
+
+#ifndef _ISHTP_DEV_H_
+#define _ISHTP_DEV_H_
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include "bus.h"
+#include "hbm.h"
+
+#define	IPC_PAYLOAD_SIZE	128
+#define ISHTP_RD_MSG_BUF_SIZE	IPC_PAYLOAD_SIZE
+#define	IPC_FULL_MSG_SIZE	132
+
+/* Number of messages to be held in ISR->BH FIFO */
+#define	RD_INT_FIFO_SIZE	64
+
+/*
+ * Number of IPC messages to be held in Tx FIFO, to be sent by ISR -
+ * Tx complete interrupt or RX_COMPLETE handler
+ */
+#define	IPC_TX_FIFO_SIZE	512
+
+/*
+ * Number of Maximum ISHTP Clients
+ */
+#define ISHTP_CLIENTS_MAX 256
+
+/*
+ * Number of File descriptors/handles
+ * that can be opened to the driver.
+ *
+ * Limit to 255: 256 Total Clients
+ * minus internal client for ISHTP Bus Messages
+ */
+#define ISHTP_MAX_OPEN_HANDLE_COUNT (ISHTP_CLIENTS_MAX - 1)
+
+/* Internal Clients Number */
+#define ISHTP_HOST_CLIENT_ID_ANY		(-1)
+#define ISHTP_HBM_HOST_CLIENT_ID		0
+
+#define	MAX_DMA_DELAY	20
+
+/* ISHTP device states */
+enum ishtp_dev_state {
+	ISHTP_DEV_INITIALIZING = 0,
+	ISHTP_DEV_INIT_CLIENTS,
+	ISHTP_DEV_ENABLED,
+	ISHTP_DEV_RESETTING,
+	ISHTP_DEV_DISABLED,
+	ISHTP_DEV_POWER_DOWN,
+	ISHTP_DEV_POWER_UP
+};
+const char *ishtp_dev_state_str(int state);
+
+struct ishtp_cl;
+
+/**
+ * struct ishtp_fw_client - representation of fw client
+ *
+ * @props - client properties
+ * @client_id - fw client id
+ */
+struct ishtp_fw_client {
+	struct ishtp_client_properties props;
+	uint8_t client_id;
+};
+
+/**
+ * struct ishtp_msg_data - ISHTP message data struct
+ * @size:	Size of data in the *data
+ * @data:	Pointer to data
+ */
+struct ishtp_msg_data {
+	uint32_t size;
+	unsigned char *data;
+};
+
+/*
+ * struct ishtp_cl_rb - request block structure
+ * @list:	Link to list members
+ * @cl:		ISHTP client instance
+ * @buffer:	message header
+ * @buf_idx:	Index into buffer
+ * @read_time:	 unused at this time
+ */
+struct ishtp_cl_rb {
+	struct list_head list;
+	struct ishtp_cl *cl;
+	struct ishtp_msg_data buffer;
+	unsigned long buf_idx;
+	unsigned long read_time;
+};
+
+/*
+ * Control info for IPC messages ISHTP/IPC sending FIFO -
+ * list with inline data buffer
+ * This structure will be filled with parameters submitted
+ * by the caller glue layer
+ * 'buf' may be pointing to the external buffer or to 'inline_data'
+ * 'offset' will be initialized to 0 by submitting
+ *
+ * 'ipc_send_compl' is intended for use by clients that send fragmented
+ * messages. When a fragment is sent down to IPC msg regs,
+ * it will be called.
+ * If it has more fragments to send, it will do it. With last fragment
+ * it will send appropriate ISHTP "message-complete" flag.
+ * It will remove the outstanding message
+ * (mark outstanding buffer as available).
+ * If counting flow control is in work and there are more flow control
+ * credits, it can put the next client message queued in cl.
+ * structure for IPC processing.
+ *
+ */
+struct wr_msg_ctl_info {
+	/* Will be called with 'ipc_send_compl_prm' as parameter */
+	void (*ipc_send_compl)(void *);
+
+	void *ipc_send_compl_prm;
+	size_t length;
+	struct list_head	link;
+	unsigned char	inline_data[IPC_FULL_MSG_SIZE];
+};
+
+/*
+ * The ISHTP layer talks to hardware IPC message using the following
+ * callbacks
+ */
+struct ishtp_hw_ops {
+	int	(*hw_reset)(struct ishtp_device *dev);
+	int	(*ipc_reset)(struct ishtp_device *dev);
+	uint32_t (*ipc_get_header)(struct ishtp_device *dev, int length,
+				   int busy);
+	int	(*write)(struct ishtp_device *dev,
+		void (*ipc_send_compl)(void *), void *ipc_send_compl_prm,
+		unsigned char *msg, int length);
+	uint32_t	(*ishtp_read_hdr)(const struct ishtp_device *dev);
+	int	(*ishtp_read)(struct ishtp_device *dev, unsigned char *buffer,
+			unsigned long buffer_length);
+	uint32_t	(*get_fw_status)(struct ishtp_device *dev);
+	void	(*sync_fw_clock)(struct ishtp_device *dev);
+};
+
+/**
+ * struct ishtp_device - ISHTP private device struct
+ */
+struct ishtp_device {
+	struct device *devc;	/* pointer to lowest device */
+	struct pci_dev *pdev;	/* PCI device to get device ids */
+
+	/* waitq for waiting for suspend response */
+	wait_queue_head_t suspend_wait;
+	bool suspend_flag;	/* Suspend is active */
+
+	/* waitq for waiting for resume response */
+	wait_queue_head_t resume_wait;
+	bool resume_flag;	/*Resume is active */
+
+	/*
+	 * lock for the device, for everything that doesn't have
+	 * a dedicated spinlock
+	 */
+	spinlock_t device_lock;
+
+	bool recvd_hw_ready;
+	struct hbm_version version;
+	int transfer_path; /* Choice of transfer path: IPC or DMA */
+
+	/* ishtp device states */
+	enum ishtp_dev_state dev_state;
+	enum ishtp_hbm_state hbm_state;
+
+	/* driver read queue */
+	struct ishtp_cl_rb read_list;
+	spinlock_t read_list_spinlock;
+
+	/* list of ishtp_cl's */
+	struct list_head cl_list;
+	spinlock_t cl_list_lock;
+	long open_handle_count;
+
+	/* List of bus devices */
+	struct list_head device_list;
+	spinlock_t device_list_lock;
+
+	/* waiting queues for receive message from FW */
+	wait_queue_head_t wait_hw_ready;
+	wait_queue_head_t wait_hbm_recvd_msg;
+
+	/* FIFO for input messages for BH processing */
+	unsigned char rd_msg_fifo[RD_INT_FIFO_SIZE * IPC_PAYLOAD_SIZE];
+	unsigned int rd_msg_fifo_head, rd_msg_fifo_tail;
+	spinlock_t rd_msg_spinlock;
+	struct work_struct bh_hbm_work;
+
+	/* IPC write queue */
+	struct wr_msg_ctl_info wr_processing_list_head, wr_free_list_head;
+	/* For both processing list  and free list */
+	spinlock_t wr_processing_spinlock;
+
+	spinlock_t out_ipc_spinlock;
+
+	struct ishtp_fw_client *fw_clients; /*Note:memory has to be allocated*/
+	DECLARE_BITMAP(fw_clients_map, ISHTP_CLIENTS_MAX);
+	DECLARE_BITMAP(host_clients_map, ISHTP_CLIENTS_MAX);
+	uint8_t fw_clients_num;
+	uint8_t fw_client_presentation_num;
+	uint8_t fw_client_index;
+	spinlock_t fw_clients_lock;
+
+	/* TX DMA buffers and slots */
+	int ishtp_host_dma_enabled;
+	void *ishtp_host_dma_tx_buf;
+	unsigned int ishtp_host_dma_tx_buf_size;
+	uint64_t ishtp_host_dma_tx_buf_phys;
+	int ishtp_dma_num_slots;
+
+	/* map of 4k blocks in Tx dma buf: 0-free, 1-used */
+	uint8_t *ishtp_dma_tx_map;
+	spinlock_t ishtp_dma_tx_lock;
+
+	/* RX DMA buffers and slots */
+	void *ishtp_host_dma_rx_buf;
+	unsigned int ishtp_host_dma_rx_buf_size;
+	uint64_t ishtp_host_dma_rx_buf_phys;
+
+	/* Dump to trace buffers if enabled*/
+	__printf(2, 3) void (*print_log)(struct ishtp_device *dev,
+					 const char *format, ...);
+
+	/* Debug stats */
+	unsigned int	ipc_rx_cnt;
+	unsigned long long	ipc_rx_bytes_cnt;
+	unsigned int	ipc_tx_cnt;
+	unsigned long long	ipc_tx_bytes_cnt;
+
+	const struct ishtp_hw_ops *ops;
+	size_t	mtu;
+	uint32_t	ishtp_msg_hdr;
+	char hw[0] __aligned(sizeof(void *));
+};
+
+static inline unsigned long ishtp_secs_to_jiffies(unsigned long sec)
+{
+	return msecs_to_jiffies(sec * MSEC_PER_SEC);
+}
+
+/*
+ * Register Access Function
+ */
+static inline int ish_ipc_reset(struct ishtp_device *dev)
+{
+	return dev->ops->ipc_reset(dev);
+}
+
+static inline int ish_hw_reset(struct ishtp_device *dev)
+{
+	return dev->ops->hw_reset(dev);
+}
+
+/* Exported function */
+void	ishtp_device_init(struct ishtp_device *dev);
+int	ishtp_start(struct ishtp_device *dev);
+
+#endif /*_ISHTP_DEV_H_*/
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
new file mode 100644
index 0000000..840634e
--- /dev/null
+++ b/drivers/hid/uhid.c
@@ -0,0 +1,802 @@
+/*
+ * 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/compat.h>
+#include <linux/cred.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];
+
+	/* blocking GET_REPORT support; state changes protected by qlock */
+	struct mutex report_lock;
+	wait_queue_head_t report_wait;
+	bool report_running;
+	u32 report_id;
+	u32 report_type;
+	struct uhid_event report_buf;
+	struct work_struct worker;
+};
+
+static struct miscdevice uhid_misc;
+
+static void uhid_device_add_worker(struct work_struct *work)
+{
+	struct uhid_device *uhid = container_of(work, struct uhid_device, worker);
+	int ret;
+
+	ret = hid_add_device(uhid->hid);
+	if (ret) {
+		hid_err(uhid->hid, "Cannot register HID device: error %d\n", ret);
+
+		hid_destroy_device(uhid->hid);
+		uhid->hid = NULL;
+		uhid->running = false;
+	}
+}
+
+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;
+	struct uhid_event *ev;
+	unsigned long flags;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = UHID_START;
+
+	if (hid->report_enum[HID_FEATURE_REPORT].numbered)
+		ev->u.start.dev_flags |= UHID_DEV_NUMBERED_FEATURE_REPORTS;
+	if (hid->report_enum[HID_OUTPUT_REPORT].numbered)
+		ev->u.start.dev_flags |= UHID_DEV_NUMBERED_OUTPUT_REPORTS;
+	if (hid->report_enum[HID_INPUT_REPORT].numbered)
+		ev->u.start.dev_flags |= UHID_DEV_NUMBERED_INPUT_REPORTS;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	return 0;
+}
+
+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_parse(struct hid_device *hid)
+{
+	struct uhid_device *uhid = hid->driver_data;
+
+	return hid_parse_report(hid, uhid->rd_data, uhid->rd_size);
+}
+
+/* must be called with report_lock held */
+static int __uhid_report_queue_and_wait(struct uhid_device *uhid,
+					struct uhid_event *ev,
+					__u32 *report_id)
+{
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+	*report_id = ++uhid->report_id;
+	uhid->report_type = ev->type + 1;
+	uhid->report_running = true;
+	uhid_queue(uhid, ev);
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+
+	ret = wait_event_interruptible_timeout(uhid->report_wait,
+				!uhid->report_running || !uhid->running,
+				5 * HZ);
+	if (!ret || !uhid->running || uhid->report_running)
+		ret = -EIO;
+	else if (ret < 0)
+		ret = -ERESTARTSYS;
+	else
+		ret = 0;
+
+	uhid->report_running = false;
+
+	return ret;
+}
+
+static void uhid_report_wake_up(struct uhid_device *uhid, u32 id,
+				const struct uhid_event *ev)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&uhid->qlock, flags);
+
+	/* id for old report; drop it silently */
+	if (uhid->report_type != ev->type || uhid->report_id != id)
+		goto unlock;
+	if (!uhid->report_running)
+		goto unlock;
+
+	memcpy(&uhid->report_buf, ev, sizeof(*ev));
+	uhid->report_running = false;
+	wake_up_interruptible(&uhid->report_wait);
+
+unlock:
+	spin_unlock_irqrestore(&uhid->qlock, flags);
+}
+
+static int uhid_hid_get_report(struct hid_device *hid, unsigned char rnum,
+			       u8 *buf, size_t count, u8 rtype)
+{
+	struct uhid_device *uhid = hid->driver_data;
+	struct uhid_get_report_reply_req *req;
+	struct uhid_event *ev;
+	int ret;
+
+	if (!uhid->running)
+		return -EIO;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = UHID_GET_REPORT;
+	ev->u.get_report.rnum = rnum;
+	ev->u.get_report.rtype = rtype;
+
+	ret = mutex_lock_interruptible(&uhid->report_lock);
+	if (ret) {
+		kfree(ev);
+		return ret;
+	}
+
+	/* this _always_ takes ownership of @ev */
+	ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.get_report.id);
+	if (ret)
+		goto unlock;
+
+	req = &uhid->report_buf.u.get_report_reply;
+	if (req->err) {
+		ret = -EIO;
+	} else {
+		ret = min3(count, (size_t)req->size, (size_t)UHID_DATA_MAX);
+		memcpy(buf, req->data, ret);
+	}
+
+unlock:
+	mutex_unlock(&uhid->report_lock);
+	return ret;
+}
+
+static int uhid_hid_set_report(struct hid_device *hid, unsigned char rnum,
+			       const u8 *buf, size_t count, u8 rtype)
+{
+	struct uhid_device *uhid = hid->driver_data;
+	struct uhid_event *ev;
+	int ret;
+
+	if (!uhid->running || count > UHID_DATA_MAX)
+		return -EIO;
+
+	ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+	if (!ev)
+		return -ENOMEM;
+
+	ev->type = UHID_SET_REPORT;
+	ev->u.set_report.rnum = rnum;
+	ev->u.set_report.rtype = rtype;
+	ev->u.set_report.size = count;
+	memcpy(ev->u.set_report.data, buf, count);
+
+	ret = mutex_lock_interruptible(&uhid->report_lock);
+	if (ret) {
+		kfree(ev);
+		return ret;
+	}
+
+	/* this _always_ takes ownership of @ev */
+	ret = __uhid_report_queue_and_wait(uhid, ev, &ev->u.set_report.id);
+	if (ret)
+		goto unlock;
+
+	if (uhid->report_buf.u.set_report_reply.err)
+		ret = -EIO;
+	else
+		ret = count;
+
+unlock:
+	mutex_unlock(&uhid->report_lock);
+	return ret;
+}
+
+static int uhid_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+				__u8 *buf, size_t len, unsigned char rtype,
+				int reqtype)
+{
+	u8 u_rtype;
+
+	switch (rtype) {
+	case HID_FEATURE_REPORT:
+		u_rtype = UHID_FEATURE_REPORT;
+		break;
+	case HID_OUTPUT_REPORT:
+		u_rtype = UHID_OUTPUT_REPORT;
+		break;
+	case HID_INPUT_REPORT:
+		u_rtype = UHID_INPUT_REPORT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return uhid_hid_get_report(hid, reportnum, buf, len, u_rtype);
+	case HID_REQ_SET_REPORT:
+		return uhid_hid_set_report(hid, reportnum, buf, len, u_rtype);
+	default:
+		return -EIO;
+	}
+}
+
+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 int uhid_hid_output_report(struct hid_device *hid, __u8 *buf,
+				  size_t count)
+{
+	return uhid_hid_output_raw(hid, buf, count, HID_OUTPUT_REPORT);
+}
+
+struct hid_ll_driver uhid_hid_driver = {
+	.start = uhid_hid_start,
+	.stop = uhid_hid_stop,
+	.open = uhid_hid_open,
+	.close = uhid_hid_close,
+	.parse = uhid_hid_parse,
+	.raw_request = uhid_hid_raw_request,
+	.output_report = uhid_hid_output_report,
+};
+EXPORT_SYMBOL_GPL(uhid_hid_driver);
+
+#ifdef CONFIG_COMPAT
+
+/* Apparently we haven't stepped on these rakes enough times yet. */
+struct uhid_create_req_compat {
+	__u8 name[128];
+	__u8 phys[64];
+	__u8 uniq[64];
+
+	compat_uptr_t rd_data;
+	__u16 rd_size;
+
+	__u16 bus;
+	__u32 vendor;
+	__u32 product;
+	__u32 version;
+	__u32 country;
+} __attribute__((__packed__));
+
+static int uhid_event_from_user(const char __user *buffer, size_t len,
+				struct uhid_event *event)
+{
+	if (in_compat_syscall()) {
+		u32 type;
+
+		if (get_user(type, buffer))
+			return -EFAULT;
+
+		if (type == UHID_CREATE) {
+			/*
+			 * This is our messed up request with compat pointer.
+			 * It is largish (more than 256 bytes) so we better
+			 * allocate it from the heap.
+			 */
+			struct uhid_create_req_compat *compat;
+
+			compat = kzalloc(sizeof(*compat), GFP_KERNEL);
+			if (!compat)
+				return -ENOMEM;
+
+			buffer += sizeof(type);
+			len -= sizeof(type);
+			if (copy_from_user(compat, buffer,
+					   min(len, sizeof(*compat)))) {
+				kfree(compat);
+				return -EFAULT;
+			}
+
+			/* Shuffle the data over to proper structure */
+			event->type = type;
+
+			memcpy(event->u.create.name, compat->name,
+				sizeof(compat->name));
+			memcpy(event->u.create.phys, compat->phys,
+				sizeof(compat->phys));
+			memcpy(event->u.create.uniq, compat->uniq,
+				sizeof(compat->uniq));
+
+			event->u.create.rd_data = compat_ptr(compat->rd_data);
+			event->u.create.rd_size = compat->rd_size;
+
+			event->u.create.bus = compat->bus;
+			event->u.create.vendor = compat->vendor;
+			event->u.create.product = compat->product;
+			event->u.create.version = compat->version;
+			event->u.create.country = compat->country;
+
+			kfree(compat);
+			return 0;
+		}
+		/* All others can be copied directly */
+	}
+
+	if (copy_from_user(event, buffer, min(len, sizeof(*event))))
+		return -EFAULT;
+
+	return 0;
+}
+#else
+static int uhid_event_from_user(const char __user *buffer, size_t len,
+				struct uhid_event *event)
+{
+	if (copy_from_user(event, buffer, min(len, sizeof(*event))))
+		return -EFAULT;
+
+	return 0;
+}
+#endif
+
+static int uhid_dev_create2(struct uhid_device *uhid,
+			    const struct uhid_event *ev)
+{
+	struct hid_device *hid;
+	size_t rd_size, len;
+	void *rd_data;
+	int ret;
+
+	if (uhid->running)
+		return -EALREADY;
+
+	rd_size = ev->u.create2.rd_size;
+	if (rd_size <= 0 || rd_size > HID_MAX_DESCRIPTOR_SIZE)
+		return -EINVAL;
+
+	rd_data = kmemdup(ev->u.create2.rd_data, rd_size, GFP_KERNEL);
+	if (!rd_data)
+		return -ENOMEM;
+
+	uhid->rd_size = rd_size;
+	uhid->rd_data = rd_data;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid)) {
+		ret = PTR_ERR(hid);
+		goto err_free;
+	}
+
+	/* @hid is zero-initialized, strncpy() is correct, strlcpy() not */
+	len = min(sizeof(hid->name), sizeof(ev->u.create2.name)) - 1;
+	strncpy(hid->name, ev->u.create2.name, len);
+	len = min(sizeof(hid->phys), sizeof(ev->u.create2.phys)) - 1;
+	strncpy(hid->phys, ev->u.create2.phys, len);
+	len = min(sizeof(hid->uniq), sizeof(ev->u.create2.uniq)) - 1;
+	strncpy(hid->uniq, ev->u.create2.uniq, len);
+
+	hid->ll_driver = &uhid_hid_driver;
+	hid->bus = ev->u.create2.bus;
+	hid->vendor = ev->u.create2.vendor;
+	hid->product = ev->u.create2.product;
+	hid->version = ev->u.create2.version;
+	hid->country = ev->u.create2.country;
+	hid->driver_data = uhid;
+	hid->dev.parent = uhid_misc.this_device;
+
+	uhid->hid = hid;
+	uhid->running = true;
+
+	/* Adding of a HID device is done through a worker, to allow HID drivers
+	 * which use feature requests during .probe to work, without they would
+	 * be blocked on devlock, which is held by uhid_char_write.
+	 */
+	schedule_work(&uhid->worker);
+
+	return 0;
+
+err_free:
+	kfree(uhid->rd_data);
+	uhid->rd_data = NULL;
+	uhid->rd_size = 0;
+	return ret;
+}
+
+static int uhid_dev_create(struct uhid_device *uhid,
+			   struct uhid_event *ev)
+{
+	struct uhid_create_req orig;
+
+	orig = ev->u.create;
+
+	if (orig.rd_size <= 0 || orig.rd_size > HID_MAX_DESCRIPTOR_SIZE)
+		return -EINVAL;
+	if (copy_from_user(&ev->u.create2.rd_data, orig.rd_data, orig.rd_size))
+		return -EFAULT;
+
+	memcpy(ev->u.create2.name, orig.name, sizeof(orig.name));
+	memcpy(ev->u.create2.phys, orig.phys, sizeof(orig.phys));
+	memcpy(ev->u.create2.uniq, orig.uniq, sizeof(orig.uniq));
+	ev->u.create2.rd_size = orig.rd_size;
+	ev->u.create2.bus = orig.bus;
+	ev->u.create2.vendor = orig.vendor;
+	ev->u.create2.product = orig.product;
+	ev->u.create2.version = orig.version;
+	ev->u.create2.country = orig.country;
+
+	return uhid_dev_create2(uhid, ev);
+}
+
+static int uhid_dev_destroy(struct uhid_device *uhid)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	uhid->running = false;
+	wake_up_interruptible(&uhid->report_wait);
+
+	cancel_work_sync(&uhid->worker);
+
+	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_input2(struct uhid_device *uhid, struct uhid_event *ev)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	hid_input_report(uhid->hid, HID_INPUT_REPORT, ev->u.input2.data,
+			 min_t(size_t, ev->u.input2.size, UHID_DATA_MAX), 0);
+
+	return 0;
+}
+
+static int uhid_dev_get_report_reply(struct uhid_device *uhid,
+				     struct uhid_event *ev)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	uhid_report_wake_up(uhid, ev->u.get_report_reply.id, ev);
+	return 0;
+}
+
+static int uhid_dev_set_report_reply(struct uhid_device *uhid,
+				     struct uhid_event *ev)
+{
+	if (!uhid->running)
+		return -EINVAL;
+
+	uhid_report_wake_up(uhid, ev->u.set_report_reply.id, ev);
+	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;
+	INIT_WORK(&uhid->worker, uhid_device_add_worker);
+
+	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));
+
+	ret = uhid_event_from_user(buffer, len, &uhid->input_buf);
+	if (ret)
+		goto unlock;
+
+	switch (uhid->input_buf.type) {
+	case UHID_CREATE:
+		/*
+		 * 'struct uhid_create_req' contains a __user pointer which is
+		 * copied from, so it's unsafe to allow this with elevated
+		 * privileges (e.g. from a setuid binary) or via kernel_write().
+		 */
+		if (file->f_cred != current_cred() || uaccess_kernel()) {
+			pr_err_once("UHID_CREATE from different security context by process %d (%s), this is not allowed.\n",
+				    task_tgid_vnr(current), current->comm);
+			ret = -EACCES;
+			goto unlock;
+		}
+		ret = uhid_dev_create(uhid, &uhid->input_buf);
+		break;
+	case UHID_CREATE2:
+		ret = uhid_dev_create2(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_INPUT2:
+		ret = uhid_dev_input2(uhid, &uhid->input_buf);
+		break;
+	case UHID_GET_REPORT_REPLY:
+		ret = uhid_dev_get_report_reply(uhid, &uhid->input_buf);
+		break;
+	case UHID_SET_REPORT_REPLY:
+		ret = uhid_dev_set_report_reply(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 __poll_t 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 EPOLLIN | EPOLLRDNORM;
+
+	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		= UHID_MINOR,
+	.name		= UHID_NAME,
+};
+module_misc_device(uhid_misc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Herrmann <dh.herrmann@gmail.com>");
+MODULE_DESCRIPTION("User-space I/O driver support for HID subsystem");
+MODULE_ALIAS_MISCDEV(UHID_MINOR);
+MODULE_ALIAS("devname:" UHID_NAME);
diff --git a/drivers/hid/usbhid/Kconfig b/drivers/hid/usbhid/Kconfig
new file mode 100644
index 0000000..e50d8fe
--- /dev/null
+++ b/drivers/hid/usbhid/Kconfig
@@ -0,0 +1,84 @@
+menu "USB HID support"
+	depends on USB
+
+config USB_HID
+	tristate "USB HID transport layer"
+	default y
+	depends on USB && INPUT
+	select HID
+	---help---
+	  Say Y here if you want to connect USB keyboards,
+	  mice, joysticks, graphic tablets, or any other HID based devices
+	  to your computer via USB, as well as Uninterruptible Power Supply
+	  (UPS) and monitor control devices.
+
+	  You can't use this driver and the HIDBP (Boot Protocol) keyboard
+	  and mouse drivers at the same time. More information is available:
+	  <file:Documentation/input/input.rst>.
+
+	  If unsure, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbhid.
+
+comment "Input core support is needed for USB HID input layer or HIDBP support"
+	depends on USB_HID && INPUT=n
+
+config HID_PID
+	bool "PID device support"
+	help
+	  Say Y here if you have a PID-compliant device and wish to enable force
+	  feedback for it. Microsoft Sidewinder Force Feedback 2 is one of such
+	  devices.
+
+config USB_HIDDEV
+	bool "/dev/hiddev raw HID device support"
+	depends on USB_HID
+	help
+	  Say Y here if you want to support HID devices (from the USB
+	  specification standpoint) that aren't strictly user interface
+	  devices, like monitor controls and Uninterruptable Power Supplies.
+
+	  This module supports these devices separately using a separate
+	  event interface on /dev/usb/hiddevX (char 180:96 to 180:111).
+
+	  If unsure, say Y.
+
+menu "USB HID Boot Protocol drivers"
+	depends on USB!=n && USB_HID!=y && EXPERT
+
+config USB_KBD
+	tristate "USB HIDBP Keyboard (simple Boot) support"
+	depends on USB && INPUT
+	---help---
+	  Say Y here only if you are absolutely sure that you don't want
+	  to use the generic HID driver for your USB keyboard and prefer
+	  to use the keyboard in its limited Boot Protocol mode instead.
+
+	  This is almost certainly not what you want.  This is mostly
+	  useful for embedded applications or simple keyboards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbkbd.
+
+	  If even remotely unsure, say N.
+
+config USB_MOUSE
+	tristate "USB HIDBP Mouse (simple Boot) support"
+	depends on USB && INPUT
+	---help---
+	  Say Y here only if you are absolutely sure that you don't want
+	  to use the generic HID driver for your USB mouse and prefer
+	  to use the mouse in its limited Boot Protocol mode instead.
+
+	  This is almost certainly not what you want.  This is mostly
+	  useful for embedded applications or simple mice.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbmouse.
+
+	  If even remotely unsure, say N.
+
+endmenu
+
+endmenu
diff --git a/drivers/hid/usbhid/Makefile b/drivers/hid/usbhid/Makefile
new file mode 100644
index 0000000..b6349e3
--- /dev/null
+++ b/drivers/hid/usbhid/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the USB input drivers
+#
+
+usbhid-y	:= hid-core.o
+usbhid-$(CONFIG_USB_HIDDEV)	+= hiddev.o
+usbhid-$(CONFIG_HID_PID)	+= hid-pidff.o
+
+obj-$(CONFIG_USB_HID)		+= usbhid.o
+obj-$(CONFIG_USB_KBD)		+= usbkbd.o
+obj-$(CONFIG_USB_MOUSE)		+= usbmouse.o
+
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
new file mode 100644
index 0000000..11103ef
--- /dev/null
+++ b/drivers/hid/usbhid/hid-core.c
@@ -0,0 +1,1682 @@
+/*
+ *  USB HID support for Linux
+ *
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2005 Vojtech Pavlik <vojtech@suse.cz>
+ *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
+ *  Copyright (c) 2007-2008 Oliver Neukum
+ *  Copyright (c) 2006-2010 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+#include <linux/input.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/string.h>
+
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/hid-debug.h>
+#include <linux/hidraw.h>
+#include "usbhid.h"
+
+/*
+ * Version Information
+ */
+
+#define DRIVER_DESC "USB HID core driver"
+
+/*
+ * Module parameters.
+ */
+
+static unsigned int hid_mousepoll_interval;
+module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
+MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
+
+static unsigned int hid_jspoll_interval;
+module_param_named(jspoll, hid_jspoll_interval, uint, 0644);
+MODULE_PARM_DESC(jspoll, "Polling interval of joysticks");
+
+static unsigned int hid_kbpoll_interval;
+module_param_named(kbpoll, hid_kbpoll_interval, uint, 0644);
+MODULE_PARM_DESC(kbpoll, "Polling interval of keyboards");
+
+static unsigned int ignoreled;
+module_param_named(ignoreled, ignoreled, uint, 0644);
+MODULE_PARM_DESC(ignoreled, "Autosuspend with active leds");
+
+/* Quirks specified at module load time */
+static char *quirks_param[MAX_USBHID_BOOT_QUIRKS];
+module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
+MODULE_PARM_DESC(quirks, "Add/modify USB HID quirks by specifying "
+		" quirks=vendorID:productID:quirks"
+		" where vendorID, productID, and quirks are all in"
+		" 0x-prefixed hex");
+/*
+ * Input submission and I/O error handler.
+ */
+static void hid_io_error(struct hid_device *hid);
+static int hid_submit_out(struct hid_device *hid);
+static int hid_submit_ctrl(struct hid_device *hid);
+static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid);
+
+/* Start up the input URB */
+static int hid_start_in(struct hid_device *hid)
+{
+	unsigned long flags;
+	int rc = 0;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irqsave(&usbhid->lock, flags);
+	if (test_bit(HID_IN_POLLING, &usbhid->iofl) &&
+	    !test_bit(HID_DISCONNECTED, &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) {
+			clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+			if (rc == -ENOSPC)
+				set_bit(HID_NO_BANDWIDTH, &usbhid->iofl);
+		} else {
+			clear_bit(HID_NO_BANDWIDTH, &usbhid->iofl);
+		}
+	}
+	spin_unlock_irqrestore(&usbhid->lock, flags);
+	return rc;
+}
+
+/* I/O retry timer routine */
+static void hid_retry_timeout(struct timer_list *t)
+{
+	struct usbhid_device *usbhid = from_timer(usbhid, t, io_retry);
+	struct hid_device *hid = usbhid->hid;
+
+	dev_dbg(&usbhid->intf->dev, "retrying intr urb\n");
+	if (hid_start_in(hid))
+		hid_io_error(hid);
+}
+
+/* Workqueue routine to reset the device or clear a halt */
+static void hid_reset(struct work_struct *work)
+{
+	struct usbhid_device *usbhid =
+		container_of(work, struct usbhid_device, reset_work);
+	struct hid_device *hid = usbhid->hid;
+	int rc;
+
+	if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) {
+		dev_dbg(&usbhid->intf->dev, "clear halt\n");
+		rc = usb_clear_halt(hid_to_usb_dev(hid), usbhid->urbin->pipe);
+		clear_bit(HID_CLEAR_HALT, &usbhid->iofl);
+		if (rc == 0) {
+			hid_start_in(hid);
+		} else {
+			dev_dbg(&usbhid->intf->dev,
+					"clear-halt failed: %d\n", rc);
+			set_bit(HID_RESET_PENDING, &usbhid->iofl);
+		}
+	}
+
+	if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) {
+		dev_dbg(&usbhid->intf->dev, "resetting device\n");
+		usb_queue_reset_device(usbhid->intf);
+	}
+}
+
+/* Main I/O error handler */
+static void hid_io_error(struct hid_device *hid)
+{
+	unsigned long flags;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irqsave(&usbhid->lock, flags);
+
+	/* Stop when disconnected */
+	if (test_bit(HID_DISCONNECTED, &usbhid->iofl))
+		goto done;
+
+	/* If it has been a while since the last error, we'll assume
+	 * this a brand new error and reset the retry timeout. */
+	if (time_after(jiffies, usbhid->stop_retry + HZ/2))
+		usbhid->retry_delay = 0;
+
+	/* When an error occurs, retry at increasing intervals */
+	if (usbhid->retry_delay == 0) {
+		usbhid->retry_delay = 13;	/* Then 26, 52, 104, 104, ... */
+		usbhid->stop_retry = jiffies + msecs_to_jiffies(1000);
+	} else if (usbhid->retry_delay < 100)
+		usbhid->retry_delay *= 2;
+
+	if (time_after(jiffies, usbhid->stop_retry)) {
+
+		/* Retries failed, so do a port reset unless we lack bandwidth*/
+		if (!test_bit(HID_NO_BANDWIDTH, &usbhid->iofl)
+		     && !test_and_set_bit(HID_RESET_PENDING, &usbhid->iofl)) {
+
+			schedule_work(&usbhid->reset_work);
+			goto done;
+		}
+	}
+
+	mod_timer(&usbhid->io_retry,
+			jiffies + msecs_to_jiffies(usbhid->retry_delay));
+done:
+	spin_unlock_irqrestore(&usbhid->lock, flags);
+}
+
+static void usbhid_mark_busy(struct usbhid_device *usbhid)
+{
+	struct usb_interface *intf = usbhid->intf;
+
+	usb_mark_last_busy(interface_to_usbdev(intf));
+}
+
+static int usbhid_restart_out_queue(struct usbhid_device *usbhid)
+{
+	struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+	int kicked;
+	int r;
+
+	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)) {
+			clear_bit(HID_OUT_RUNNING, &usbhid->iofl);
+			usb_autopm_put_interface_async(usbhid->intf);
+		}
+		wake_up(&usbhid->wait);
+	}
+	return kicked;
+}
+
+static int usbhid_restart_ctrl_queue(struct usbhid_device *usbhid)
+{
+	struct hid_device *hid = usb_get_intfdata(usbhid->intf);
+	int kicked;
+	int r;
+
+	WARN_ON(hid == NULL);
+	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)) {
+			clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+			usb_autopm_put_interface_async(usbhid->intf);
+		}
+		wake_up(&usbhid->wait);
+	}
+	return kicked;
+}
+
+/*
+ * Input interrupt completion handler.
+ */
+
+static void hid_irq_in(struct urb *urb)
+{
+	struct hid_device	*hid = urb->context;
+	struct usbhid_device	*usbhid = hid->driver_data;
+	int			status;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		usbhid->retry_delay = 0;
+		if (!test_bit(HID_OPENED, &usbhid->iofl))
+			break;
+		usbhid_mark_busy(usbhid);
+		if (!test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) {
+			hid_input_report(urb->context, HID_INPUT_REPORT,
+					 urb->transfer_buffer,
+					 urb->actual_length, 1);
+			/*
+			 * autosuspend refused while keys are pressed
+			 * because most keyboards don't wake up when
+			 * a key is released
+			 */
+			if (hid_check_keys_pressed(hid))
+				set_bit(HID_KEYS_PRESSED, &usbhid->iofl);
+			else
+				clear_bit(HID_KEYS_PRESSED, &usbhid->iofl);
+		}
+		break;
+	case -EPIPE:		/* stall */
+		usbhid_mark_busy(usbhid);
+		clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+		set_bit(HID_CLEAR_HALT, &usbhid->iofl);
+		schedule_work(&usbhid->reset_work);
+		return;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:	/* unplug */
+		clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+		return;
+	case -EILSEQ:		/* protocol error or unplug */
+	case -EPROTO:		/* protocol error or unplug */
+	case -ETIME:		/* protocol error or unplug */
+	case -ETIMEDOUT:	/* Should never happen, but... */
+		usbhid_mark_busy(usbhid);
+		clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+		hid_io_error(hid);
+		return;
+	default:		/* error */
+		hid_warn(urb->dev, "input irq status %d received\n",
+			 urb->status);
+	}
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status) {
+		clear_bit(HID_IN_RUNNING, &usbhid->iofl);
+		if (status != -EPERM) {
+			hid_err(hid, "can't resubmit intr, %s-%s/input%d, status %d\n",
+				hid_to_usb_dev(hid)->bus->bus_name,
+				hid_to_usb_dev(hid)->devpath,
+				usbhid->ifnum, status);
+			hid_io_error(hid);
+		}
+	}
+}
+
+static int hid_submit_out(struct hid_device *hid)
+{
+	struct hid_report *report;
+	char *raw_report;
+	struct usbhid_device *usbhid = hid->driver_data;
+	int r;
+
+	report = usbhid->out[usbhid->outtail].report;
+	raw_report = usbhid->out[usbhid->outtail].raw_report;
+
+	usbhid->urbout->transfer_buffer_length = hid_report_len(report);
+	usbhid->urbout->dev = hid_to_usb_dev(hid);
+	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");
+
+	r = usb_submit_urb(usbhid->urbout, GFP_ATOMIC);
+	if (r < 0) {
+		hid_err(hid, "usb_submit_urb(out) failed: %d\n", r);
+		return r;
+	}
+	usbhid->last_out = jiffies;
+	return 0;
+}
+
+static int hid_submit_ctrl(struct hid_device *hid)
+{
+	struct hid_report *report;
+	unsigned char dir;
+	char *raw_report;
+	int len, r;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	report = usbhid->ctrl[usbhid->ctrltail].report;
+	raw_report = usbhid->ctrl[usbhid->ctrltail].raw_report;
+	dir = usbhid->ctrl[usbhid->ctrltail].dir;
+
+	len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
+	if (dir == USB_DIR_OUT) {
+		usbhid->urbctrl->pipe = usb_sndctrlpipe(hid_to_usb_dev(hid), 0);
+		usbhid->urbctrl->transfer_buffer_length = len;
+		if (raw_report) {
+			memcpy(usbhid->ctrlbuf, raw_report, len);
+			kfree(raw_report);
+			usbhid->ctrl[usbhid->ctrltail].raw_report = NULL;
+		}
+	} else {
+		int maxpacket, padlen;
+
+		usbhid->urbctrl->pipe = usb_rcvctrlpipe(hid_to_usb_dev(hid), 0);
+		maxpacket = usb_maxpacket(hid_to_usb_dev(hid),
+					  usbhid->urbctrl->pipe, 0);
+		if (maxpacket > 0) {
+			padlen = DIV_ROUND_UP(len, maxpacket);
+			padlen *= maxpacket;
+			if (padlen > usbhid->bufsize)
+				padlen = usbhid->bufsize;
+		} else
+			padlen = 0;
+		usbhid->urbctrl->transfer_buffer_length = padlen;
+	}
+	usbhid->urbctrl->dev = hid_to_usb_dev(hid);
+
+	usbhid->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | dir;
+	usbhid->cr->bRequest = (dir == USB_DIR_OUT) ? HID_REQ_SET_REPORT :
+						      HID_REQ_GET_REPORT;
+	usbhid->cr->wValue = cpu_to_le16(((report->type + 1) << 8) |
+					 report->id);
+	usbhid->cr->wIndex = cpu_to_le16(usbhid->ifnum);
+	usbhid->cr->wLength = cpu_to_le16(len);
+
+	dbg_hid("submitting ctrl urb: %s wValue=0x%04x wIndex=0x%04x wLength=%u\n",
+		usbhid->cr->bRequest == HID_REQ_SET_REPORT ? "Set_Report" :
+							     "Get_Report",
+		usbhid->cr->wValue, usbhid->cr->wIndex, usbhid->cr->wLength);
+
+	r = usb_submit_urb(usbhid->urbctrl, GFP_ATOMIC);
+	if (r < 0) {
+		hid_err(hid, "usb_submit_urb(ctrl) failed: %d\n", r);
+		return r;
+	}
+	usbhid->last_ctrl = jiffies;
+	return 0;
+}
+
+/*
+ * Output interrupt completion handler.
+ */
+
+static void hid_irq_out(struct urb *urb)
+{
+	struct hid_device *hid = urb->context;
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned long flags;
+	int unplug = 0;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ESHUTDOWN:	/* unplug */
+		unplug = 1;
+	case -EILSEQ:		/* protocol error or unplug */
+	case -EPROTO:		/* protocol error or unplug */
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+		break;
+	default:		/* error */
+		hid_warn(urb->dev, "output irq status %d received\n",
+			 urb->status);
+	}
+
+	spin_lock_irqsave(&usbhid->lock, flags);
+
+	if (unplug) {
+		usbhid->outtail = usbhid->outhead;
+	} else {
+		usbhid->outtail = (usbhid->outtail + 1) & (HID_OUTPUT_FIFO_SIZE - 1);
+
+		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);
+	spin_unlock_irqrestore(&usbhid->lock, flags);
+	usb_autopm_put_interface_async(usbhid->intf);
+	wake_up(&usbhid->wait);
+}
+
+/*
+ * Control pipe completion handler.
+ */
+
+static void hid_ctrl(struct urb *urb)
+{
+	struct hid_device *hid = urb->context;
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned long flags;
+	int unplug = 0, status = urb->status;
+
+	switch (status) {
+	case 0:			/* success */
+		if (usbhid->ctrl[usbhid->ctrltail].dir == USB_DIR_IN)
+			hid_input_report(urb->context,
+				usbhid->ctrl[usbhid->ctrltail].report->type,
+				urb->transfer_buffer, urb->actual_length, 0);
+		break;
+	case -ESHUTDOWN:	/* unplug */
+		unplug = 1;
+	case -EILSEQ:		/* protocol error or unplug */
+	case -EPROTO:		/* protocol error or unplug */
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -EPIPE:		/* report not available */
+		break;
+	default:		/* error */
+		hid_warn(urb->dev, "ctrl urb status %d received\n", status);
+	}
+
+	spin_lock_irqsave(&usbhid->lock, flags);
+
+	if (unplug) {
+		usbhid->ctrltail = usbhid->ctrlhead;
+	} else {
+		usbhid->ctrltail = (usbhid->ctrltail + 1) & (HID_CONTROL_FIFO_SIZE - 1);
+
+		if (usbhid->ctrlhead != usbhid->ctrltail &&
+				hid_submit_ctrl(hid) == 0) {
+			/* Successfully submitted next urb in queue */
+			spin_unlock_irqrestore(&usbhid->lock, flags);
+			return;
+		}
+	}
+
+	clear_bit(HID_CTRL_RUNNING, &usbhid->iofl);
+	spin_unlock_irqrestore(&usbhid->lock, flags);
+	usb_autopm_put_interface_async(usbhid->intf);
+	wake_up(&usbhid->wait);
+}
+
+static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *report,
+				   unsigned char dir)
+{
+	int head;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) ||
+		test_bit(HID_DISCONNECTED, &usbhid->iofl))
+		return;
+
+	if (usbhid->urbout && dir == USB_DIR_OUT && report->type == HID_OUTPUT_REPORT) {
+		if ((head = (usbhid->outhead + 1) & (HID_OUTPUT_FIFO_SIZE - 1)) == usbhid->outtail) {
+			hid_warn(hid, "output queue full\n");
+			return;
+		}
+
+		usbhid->out[usbhid->outhead].raw_report = hid_alloc_report_buf(report, GFP_ATOMIC);
+		if (!usbhid->out[usbhid->outhead].raw_report) {
+			hid_warn(hid, "output queueing failed\n");
+			return;
+		}
+		hid_output_report(report, usbhid->out[usbhid->outhead].raw_report);
+		usbhid->out[usbhid->outhead].report = report;
+		usbhid->outhead = head;
+
+		/* If the queue isn't running, restart it */
+		if (!test_bit(HID_OUT_RUNNING, &usbhid->iofl)) {
+			usbhid_restart_out_queue(usbhid);
+
+		/* Otherwise see if an earlier request has timed out */
+		} else if (time_after(jiffies, usbhid->last_out + HZ * 5)) {
+
+			/* Prevent autosuspend following the unlink */
+			usb_autopm_get_interface_no_resume(usbhid->intf);
+
+			/*
+			 * Prevent resubmission in case the URB completes
+			 * before we can unlink it.  We don't want to cancel
+			 * the wrong transfer!
+			 */
+			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;
+	}
+
+	if ((head = (usbhid->ctrlhead + 1) & (HID_CONTROL_FIFO_SIZE - 1)) == usbhid->ctrltail) {
+		hid_warn(hid, "control queue full\n");
+		return;
+	}
+
+	if (dir == USB_DIR_OUT) {
+		usbhid->ctrl[usbhid->ctrlhead].raw_report = hid_alloc_report_buf(report, GFP_ATOMIC);
+		if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) {
+			hid_warn(hid, "control queueing failed\n");
+			return;
+		}
+		hid_output_report(report, usbhid->ctrl[usbhid->ctrlhead].raw_report);
+	}
+	usbhid->ctrl[usbhid->ctrlhead].report = report;
+	usbhid->ctrl[usbhid->ctrlhead].dir = dir;
+	usbhid->ctrlhead = head;
+
+	/* If the queue isn't running, restart it */
+	if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl)) {
+		usbhid_restart_ctrl_queue(usbhid);
+
+	/* Otherwise see if an earlier request has timed out */
+	} else if (time_after(jiffies, usbhid->last_ctrl + HZ * 5)) {
+
+		/* Prevent autosuspend following the unlink */
+		usb_autopm_get_interface_no_resume(usbhid->intf);
+
+		/*
+		 * Prevent resubmission in case the URB completes
+		 * before we can unlink it.  We don't want to cancel
+		 * the wrong transfer!
+		 */
+		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);
+	}
+}
+
+static void usbhid_submit_report(struct hid_device *hid, struct hid_report *report, unsigned char dir)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&usbhid->lock, flags);
+	__usbhid_submit_report(hid, report, dir);
+	spin_unlock_irqrestore(&usbhid->lock, flags);
+}
+
+static int usbhid_wait_io(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (!wait_event_timeout(usbhid->wait,
+				(!test_bit(HID_CTRL_RUNNING, &usbhid->iofl) &&
+				!test_bit(HID_OUT_RUNNING, &usbhid->iofl)),
+					10*HZ)) {
+		dbg_hid("timeout waiting for ctrl or out queue to clear\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int hid_set_idle(struct usb_device *dev, int ifnum, int report, int idle)
+{
+	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+		HID_REQ_SET_IDLE, USB_TYPE_CLASS | USB_RECIP_INTERFACE, (idle << 8) | report,
+		ifnum, NULL, 0, USB_CTRL_SET_TIMEOUT);
+}
+
+static int hid_get_class_descriptor(struct usb_device *dev, int ifnum,
+		unsigned char type, void *buf, int size)
+{
+	int result, retries = 4;
+
+	memset(buf, 0, size);
+
+	do {
+		result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+				USB_REQ_GET_DESCRIPTOR, USB_RECIP_INTERFACE | USB_DIR_IN,
+				(type << 8), ifnum, buf, size, USB_CTRL_GET_TIMEOUT);
+		retries--;
+	} while (result < size && retries);
+	return result;
+}
+
+static int usbhid_open(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int res;
+
+	set_bit(HID_OPENED, &usbhid->iofl);
+
+	if (hid->quirks & HID_QUIRK_ALWAYS_POLL)
+		return 0;
+
+	res = usb_autopm_get_interface(usbhid->intf);
+	/* the device must be awake to reliably request remote wakeup */
+	if (res < 0) {
+		clear_bit(HID_OPENED, &usbhid->iofl);
+		return -EIO;
+	}
+
+	usbhid->intf->needs_remote_wakeup = 1;
+
+	set_bit(HID_RESUME_RUNNING, &usbhid->iofl);
+	set_bit(HID_IN_POLLING, &usbhid->iofl);
+
+	res = hid_start_in(hid);
+	if (res) {
+		if (res != -ENOSPC) {
+			hid_io_error(hid);
+			res = 0;
+		} else {
+			/* no use opening if resources are insufficient */
+			res = -EBUSY;
+			clear_bit(HID_OPENED, &usbhid->iofl);
+			clear_bit(HID_IN_POLLING, &usbhid->iofl);
+			usbhid->intf->needs_remote_wakeup = 0;
+		}
+	}
+
+	usb_autopm_put_interface(usbhid->intf);
+
+	/*
+	 * In case events are generated while nobody was listening,
+	 * some are released when the device is re-opened.
+	 * Wait 50 msec for the queue to empty before allowing events
+	 * to go through hid.
+	 */
+	if (res == 0)
+		msleep(50);
+
+	clear_bit(HID_RESUME_RUNNING, &usbhid->iofl);
+	return res;
+}
+
+static void usbhid_close(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	/*
+	 * Make sure we don't restart data acquisition due to
+	 * a resumption we no longer care about by avoiding racing
+	 * with hid_start_in().
+	 */
+	spin_lock_irq(&usbhid->lock);
+	clear_bit(HID_OPENED, &usbhid->iofl);
+	if (!(hid->quirks & HID_QUIRK_ALWAYS_POLL))
+		clear_bit(HID_IN_POLLING, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->lock);
+
+	if (hid->quirks & HID_QUIRK_ALWAYS_POLL)
+		return;
+
+	hid_cancel_delayed_stuff(usbhid);
+	usb_kill_urb(usbhid->urbin);
+	usbhid->intf->needs_remote_wakeup = 0;
+}
+
+/*
+ * Initialize all reports
+ */
+
+void usbhid_init_reports(struct hid_device *hid)
+{
+	struct hid_report *report;
+	struct usbhid_device *usbhid = hid->driver_data;
+	struct hid_report_enum *report_enum;
+	int err, ret;
+
+	report_enum = &hid->report_enum[HID_INPUT_REPORT];
+	list_for_each_entry(report, &report_enum->report_list, list)
+		usbhid_submit_report(hid, report, USB_DIR_IN);
+
+	report_enum = &hid->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(report, &report_enum->report_list, list)
+		usbhid_submit_report(hid, report, USB_DIR_IN);
+
+	err = 0;
+	ret = usbhid_wait_io(hid);
+	while (ret) {
+		err |= ret;
+		if (test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+			usb_kill_urb(usbhid->urbctrl);
+		if (test_bit(HID_OUT_RUNNING, &usbhid->iofl))
+			usb_kill_urb(usbhid->urbout);
+		ret = usbhid_wait_io(hid);
+	}
+
+	if (err)
+		hid_warn(hid, "timeout initializing reports\n");
+}
+
+/*
+ * Reset LEDs which BIOS might have left on. For now, just NumLock (0x01).
+ */
+static int hid_find_field_early(struct hid_device *hid, unsigned int page,
+    unsigned int hid_code, struct hid_field **pfield)
+{
+	struct hid_report *report;
+	struct hid_field *field;
+	struct hid_usage *usage;
+	int i, j;
+
+	list_for_each_entry(report, &hid->report_enum[HID_OUTPUT_REPORT].report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++) {
+				usage = &field->usage[j];
+				if ((usage->hid & HID_USAGE_PAGE) == page &&
+				    (usage->hid & 0xFFFF) == hid_code) {
+					*pfield = field;
+					return j;
+				}
+			}
+		}
+	}
+	return -1;
+}
+
+static void usbhid_set_leds(struct hid_device *hid)
+{
+	struct hid_field *field;
+	int offset;
+
+	if ((offset = hid_find_field_early(hid, HID_UP_LED, 0x01, &field)) != -1) {
+		hid_set_field(field, offset, 0);
+		usbhid_submit_report(hid, field->report, USB_DIR_OUT);
+	}
+}
+
+/*
+ * Traverse the supplied list of reports and find the longest
+ */
+static void hid_find_max_report(struct hid_device *hid, unsigned int type,
+		unsigned int *max)
+{
+	struct hid_report *report;
+	unsigned int size;
+
+	list_for_each_entry(report, &hid->report_enum[type].report_list, list) {
+		size = ((report->size - 1) >> 3) + 1 + hid->report_enum[type].numbered;
+		if (*max < size)
+			*max = size;
+	}
+}
+
+static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	usbhid->inbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL,
+			&usbhid->inbuf_dma);
+	usbhid->outbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL,
+			&usbhid->outbuf_dma);
+	usbhid->cr = kmalloc(sizeof(*usbhid->cr), GFP_KERNEL);
+	usbhid->ctrlbuf = usb_alloc_coherent(dev, usbhid->bufsize, GFP_KERNEL,
+			&usbhid->ctrlbuf_dma);
+	if (!usbhid->inbuf || !usbhid->outbuf || !usbhid->cr ||
+			!usbhid->ctrlbuf)
+		return -1;
+
+	return 0;
+}
+
+static int usbhid_get_raw_report(struct hid_device *hid,
+		unsigned char report_number, __u8 *buf, size_t count,
+		unsigned char report_type)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	struct usb_interface *intf = usbhid->intf;
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	int skipped_report_id = 0;
+	int ret;
+
+	/* Byte 0 is the report number. Report data starts at byte 1.*/
+	buf[0] = report_number;
+	if (report_number == 0x0) {
+		/* Offset the return buffer by 1, so that the report ID
+		   will remain in byte 0. */
+		buf++;
+		count--;
+		skipped_report_id = 1;
+	}
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+		HID_REQ_GET_REPORT,
+		USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+		((report_type + 1) << 8) | report_number,
+		interface->desc.bInterfaceNumber, buf, count,
+		USB_CTRL_SET_TIMEOUT);
+
+	/* count also the report id */
+	if (ret > 0 && skipped_report_id)
+		ret++;
+
+	return ret;
+}
+
+static int usbhid_set_raw_report(struct hid_device *hid, unsigned int reportnum,
+				 __u8 *buf, size_t count, unsigned char rtype)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	struct usb_interface *intf = usbhid->intf;
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	int ret, skipped_report_id = 0;
+
+	/* Byte 0 is the report number. Report data starts at byte 1.*/
+	if ((rtype == HID_OUTPUT_REPORT) &&
+	    (hid->quirks & HID_QUIRK_SKIP_OUTPUT_REPORT_ID))
+		buf[0] = 0;
+	else
+		buf[0] = reportnum;
+
+	if (buf[0] == 0x0) {
+		/* Don't send the Report ID */
+		buf++;
+		count--;
+		skipped_report_id = 1;
+	}
+
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			HID_REQ_SET_REPORT,
+			USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+			((rtype + 1) << 8) | reportnum,
+			interface->desc.bInterfaceNumber, buf, count,
+			USB_CTRL_SET_TIMEOUT);
+	/* count also the report id, if this was a numbered report. */
+	if (ret > 0 && skipped_report_id)
+		ret++;
+
+	return ret;
+}
+
+static int usbhid_output_report(struct hid_device *hid, __u8 *buf, size_t count)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	int actual_length, skipped_report_id = 0, ret;
+
+	if (!usbhid->urbout)
+		return -ENOSYS;
+
+	if (buf[0] == 0x0) {
+		/* Don't send the Report ID */
+		buf++;
+		count--;
+		skipped_report_id = 1;
+	}
+
+	ret = usb_interrupt_msg(dev, usbhid->urbout->pipe,
+				buf, count, &actual_length,
+				USB_CTRL_SET_TIMEOUT);
+	/* return the number of bytes transferred */
+	if (ret == 0) {
+		ret = actual_length;
+		/* count also the report id */
+		if (skipped_report_id)
+			ret++;
+	}
+
+	return ret;
+}
+
+static void hid_free_buffers(struct usb_device *dev, struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	usb_free_coherent(dev, usbhid->bufsize, usbhid->inbuf, usbhid->inbuf_dma);
+	usb_free_coherent(dev, usbhid->bufsize, usbhid->outbuf, usbhid->outbuf_dma);
+	kfree(usbhid->cr);
+	usb_free_coherent(dev, usbhid->bufsize, usbhid->ctrlbuf, usbhid->ctrlbuf_dma);
+}
+
+static int usbhid_parse(struct hid_device *hid)
+{
+	struct usb_interface *intf = to_usb_interface(hid->dev.parent);
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	struct usb_device *dev = interface_to_usbdev (intf);
+	struct hid_descriptor *hdesc;
+	u32 quirks = 0;
+	unsigned int rsize = 0;
+	char *rdesc;
+	int ret, n;
+	int num_descriptors;
+	size_t offset = offsetof(struct hid_descriptor, desc);
+
+	quirks = hid_lookup_quirk(hid);
+
+	if (quirks & HID_QUIRK_IGNORE)
+		return -ENODEV;
+
+	/* Many keyboards and mice don't like to be polled for reports,
+	 * so we will always set the HID_QUIRK_NOGET flag for them. */
+	if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {
+		if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||
+			interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)
+				quirks |= HID_QUIRK_NOGET;
+	}
+
+	if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&
+	    (!interface->desc.bNumEndpoints ||
+	     usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {
+		dbg_hid("class descriptor not present\n");
+		return -ENODEV;
+	}
+
+	if (hdesc->bLength < sizeof(struct hid_descriptor)) {
+		dbg_hid("hid descriptor is too short\n");
+		return -EINVAL;
+	}
+
+	hid->version = le16_to_cpu(hdesc->bcdHID);
+	hid->country = hdesc->bCountryCode;
+
+	num_descriptors = min_t(int, hdesc->bNumDescriptors,
+	       (hdesc->bLength - offset) / sizeof(struct hid_class_descriptor));
+
+	for (n = 0; n < num_descriptors; n++)
+		if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
+			rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
+
+	if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
+		dbg_hid("weird size of report descriptor (%u)\n", rsize);
+		return -EINVAL;
+	}
+
+	rdesc = kmalloc(rsize, GFP_KERNEL);
+	if (!rdesc)
+		return -ENOMEM;
+
+	hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);
+
+	ret = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber,
+			HID_DT_REPORT, rdesc, rsize);
+	if (ret < 0) {
+		dbg_hid("reading report descriptor failed\n");
+		kfree(rdesc);
+		goto err;
+	}
+
+	ret = hid_parse_report(hid, rdesc, rsize);
+	kfree(rdesc);
+	if (ret) {
+		dbg_hid("parsing report descriptor failed\n");
+		goto err;
+	}
+
+	hid->quirks |= quirks;
+
+	return 0;
+err:
+	return ret;
+}
+
+static int usbhid_start(struct hid_device *hid)
+{
+	struct usb_interface *intf = to_usb_interface(hid->dev.parent);
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct usbhid_device *usbhid = hid->driver_data;
+	unsigned int n, insize = 0;
+	int ret;
+
+	clear_bit(HID_DISCONNECTED, &usbhid->iofl);
+
+	usbhid->bufsize = HID_MIN_BUFFER_SIZE;
+	hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);
+	hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);
+	hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);
+
+	if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)
+		usbhid->bufsize = HID_MAX_BUFFER_SIZE;
+
+	hid_find_max_report(hid, HID_INPUT_REPORT, &insize);
+
+	if (insize > HID_MAX_BUFFER_SIZE)
+		insize = HID_MAX_BUFFER_SIZE;
+
+	if (hid_alloc_buffers(dev, hid)) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	for (n = 0; n < interface->desc.bNumEndpoints; n++) {
+		struct usb_endpoint_descriptor *endpoint;
+		int pipe;
+		int interval;
+
+		endpoint = &interface->endpoint[n].desc;
+		if (!usb_endpoint_xfer_int(endpoint))
+			continue;
+
+		interval = endpoint->bInterval;
+
+		/* Some vendors give fullspeed interval on highspeed devides */
+		if (hid->quirks & HID_QUIRK_FULLSPEED_INTERVAL &&
+		    dev->speed == USB_SPEED_HIGH) {
+			interval = fls(endpoint->bInterval*8);
+			pr_info("%s: Fixing fullspeed to highspeed interval: %d -> %d\n",
+				hid->name, endpoint->bInterval, interval);
+		}
+
+		/* Change the polling interval of mice, joysticks
+		 * and keyboards.
+		 */
+		switch (hid->collection->usage) {
+		case HID_GD_MOUSE:
+			if (hid_mousepoll_interval > 0)
+				interval = hid_mousepoll_interval;
+			break;
+		case HID_GD_JOYSTICK:
+			if (hid_jspoll_interval > 0)
+				interval = hid_jspoll_interval;
+			break;
+		case HID_GD_KEYBOARD:
+			if (hid_kbpoll_interval > 0)
+				interval = hid_kbpoll_interval;
+			break;
+		}
+
+		ret = -ENOMEM;
+		if (usb_endpoint_dir_in(endpoint)) {
+			if (usbhid->urbin)
+				continue;
+			if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))
+				goto fail;
+			pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+			usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,
+					 hid_irq_in, hid, interval);
+			usbhid->urbin->transfer_dma = usbhid->inbuf_dma;
+			usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		} else {
+			if (usbhid->urbout)
+				continue;
+			if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))
+				goto fail;
+			pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);
+			usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,
+					 hid_irq_out, hid, interval);
+			usbhid->urbout->transfer_dma = usbhid->outbuf_dma;
+			usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		}
+	}
+
+	usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);
+	if (!usbhid->urbctrl) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,
+			     usbhid->ctrlbuf, 1, hid_ctrl, hid);
+	usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;
+	usbhid->urbctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	set_bit(HID_STARTED, &usbhid->iofl);
+
+	if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
+		ret = usb_autopm_get_interface(usbhid->intf);
+		if (ret)
+			goto fail;
+		set_bit(HID_IN_POLLING, &usbhid->iofl);
+		usbhid->intf->needs_remote_wakeup = 1;
+		ret = hid_start_in(hid);
+		if (ret) {
+			dev_err(&hid->dev,
+				"failed to start in urb: %d\n", ret);
+		}
+		usb_autopm_put_interface(usbhid->intf);
+	}
+
+	/* Some keyboards don't work until their LEDs have been set.
+	 * Since BIOSes do set the LEDs, it must be safe for any device
+	 * that supports the keyboard boot protocol.
+	 * In addition, enable remote wakeup by default for all keyboard
+	 * devices supporting the boot protocol.
+	 */
+	if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT &&
+			interface->desc.bInterfaceProtocol ==
+				USB_INTERFACE_PROTOCOL_KEYBOARD) {
+		usbhid_set_leds(hid);
+		device_set_wakeup_enable(&dev->dev, 1);
+	}
+	return 0;
+
+fail:
+	usb_free_urb(usbhid->urbin);
+	usb_free_urb(usbhid->urbout);
+	usb_free_urb(usbhid->urbctrl);
+	usbhid->urbin = NULL;
+	usbhid->urbout = NULL;
+	usbhid->urbctrl = NULL;
+	hid_free_buffers(dev, hid);
+	return ret;
+}
+
+static void usbhid_stop(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	if (WARN_ON(!usbhid))
+		return;
+
+	if (hid->quirks & HID_QUIRK_ALWAYS_POLL) {
+		clear_bit(HID_IN_POLLING, &usbhid->iofl);
+		usbhid->intf->needs_remote_wakeup = 0;
+	}
+
+	clear_bit(HID_STARTED, &usbhid->iofl);
+	spin_lock_irq(&usbhid->lock);	/* Sync with error and led handlers */
+	set_bit(HID_DISCONNECTED, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->lock);
+	usb_kill_urb(usbhid->urbin);
+	usb_kill_urb(usbhid->urbout);
+	usb_kill_urb(usbhid->urbctrl);
+
+	hid_cancel_delayed_stuff(usbhid);
+
+	hid->claimed = 0;
+
+	usb_free_urb(usbhid->urbin);
+	usb_free_urb(usbhid->urbctrl);
+	usb_free_urb(usbhid->urbout);
+	usbhid->urbin = NULL; /* don't mess up next start */
+	usbhid->urbctrl = NULL;
+	usbhid->urbout = NULL;
+
+	hid_free_buffers(hid_to_usb_dev(hid), hid);
+}
+
+static int usbhid_power(struct hid_device *hid, int lvl)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int r = 0;
+
+	switch (lvl) {
+	case PM_HINT_FULLON:
+		r = usb_autopm_get_interface(usbhid->intf);
+		break;
+
+	case PM_HINT_NORMAL:
+		usb_autopm_put_interface(usbhid->intf);
+		break;
+	}
+
+	return r;
+}
+
+static void usbhid_request(struct hid_device *hid, struct hid_report *rep, int reqtype)
+{
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		usbhid_submit_report(hid, rep, USB_DIR_IN);
+		break;
+	case HID_REQ_SET_REPORT:
+		usbhid_submit_report(hid, rep, USB_DIR_OUT);
+		break;
+	}
+}
+
+static int usbhid_raw_request(struct hid_device *hid, unsigned char reportnum,
+			      __u8 *buf, size_t len, unsigned char rtype,
+			      int reqtype)
+{
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return usbhid_get_raw_report(hid, reportnum, buf, len, rtype);
+	case HID_REQ_SET_REPORT:
+		return usbhid_set_raw_report(hid, reportnum, buf, len, rtype);
+	default:
+		return -EIO;
+	}
+}
+
+static int usbhid_idle(struct hid_device *hid, int report, int idle,
+		int reqtype)
+{
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	struct usb_interface *intf = to_usb_interface(hid->dev.parent);
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	int ifnum = interface->desc.bInterfaceNumber;
+
+	if (reqtype != HID_REQ_SET_IDLE)
+		return -EINVAL;
+
+	return hid_set_idle(dev, ifnum, report, idle);
+}
+
+struct hid_ll_driver usb_hid_driver = {
+	.parse = usbhid_parse,
+	.start = usbhid_start,
+	.stop = usbhid_stop,
+	.open = usbhid_open,
+	.close = usbhid_close,
+	.power = usbhid_power,
+	.request = usbhid_request,
+	.wait = usbhid_wait_io,
+	.raw_request = usbhid_raw_request,
+	.output_report = usbhid_output_report,
+	.idle = usbhid_idle,
+};
+EXPORT_SYMBOL_GPL(usb_hid_driver);
+
+static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct usbhid_device *usbhid;
+	struct hid_device *hid;
+	unsigned int n, has_in = 0;
+	size_t len;
+	int ret;
+
+	dbg_hid("HID probe called for ifnum %d\n",
+			intf->altsetting->desc.bInterfaceNumber);
+
+	for (n = 0; n < interface->desc.bNumEndpoints; n++)
+		if (usb_endpoint_is_int_in(&interface->endpoint[n].desc))
+			has_in++;
+	if (!has_in) {
+		hid_err(intf, "couldn't find an input interrupt endpoint\n");
+		return -ENODEV;
+	}
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid))
+		return PTR_ERR(hid);
+
+	usb_set_intfdata(intf, hid);
+	hid->ll_driver = &usb_hid_driver;
+	hid->ff_init = hid_pidff_init;
+#ifdef CONFIG_USB_HIDDEV
+	hid->hiddev_connect = hiddev_connect;
+	hid->hiddev_disconnect = hiddev_disconnect;
+	hid->hiddev_hid_event = hiddev_hid_event;
+	hid->hiddev_report_event = hiddev_report_event;
+#endif
+	hid->dev.parent = &intf->dev;
+	hid->bus = BUS_USB;
+	hid->vendor = le16_to_cpu(dev->descriptor.idVendor);
+	hid->product = le16_to_cpu(dev->descriptor.idProduct);
+	hid->version = le16_to_cpu(dev->descriptor.bcdDevice);
+	hid->name[0] = 0;
+	if (intf->cur_altsetting->desc.bInterfaceProtocol ==
+			USB_INTERFACE_PROTOCOL_MOUSE)
+		hid->type = HID_TYPE_USBMOUSE;
+	else if (intf->cur_altsetting->desc.bInterfaceProtocol == 0)
+		hid->type = HID_TYPE_USBNONE;
+
+	if (dev->manufacturer)
+		strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(hid->name, " ", sizeof(hid->name));
+		strlcat(hid->name, dev->product, sizeof(hid->name));
+	}
+
+	if (!strlen(hid->name))
+		snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	usb_make_path(dev, hid->phys, sizeof(hid->phys));
+	strlcat(hid->phys, "/input", sizeof(hid->phys));
+	len = strlen(hid->phys);
+	if (len < sizeof(hid->phys) - 1)
+		snprintf(hid->phys + len, sizeof(hid->phys) - len,
+			 "%d", intf->altsetting[0].desc.bInterfaceNumber);
+
+	if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)
+		hid->uniq[0] = 0;
+
+	usbhid = kzalloc(sizeof(*usbhid), GFP_KERNEL);
+	if (usbhid == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	hid->driver_data = usbhid;
+	usbhid->hid = hid;
+	usbhid->intf = intf;
+	usbhid->ifnum = interface->desc.bInterfaceNumber;
+
+	init_waitqueue_head(&usbhid->wait);
+	INIT_WORK(&usbhid->reset_work, hid_reset);
+	timer_setup(&usbhid->io_retry, hid_retry_timeout, 0);
+	spin_lock_init(&usbhid->lock);
+
+	ret = hid_add_device(hid);
+	if (ret) {
+		if (ret != -ENODEV)
+			hid_err(intf, "can't add hid device: %d\n", ret);
+		goto err_free;
+	}
+
+	return 0;
+err_free:
+	kfree(usbhid);
+err:
+	hid_destroy_device(hid);
+	return ret;
+}
+
+static void usbhid_disconnect(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata(intf);
+	struct usbhid_device *usbhid;
+
+	if (WARN_ON(!hid))
+		return;
+
+	usbhid = hid->driver_data;
+	spin_lock_irq(&usbhid->lock);	/* Sync with error and led handlers */
+	set_bit(HID_DISCONNECTED, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->lock);
+	hid_destroy_device(hid);
+	kfree(usbhid);
+}
+
+static void hid_cancel_delayed_stuff(struct usbhid_device *usbhid)
+{
+	del_timer_sync(&usbhid->io_retry);
+	cancel_work_sync(&usbhid->reset_work);
+}
+
+static void hid_cease_io(struct usbhid_device *usbhid)
+{
+	del_timer_sync(&usbhid->io_retry);
+	usb_kill_urb(usbhid->urbin);
+	usb_kill_urb(usbhid->urbctrl);
+	usb_kill_urb(usbhid->urbout);
+}
+
+static void hid_restart_io(struct hid_device *hid)
+{
+	struct usbhid_device *usbhid = hid->driver_data;
+	int clear_halt = test_bit(HID_CLEAR_HALT, &usbhid->iofl);
+	int reset_pending = test_bit(HID_RESET_PENDING, &usbhid->iofl);
+
+	spin_lock_irq(&usbhid->lock);
+	clear_bit(HID_SUSPENDED, &usbhid->iofl);
+	usbhid_mark_busy(usbhid);
+
+	if (clear_halt || reset_pending)
+		schedule_work(&usbhid->reset_work);
+	usbhid->retry_delay = 0;
+	spin_unlock_irq(&usbhid->lock);
+
+	if (reset_pending || !test_bit(HID_STARTED, &usbhid->iofl))
+		return;
+
+	if (!clear_halt) {
+		if (hid_start_in(hid) < 0)
+			hid_io_error(hid);
+	}
+
+	spin_lock_irq(&usbhid->lock);
+	if (usbhid->urbout && !test_bit(HID_OUT_RUNNING, &usbhid->iofl))
+		usbhid_restart_out_queue(usbhid);
+	if (!test_bit(HID_CTRL_RUNNING, &usbhid->iofl))
+		usbhid_restart_ctrl_queue(usbhid);
+	spin_unlock_irq(&usbhid->lock);
+}
+
+/* Treat USB reset pretty much the same as suspend/resume */
+static int hid_pre_reset(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata(intf);
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	spin_lock_irq(&usbhid->lock);
+	set_bit(HID_RESET_PENDING, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->lock);
+	hid_cease_io(usbhid);
+
+	return 0;
+}
+
+/* Same routine used for post_reset and reset_resume */
+static int hid_post_reset(struct usb_interface *intf)
+{
+	struct usb_device *dev = interface_to_usbdev (intf);
+	struct hid_device *hid = usb_get_intfdata(intf);
+	struct usbhid_device *usbhid = hid->driver_data;
+	struct usb_host_interface *interface = intf->cur_altsetting;
+	int status;
+	char *rdesc;
+
+	/* Fetch and examine the HID report descriptor. If this
+	 * has changed, then rebind. Since usbcore's check of the
+	 * configuration descriptors passed, we already know that
+	 * the size of the HID report descriptor has not changed.
+	 */
+	rdesc = kmalloc(hid->dev_rsize, GFP_KERNEL);
+	if (!rdesc)
+		return -ENOMEM;
+
+	status = hid_get_class_descriptor(dev,
+				interface->desc.bInterfaceNumber,
+				HID_DT_REPORT, rdesc, hid->dev_rsize);
+	if (status < 0) {
+		dbg_hid("reading report descriptor failed (post_reset)\n");
+		kfree(rdesc);
+		return status;
+	}
+	status = memcmp(rdesc, hid->dev_rdesc, hid->dev_rsize);
+	kfree(rdesc);
+	if (status != 0) {
+		dbg_hid("report descriptor changed\n");
+		return -EPERM;
+	}
+
+	/* No need to do another reset or clear a halted endpoint */
+	spin_lock_irq(&usbhid->lock);
+	clear_bit(HID_RESET_PENDING, &usbhid->iofl);
+	clear_bit(HID_CLEAR_HALT, &usbhid->iofl);
+	spin_unlock_irq(&usbhid->lock);
+	hid_set_idle(dev, intf->cur_altsetting->desc.bInterfaceNumber, 0, 0);
+
+	hid_restart_io(hid);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int hid_resume_common(struct hid_device *hid, bool driver_suspended)
+{
+	int status = 0;
+
+	hid_restart_io(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 = 0;
+	bool driver_suspended = false;
+	unsigned int ledcount;
+
+	if (PMSG_IS_AUTO(message)) {
+		ledcount = hidinput_count_leds(hid);
+		spin_lock_irq(&usbhid->lock);	/* Sync with error handler */
+		if (!test_bit(HID_RESET_PENDING, &usbhid->iofl)
+		    && !test_bit(HID_CLEAR_HALT, &usbhid->iofl)
+		    && !test_bit(HID_OUT_RUNNING, &usbhid->iofl)
+		    && !test_bit(HID_CTRL_RUNNING, &usbhid->iofl)
+		    && !test_bit(HID_KEYS_PRESSED, &usbhid->iofl)
+		    && (!ledcount || ignoreled))
+		{
+			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)
+					goto failed;
+			}
+			driver_suspended = true;
+		} else {
+			usbhid_mark_busy(usbhid);
+			spin_unlock_irq(&usbhid->lock);
+			return -EBUSY;
+		}
+
+	} else {
+		/* TODO: resume() might need to handle suspend failure */
+		if (hid->driver && hid->driver->suspend)
+			status = hid->driver->suspend(hid, message);
+		driver_suspended = true;
+		spin_lock_irq(&usbhid->lock);
+		set_bit(HID_SUSPENDED, &usbhid->iofl);
+		spin_unlock_irq(&usbhid->lock);
+		if (usbhid_wait_io(hid) < 0)
+			status = -EIO;
+	}
+
+	hid_cancel_delayed_stuff(usbhid);
+	hid_cease_io(usbhid);
+
+	if (PMSG_IS_AUTO(message) && test_bit(HID_KEYS_PRESSED, &usbhid->iofl)) {
+		/* lost race against keypresses */
+		status = -EBUSY;
+		goto failed;
+	}
+	dev_dbg(&intf->dev, "suspend\n");
+	return status;
+
+ failed:
+	hid_resume_common(hid, driver_suspended);
+	return status;
+}
+
+static int hid_resume(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata (intf);
+	int status;
+
+	status = hid_resume_common(hid, true);
+	dev_dbg(&intf->dev, "resume status %d\n", status);
+	return 0;
+}
+
+static int hid_reset_resume(struct usb_interface *intf)
+{
+	struct hid_device *hid = usb_get_intfdata(intf);
+	int status;
+
+	status = hid_post_reset(intf);
+	if (status >= 0 && hid->driver && hid->driver->reset_resume) {
+		int ret = hid->driver->reset_resume(hid);
+		if (ret < 0)
+			status = ret;
+	}
+	return status;
+}
+
+#endif /* CONFIG_PM */
+
+static const struct usb_device_id hid_usb_ids[] = {
+	{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
+		.bInterfaceClass = USB_INTERFACE_CLASS_HID },
+	{ }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, hid_usb_ids);
+
+static struct usb_driver hid_driver = {
+	.name =		"usbhid",
+	.probe =	usbhid_probe,
+	.disconnect =	usbhid_disconnect,
+#ifdef CONFIG_PM
+	.suspend =	hid_suspend,
+	.resume =	hid_resume,
+	.reset_resume =	hid_reset_resume,
+#endif
+	.pre_reset =	hid_pre_reset,
+	.post_reset =	hid_post_reset,
+	.id_table =	hid_usb_ids,
+	.supports_autosuspend = 1,
+};
+
+struct usb_interface *usbhid_find_interface(int minor)
+{
+	return usb_find_interface(&hid_driver, minor);
+}
+
+static int __init hid_init(void)
+{
+	int retval = -ENOMEM;
+
+	retval = hid_quirks_init(quirks_param, BUS_USB, MAX_USBHID_BOOT_QUIRKS);
+	if (retval)
+		goto usbhid_quirks_init_fail;
+	retval = usb_register(&hid_driver);
+	if (retval)
+		goto usb_register_fail;
+	pr_info(KBUILD_MODNAME ": " DRIVER_DESC "\n");
+
+	return 0;
+usb_register_fail:
+	hid_quirks_exit(BUS_USB);
+usbhid_quirks_init_fail:
+	return retval;
+}
+
+static void __exit hid_exit(void)
+{
+	usb_deregister(&hid_driver);
+	hid_quirks_exit(BUS_USB);
+}
+
+module_init(hid_init);
+module_exit(hid_exit);
+
+MODULE_AUTHOR("Andreas Gal");
+MODULE_AUTHOR("Vojtech Pavlik");
+MODULE_AUTHOR("Jiri Kosina");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/usbhid/hid-pidff.c b/drivers/hid/usbhid/hid-pidff.c
new file mode 100644
index 0000000..08174d3
--- /dev/null
+++ b/drivers/hid/usbhid/hid-pidff.c
@@ -0,0 +1,1335 @@
+/*
+ *  Force feedback driver for USB HID PID compliant devices
+ *
+ *  Copyright (c) 2005, 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define DEBUG */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <linux/hid.h>
+
+#include "usbhid.h"
+
+#define	PID_EFFECTS_MAX		64
+
+/* Report usage table used to put reports into an array */
+
+#define PID_SET_EFFECT		0
+#define PID_EFFECT_OPERATION	1
+#define PID_DEVICE_GAIN		2
+#define PID_POOL		3
+#define PID_BLOCK_LOAD		4
+#define PID_BLOCK_FREE		5
+#define PID_DEVICE_CONTROL	6
+#define PID_CREATE_NEW_EFFECT	7
+
+#define PID_REQUIRED_REPORTS	7
+
+#define PID_SET_ENVELOPE	8
+#define PID_SET_CONDITION	9
+#define PID_SET_PERIODIC	10
+#define PID_SET_CONSTANT	11
+#define PID_SET_RAMP		12
+static const u8 pidff_reports[] = {
+	0x21, 0x77, 0x7d, 0x7f, 0x89, 0x90, 0x96, 0xab,
+	0x5a, 0x5f, 0x6e, 0x73, 0x74
+};
+
+/* device_control is really 0x95, but 0x96 specified as it is the usage of
+the only field in that report */
+
+/* Value usage tables used to put fields and values into arrays */
+
+#define PID_EFFECT_BLOCK_INDEX	0
+
+#define PID_DURATION		1
+#define PID_GAIN		2
+#define PID_TRIGGER_BUTTON	3
+#define PID_TRIGGER_REPEAT_INT	4
+#define PID_DIRECTION_ENABLE	5
+#define PID_START_DELAY		6
+static const u8 pidff_set_effect[] = {
+	0x22, 0x50, 0x52, 0x53, 0x54, 0x56, 0xa7
+};
+
+#define PID_ATTACK_LEVEL	1
+#define PID_ATTACK_TIME		2
+#define PID_FADE_LEVEL		3
+#define PID_FADE_TIME		4
+static const u8 pidff_set_envelope[] = { 0x22, 0x5b, 0x5c, 0x5d, 0x5e };
+
+#define PID_PARAM_BLOCK_OFFSET	1
+#define PID_CP_OFFSET		2
+#define PID_POS_COEFFICIENT	3
+#define PID_NEG_COEFFICIENT	4
+#define PID_POS_SATURATION	5
+#define PID_NEG_SATURATION	6
+#define PID_DEAD_BAND		7
+static const u8 pidff_set_condition[] = {
+	0x22, 0x23, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65
+};
+
+#define PID_MAGNITUDE		1
+#define PID_OFFSET		2
+#define PID_PHASE		3
+#define PID_PERIOD		4
+static const u8 pidff_set_periodic[] = { 0x22, 0x70, 0x6f, 0x71, 0x72 };
+static const u8 pidff_set_constant[] = { 0x22, 0x70 };
+
+#define PID_RAMP_START		1
+#define PID_RAMP_END		2
+static const u8 pidff_set_ramp[] = { 0x22, 0x75, 0x76 };
+
+#define PID_RAM_POOL_AVAILABLE	1
+static const u8 pidff_block_load[] = { 0x22, 0xac };
+
+#define PID_LOOP_COUNT		1
+static const u8 pidff_effect_operation[] = { 0x22, 0x7c };
+
+static const u8 pidff_block_free[] = { 0x22 };
+
+#define PID_DEVICE_GAIN_FIELD	0
+static const u8 pidff_device_gain[] = { 0x7e };
+
+#define PID_RAM_POOL_SIZE	0
+#define PID_SIMULTANEOUS_MAX	1
+#define PID_DEVICE_MANAGED_POOL	2
+static const u8 pidff_pool[] = { 0x80, 0x83, 0xa9 };
+
+/* Special field key tables used to put special field keys into arrays */
+
+#define PID_ENABLE_ACTUATORS	0
+#define PID_RESET		1
+static const u8 pidff_device_control[] = { 0x97, 0x9a };
+
+#define PID_CONSTANT	0
+#define PID_RAMP	1
+#define PID_SQUARE	2
+#define PID_SINE	3
+#define PID_TRIANGLE	4
+#define PID_SAW_UP	5
+#define PID_SAW_DOWN	6
+#define PID_SPRING	7
+#define PID_DAMPER	8
+#define PID_INERTIA	9
+#define PID_FRICTION	10
+static const u8 pidff_effect_types[] = {
+	0x26, 0x27, 0x30, 0x31, 0x32, 0x33, 0x34,
+	0x40, 0x41, 0x42, 0x43
+};
+
+#define PID_BLOCK_LOAD_SUCCESS	0
+#define PID_BLOCK_LOAD_FULL	1
+static const u8 pidff_block_load_status[] = { 0x8c, 0x8d };
+
+#define PID_EFFECT_START	0
+#define PID_EFFECT_STOP		1
+static const u8 pidff_effect_operation_status[] = { 0x79, 0x7b };
+
+struct pidff_usage {
+	struct hid_field *field;
+	s32 *value;
+};
+
+struct pidff_device {
+	struct hid_device *hid;
+
+	struct hid_report *reports[sizeof(pidff_reports)];
+
+	struct pidff_usage set_effect[sizeof(pidff_set_effect)];
+	struct pidff_usage set_envelope[sizeof(pidff_set_envelope)];
+	struct pidff_usage set_condition[sizeof(pidff_set_condition)];
+	struct pidff_usage set_periodic[sizeof(pidff_set_periodic)];
+	struct pidff_usage set_constant[sizeof(pidff_set_constant)];
+	struct pidff_usage set_ramp[sizeof(pidff_set_ramp)];
+
+	struct pidff_usage device_gain[sizeof(pidff_device_gain)];
+	struct pidff_usage block_load[sizeof(pidff_block_load)];
+	struct pidff_usage pool[sizeof(pidff_pool)];
+	struct pidff_usage effect_operation[sizeof(pidff_effect_operation)];
+	struct pidff_usage block_free[sizeof(pidff_block_free)];
+
+	/* Special field is a field that is not composed of
+	   usage<->value pairs that pidff_usage values are */
+
+	/* Special field in create_new_effect */
+	struct hid_field *create_new_effect_type;
+
+	/* Special fields in set_effect */
+	struct hid_field *set_effect_type;
+	struct hid_field *effect_direction;
+
+	/* Special field in device_control */
+	struct hid_field *device_control;
+
+	/* Special field in block_load */
+	struct hid_field *block_load_status;
+
+	/* Special field in effect_operation */
+	struct hid_field *effect_operation_status;
+
+	int control_id[sizeof(pidff_device_control)];
+	int type_id[sizeof(pidff_effect_types)];
+	int status_id[sizeof(pidff_block_load_status)];
+	int operation_id[sizeof(pidff_effect_operation_status)];
+
+	int pid_id[PID_EFFECTS_MAX];
+};
+
+/*
+ * Scale an unsigned value with range 0..max for the given field
+ */
+static int pidff_rescale(int i, int max, struct hid_field *field)
+{
+	return i * (field->logical_maximum - field->logical_minimum) / max +
+	    field->logical_minimum;
+}
+
+/*
+ * Scale a signed value in range -0x8000..0x7fff for the given field
+ */
+static int pidff_rescale_signed(int i, struct hid_field *field)
+{
+	return i == 0 ? 0 : i >
+	    0 ? i * field->logical_maximum / 0x7fff : i *
+	    field->logical_minimum / -0x8000;
+}
+
+static void pidff_set(struct pidff_usage *usage, u16 value)
+{
+	usage->value[0] = pidff_rescale(value, 0xffff, usage->field);
+	pr_debug("calculated from %d to %d\n", value, usage->value[0]);
+}
+
+static void pidff_set_signed(struct pidff_usage *usage, s16 value)
+{
+	if (usage->field->logical_minimum < 0)
+		usage->value[0] = pidff_rescale_signed(value, usage->field);
+	else {
+		if (value < 0)
+			usage->value[0] =
+			    pidff_rescale(-value, 0x8000, usage->field);
+		else
+			usage->value[0] =
+			    pidff_rescale(value, 0x7fff, usage->field);
+	}
+	pr_debug("calculated from %d to %d\n", value, usage->value[0]);
+}
+
+/*
+ * Send envelope report to the device
+ */
+static void pidff_set_envelope_report(struct pidff_device *pidff,
+				      struct ff_envelope *envelope)
+{
+	pidff->set_envelope[PID_EFFECT_BLOCK_INDEX].value[0] =
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	pidff->set_envelope[PID_ATTACK_LEVEL].value[0] =
+	    pidff_rescale(envelope->attack_level >
+			  0x7fff ? 0x7fff : envelope->attack_level, 0x7fff,
+			  pidff->set_envelope[PID_ATTACK_LEVEL].field);
+	pidff->set_envelope[PID_FADE_LEVEL].value[0] =
+	    pidff_rescale(envelope->fade_level >
+			  0x7fff ? 0x7fff : envelope->fade_level, 0x7fff,
+			  pidff->set_envelope[PID_FADE_LEVEL].field);
+
+	pidff->set_envelope[PID_ATTACK_TIME].value[0] = envelope->attack_length;
+	pidff->set_envelope[PID_FADE_TIME].value[0] = envelope->fade_length;
+
+	hid_dbg(pidff->hid, "attack %u => %d\n",
+		envelope->attack_level,
+		pidff->set_envelope[PID_ATTACK_LEVEL].value[0]);
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_ENVELOPE],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * Test if the new envelope differs from old one
+ */
+static int pidff_needs_set_envelope(struct ff_envelope *envelope,
+				    struct ff_envelope *old)
+{
+	return envelope->attack_level != old->attack_level ||
+	       envelope->fade_level != old->fade_level ||
+	       envelope->attack_length != old->attack_length ||
+	       envelope->fade_length != old->fade_length;
+}
+
+/*
+ * Send constant force report to the device
+ */
+static void pidff_set_constant_force_report(struct pidff_device *pidff,
+					    struct ff_effect *effect)
+{
+	pidff->set_constant[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_constant[PID_MAGNITUDE],
+			 effect->u.constant.level);
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONSTANT],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * Test if the constant parameters have changed between effects
+ */
+static int pidff_needs_set_constant(struct ff_effect *effect,
+				    struct ff_effect *old)
+{
+	return effect->u.constant.level != old->u.constant.level;
+}
+
+/*
+ * Send set effect report to the device
+ */
+static void pidff_set_effect_report(struct pidff_device *pidff,
+				    struct ff_effect *effect)
+{
+	pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff->set_effect_type->value[0] =
+		pidff->create_new_effect_type->value[0];
+	pidff->set_effect[PID_DURATION].value[0] = effect->replay.length;
+	pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = effect->trigger.button;
+	pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] =
+		effect->trigger.interval;
+	pidff->set_effect[PID_GAIN].value[0] =
+		pidff->set_effect[PID_GAIN].field->logical_maximum;
+	pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
+	pidff->effect_direction->value[0] =
+		pidff_rescale(effect->direction, 0xffff,
+				pidff->effect_direction);
+	pidff->set_effect[PID_START_DELAY].value[0] = effect->replay.delay;
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * Test if the values used in set_effect have changed
+ */
+static int pidff_needs_set_effect(struct ff_effect *effect,
+				  struct ff_effect *old)
+{
+	return effect->replay.length != old->replay.length ||
+	       effect->trigger.interval != old->trigger.interval ||
+	       effect->trigger.button != old->trigger.button ||
+	       effect->direction != old->direction ||
+	       effect->replay.delay != old->replay.delay;
+}
+
+/*
+ * Send periodic effect report to the device
+ */
+static void pidff_set_periodic_report(struct pidff_device *pidff,
+				      struct ff_effect *effect)
+{
+	pidff->set_periodic[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_periodic[PID_MAGNITUDE],
+			 effect->u.periodic.magnitude);
+	pidff_set_signed(&pidff->set_periodic[PID_OFFSET],
+			 effect->u.periodic.offset);
+	pidff_set(&pidff->set_periodic[PID_PHASE], effect->u.periodic.phase);
+	pidff->set_periodic[PID_PERIOD].value[0] = effect->u.periodic.period;
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_PERIODIC],
+			HID_REQ_SET_REPORT);
+
+}
+
+/*
+ * Test if periodic effect parameters have changed
+ */
+static int pidff_needs_set_periodic(struct ff_effect *effect,
+				    struct ff_effect *old)
+{
+	return effect->u.periodic.magnitude != old->u.periodic.magnitude ||
+	       effect->u.periodic.offset != old->u.periodic.offset ||
+	       effect->u.periodic.phase != old->u.periodic.phase ||
+	       effect->u.periodic.period != old->u.periodic.period;
+}
+
+/*
+ * Send condition effect reports to the device
+ */
+static void pidff_set_condition_report(struct pidff_device *pidff,
+				       struct ff_effect *effect)
+{
+	int i;
+
+	pidff->set_condition[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	for (i = 0; i < 2; i++) {
+		pidff->set_condition[PID_PARAM_BLOCK_OFFSET].value[0] = i;
+		pidff_set_signed(&pidff->set_condition[PID_CP_OFFSET],
+				 effect->u.condition[i].center);
+		pidff_set_signed(&pidff->set_condition[PID_POS_COEFFICIENT],
+				 effect->u.condition[i].right_coeff);
+		pidff_set_signed(&pidff->set_condition[PID_NEG_COEFFICIENT],
+				 effect->u.condition[i].left_coeff);
+		pidff_set(&pidff->set_condition[PID_POS_SATURATION],
+			  effect->u.condition[i].right_saturation);
+		pidff_set(&pidff->set_condition[PID_NEG_SATURATION],
+			  effect->u.condition[i].left_saturation);
+		pidff_set(&pidff->set_condition[PID_DEAD_BAND],
+			  effect->u.condition[i].deadband);
+		hid_hw_request(pidff->hid, pidff->reports[PID_SET_CONDITION],
+				HID_REQ_SET_REPORT);
+	}
+}
+
+/*
+ * Test if condition effect parameters have changed
+ */
+static int pidff_needs_set_condition(struct ff_effect *effect,
+				     struct ff_effect *old)
+{
+	int i;
+	int ret = 0;
+
+	for (i = 0; i < 2; i++) {
+		struct ff_condition_effect *cond = &effect->u.condition[i];
+		struct ff_condition_effect *old_cond = &old->u.condition[i];
+
+		ret |= cond->center != old_cond->center ||
+		       cond->right_coeff != old_cond->right_coeff ||
+		       cond->left_coeff != old_cond->left_coeff ||
+		       cond->right_saturation != old_cond->right_saturation ||
+		       cond->left_saturation != old_cond->left_saturation ||
+		       cond->deadband != old_cond->deadband;
+	}
+
+	return ret;
+}
+
+/*
+ * Send ramp force report to the device
+ */
+static void pidff_set_ramp_force_report(struct pidff_device *pidff,
+					struct ff_effect *effect)
+{
+	pidff->set_ramp[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+	pidff_set_signed(&pidff->set_ramp[PID_RAMP_START],
+			 effect->u.ramp.start_level);
+	pidff_set_signed(&pidff->set_ramp[PID_RAMP_END],
+			 effect->u.ramp.end_level);
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_RAMP],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * Test if ramp force parameters have changed
+ */
+static int pidff_needs_set_ramp(struct ff_effect *effect, struct ff_effect *old)
+{
+	return effect->u.ramp.start_level != old->u.ramp.start_level ||
+	       effect->u.ramp.end_level != old->u.ramp.end_level;
+}
+
+/*
+ * Send a request for effect upload to the device
+ *
+ * Returns 0 if device reported success, -ENOSPC if the device reported memory
+ * is full. Upon unknown response the function will retry for 60 times, if
+ * still unsuccessful -EIO is returned.
+ */
+static int pidff_request_effect_upload(struct pidff_device *pidff, int efnum)
+{
+	int j;
+
+	pidff->create_new_effect_type->value[0] = efnum;
+	hid_hw_request(pidff->hid, pidff->reports[PID_CREATE_NEW_EFFECT],
+			HID_REQ_SET_REPORT);
+	hid_dbg(pidff->hid, "create_new_effect sent, type: %d\n", efnum);
+
+	pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+	pidff->block_load_status->value[0] = 0;
+	hid_hw_wait(pidff->hid);
+
+	for (j = 0; j < 60; j++) {
+		hid_dbg(pidff->hid, "pid_block_load requested\n");
+		hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_LOAD],
+				HID_REQ_GET_REPORT);
+		hid_hw_wait(pidff->hid);
+		if (pidff->block_load_status->value[0] ==
+		    pidff->status_id[PID_BLOCK_LOAD_SUCCESS]) {
+			hid_dbg(pidff->hid, "device reported free memory: %d bytes\n",
+				 pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
+				 pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+			return 0;
+		}
+		if (pidff->block_load_status->value[0] ==
+		    pidff->status_id[PID_BLOCK_LOAD_FULL]) {
+			hid_dbg(pidff->hid, "not enough memory free: %d bytes\n",
+				pidff->block_load[PID_RAM_POOL_AVAILABLE].value ?
+				pidff->block_load[PID_RAM_POOL_AVAILABLE].value[0] : -1);
+			return -ENOSPC;
+		}
+	}
+	hid_err(pidff->hid, "pid_block_load failed 60 times\n");
+	return -EIO;
+}
+
+/*
+ * Play the effect with PID id n times
+ */
+static void pidff_playback_pid(struct pidff_device *pidff, int pid_id, int n)
+{
+	pidff->effect_operation[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+
+	if (n == 0) {
+		pidff->effect_operation_status->value[0] =
+			pidff->operation_id[PID_EFFECT_STOP];
+	} else {
+		pidff->effect_operation_status->value[0] =
+			pidff->operation_id[PID_EFFECT_START];
+		pidff->effect_operation[PID_LOOP_COUNT].value[0] = n;
+	}
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_EFFECT_OPERATION],
+			HID_REQ_SET_REPORT);
+}
+
+/**
+ * Play the effect with effect id @effect_id for @value times
+ */
+static int pidff_playback(struct input_dev *dev, int effect_id, int value)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_playback_pid(pidff, pidff->pid_id[effect_id], value);
+
+	return 0;
+}
+
+/*
+ * Erase effect with PID id
+ */
+static void pidff_erase_pid(struct pidff_device *pidff, int pid_id)
+{
+	pidff->block_free[PID_EFFECT_BLOCK_INDEX].value[0] = pid_id;
+	hid_hw_request(pidff->hid, pidff->reports[PID_BLOCK_FREE],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * Stop and erase effect with effect_id
+ */
+static int pidff_erase_effect(struct input_dev *dev, int effect_id)
+{
+	struct pidff_device *pidff = dev->ff->private;
+	int pid_id = pidff->pid_id[effect_id];
+
+	hid_dbg(pidff->hid, "starting to erase %d/%d\n",
+		effect_id, pidff->pid_id[effect_id]);
+	/* Wait for the queue to clear. We do not want a full fifo to
+	   prevent the effect removal. */
+	hid_hw_wait(pidff->hid);
+	pidff_playback_pid(pidff, pid_id, 0);
+	pidff_erase_pid(pidff, pid_id);
+
+	return 0;
+}
+
+/*
+ * Effect upload handler
+ */
+static int pidff_upload_effect(struct input_dev *dev, struct ff_effect *effect,
+			       struct ff_effect *old)
+{
+	struct pidff_device *pidff = dev->ff->private;
+	int type_id;
+	int error;
+
+	pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] = 0;
+	if (old) {
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] =
+			pidff->pid_id[effect->id];
+	}
+
+	switch (effect->type) {
+	case FF_CONSTANT:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_CONSTANT]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_constant(effect, old))
+			pidff_set_constant_force_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.constant.envelope,
+					&old->u.constant.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.constant.envelope);
+		break;
+
+	case FF_PERIODIC:
+		if (!old) {
+			switch (effect->u.periodic.waveform) {
+			case FF_SQUARE:
+				type_id = PID_SQUARE;
+				break;
+			case FF_TRIANGLE:
+				type_id = PID_TRIANGLE;
+				break;
+			case FF_SINE:
+				type_id = PID_SINE;
+				break;
+			case FF_SAW_UP:
+				type_id = PID_SAW_UP;
+				break;
+			case FF_SAW_DOWN:
+				type_id = PID_SAW_DOWN;
+				break;
+			default:
+				hid_err(pidff->hid, "invalid waveform\n");
+				return -EINVAL;
+			}
+
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[type_id]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_periodic(effect, old))
+			pidff_set_periodic_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.periodic.envelope,
+					&old->u.periodic.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.periodic.envelope);
+		break;
+
+	case FF_RAMP:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_RAMP]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_ramp(effect, old))
+			pidff_set_ramp_force_report(pidff, effect);
+		if (!old ||
+		    pidff_needs_set_envelope(&effect->u.ramp.envelope,
+					&old->u.ramp.envelope))
+			pidff_set_envelope_report(pidff,
+					&effect->u.ramp.envelope);
+		break;
+
+	case FF_SPRING:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_SPRING]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_FRICTION:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_FRICTION]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_DAMPER:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_DAMPER]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	case FF_INERTIA:
+		if (!old) {
+			error = pidff_request_effect_upload(pidff,
+					pidff->type_id[PID_INERTIA]);
+			if (error)
+				return error;
+		}
+		if (!old || pidff_needs_set_effect(effect, old))
+			pidff_set_effect_report(pidff, effect);
+		if (!old || pidff_needs_set_condition(effect, old))
+			pidff_set_condition_report(pidff, effect);
+		break;
+
+	default:
+		hid_err(pidff->hid, "invalid type\n");
+		return -EINVAL;
+	}
+
+	if (!old)
+		pidff->pid_id[effect->id] =
+		    pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0];
+
+	hid_dbg(pidff->hid, "uploaded\n");
+
+	return 0;
+}
+
+/*
+ * set_gain() handler
+ */
+static void pidff_set_gain(struct input_dev *dev, u16 gain)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], gain);
+	hid_hw_request(pidff->hid, pidff->reports[PID_DEVICE_GAIN],
+			HID_REQ_SET_REPORT);
+}
+
+static void pidff_autocenter(struct pidff_device *pidff, u16 magnitude)
+{
+	struct hid_field *field =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].field;
+
+	if (!magnitude) {
+		pidff_playback_pid(pidff, field->logical_minimum, 0);
+		return;
+	}
+
+	pidff_playback_pid(pidff, field->logical_minimum, 1);
+
+	pidff->set_effect[PID_EFFECT_BLOCK_INDEX].value[0] =
+		pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum;
+	pidff->set_effect_type->value[0] = pidff->type_id[PID_SPRING];
+	pidff->set_effect[PID_DURATION].value[0] = 0;
+	pidff->set_effect[PID_TRIGGER_BUTTON].value[0] = 0;
+	pidff->set_effect[PID_TRIGGER_REPEAT_INT].value[0] = 0;
+	pidff_set(&pidff->set_effect[PID_GAIN], magnitude);
+	pidff->set_effect[PID_DIRECTION_ENABLE].value[0] = 1;
+	pidff->set_effect[PID_START_DELAY].value[0] = 0;
+
+	hid_hw_request(pidff->hid, pidff->reports[PID_SET_EFFECT],
+			HID_REQ_SET_REPORT);
+}
+
+/*
+ * pidff_set_autocenter() handler
+ */
+static void pidff_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+	struct pidff_device *pidff = dev->ff->private;
+
+	pidff_autocenter(pidff, magnitude);
+}
+
+/*
+ * Find fields from a report and fill a pidff_usage
+ */
+static int pidff_find_fields(struct pidff_usage *usage, const u8 *table,
+			     struct hid_report *report, int count, int strict)
+{
+	int i, j, k, found;
+
+	for (k = 0; k < count; k++) {
+		found = 0;
+		for (i = 0; i < report->maxfield; i++) {
+			if (report->field[i]->maxusage !=
+			    report->field[i]->report_count) {
+				pr_debug("maxusage and report_count do not match, skipping\n");
+				continue;
+			}
+			for (j = 0; j < report->field[i]->maxusage; j++) {
+				if (report->field[i]->usage[j].hid ==
+				    (HID_UP_PID | table[k])) {
+					pr_debug("found %d at %d->%d\n",
+						 k, i, j);
+					usage[k].field = report->field[i];
+					usage[k].value =
+						&report->field[i]->value[j];
+					found = 1;
+					break;
+				}
+			}
+			if (found)
+				break;
+		}
+		if (!found && strict) {
+			pr_debug("failed to locate %d\n", k);
+			return -1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Return index into pidff_reports for the given usage
+ */
+static int pidff_check_usage(int usage)
+{
+	int i;
+
+	for (i = 0; i < sizeof(pidff_reports); i++)
+		if (usage == (HID_UP_PID | pidff_reports[i]))
+			return i;
+
+	return -1;
+}
+
+/*
+ * Find the reports and fill pidff->reports[]
+ * report_type specifies either OUTPUT or FEATURE reports
+ */
+static void pidff_find_reports(struct hid_device *hid, int report_type,
+			       struct pidff_device *pidff)
+{
+	struct hid_report *report;
+	int i, ret;
+
+	list_for_each_entry(report,
+			    &hid->report_enum[report_type].report_list, list) {
+		if (report->maxfield < 1)
+			continue;
+		ret = pidff_check_usage(report->field[0]->logical);
+		if (ret != -1) {
+			hid_dbg(hid, "found usage 0x%02x from field->logical\n",
+				pidff_reports[ret]);
+			pidff->reports[ret] = report;
+			continue;
+		}
+
+		/*
+		 * Sometimes logical collections are stacked to indicate
+		 * different usages for the report and the field, in which
+		 * case we want the usage of the parent. However, Linux HID
+		 * implementation hides this fact, so we have to dig it up
+		 * ourselves
+		 */
+		i = report->field[0]->usage[0].collection_index;
+		if (i <= 0 ||
+		    hid->collection[i - 1].type != HID_COLLECTION_LOGICAL)
+			continue;
+		ret = pidff_check_usage(hid->collection[i - 1].usage);
+		if (ret != -1 && !pidff->reports[ret]) {
+			hid_dbg(hid,
+				"found usage 0x%02x from collection array\n",
+				pidff_reports[ret]);
+			pidff->reports[ret] = report;
+		}
+	}
+}
+
+/*
+ * Test if the required reports have been found
+ */
+static int pidff_reports_ok(struct pidff_device *pidff)
+{
+	int i;
+
+	for (i = 0; i <= PID_REQUIRED_REPORTS; i++) {
+		if (!pidff->reports[i]) {
+			hid_dbg(pidff->hid, "%d missing\n", i);
+			return 0;
+		}
+	}
+
+	return 1;
+}
+
+/*
+ * Find a field with a specific usage within a report
+ */
+static struct hid_field *pidff_find_special_field(struct hid_report *report,
+						  int usage, int enforce_min)
+{
+	int i;
+
+	for (i = 0; i < report->maxfield; i++) {
+		if (report->field[i]->logical == (HID_UP_PID | usage) &&
+		    report->field[i]->report_count > 0) {
+			if (!enforce_min ||
+			    report->field[i]->logical_minimum == 1)
+				return report->field[i];
+			else {
+				pr_err("logical_minimum is not 1 as it should be\n");
+				return NULL;
+			}
+		}
+	}
+	return NULL;
+}
+
+/*
+ * Fill a pidff->*_id struct table
+ */
+static int pidff_find_special_keys(int *keys, struct hid_field *fld,
+				   const u8 *usagetable, int count)
+{
+
+	int i, j;
+	int found = 0;
+
+	for (i = 0; i < count; i++) {
+		for (j = 0; j < fld->maxusage; j++) {
+			if (fld->usage[j].hid == (HID_UP_PID | usagetable[i])) {
+				keys[i] = j + 1;
+				found++;
+				break;
+			}
+		}
+	}
+	return found;
+}
+
+#define PIDFF_FIND_SPECIAL_KEYS(keys, field, name) \
+	pidff_find_special_keys(pidff->keys, pidff->field, pidff_ ## name, \
+		sizeof(pidff_ ## name))
+
+/*
+ * Find and check the special fields
+ */
+static int pidff_find_special_fields(struct pidff_device *pidff)
+{
+	hid_dbg(pidff->hid, "finding special fields\n");
+
+	pidff->create_new_effect_type =
+		pidff_find_special_field(pidff->reports[PID_CREATE_NEW_EFFECT],
+					 0x25, 1);
+	pidff->set_effect_type =
+		pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+					 0x25, 1);
+	pidff->effect_direction =
+		pidff_find_special_field(pidff->reports[PID_SET_EFFECT],
+					 0x57, 0);
+	pidff->device_control =
+		pidff_find_special_field(pidff->reports[PID_DEVICE_CONTROL],
+					 0x96, 1);
+	pidff->block_load_status =
+		pidff_find_special_field(pidff->reports[PID_BLOCK_LOAD],
+					 0x8b, 1);
+	pidff->effect_operation_status =
+		pidff_find_special_field(pidff->reports[PID_EFFECT_OPERATION],
+					 0x78, 1);
+
+	hid_dbg(pidff->hid, "search done\n");
+
+	if (!pidff->create_new_effect_type || !pidff->set_effect_type) {
+		hid_err(pidff->hid, "effect lists not found\n");
+		return -1;
+	}
+
+	if (!pidff->effect_direction) {
+		hid_err(pidff->hid, "direction field not found\n");
+		return -1;
+	}
+
+	if (!pidff->device_control) {
+		hid_err(pidff->hid, "device control field not found\n");
+		return -1;
+	}
+
+	if (!pidff->block_load_status) {
+		hid_err(pidff->hid, "block load status field not found\n");
+		return -1;
+	}
+
+	if (!pidff->effect_operation_status) {
+		hid_err(pidff->hid, "effect operation field not found\n");
+		return -1;
+	}
+
+	pidff_find_special_keys(pidff->control_id, pidff->device_control,
+				pidff_device_control,
+				sizeof(pidff_device_control));
+
+	PIDFF_FIND_SPECIAL_KEYS(control_id, device_control, device_control);
+
+	if (!PIDFF_FIND_SPECIAL_KEYS(type_id, create_new_effect_type,
+				     effect_types)) {
+		hid_err(pidff->hid, "no effect types found\n");
+		return -1;
+	}
+
+	if (PIDFF_FIND_SPECIAL_KEYS(status_id, block_load_status,
+				    block_load_status) !=
+			sizeof(pidff_block_load_status)) {
+		hid_err(pidff->hid,
+			"block load status identifiers not found\n");
+		return -1;
+	}
+
+	if (PIDFF_FIND_SPECIAL_KEYS(operation_id, effect_operation_status,
+				    effect_operation_status) !=
+			sizeof(pidff_effect_operation_status)) {
+		hid_err(pidff->hid, "effect operation identifiers not found\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/**
+ * Find the implemented effect types
+ */
+static int pidff_find_effects(struct pidff_device *pidff,
+			      struct input_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < sizeof(pidff_effect_types); i++) {
+		int pidff_type = pidff->type_id[i];
+		if (pidff->set_effect_type->usage[pidff_type].hid !=
+		    pidff->create_new_effect_type->usage[pidff_type].hid) {
+			hid_err(pidff->hid,
+				"effect type number %d is invalid\n", i);
+			return -1;
+		}
+	}
+
+	if (pidff->type_id[PID_CONSTANT])
+		set_bit(FF_CONSTANT, dev->ffbit);
+	if (pidff->type_id[PID_RAMP])
+		set_bit(FF_RAMP, dev->ffbit);
+	if (pidff->type_id[PID_SQUARE]) {
+		set_bit(FF_SQUARE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SINE]) {
+		set_bit(FF_SINE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_TRIANGLE]) {
+		set_bit(FF_TRIANGLE, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SAW_UP]) {
+		set_bit(FF_SAW_UP, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SAW_DOWN]) {
+		set_bit(FF_SAW_DOWN, dev->ffbit);
+		set_bit(FF_PERIODIC, dev->ffbit);
+	}
+	if (pidff->type_id[PID_SPRING])
+		set_bit(FF_SPRING, dev->ffbit);
+	if (pidff->type_id[PID_DAMPER])
+		set_bit(FF_DAMPER, dev->ffbit);
+	if (pidff->type_id[PID_INERTIA])
+		set_bit(FF_INERTIA, dev->ffbit);
+	if (pidff->type_id[PID_FRICTION])
+		set_bit(FF_FRICTION, dev->ffbit);
+
+	return 0;
+
+}
+
+#define PIDFF_FIND_FIELDS(name, report, strict) \
+	pidff_find_fields(pidff->name, pidff_ ## name, \
+		pidff->reports[report], \
+		sizeof(pidff_ ## name), strict)
+
+/*
+ * Fill and check the pidff_usages
+ */
+static int pidff_init_fields(struct pidff_device *pidff, struct input_dev *dev)
+{
+	int envelope_ok = 0;
+
+	if (PIDFF_FIND_FIELDS(set_effect, PID_SET_EFFECT, 1)) {
+		hid_err(pidff->hid, "unknown set_effect report layout\n");
+		return -ENODEV;
+	}
+
+	PIDFF_FIND_FIELDS(block_load, PID_BLOCK_LOAD, 0);
+	if (!pidff->block_load[PID_EFFECT_BLOCK_INDEX].value) {
+		hid_err(pidff->hid, "unknown pid_block_load report layout\n");
+		return -ENODEV;
+	}
+
+	if (PIDFF_FIND_FIELDS(effect_operation, PID_EFFECT_OPERATION, 1)) {
+		hid_err(pidff->hid, "unknown effect_operation report layout\n");
+		return -ENODEV;
+	}
+
+	if (PIDFF_FIND_FIELDS(block_free, PID_BLOCK_FREE, 1)) {
+		hid_err(pidff->hid, "unknown pid_block_free report layout\n");
+		return -ENODEV;
+	}
+
+	if (!PIDFF_FIND_FIELDS(set_envelope, PID_SET_ENVELOPE, 1))
+		envelope_ok = 1;
+
+	if (pidff_find_special_fields(pidff) || pidff_find_effects(pidff, dev))
+		return -ENODEV;
+
+	if (!envelope_ok) {
+		if (test_and_clear_bit(FF_CONSTANT, dev->ffbit))
+			hid_warn(pidff->hid,
+				 "has constant effect but no envelope\n");
+		if (test_and_clear_bit(FF_RAMP, dev->ffbit))
+			hid_warn(pidff->hid,
+				 "has ramp effect but no envelope\n");
+
+		if (test_and_clear_bit(FF_PERIODIC, dev->ffbit))
+			hid_warn(pidff->hid,
+				 "has periodic effect but no envelope\n");
+	}
+
+	if (test_bit(FF_CONSTANT, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_constant, PID_SET_CONSTANT, 1)) {
+		hid_warn(pidff->hid, "unknown constant effect layout\n");
+		clear_bit(FF_CONSTANT, dev->ffbit);
+	}
+
+	if (test_bit(FF_RAMP, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_ramp, PID_SET_RAMP, 1)) {
+		hid_warn(pidff->hid, "unknown ramp effect layout\n");
+		clear_bit(FF_RAMP, dev->ffbit);
+	}
+
+	if ((test_bit(FF_SPRING, dev->ffbit) ||
+	     test_bit(FF_DAMPER, dev->ffbit) ||
+	     test_bit(FF_FRICTION, dev->ffbit) ||
+	     test_bit(FF_INERTIA, dev->ffbit)) &&
+	    PIDFF_FIND_FIELDS(set_condition, PID_SET_CONDITION, 1)) {
+		hid_warn(pidff->hid, "unknown condition effect layout\n");
+		clear_bit(FF_SPRING, dev->ffbit);
+		clear_bit(FF_DAMPER, dev->ffbit);
+		clear_bit(FF_FRICTION, dev->ffbit);
+		clear_bit(FF_INERTIA, dev->ffbit);
+	}
+
+	if (test_bit(FF_PERIODIC, dev->ffbit) &&
+	    PIDFF_FIND_FIELDS(set_periodic, PID_SET_PERIODIC, 1)) {
+		hid_warn(pidff->hid, "unknown periodic effect layout\n");
+		clear_bit(FF_PERIODIC, dev->ffbit);
+	}
+
+	PIDFF_FIND_FIELDS(pool, PID_POOL, 0);
+
+	if (!PIDFF_FIND_FIELDS(device_gain, PID_DEVICE_GAIN, 1))
+		set_bit(FF_GAIN, dev->ffbit);
+
+	return 0;
+}
+
+/*
+ * Reset the device
+ */
+static void pidff_reset(struct pidff_device *pidff)
+{
+	struct hid_device *hid = pidff->hid;
+	int i = 0;
+
+	pidff->device_control->value[0] = pidff->control_id[PID_RESET];
+	/* We reset twice as sometimes hid_wait_io isn't waiting long enough */
+	hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
+	hid_hw_wait(hid);
+	hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
+	hid_hw_wait(hid);
+
+	pidff->device_control->value[0] =
+		pidff->control_id[PID_ENABLE_ACTUATORS];
+	hid_hw_request(hid, pidff->reports[PID_DEVICE_CONTROL], HID_REQ_SET_REPORT);
+	hid_hw_wait(hid);
+
+	/* pool report is sometimes messed up, refetch it */
+	hid_hw_request(hid, pidff->reports[PID_POOL], HID_REQ_GET_REPORT);
+	hid_hw_wait(hid);
+
+	if (pidff->pool[PID_SIMULTANEOUS_MAX].value) {
+		while (pidff->pool[PID_SIMULTANEOUS_MAX].value[0] < 2) {
+			if (i++ > 20) {
+				hid_warn(pidff->hid,
+					 "device reports %d simultaneous effects\n",
+					 pidff->pool[PID_SIMULTANEOUS_MAX].value[0]);
+				break;
+			}
+			hid_dbg(pidff->hid, "pid_pool requested again\n");
+			hid_hw_request(hid, pidff->reports[PID_POOL],
+					  HID_REQ_GET_REPORT);
+			hid_hw_wait(hid);
+		}
+	}
+}
+
+/*
+ * Test if autocenter modification is using the supported method
+ */
+static int pidff_check_autocenter(struct pidff_device *pidff,
+				  struct input_dev *dev)
+{
+	int error;
+
+	/*
+	 * Let's find out if autocenter modification is supported
+	 * Specification doesn't specify anything, so we request an
+	 * effect upload and cancel it immediately. If the approved
+	 * effect id was one above the minimum, then we assume the first
+	 * effect id is a built-in spring type effect used for autocenter
+	 */
+
+	error = pidff_request_effect_upload(pidff, 1);
+	if (error) {
+		hid_err(pidff->hid, "upload request failed\n");
+		return error;
+	}
+
+	if (pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0] ==
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum + 1) {
+		pidff_autocenter(pidff, 0xffff);
+		set_bit(FF_AUTOCENTER, dev->ffbit);
+	} else {
+		hid_notice(pidff->hid,
+			   "device has unknown autocenter control method\n");
+	}
+
+	pidff_erase_pid(pidff,
+			pidff->block_load[PID_EFFECT_BLOCK_INDEX].value[0]);
+
+	return 0;
+
+}
+
+/*
+ * Check if the device is PID and initialize it
+ */
+int hid_pidff_init(struct hid_device *hid)
+{
+	struct pidff_device *pidff;
+	struct hid_input *hidinput = list_entry(hid->inputs.next,
+						struct hid_input, list);
+	struct input_dev *dev = hidinput->input;
+	struct ff_device *ff;
+	int max_effects;
+	int error;
+
+	hid_dbg(hid, "starting pid init\n");
+
+	if (list_empty(&hid->report_enum[HID_OUTPUT_REPORT].report_list)) {
+		hid_dbg(hid, "not a PID device, no output report\n");
+		return -ENODEV;
+	}
+
+	pidff = kzalloc(sizeof(*pidff), GFP_KERNEL);
+	if (!pidff)
+		return -ENOMEM;
+
+	pidff->hid = hid;
+
+	hid_device_io_start(hid);
+
+	pidff_find_reports(hid, HID_OUTPUT_REPORT, pidff);
+	pidff_find_reports(hid, HID_FEATURE_REPORT, pidff);
+
+	if (!pidff_reports_ok(pidff)) {
+		hid_dbg(hid, "reports not ok, aborting\n");
+		error = -ENODEV;
+		goto fail;
+	}
+
+	error = pidff_init_fields(pidff, dev);
+	if (error)
+		goto fail;
+
+	pidff_reset(pidff);
+
+	if (test_bit(FF_GAIN, dev->ffbit)) {
+		pidff_set(&pidff->device_gain[PID_DEVICE_GAIN_FIELD], 0xffff);
+		hid_hw_request(hid, pidff->reports[PID_DEVICE_GAIN],
+				     HID_REQ_SET_REPORT);
+	}
+
+	error = pidff_check_autocenter(pidff, dev);
+	if (error)
+		goto fail;
+
+	max_effects =
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_maximum -
+	    pidff->block_load[PID_EFFECT_BLOCK_INDEX].field->logical_minimum +
+	    1;
+	hid_dbg(hid, "max effects is %d\n", max_effects);
+
+	if (max_effects > PID_EFFECTS_MAX)
+		max_effects = PID_EFFECTS_MAX;
+
+	if (pidff->pool[PID_SIMULTANEOUS_MAX].value)
+		hid_dbg(hid, "max simultaneous effects is %d\n",
+			pidff->pool[PID_SIMULTANEOUS_MAX].value[0]);
+
+	if (pidff->pool[PID_RAM_POOL_SIZE].value)
+		hid_dbg(hid, "device memory size is %d bytes\n",
+			pidff->pool[PID_RAM_POOL_SIZE].value[0]);
+
+	if (pidff->pool[PID_DEVICE_MANAGED_POOL].value &&
+	    pidff->pool[PID_DEVICE_MANAGED_POOL].value[0] == 0) {
+		hid_notice(hid,
+			   "device does not support device managed pool\n");
+		goto fail;
+	}
+
+	error = input_ff_create(dev, max_effects);
+	if (error)
+		goto fail;
+
+	ff = dev->ff;
+	ff->private = pidff;
+	ff->upload = pidff_upload_effect;
+	ff->erase = pidff_erase_effect;
+	ff->set_gain = pidff_set_gain;
+	ff->set_autocenter = pidff_set_autocenter;
+	ff->playback = pidff_playback;
+
+	hid_info(dev, "Force feedback for USB HID PID devices by Anssi Hannula <anssi.hannula@gmail.com>\n");
+
+	hid_device_io_stop(hid);
+
+	return 0;
+
+ fail:
+	hid_device_io_stop(hid);
+
+	kfree(pidff);
+	return error;
+}
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
new file mode 100644
index 0000000..a746017
--- /dev/null
+++ b/drivers/hid/usbhid/hiddev.c
@@ -0,0 +1,965 @@
+/*
+ *  Copyright (c) 2001 Paul Stewart
+ *  Copyright (c) 2001 Vojtech Pavlik
+ *
+ *  HID char devices, giving access to raw HID device events.
+ *
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to Paul Stewart <stewart@wetlogic.net>
+ */
+
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/hiddev.h>
+#include <linux/compat.h>
+#include <linux/vmalloc.h>
+#include <linux/nospec.h>
+#include "usbhid.h"
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define HIDDEV_MINOR_BASE	0
+#define HIDDEV_MINORS		256
+#else
+#define HIDDEV_MINOR_BASE	96
+#define HIDDEV_MINORS		16
+#endif
+#define HIDDEV_BUFFER_SIZE	2048
+
+struct hiddev_list {
+	struct hiddev_usage_ref buffer[HIDDEV_BUFFER_SIZE];
+	int head;
+	int tail;
+	unsigned flags;
+	struct fasync_struct *fasync;
+	struct hiddev *hiddev;
+	struct list_head node;
+	struct mutex thread_lock;
+};
+
+/*
+ * Find a report, given the report's type and ID.  The ID can be specified
+ * indirectly by REPORT_ID_FIRST (which returns the first report of the given
+ * type) or by (REPORT_ID_NEXT | old_id), which returns the next report of the
+ * given type which follows old_id.
+ */
+static struct hid_report *
+hiddev_lookup_report(struct hid_device *hid, struct hiddev_report_info *rinfo)
+{
+	unsigned int flags = rinfo->report_id & ~HID_REPORT_ID_MASK;
+	unsigned int rid = rinfo->report_id & HID_REPORT_ID_MASK;
+	struct hid_report_enum *report_enum;
+	struct hid_report *report;
+	struct list_head *list;
+
+	if (rinfo->report_type < HID_REPORT_TYPE_MIN ||
+	    rinfo->report_type > HID_REPORT_TYPE_MAX)
+		return NULL;
+
+	report_enum = hid->report_enum +
+		(rinfo->report_type - HID_REPORT_TYPE_MIN);
+
+	switch (flags) {
+	case 0: /* Nothing to do -- report_id is already set correctly */
+		break;
+
+	case HID_REPORT_ID_FIRST:
+		if (list_empty(&report_enum->report_list))
+			return NULL;
+
+		list = report_enum->report_list.next;
+		report = list_entry(list, struct hid_report, list);
+		rinfo->report_id = report->id;
+		break;
+
+	case HID_REPORT_ID_NEXT:
+		report = report_enum->report_id_hash[rid];
+		if (!report)
+			return NULL;
+
+		list = report->list.next;
+		if (list == &report_enum->report_list)
+			return NULL;
+
+		report = list_entry(list, struct hid_report, list);
+		rinfo->report_id = report->id;
+		break;
+
+	default:
+		return NULL;
+	}
+
+	return report_enum->report_id_hash[rinfo->report_id];
+}
+
+/*
+ * Perform an exhaustive search of the report table for a usage, given its
+ * type and usage id.
+ */
+static struct hid_field *
+hiddev_lookup_usage(struct hid_device *hid, struct hiddev_usage_ref *uref)
+{
+	int i, j;
+	struct hid_report *report;
+	struct hid_report_enum *report_enum;
+	struct hid_field *field;
+
+	if (uref->report_type < HID_REPORT_TYPE_MIN ||
+	    uref->report_type > HID_REPORT_TYPE_MAX)
+		return NULL;
+
+	report_enum = hid->report_enum +
+		(uref->report_type - HID_REPORT_TYPE_MIN);
+
+	list_for_each_entry(report, &report_enum->report_list, list) {
+		for (i = 0; i < report->maxfield; i++) {
+			field = report->field[i];
+			for (j = 0; j < field->maxusage; j++) {
+				if (field->usage[j].hid == uref->usage_code) {
+					uref->report_id = report->id;
+					uref->field_index = i;
+					uref->usage_index = j;
+					return field;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static void hiddev_send_event(struct hid_device *hid,
+			      struct hiddev_usage_ref *uref)
+{
+	struct hiddev *hiddev = hid->hiddev;
+	struct hiddev_list *list;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hiddev->list_lock, flags);
+	list_for_each_entry(list, &hiddev->list, node) {
+		if (uref->field_index != HID_FIELD_INDEX_NONE ||
+		    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+			list->buffer[list->head] = *uref;
+			list->head = (list->head + 1) &
+				(HIDDEV_BUFFER_SIZE - 1);
+			kill_fasync(&list->fasync, SIGIO, POLL_IN);
+		}
+	}
+	spin_unlock_irqrestore(&hiddev->list_lock, flags);
+
+	wake_up_interruptible(&hiddev->wait);
+}
+
+/*
+ * This is where hid.c calls into hiddev to pass an event that occurred over
+ * the interrupt pipe
+ */
+void hiddev_hid_event(struct hid_device *hid, struct hid_field *field,
+		      struct hid_usage *usage, __s32 value)
+{
+	unsigned type = field->report_type;
+	struct hiddev_usage_ref uref;
+
+	uref.report_type =
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
+	uref.report_id = field->report->id;
+	uref.field_index = field->index;
+	uref.usage_index = (usage - field->usage);
+	uref.usage_code = usage->hid;
+	uref.value = value;
+
+	hiddev_send_event(hid, &uref);
+}
+EXPORT_SYMBOL_GPL(hiddev_hid_event);
+
+void hiddev_report_event(struct hid_device *hid, struct hid_report *report)
+{
+	unsigned type = report->type;
+	struct hiddev_usage_ref uref;
+
+	memset(&uref, 0, sizeof(uref));
+	uref.report_type =
+	  (type == HID_INPUT_REPORT) ? HID_REPORT_TYPE_INPUT :
+	  ((type == HID_OUTPUT_REPORT) ? HID_REPORT_TYPE_OUTPUT :
+	   ((type == HID_FEATURE_REPORT) ? HID_REPORT_TYPE_FEATURE : 0));
+	uref.report_id = report->id;
+	uref.field_index = HID_FIELD_INDEX_NONE;
+
+	hiddev_send_event(hid, &uref);
+}
+
+/*
+ * fasync file op
+ */
+static int hiddev_fasync(int fd, struct file *file, int on)
+{
+	struct hiddev_list *list = file->private_data;
+
+	return fasync_helper(fd, file, on, &list->fasync);
+}
+
+
+/*
+ * release file op
+ */
+static int hiddev_release(struct inode * inode, struct file * file)
+{
+	struct hiddev_list *list = file->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&list->hiddev->list_lock, flags);
+	list_del(&list->node);
+	spin_unlock_irqrestore(&list->hiddev->list_lock, flags);
+
+	mutex_lock(&list->hiddev->existancelock);
+	if (!--list->hiddev->open) {
+		if (list->hiddev->exist) {
+			hid_hw_close(list->hiddev->hid);
+			hid_hw_power(list->hiddev->hid, PM_HINT_NORMAL);
+		} else {
+			mutex_unlock(&list->hiddev->existancelock);
+			kfree(list->hiddev);
+			vfree(list);
+			return 0;
+		}
+	}
+
+	mutex_unlock(&list->hiddev->existancelock);
+	vfree(list);
+
+	return 0;
+}
+
+/*
+ * open file op
+ */
+static int hiddev_open(struct inode *inode, struct file *file)
+{
+	struct hiddev_list *list;
+	struct usb_interface *intf;
+	struct hid_device *hid;
+	struct hiddev *hiddev;
+	int res;
+
+	intf = usbhid_find_interface(iminor(inode));
+	if (!intf)
+		return -ENODEV;
+	hid = usb_get_intfdata(intf);
+	hiddev = hid->hiddev;
+
+	if (!(list = vzalloc(sizeof(struct hiddev_list))))
+		return -ENOMEM;
+	mutex_init(&list->thread_lock);
+	list->hiddev = hiddev;
+	file->private_data = list;
+
+	/*
+	 * no need for locking because the USB major number
+	 * is shared which usbcore guards against disconnect
+	 */
+	if (list->hiddev->exist) {
+		if (!list->hiddev->open++) {
+			res = hid_hw_open(hiddev->hid);
+			if (res < 0)
+				goto bail;
+		}
+	} else {
+		res = -ENODEV;
+		goto bail;
+	}
+
+	spin_lock_irq(&list->hiddev->list_lock);
+	list_add_tail(&list->node, &hiddev->list);
+	spin_unlock_irq(&list->hiddev->list_lock);
+
+	mutex_lock(&hiddev->existancelock);
+	if (!list->hiddev->open++)
+		if (list->hiddev->exist) {
+			struct hid_device *hid = hiddev->hid;
+			res = hid_hw_power(hid, PM_HINT_FULLON);
+			if (res < 0)
+				goto bail_unlock;
+			res = hid_hw_open(hid);
+			if (res < 0)
+				goto bail_normal_power;
+		}
+	mutex_unlock(&hiddev->existancelock);
+	return 0;
+bail_normal_power:
+	hid_hw_power(hid, PM_HINT_NORMAL);
+bail_unlock:
+	mutex_unlock(&hiddev->existancelock);
+bail:
+	file->private_data = NULL;
+	vfree(list);
+	return res;
+}
+
+/*
+ * "write" file op
+ */
+static ssize_t hiddev_write(struct file * file, const char __user * buffer, size_t count, loff_t *ppos)
+{
+	return -EINVAL;
+}
+
+/*
+ * "read" file op
+ */
+static ssize_t hiddev_read(struct file * file, char __user * buffer, size_t count, loff_t *ppos)
+{
+	DEFINE_WAIT(wait);
+	struct hiddev_list *list = file->private_data;
+	int event_size;
+	int retval;
+
+	event_size = ((list->flags & HIDDEV_FLAG_UREF) != 0) ?
+		sizeof(struct hiddev_usage_ref) : sizeof(struct hiddev_event);
+
+	if (count < event_size)
+		return 0;
+
+	/* lock against other threads */
+	retval = mutex_lock_interruptible(&list->thread_lock);
+	if (retval)
+		return -ERESTARTSYS;
+
+	while (retval == 0) {
+		if (list->head == list->tail) {
+			prepare_to_wait(&list->hiddev->wait, &wait, TASK_INTERRUPTIBLE);
+
+			while (list->head == list->tail) {
+				if (signal_pending(current)) {
+					retval = -ERESTARTSYS;
+					break;
+				}
+				if (!list->hiddev->exist) {
+					retval = -EIO;
+					break;
+				}
+				if (file->f_flags & O_NONBLOCK) {
+					retval = -EAGAIN;
+					break;
+				}
+
+				/* let O_NONBLOCK tasks run */
+				mutex_unlock(&list->thread_lock);
+				schedule();
+				if (mutex_lock_interruptible(&list->thread_lock)) {
+					finish_wait(&list->hiddev->wait, &wait);
+					return -EINTR;
+				}
+				set_current_state(TASK_INTERRUPTIBLE);
+			}
+			finish_wait(&list->hiddev->wait, &wait);
+
+		}
+
+		if (retval) {
+			mutex_unlock(&list->thread_lock);
+			return retval;
+		}
+
+
+		while (list->head != list->tail &&
+		       retval + event_size <= count) {
+			if ((list->flags & HIDDEV_FLAG_UREF) == 0) {
+				if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE) {
+					struct hiddev_event event;
+
+					event.hid = list->buffer[list->tail].usage_code;
+					event.value = list->buffer[list->tail].value;
+					if (copy_to_user(buffer + retval, &event, sizeof(struct hiddev_event))) {
+						mutex_unlock(&list->thread_lock);
+						return -EFAULT;
+					}
+					retval += sizeof(struct hiddev_event);
+				}
+			} else {
+				if (list->buffer[list->tail].field_index != HID_FIELD_INDEX_NONE ||
+				    (list->flags & HIDDEV_FLAG_REPORT) != 0) {
+
+					if (copy_to_user(buffer + retval, list->buffer + list->tail, sizeof(struct hiddev_usage_ref))) {
+						mutex_unlock(&list->thread_lock);
+						return -EFAULT;
+					}
+					retval += sizeof(struct hiddev_usage_ref);
+				}
+			}
+			list->tail = (list->tail + 1) & (HIDDEV_BUFFER_SIZE - 1);
+		}
+
+	}
+	mutex_unlock(&list->thread_lock);
+
+	return retval;
+}
+
+/*
+ * "poll" file op
+ * No kernel lock - fine
+ */
+static __poll_t hiddev_poll(struct file *file, poll_table *wait)
+{
+	struct hiddev_list *list = file->private_data;
+
+	poll_wait(file, &list->hiddev->wait, wait);
+	if (list->head != list->tail)
+		return EPOLLIN | EPOLLRDNORM;
+	if (!list->hiddev->exist)
+		return EPOLLERR | EPOLLHUP;
+	return 0;
+}
+
+/*
+ * "ioctl" file op
+ */
+static noinline int hiddev_ioctl_usage(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg)
+{
+	struct hid_device *hid = hiddev->hid;
+	struct hiddev_report_info rinfo;
+	struct hiddev_usage_ref_multi *uref_multi = NULL;
+	struct hiddev_usage_ref *uref;
+	struct hid_report *report;
+	struct hid_field *field;
+	int i;
+
+	uref_multi = kmalloc(sizeof(struct hiddev_usage_ref_multi), GFP_KERNEL);
+	if (!uref_multi)
+		return -ENOMEM;
+	uref = &uref_multi->uref;
+	if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
+		if (copy_from_user(uref_multi, user_arg,
+				   sizeof(*uref_multi)))
+			goto fault;
+	} else {
+		if (copy_from_user(uref, user_arg, sizeof(*uref)))
+			goto fault;
+	}
+
+	switch (cmd) {
+	case HIDIOCGUCODE:
+		rinfo.report_type = uref->report_type;
+		rinfo.report_id = uref->report_id;
+		if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+			goto inval;
+
+		if (uref->field_index >= report->maxfield)
+			goto inval;
+		uref->field_index = array_index_nospec(uref->field_index,
+						       report->maxfield);
+
+		field = report->field[uref->field_index];
+		if (uref->usage_index >= field->maxusage)
+			goto inval;
+		uref->usage_index = array_index_nospec(uref->usage_index,
+						       field->maxusage);
+
+		uref->usage_code = field->usage[uref->usage_index].hid;
+
+		if (copy_to_user(user_arg, uref, sizeof(*uref)))
+			goto fault;
+
+		goto goodreturn;
+
+	default:
+		if (cmd != HIDIOCGUSAGE &&
+		    cmd != HIDIOCGUSAGES &&
+		    uref->report_type == HID_REPORT_TYPE_INPUT)
+			goto inval;
+
+		if (uref->report_id == HID_REPORT_ID_UNKNOWN) {
+			field = hiddev_lookup_usage(hid, uref);
+			if (field == NULL)
+				goto inval;
+		} else {
+			rinfo.report_type = uref->report_type;
+			rinfo.report_id = uref->report_id;
+			if ((report = hiddev_lookup_report(hid, &rinfo)) == NULL)
+				goto inval;
+
+			if (uref->field_index >= report->maxfield)
+				goto inval;
+			uref->field_index = array_index_nospec(uref->field_index,
+							       report->maxfield);
+
+			field = report->field[uref->field_index];
+
+			if (cmd == HIDIOCGCOLLECTIONINDEX) {
+				if (uref->usage_index >= field->maxusage)
+					goto inval;
+				uref->usage_index =
+					array_index_nospec(uref->usage_index,
+							   field->maxusage);
+			} else if (uref->usage_index >= field->report_count)
+				goto inval;
+		}
+
+		if (cmd == HIDIOCGUSAGES || cmd == HIDIOCSUSAGES) {
+			if (uref_multi->num_values > HID_MAX_MULTI_USAGES ||
+			    uref->usage_index + uref_multi->num_values >
+			    field->report_count)
+				goto inval;
+
+			uref->usage_index =
+				array_index_nospec(uref->usage_index,
+						   field->report_count -
+						   uref_multi->num_values);
+		}
+
+		switch (cmd) {
+		case HIDIOCGUSAGE:
+			uref->value = field->value[uref->usage_index];
+			if (copy_to_user(user_arg, uref, sizeof(*uref)))
+				goto fault;
+			goto goodreturn;
+
+		case HIDIOCSUSAGE:
+			field->value[uref->usage_index] = uref->value;
+			goto goodreturn;
+
+		case HIDIOCGCOLLECTIONINDEX:
+			i = field->usage[uref->usage_index].collection_index;
+			kfree(uref_multi);
+			return i;
+		case HIDIOCGUSAGES:
+			for (i = 0; i < uref_multi->num_values; i++)
+				uref_multi->values[i] =
+				    field->value[uref->usage_index + i];
+			if (copy_to_user(user_arg, uref_multi,
+					 sizeof(*uref_multi)))
+				goto fault;
+			goto goodreturn;
+		case HIDIOCSUSAGES:
+			for (i = 0; i < uref_multi->num_values; i++)
+				field->value[uref->usage_index + i] =
+				    uref_multi->values[i];
+			goto goodreturn;
+		}
+
+goodreturn:
+		kfree(uref_multi);
+		return 0;
+fault:
+		kfree(uref_multi);
+		return -EFAULT;
+inval:
+		kfree(uref_multi);
+		return -EINVAL;
+	}
+}
+
+static noinline int hiddev_ioctl_string(struct hiddev *hiddev, unsigned int cmd, void __user *user_arg)
+{
+	struct hid_device *hid = hiddev->hid;
+	struct usb_device *dev = hid_to_usb_dev(hid);
+	int idx, len;
+	char *buf;
+
+	if (get_user(idx, (int __user *)user_arg))
+		return -EFAULT;
+
+	if ((buf = kmalloc(HID_STRING_SIZE, GFP_KERNEL)) == NULL)
+		return -ENOMEM;
+
+	if ((len = usb_string(dev, idx, buf, HID_STRING_SIZE-1)) < 0) {
+		kfree(buf);
+		return -EINVAL;
+	}
+
+	if (copy_to_user(user_arg+sizeof(int), buf, len+1)) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	kfree(buf);
+
+	return len;
+}
+
+static long hiddev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct hiddev_list *list = file->private_data;
+	struct hiddev *hiddev = list->hiddev;
+	struct hid_device *hid;
+	struct hiddev_collection_info cinfo;
+	struct hiddev_report_info rinfo;
+	struct hiddev_field_info finfo;
+	struct hiddev_devinfo dinfo;
+	struct hid_report *report;
+	struct hid_field *field;
+	void __user *user_arg = (void __user *)arg;
+	int i, r = -EINVAL;
+
+	/* Called without BKL by compat methods so no BKL taken */
+
+	mutex_lock(&hiddev->existancelock);
+	if (!hiddev->exist) {
+		r = -ENODEV;
+		goto ret_unlock;
+	}
+
+	hid = hiddev->hid;
+
+	switch (cmd) {
+
+	case HIDIOCGVERSION:
+		r = put_user(HID_VERSION, (int __user *)arg) ?
+			-EFAULT : 0;
+		break;
+
+	case HIDIOCAPPLICATION:
+		if (arg >= hid->maxapplication)
+			break;
+
+		for (i = 0; i < hid->maxcollection; i++)
+			if (hid->collection[i].type ==
+			    HID_COLLECTION_APPLICATION && arg-- == 0)
+				break;
+
+		if (i < hid->maxcollection)
+			r = hid->collection[i].usage;
+		break;
+
+	case HIDIOCGDEVINFO:
+		{
+			struct usb_device *dev = hid_to_usb_dev(hid);
+			struct usbhid_device *usbhid = hid->driver_data;
+
+			memset(&dinfo, 0, sizeof(dinfo));
+
+			dinfo.bustype = BUS_USB;
+			dinfo.busnum = dev->bus->busnum;
+			dinfo.devnum = dev->devnum;
+			dinfo.ifnum = usbhid->ifnum;
+			dinfo.vendor = le16_to_cpu(dev->descriptor.idVendor);
+			dinfo.product = le16_to_cpu(dev->descriptor.idProduct);
+			dinfo.version = le16_to_cpu(dev->descriptor.bcdDevice);
+			dinfo.num_applications = hid->maxapplication;
+
+			r = copy_to_user(user_arg, &dinfo, sizeof(dinfo)) ?
+				-EFAULT : 0;
+			break;
+		}
+
+	case HIDIOCGFLAG:
+		r = put_user(list->flags, (int __user *)arg) ?
+			-EFAULT : 0;
+		break;
+
+	case HIDIOCSFLAG:
+		{
+			int newflags;
+
+			if (get_user(newflags, (int __user *)arg)) {
+				r = -EFAULT;
+				break;
+			}
+
+			if ((newflags & ~HIDDEV_FLAGS) != 0 ||
+			    ((newflags & HIDDEV_FLAG_REPORT) != 0 &&
+			     (newflags & HIDDEV_FLAG_UREF) == 0))
+				break;
+
+			list->flags = newflags;
+
+			r = 0;
+			break;
+		}
+
+	case HIDIOCGSTRING:
+		r = hiddev_ioctl_string(hiddev, cmd, user_arg);
+		break;
+
+	case HIDIOCINITREPORT:
+		usbhid_init_reports(hid);
+		hiddev->initialized = true;
+		r = 0;
+		break;
+
+	case HIDIOCGREPORT:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
+			r = -EFAULT;
+			break;
+		}
+
+		if (rinfo.report_type == HID_REPORT_TYPE_OUTPUT)
+			break;
+
+		report = hiddev_lookup_report(hid, &rinfo);
+		if (report == NULL)
+			break;
+
+		hid_hw_request(hid, report, HID_REQ_GET_REPORT);
+		hid_hw_wait(hid);
+
+		r = 0;
+		break;
+
+	case HIDIOCSREPORT:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
+			r = -EFAULT;
+			break;
+		}
+
+		if (rinfo.report_type == HID_REPORT_TYPE_INPUT)
+			break;
+
+		report = hiddev_lookup_report(hid, &rinfo);
+		if (report == NULL)
+			break;
+
+		hid_hw_request(hid, report, HID_REQ_SET_REPORT);
+		hid_hw_wait(hid);
+
+		r = 0;
+		break;
+
+	case HIDIOCGREPORTINFO:
+		if (copy_from_user(&rinfo, user_arg, sizeof(rinfo))) {
+			r = -EFAULT;
+			break;
+		}
+
+		report = hiddev_lookup_report(hid, &rinfo);
+		if (report == NULL)
+			break;
+
+		rinfo.num_fields = report->maxfield;
+
+		r = copy_to_user(user_arg, &rinfo, sizeof(rinfo)) ?
+			-EFAULT : 0;
+		break;
+
+	case HIDIOCGFIELDINFO:
+		if (copy_from_user(&finfo, user_arg, sizeof(finfo))) {
+			r = -EFAULT;
+			break;
+		}
+
+		rinfo.report_type = finfo.report_type;
+		rinfo.report_id = finfo.report_id;
+
+		report = hiddev_lookup_report(hid, &rinfo);
+		if (report == NULL)
+			break;
+
+		if (finfo.field_index >= report->maxfield)
+			break;
+		finfo.field_index = array_index_nospec(finfo.field_index,
+						       report->maxfield);
+
+		field = report->field[finfo.field_index];
+		memset(&finfo, 0, sizeof(finfo));
+		finfo.report_type = rinfo.report_type;
+		finfo.report_id = rinfo.report_id;
+		finfo.field_index = field->report_count - 1;
+		finfo.maxusage = field->maxusage;
+		finfo.flags = field->flags;
+		finfo.physical = field->physical;
+		finfo.logical = field->logical;
+		finfo.application = field->application;
+		finfo.logical_minimum = field->logical_minimum;
+		finfo.logical_maximum = field->logical_maximum;
+		finfo.physical_minimum = field->physical_minimum;
+		finfo.physical_maximum = field->physical_maximum;
+		finfo.unit_exponent = field->unit_exponent;
+		finfo.unit = field->unit;
+
+		r = copy_to_user(user_arg, &finfo, sizeof(finfo)) ?
+			-EFAULT : 0;
+		break;
+
+	case HIDIOCGUCODE:
+		/* fall through */
+	case HIDIOCGUSAGE:
+	case HIDIOCSUSAGE:
+	case HIDIOCGUSAGES:
+	case HIDIOCSUSAGES:
+	case HIDIOCGCOLLECTIONINDEX:
+		if (!hiddev->initialized) {
+			usbhid_init_reports(hid);
+			hiddev->initialized = true;
+		}
+		r = hiddev_ioctl_usage(hiddev, cmd, user_arg);
+		break;
+
+	case HIDIOCGCOLLECTIONINFO:
+		if (copy_from_user(&cinfo, user_arg, sizeof(cinfo))) {
+			r = -EFAULT;
+			break;
+		}
+
+		if (cinfo.index >= hid->maxcollection)
+			break;
+		cinfo.index = array_index_nospec(cinfo.index,
+						 hid->maxcollection);
+
+		cinfo.type = hid->collection[cinfo.index].type;
+		cinfo.usage = hid->collection[cinfo.index].usage;
+		cinfo.level = hid->collection[cinfo.index].level;
+
+		r = copy_to_user(user_arg, &cinfo, sizeof(cinfo)) ?
+			-EFAULT : 0;
+		break;
+
+	default:
+		if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ)
+			break;
+
+		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGNAME(0))) {
+			int len = strlen(hid->name) + 1;
+			if (len > _IOC_SIZE(cmd))
+				 len = _IOC_SIZE(cmd);
+			r = copy_to_user(user_arg, hid->name, len) ?
+				-EFAULT : len;
+			break;
+		}
+
+		if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGPHYS(0))) {
+			int len = strlen(hid->phys) + 1;
+			if (len > _IOC_SIZE(cmd))
+				len = _IOC_SIZE(cmd);
+			r = copy_to_user(user_arg, hid->phys, len) ?
+				-EFAULT : len;
+			break;
+		}
+	}
+
+ret_unlock:
+	mutex_unlock(&hiddev->existancelock);
+	return r;
+}
+
+#ifdef CONFIG_COMPAT
+static long hiddev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	return hiddev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations hiddev_fops = {
+	.owner =	THIS_MODULE,
+	.read =		hiddev_read,
+	.write =	hiddev_write,
+	.poll =		hiddev_poll,
+	.open =		hiddev_open,
+	.release =	hiddev_release,
+	.unlocked_ioctl =	hiddev_ioctl,
+	.fasync =	hiddev_fasync,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl	= hiddev_compat_ioctl,
+#endif
+	.llseek		= noop_llseek,
+};
+
+static char *hiddev_devnode(struct device *dev, umode_t *mode)
+{
+	return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
+}
+
+static struct usb_class_driver hiddev_class = {
+	.name =		"hiddev%d",
+	.devnode =	hiddev_devnode,
+	.fops =		&hiddev_fops,
+	.minor_base =	HIDDEV_MINOR_BASE,
+};
+
+/*
+ * This is where hid.c calls us to connect a hid device to the hiddev driver
+ */
+int hiddev_connect(struct hid_device *hid, unsigned int force)
+{
+	struct hiddev *hiddev;
+	struct usbhid_device *usbhid = hid->driver_data;
+	int retval;
+
+	if (!force) {
+		unsigned int i;
+		for (i = 0; i < hid->maxcollection; i++)
+			if (hid->collection[i].type ==
+			    HID_COLLECTION_APPLICATION &&
+			    !IS_INPUT_APPLICATION(hid->collection[i].usage))
+				break;
+
+		if (i == hid->maxcollection)
+			return -1;
+	}
+
+	if (!(hiddev = kzalloc(sizeof(struct hiddev), GFP_KERNEL)))
+		return -1;
+
+	init_waitqueue_head(&hiddev->wait);
+	INIT_LIST_HEAD(&hiddev->list);
+	spin_lock_init(&hiddev->list_lock);
+	mutex_init(&hiddev->existancelock);
+	hid->hiddev = hiddev;
+	hiddev->hid = hid;
+	hiddev->exist = 1;
+	retval = usb_register_dev(usbhid->intf, &hiddev_class);
+	if (retval) {
+		hid_err(hid, "Not able to get a minor for this device\n");
+		hid->hiddev = NULL;
+		kfree(hiddev);
+		return -1;
+	}
+
+	/*
+	 * If HID_QUIRK_NO_INIT_REPORTS is set, make sure we don't initialize
+	 * the reports.
+	 */
+	hiddev->initialized = hid->quirks & HID_QUIRK_NO_INIT_REPORTS;
+
+	hiddev->minor = usbhid->intf->minor;
+
+	return 0;
+}
+
+/*
+ * This is where hid.c calls us to disconnect a hiddev device from the
+ * corresponding hid device (usually because the usb device has disconnected)
+ */
+static struct usb_class_driver hiddev_class;
+void hiddev_disconnect(struct hid_device *hid)
+{
+	struct hiddev *hiddev = hid->hiddev;
+	struct usbhid_device *usbhid = hid->driver_data;
+
+	usb_deregister_dev(usbhid->intf, &hiddev_class);
+
+	mutex_lock(&hiddev->existancelock);
+	hiddev->exist = 0;
+
+	if (hiddev->open) {
+		mutex_unlock(&hiddev->existancelock);
+		hid_hw_close(hiddev->hid);
+		wake_up_interruptible(&hiddev->wait);
+	} else {
+		mutex_unlock(&hiddev->existancelock);
+		kfree(hiddev);
+	}
+}
diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h
new file mode 100644
index 0000000..da9c61d
--- /dev/null
+++ b/drivers/hid/usbhid/usbhid.h
@@ -0,0 +1,109 @@
+#ifndef __USBHID_H
+#define __USBHID_H
+
+/*
+ *  Copyright (c) 1999 Andreas Gal
+ *  Copyright (c) 2000-2001 Vojtech Pavlik
+ *  Copyright (c) 2006 Jiri Kosina
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/input.h>
+
+/*  API provided by hid-core.c for USB HID drivers */
+void usbhid_init_reports(struct hid_device *hid);
+struct usb_interface *usbhid_find_interface(int minor);
+
+/* iofl flags */
+#define HID_CTRL_RUNNING	1
+#define HID_OUT_RUNNING		2
+#define HID_IN_RUNNING		3
+#define HID_RESET_PENDING	4
+#define HID_SUSPENDED		5
+#define HID_CLEAR_HALT		6
+#define HID_DISCONNECTED	7
+#define HID_STARTED		8
+#define HID_KEYS_PRESSED	10
+#define HID_NO_BANDWIDTH	11
+#define HID_RESUME_RUNNING	12
+/*
+ * The device is opened, meaning there is a client that is interested
+ * in data coming from the device.
+ */
+#define HID_OPENED		13
+/*
+ * We are polling input endpoint by [re]submitting IN URB, because
+ * either HID device is opened or ALWAYS POLL quirk is set for the
+ * device.
+ */
+#define HID_IN_POLLING		14
+
+/*
+ * USB-specific HID struct, to be pointed to
+ * from struct hid_device->driver_data
+ */
+
+struct usbhid_device {
+	struct hid_device *hid;						/* pointer to corresponding HID dev */
+
+	struct usb_interface *intf;                                     /* USB interface */
+	int ifnum;                                                      /* USB interface number */
+
+	unsigned int bufsize;                                           /* URB buffer size */
+
+	struct urb *urbin;                                              /* Input URB */
+	char *inbuf;                                                    /* Input buffer */
+	dma_addr_t inbuf_dma;                                           /* Input buffer dma */
+
+	struct urb *urbctrl;                                            /* Control URB */
+	struct usb_ctrlrequest *cr;                                     /* Control request struct */
+	struct hid_control_fifo ctrl[HID_CONTROL_FIFO_SIZE];  		/* Control fifo */
+	unsigned char ctrlhead, ctrltail;                               /* Control fifo head & tail */
+	char *ctrlbuf;                                                  /* Control buffer */
+	dma_addr_t ctrlbuf_dma;                                         /* Control buffer dma */
+	unsigned long last_ctrl;						/* record of last output for timeouts */
+
+	struct urb *urbout;                                             /* Output URB */
+	struct hid_output_fifo out[HID_CONTROL_FIFO_SIZE];              /* Output pipe fifo */
+	unsigned char outhead, outtail;                                 /* Output pipe fifo head & tail */
+	char *outbuf;                                                   /* Output buffer */
+	dma_addr_t outbuf_dma;                                          /* Output buffer dma */
+	unsigned long last_out;							/* record of last output for timeouts */
+
+	spinlock_t lock;						/* fifo spinlock */
+	unsigned long iofl;                                             /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */
+	struct timer_list io_retry;                                     /* Retry timer */
+	unsigned long stop_retry;                                       /* Time to give up, in jiffies */
+	unsigned int retry_delay;                                       /* Delay length in ms */
+	struct work_struct reset_work;                                  /* Task context for resets */
+	wait_queue_head_t wait;						/* For sleeping */
+};
+
+#define	hid_to_usb_dev(hid_dev) \
+	to_usb_device(hid_dev->dev.parent->parent)
+
+#endif
+
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c
new file mode 100644
index 0000000..ed01dc4
--- /dev/null
+++ b/drivers/hid/usbhid/usbkbd.c
@@ -0,0 +1,410 @@
+/*
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ *
+ *  USB HIDBP Keyboard support
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb/input.h>
+#include <linux/hid.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION ""
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
+#define DRIVER_DESC "USB HID Boot Protocol keyboard driver"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static const unsigned char usb_kbd_keycode[256] = {
+	  0,  0,  0,  0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
+	 50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44,  2,  3,
+	  4,  5,  6,  7,  8,  9, 10, 11, 28,  1, 14, 15, 57, 12, 13, 26,
+	 27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
+	 65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
+	105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
+	 72, 73, 82, 83, 86,127,116,117,183,184,185,186,187,188,189,190,
+	191,192,193,194,134,138,130,132,128,129,131,137,133,135,136,113,
+	115,114,  0,  0,  0,121,  0, 89, 93,124, 92, 94, 95,  0,  0,  0,
+	122,123, 90, 91, 85,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+	 29, 42, 56,125, 97, 54,100,126,164,166,165,163,161,115,114,113,
+	150,158,159,128,136,177,178,176,142,152,173,140
+};
+
+
+/**
+ * struct usb_kbd - state of each attached keyboard
+ * @dev:	input device associated with this keyboard
+ * @usbdev:	usb device associated with this keyboard
+ * @old:	data received in the past from the @irq URB representing which
+ *		keys were pressed. By comparing with the current list of keys
+ *		that are pressed, we are able to see key releases.
+ * @irq:	URB for receiving a list of keys that are pressed when a
+ *		new key is pressed or a key that was pressed is released.
+ * @led:	URB for sending LEDs (e.g. numlock, ...)
+ * @newleds:	data that will be sent with the @led URB representing which LEDs
+ 		should be on
+ * @name:	Name of the keyboard. @dev's name field points to this buffer
+ * @phys:	Physical path of the keyboard. @dev's phys field points to this
+ *		buffer
+ * @new:	Buffer for the @irq URB
+ * @cr:		Control request for @led URB
+ * @leds:	Buffer for the @led URB
+ * @new_dma:	DMA address for @irq URB
+ * @leds_dma:	DMA address for @led URB
+ * @leds_lock:	spinlock that protects @leds, @newleds, and @led_urb_submitted
+ * @led_urb_submitted: indicates whether @led is in progress, i.e. it has been
+ *		submitted and its completion handler has not returned yet
+ *		without	resubmitting @led
+ */
+struct usb_kbd {
+	struct input_dev *dev;
+	struct usb_device *usbdev;
+	unsigned char old[8];
+	struct urb *irq, *led;
+	unsigned char newleds;
+	char name[128];
+	char phys[64];
+
+	unsigned char *new;
+	struct usb_ctrlrequest *cr;
+	unsigned char *leds;
+	dma_addr_t new_dma;
+	dma_addr_t leds_dma;
+	
+	spinlock_t leds_lock;
+	bool led_urb_submitted;
+
+};
+
+static void usb_kbd_irq(struct urb *urb)
+{
+	struct usb_kbd *kbd = urb->context;
+	int i;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		goto resubmit;
+	}
+
+	for (i = 0; i < 8; i++)
+		input_report_key(kbd->dev, usb_kbd_keycode[i + 224], (kbd->new[0] >> i) & 1);
+
+	for (i = 2; i < 8; i++) {
+
+		if (kbd->old[i] > 3 && memscan(kbd->new + 2, kbd->old[i], 6) == kbd->new + 8) {
+			if (usb_kbd_keycode[kbd->old[i]])
+				input_report_key(kbd->dev, usb_kbd_keycode[kbd->old[i]], 0);
+			else
+				hid_info(urb->dev,
+					 "Unknown key (scancode %#x) released.\n",
+					 kbd->old[i]);
+		}
+
+		if (kbd->new[i] > 3 && memscan(kbd->old + 2, kbd->new[i], 6) == kbd->old + 8) {
+			if (usb_kbd_keycode[kbd->new[i]])
+				input_report_key(kbd->dev, usb_kbd_keycode[kbd->new[i]], 1);
+			else
+				hid_info(urb->dev,
+					 "Unknown key (scancode %#x) pressed.\n",
+					 kbd->new[i]);
+		}
+	}
+
+	input_sync(kbd->dev);
+
+	memcpy(kbd->old, kbd->new, 8);
+
+resubmit:
+	i = usb_submit_urb (urb, GFP_ATOMIC);
+	if (i)
+		hid_err(urb->dev, "can't resubmit intr, %s-%s/input0, status %d",
+			kbd->usbdev->bus->bus_name,
+			kbd->usbdev->devpath, i);
+}
+
+static int usb_kbd_event(struct input_dev *dev, unsigned int type,
+			 unsigned int code, int value)
+{
+	unsigned long flags;
+	struct usb_kbd *kbd = input_get_drvdata(dev);
+
+	if (type != EV_LED)
+		return -1;
+
+	spin_lock_irqsave(&kbd->leds_lock, flags);
+	kbd->newleds = (!!test_bit(LED_KANA,    dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) |
+		       (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL,   dev->led) << 1) |
+		       (!!test_bit(LED_NUML,    dev->led));
+
+	if (kbd->led_urb_submitted){
+		spin_unlock_irqrestore(&kbd->leds_lock, flags);
+		return 0;
+	}
+
+	if (*(kbd->leds) == kbd->newleds){
+		spin_unlock_irqrestore(&kbd->leds_lock, flags);
+		return 0;
+	}
+
+	*(kbd->leds) = kbd->newleds;
+	
+	kbd->led->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->led, GFP_ATOMIC))
+		pr_err("usb_submit_urb(leds) failed\n");
+	else
+		kbd->led_urb_submitted = true;
+	
+	spin_unlock_irqrestore(&kbd->leds_lock, flags);
+	
+	return 0;
+}
+
+static void usb_kbd_led(struct urb *urb)
+{
+	unsigned long flags;
+	struct usb_kbd *kbd = urb->context;
+
+	if (urb->status)
+		hid_warn(urb->dev, "led urb status %d received\n",
+			 urb->status);
+
+	spin_lock_irqsave(&kbd->leds_lock, flags);
+
+	if (*(kbd->leds) == kbd->newleds){
+		kbd->led_urb_submitted = false;
+		spin_unlock_irqrestore(&kbd->leds_lock, flags);
+		return;
+	}
+
+	*(kbd->leds) = kbd->newleds;
+	
+	kbd->led->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->led, GFP_ATOMIC)){
+		hid_err(urb->dev, "usb_submit_urb(leds) failed\n");
+		kbd->led_urb_submitted = false;
+	}
+	spin_unlock_irqrestore(&kbd->leds_lock, flags);
+	
+}
+
+static int usb_kbd_open(struct input_dev *dev)
+{
+	struct usb_kbd *kbd = input_get_drvdata(dev);
+
+	kbd->irq->dev = kbd->usbdev;
+	if (usb_submit_urb(kbd->irq, GFP_KERNEL))
+		return -EIO;
+
+	return 0;
+}
+
+static void usb_kbd_close(struct input_dev *dev)
+{
+	struct usb_kbd *kbd = input_get_drvdata(dev);
+
+	usb_kill_urb(kbd->irq);
+}
+
+static int usb_kbd_alloc_mem(struct usb_device *dev, struct usb_kbd *kbd)
+{
+	if (!(kbd->irq = usb_alloc_urb(0, GFP_KERNEL)))
+		return -1;
+	if (!(kbd->led = usb_alloc_urb(0, GFP_KERNEL)))
+		return -1;
+	if (!(kbd->new = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &kbd->new_dma)))
+		return -1;
+	if (!(kbd->cr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL)))
+		return -1;
+	if (!(kbd->leds = usb_alloc_coherent(dev, 1, GFP_ATOMIC, &kbd->leds_dma)))
+		return -1;
+
+	return 0;
+}
+
+static void usb_kbd_free_mem(struct usb_device *dev, struct usb_kbd *kbd)
+{
+	usb_free_urb(kbd->irq);
+	usb_free_urb(kbd->led);
+	usb_free_coherent(dev, 8, kbd->new, kbd->new_dma);
+	kfree(kbd->cr);
+	usb_free_coherent(dev, 1, kbd->leds, kbd->leds_dma);
+}
+
+static int usb_kbd_probe(struct usb_interface *iface,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *dev = interface_to_usbdev(iface);
+	struct usb_host_interface *interface;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_kbd *kbd;
+	struct input_dev *input_dev;
+	int i, pipe, maxp;
+	int error = -ENOMEM;
+
+	interface = iface->cur_altsetting;
+
+	if (interface->desc.bNumEndpoints != 1)
+		return -ENODEV;
+
+	endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(endpoint))
+		return -ENODEV;
+
+	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+	kbd = kzalloc(sizeof(struct usb_kbd), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!kbd || !input_dev)
+		goto fail1;
+
+	if (usb_kbd_alloc_mem(dev, kbd))
+		goto fail2;
+
+	kbd->usbdev = dev;
+	kbd->dev = input_dev;
+	spin_lock_init(&kbd->leds_lock);
+
+	if (dev->manufacturer)
+		strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(kbd->name, " ", sizeof(kbd->name));
+		strlcat(kbd->name, dev->product, sizeof(kbd->name));
+	}
+
+	if (!strlen(kbd->name))
+		snprintf(kbd->name, sizeof(kbd->name),
+			 "USB HIDBP Keyboard %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	usb_make_path(dev, kbd->phys, sizeof(kbd->phys));
+	strlcat(kbd->phys, "/input0", sizeof(kbd->phys));
+
+	input_dev->name = kbd->name;
+	input_dev->phys = kbd->phys;
+	usb_to_input_id(dev, &input_dev->id);
+	input_dev->dev.parent = &iface->dev;
+
+	input_set_drvdata(input_dev, kbd);
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_LED) |
+		BIT_MASK(EV_REP);
+	input_dev->ledbit[0] = BIT_MASK(LED_NUML) | BIT_MASK(LED_CAPSL) |
+		BIT_MASK(LED_SCROLLL) | BIT_MASK(LED_COMPOSE) |
+		BIT_MASK(LED_KANA);
+
+	for (i = 0; i < 255; i++)
+		set_bit(usb_kbd_keycode[i], input_dev->keybit);
+	clear_bit(0, input_dev->keybit);
+
+	input_dev->event = usb_kbd_event;
+	input_dev->open = usb_kbd_open;
+	input_dev->close = usb_kbd_close;
+
+	usb_fill_int_urb(kbd->irq, dev, pipe,
+			 kbd->new, (maxp > 8 ? 8 : maxp),
+			 usb_kbd_irq, kbd, endpoint->bInterval);
+	kbd->irq->transfer_dma = kbd->new_dma;
+	kbd->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	kbd->cr->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+	kbd->cr->bRequest = 0x09;
+	kbd->cr->wValue = cpu_to_le16(0x200);
+	kbd->cr->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber);
+	kbd->cr->wLength = cpu_to_le16(1);
+
+	usb_fill_control_urb(kbd->led, dev, usb_sndctrlpipe(dev, 0),
+			     (void *) kbd->cr, kbd->leds, 1,
+			     usb_kbd_led, kbd);
+	kbd->led->transfer_dma = kbd->leds_dma;
+	kbd->led->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	error = input_register_device(kbd->dev);
+	if (error)
+		goto fail2;
+
+	usb_set_intfdata(iface, kbd);
+	device_set_wakeup_enable(&dev->dev, 1);
+	return 0;
+
+fail2:	
+	usb_kbd_free_mem(dev, kbd);
+fail1:	
+	input_free_device(input_dev);
+	kfree(kbd);
+	return error;
+}
+
+static void usb_kbd_disconnect(struct usb_interface *intf)
+{
+	struct usb_kbd *kbd = usb_get_intfdata (intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (kbd) {
+		usb_kill_urb(kbd->irq);
+		input_unregister_device(kbd->dev);
+		usb_kill_urb(kbd->led);
+		usb_kbd_free_mem(interface_to_usbdev(intf), kbd);
+		kfree(kbd);
+	}
+}
+
+static const struct usb_device_id usb_kbd_id_table[] = {
+	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
+		USB_INTERFACE_PROTOCOL_KEYBOARD) },
+	{ }						/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_kbd_id_table);
+
+static struct usb_driver usb_kbd_driver = {
+	.name =		"usbkbd",
+	.probe =	usb_kbd_probe,
+	.disconnect =	usb_kbd_disconnect,
+	.id_table =	usb_kbd_id_table,
+};
+
+module_usb_driver(usb_kbd_driver);
diff --git a/drivers/hid/usbhid/usbmouse.c b/drivers/hid/usbhid/usbmouse.c
new file mode 100644
index 0000000..589ad7c
--- /dev/null
+++ b/drivers/hid/usbhid/usbmouse.c
@@ -0,0 +1,244 @@
+/*
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ *
+ *  USB HIDBP Mouse support
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Should you need to contact me, the author, you can do so either by
+ * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail:
+ * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb/input.h>
+#include <linux/hid.h>
+
+/* for apple IDs */
+#ifdef CONFIG_USB_HID_MODULE
+#include "../hid-ids.h"
+#endif
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v1.6"
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
+#define DRIVER_DESC "USB HID Boot Protocol mouse driver"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+struct usb_mouse {
+	char name[128];
+	char phys[64];
+	struct usb_device *usbdev;
+	struct input_dev *dev;
+	struct urb *irq;
+
+	signed char *data;
+	dma_addr_t data_dma;
+};
+
+static void usb_mouse_irq(struct urb *urb)
+{
+	struct usb_mouse *mouse = urb->context;
+	signed char *data = mouse->data;
+	struct input_dev *dev = mouse->dev;
+	int status;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		goto resubmit;
+	}
+
+	input_report_key(dev, BTN_LEFT,   data[0] & 0x01);
+	input_report_key(dev, BTN_RIGHT,  data[0] & 0x02);
+	input_report_key(dev, BTN_MIDDLE, data[0] & 0x04);
+	input_report_key(dev, BTN_SIDE,   data[0] & 0x08);
+	input_report_key(dev, BTN_EXTRA,  data[0] & 0x10);
+
+	input_report_rel(dev, REL_X,     data[1]);
+	input_report_rel(dev, REL_Y,     data[2]);
+	input_report_rel(dev, REL_WHEEL, data[3]);
+
+	input_sync(dev);
+resubmit:
+	status = usb_submit_urb (urb, GFP_ATOMIC);
+	if (status)
+		dev_err(&mouse->usbdev->dev,
+			"can't resubmit intr, %s-%s/input0, status %d\n",
+			mouse->usbdev->bus->bus_name,
+			mouse->usbdev->devpath, status);
+}
+
+static int usb_mouse_open(struct input_dev *dev)
+{
+	struct usb_mouse *mouse = input_get_drvdata(dev);
+
+	mouse->irq->dev = mouse->usbdev;
+	if (usb_submit_urb(mouse->irq, GFP_KERNEL))
+		return -EIO;
+
+	return 0;
+}
+
+static void usb_mouse_close(struct input_dev *dev)
+{
+	struct usb_mouse *mouse = input_get_drvdata(dev);
+
+	usb_kill_urb(mouse->irq);
+}
+
+static int usb_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct usb_host_interface *interface;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_mouse *mouse;
+	struct input_dev *input_dev;
+	int pipe, maxp;
+	int error = -ENOMEM;
+
+	interface = intf->cur_altsetting;
+
+	if (interface->desc.bNumEndpoints != 1)
+		return -ENODEV;
+
+	endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(endpoint))
+		return -ENODEV;
+
+	pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
+	maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe));
+
+	mouse = kzalloc(sizeof(struct usb_mouse), GFP_KERNEL);
+	input_dev = input_allocate_device();
+	if (!mouse || !input_dev)
+		goto fail1;
+
+	mouse->data = usb_alloc_coherent(dev, 8, GFP_ATOMIC, &mouse->data_dma);
+	if (!mouse->data)
+		goto fail1;
+
+	mouse->irq = usb_alloc_urb(0, GFP_KERNEL);
+	if (!mouse->irq)
+		goto fail2;
+
+	mouse->usbdev = dev;
+	mouse->dev = input_dev;
+
+	if (dev->manufacturer)
+		strlcpy(mouse->name, dev->manufacturer, sizeof(mouse->name));
+
+	if (dev->product) {
+		if (dev->manufacturer)
+			strlcat(mouse->name, " ", sizeof(mouse->name));
+		strlcat(mouse->name, dev->product, sizeof(mouse->name));
+	}
+
+	if (!strlen(mouse->name))
+		snprintf(mouse->name, sizeof(mouse->name),
+			 "USB HIDBP Mouse %04x:%04x",
+			 le16_to_cpu(dev->descriptor.idVendor),
+			 le16_to_cpu(dev->descriptor.idProduct));
+
+	usb_make_path(dev, mouse->phys, sizeof(mouse->phys));
+	strlcat(mouse->phys, "/input0", sizeof(mouse->phys));
+
+	input_dev->name = mouse->name;
+	input_dev->phys = mouse->phys;
+	usb_to_input_id(dev, &input_dev->id);
+	input_dev->dev.parent = &intf->dev;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL);
+	input_dev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) |
+		BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE);
+	input_dev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);
+	input_dev->keybit[BIT_WORD(BTN_MOUSE)] |= BIT_MASK(BTN_SIDE) |
+		BIT_MASK(BTN_EXTRA);
+	input_dev->relbit[0] |= BIT_MASK(REL_WHEEL);
+
+	input_set_drvdata(input_dev, mouse);
+
+	input_dev->open = usb_mouse_open;
+	input_dev->close = usb_mouse_close;
+
+	usb_fill_int_urb(mouse->irq, dev, pipe, mouse->data,
+			 (maxp > 8 ? 8 : maxp),
+			 usb_mouse_irq, mouse, endpoint->bInterval);
+	mouse->irq->transfer_dma = mouse->data_dma;
+	mouse->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	error = input_register_device(mouse->dev);
+	if (error)
+		goto fail3;
+
+	usb_set_intfdata(intf, mouse);
+	return 0;
+
+fail3:	
+	usb_free_urb(mouse->irq);
+fail2:	
+	usb_free_coherent(dev, 8, mouse->data, mouse->data_dma);
+fail1:	
+	input_free_device(input_dev);
+	kfree(mouse);
+	return error;
+}
+
+static void usb_mouse_disconnect(struct usb_interface *intf)
+{
+	struct usb_mouse *mouse = usb_get_intfdata (intf);
+
+	usb_set_intfdata(intf, NULL);
+	if (mouse) {
+		usb_kill_urb(mouse->irq);
+		input_unregister_device(mouse->dev);
+		usb_free_urb(mouse->irq);
+		usb_free_coherent(interface_to_usbdev(intf), 8, mouse->data, mouse->data_dma);
+		kfree(mouse);
+	}
+}
+
+static const struct usb_device_id usb_mouse_id_table[] = {
+	{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
+		USB_INTERFACE_PROTOCOL_MOUSE) },
+	{ }	/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_mouse_id_table);
+
+static struct usb_driver usb_mouse_driver = {
+	.name		= "usbmouse",
+	.probe		= usb_mouse_probe,
+	.disconnect	= usb_mouse_disconnect,
+	.id_table	= usb_mouse_id_table,
+};
+
+module_usb_driver(usb_mouse_driver);
diff --git a/drivers/hid/wacom.h b/drivers/hid/wacom.h
new file mode 100644
index 0000000..3c37c3c
--- /dev/null
+++ b/drivers/hid/wacom.h
@@ -0,0 +1,230 @@
+/*
+ * drivers/input/tablet/wacom.h
+ *
+ *  USB Wacom tablet support
+ *
+ *  Copyright (c) 2000-2004 Vojtech Pavlik	<vojtech@ucw.cz>
+ *  Copyright (c) 2000 Andreas Bach Aaen	<abach@stofanet.dk>
+ *  Copyright (c) 2000 Clifford Wolf		<clifford@clifford.at>
+ *  Copyright (c) 2000 Sam Mosel		<sam.mosel@computer.org>
+ *  Copyright (c) 2000 James E. Blair		<corvus@gnu.org>
+ *  Copyright (c) 2000 Daniel Egger		<egger@suse.de>
+ *  Copyright (c) 2001 Frederic Lepied		<flepied@mandrakesoft.com>
+ *  Copyright (c) 2004 Panagiotis Issaris	<panagiotis.issaris@mech.kuleuven.ac.be>
+ *  Copyright (c) 2002-2011 Ping Cheng		<pingc@wacom.com>
+ *  Copyright (c) 2014 Benjamin Tissoires	<benjamin.tissoires@redhat.com>
+ *
+ *  ChangeLog:
+ *      v0.1 (vp)  - Initial release
+ *      v0.2 (aba) - Support for all buttons / combinations
+ *      v0.3 (vp)  - Support for Intuos added
+ *	v0.4 (sm)  - Support for more Intuos models, menustrip
+ *			relative mode, proximity.
+ *	v0.5 (vp)  - Big cleanup, nifty features removed,
+ *			they belong in userspace
+ *	v1.8 (vp)  - Submit URB only when operating, moved to CVS,
+ *			use input_report_key instead of report_btn and
+ *			other cleanups
+ *	v1.11 (vp) - Add URB ->dev setting for new kernels
+ *	v1.11 (jb) - Add support for the 4D Mouse & Lens
+ *	v1.12 (de) - Add support for two more inking pen IDs
+ *	v1.14 (vp) - Use new USB device id probing scheme.
+ *		     Fix Wacom Graphire mouse wheel
+ *	v1.18 (vp) - Fix mouse wheel direction
+ *		     Make mouse relative
+ *      v1.20 (fl) - Report tool id for Intuos devices
+ *                 - Multi tools support
+ *                 - Corrected Intuos protocol decoding (airbrush, 4D mouse, lens cursor...)
+ *                 - Add PL models support
+ *		   - Fix Wacom Graphire mouse wheel again
+ *	v1.21 (vp) - Removed protocol descriptions
+ *		   - Added MISC_SERIAL for tool serial numbers
+ *	      (gb) - Identify version on module load.
+ *    v1.21.1 (fl) - added Graphire2 support
+ *    v1.21.2 (fl) - added Intuos2 support
+ *                 - added all the PL ids
+ *    v1.21.3 (fl) - added another eraser id from Neil Okamoto
+ *                 - added smooth filter for Graphire from Peri Hankey
+ *                 - added PenPartner support from Olaf van Es
+ *                 - new tool ids from Ole Martin Bjoerndalen
+ *	v1.29 (pc) - Add support for more tablets
+ *		   - Fix pressure reporting
+ *	v1.30 (vp) - Merge 2.4 and 2.5 drivers
+ *		   - Since 2.5 now has input_sync(), remove MSC_SERIAL abuse
+ *		   - Cleanups here and there
+ *    v1.30.1 (pi) - Added Graphire3 support
+ *	v1.40 (pc) - Add support for several new devices, fix eraser reporting, ...
+ *	v1.43 (pc) - Added support for Cintiq 21UX
+ *		   - Fixed a Graphire bug
+ *		   - Merged wacom_intuos3_irq into wacom_intuos_irq
+ *	v1.44 (pc) - Added support for Graphire4, Cintiq 710, Intuos3 6x11, etc.
+ *		   - Report Device IDs
+ *      v1.45 (pc) - Added support for DTF 521, Intuos3 12x12 and 12x19
+ *                 - Minor data report fix
+ *      v1.46 (pc) - Split wacom.c into wacom_sys.c and wacom_wac.c,
+ *		   - where wacom_sys.c deals with system specific code,
+ *		   - and wacom_wac.c deals with Wacom specific code
+ *		   - Support Intuos3 4x6
+ *      v1.47 (pc) - Added support for Bamboo
+ *      v1.48 (pc) - Added support for Bamboo1, BambooFun, and Cintiq 12WX
+ *      v1.49 (pc) - Added support for USB Tablet PC (0x90, 0x93, and 0x9A)
+ *      v1.50 (pc) - Fixed a TabletPC touch bug in 2.6.28
+ *      v1.51 (pc) - Added support for Intuos4
+ *      v1.52 (pc) - Query Wacom data upon system resume
+ *                 - add defines for features->type
+ *                 - add new devices (0x9F, 0xE2, and 0XE3)
+ *      v2.00 (bt) - conversion to a HID driver
+ *                 - integration of the Bluetooth devices
+ */
+
+/*
+ * 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.
+ */
+#ifndef WACOM_H
+#define WACOM_H
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/hid.h>
+#include <linux/kfifo.h>
+#include <linux/leds.h>
+#include <linux/usb/input.h>
+#include <linux/power_supply.h>
+#include <asm/unaligned.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "v2.00"
+#define DRIVER_AUTHOR "Vojtech Pavlik <vojtech@ucw.cz>"
+#define DRIVER_DESC "USB Wacom tablet driver"
+
+#define USB_VENDOR_ID_WACOM	0x056a
+#define USB_VENDOR_ID_LENOVO	0x17ef
+
+enum wacom_worker {
+	WACOM_WORKER_WIRELESS,
+	WACOM_WORKER_BATTERY,
+	WACOM_WORKER_REMOTE,
+	WACOM_WORKER_MODE_CHANGE,
+};
+
+struct wacom;
+
+struct wacom_led {
+	struct led_classdev cdev;
+	struct led_trigger trigger;
+	struct wacom *wacom;
+	unsigned int group;
+	unsigned int id;
+	u8 llv;
+	u8 hlv;
+	bool held;
+};
+
+struct wacom_group_leds {
+	u8 select; /* status led selector (0..3) */
+	struct wacom_led *leds;
+	unsigned int count;
+	struct device *dev;
+};
+
+struct wacom_battery {
+	struct wacom *wacom;
+	struct power_supply_desc bat_desc;
+	struct power_supply *battery;
+	char bat_name[WACOM_NAME_MAX];
+	int bat_status;
+	int battery_capacity;
+	int bat_charging;
+	int bat_connected;
+	int ps_connected;
+};
+
+struct wacom_remote {
+	spinlock_t remote_lock;
+	struct kfifo remote_fifo;
+	struct kobject *remote_dir;
+	struct {
+		struct attribute_group group;
+		u32 serial;
+		struct input_dev *input;
+		bool registered;
+		struct wacom_battery battery;
+	} remotes[WACOM_MAX_REMOTES];
+};
+
+struct wacom {
+	struct usb_device *usbdev;
+	struct usb_interface *intf;
+	struct wacom_wac wacom_wac;
+	struct hid_device *hdev;
+	struct mutex lock;
+	struct work_struct wireless_work;
+	struct work_struct battery_work;
+	struct work_struct remote_work;
+	struct delayed_work init_work;
+	struct wacom_remote *remote;
+	struct work_struct mode_change_work;
+	bool generic_has_leds;
+	struct wacom_leds {
+		struct wacom_group_leds *groups;
+		unsigned int count;
+		u8 llv;       /* status led brightness no button (1..127) */
+		u8 hlv;       /* status led brightness button pressed (1..127) */
+		u8 img_lum;   /* OLED matrix display brightness */
+		u8 max_llv;   /* maximum brightness of LED (llv) */
+		u8 max_hlv;   /* maximum brightness of LED (hlv) */
+	} led;
+	struct wacom_battery battery;
+	bool resources;
+};
+
+static inline void wacom_schedule_work(struct wacom_wac *wacom_wac,
+				       enum wacom_worker which)
+{
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+
+	switch (which) {
+	case WACOM_WORKER_WIRELESS:
+		schedule_work(&wacom->wireless_work);
+		break;
+	case WACOM_WORKER_BATTERY:
+		schedule_work(&wacom->battery_work);
+		break;
+	case WACOM_WORKER_REMOTE:
+		schedule_work(&wacom->remote_work);
+		break;
+	case WACOM_WORKER_MODE_CHANGE:
+		schedule_work(&wacom->mode_change_work);
+		break;
+	}
+}
+
+extern const struct hid_device_id wacom_ids[];
+
+void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len);
+void wacom_setup_device_quirks(struct wacom *wacom);
+int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
+				   struct wacom_wac *wacom_wac);
+int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
+				   struct wacom_wac *wacom_wac);
+int wacom_setup_pad_input_capabilities(struct input_dev *input_dev,
+				       struct wacom_wac *wacom_wac);
+void wacom_wac_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage);
+void wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value);
+void wacom_wac_report(struct hid_device *hdev, struct hid_report *report);
+void wacom_battery_work(struct work_struct *work);
+enum led_brightness wacom_leds_brightness_get(struct wacom_led *led);
+struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group,
+				 unsigned int id);
+struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur);
+int wacom_equivalent_usage(int usage);
+int wacom_initialize_leds(struct wacom *wacom);
+#endif
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
new file mode 100644
index 0000000..0bdd85d
--- /dev/null
+++ b/drivers/hid/wacom_sys.c
@@ -0,0 +1,2803 @@
+/*
+ * drivers/input/tablet/wacom_sys.c
+ *
+ *  USB Wacom tablet support - system specific code
+ */
+
+/*
+ * 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 "wacom_wac.h"
+#include "wacom.h"
+#include <linux/input/mt.h>
+
+#define WAC_MSG_RETRIES		5
+#define WAC_CMD_RETRIES		10
+
+#define DEV_ATTR_RW_PERM (S_IRUGO | S_IWUSR | S_IWGRP)
+#define DEV_ATTR_WO_PERM (S_IWUSR | S_IWGRP)
+#define DEV_ATTR_RO_PERM (S_IRUSR | S_IRGRP)
+
+static int wacom_get_report(struct hid_device *hdev, u8 type, u8 *buf,
+			    size_t size, unsigned int retries)
+{
+	int retval;
+
+	do {
+		retval = hid_hw_raw_request(hdev, buf[0], buf, size, type,
+				HID_REQ_GET_REPORT);
+	} while ((retval == -ETIMEDOUT || retval == -EAGAIN) && --retries);
+
+	if (retval < 0)
+		hid_err(hdev, "wacom_get_report: ran out of retries "
+			"(last error = %d)\n", retval);
+
+	return retval;
+}
+
+static int wacom_set_report(struct hid_device *hdev, u8 type, u8 *buf,
+			    size_t size, unsigned int retries)
+{
+	int retval;
+
+	do {
+		retval = hid_hw_raw_request(hdev, buf[0], buf, size, type,
+				HID_REQ_SET_REPORT);
+	} while ((retval == -ETIMEDOUT || retval == -EAGAIN) && --retries);
+
+	if (retval < 0)
+		hid_err(hdev, "wacom_set_report: ran out of retries "
+			"(last error = %d)\n", retval);
+
+	return retval;
+}
+
+static void wacom_wac_queue_insert(struct hid_device *hdev,
+				   struct kfifo_rec_ptr_2 *fifo,
+				   u8 *raw_data, int size)
+{
+	bool warned = false;
+
+	while (kfifo_avail(fifo) < size) {
+		if (!warned)
+			hid_warn(hdev, "%s: kfifo has filled, starting to drop events\n", __func__);
+		warned = true;
+
+		kfifo_skip(fifo);
+	}
+
+	kfifo_in(fifo, raw_data, size);
+}
+
+static void wacom_wac_queue_flush(struct hid_device *hdev,
+				  struct kfifo_rec_ptr_2 *fifo)
+{
+	while (!kfifo_is_empty(fifo)) {
+		u8 buf[WACOM_PKGLEN_MAX];
+		int size;
+		int err;
+
+		size = kfifo_out(fifo, buf, sizeof(buf));
+		err = hid_report_raw_event(hdev, HID_INPUT_REPORT, buf, size, false);
+		if (err) {
+			hid_warn(hdev, "%s: unable to flush event due to error %d\n",
+				 __func__, err);
+		}
+	}
+}
+
+static int wacom_wac_pen_serial_enforce(struct hid_device *hdev,
+		struct hid_report *report, u8 *raw_data, int size)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	bool flush = false;
+	bool insert = false;
+	int i, j;
+
+	if (wacom_wac->serial[0] || !(features->quirks & WACOM_QUIRK_TOOLSERIAL))
+		return 0;
+
+	/* Queue events which have invalid tool type or serial number */
+	for (i = 0; i < report->maxfield; i++) {
+		for (j = 0; j < report->field[i]->maxusage; j++) {
+			struct hid_field *field = report->field[i];
+			struct hid_usage *usage = &field->usage[j];
+			unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
+			unsigned int offset;
+			unsigned int size;
+			unsigned int value;
+
+			if (equivalent_usage != HID_DG_INRANGE &&
+			    equivalent_usage != HID_DG_TOOLSERIALNUMBER &&
+			    equivalent_usage != WACOM_HID_WD_SERIALHI &&
+			    equivalent_usage != WACOM_HID_WD_TOOLTYPE)
+				continue;
+
+			offset = field->report_offset;
+			size = field->report_size;
+			value = hid_field_extract(hdev, raw_data+1, offset + j * size, size);
+
+			/* If we go out of range, we need to flush the queue ASAP */
+			if (equivalent_usage == HID_DG_INRANGE)
+				value = !value;
+
+			if (value) {
+				flush = true;
+				switch (equivalent_usage) {
+				case HID_DG_TOOLSERIALNUMBER:
+					wacom_wac->serial[0] = value;
+					break;
+
+				case WACOM_HID_WD_SERIALHI:
+					wacom_wac->serial[0] |= ((__u64)value) << 32;
+					break;
+
+				case WACOM_HID_WD_TOOLTYPE:
+					wacom_wac->id[0] = value;
+					break;
+				}
+			}
+			else {
+				insert = true;
+			}
+		}
+	}
+
+	if (flush)
+		wacom_wac_queue_flush(hdev, &wacom_wac->pen_fifo);
+	else if (insert)
+		wacom_wac_queue_insert(hdev, &wacom_wac->pen_fifo, raw_data, size);
+
+	return insert && !flush;
+}
+
+static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report,
+		u8 *raw_data, int size)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+
+	if (size > WACOM_PKGLEN_MAX)
+		return 1;
+
+	if (wacom_wac_pen_serial_enforce(hdev, report, raw_data, size))
+		return -1;
+
+	memcpy(wacom->wacom_wac.data, raw_data, size);
+
+	wacom_wac_irq(&wacom->wacom_wac, size);
+
+	return 0;
+}
+
+static int wacom_open(struct input_dev *dev)
+{
+	struct wacom *wacom = input_get_drvdata(dev);
+
+	return hid_hw_open(wacom->hdev);
+}
+
+static void wacom_close(struct input_dev *dev)
+{
+	struct wacom *wacom = input_get_drvdata(dev);
+
+	/*
+	 * wacom->hdev should never be null, but surprisingly, I had the case
+	 * once while unplugging the Wacom Wireless Receiver.
+	 */
+	if (wacom->hdev)
+		hid_hw_close(wacom->hdev);
+}
+
+/*
+ * Calculate the resolution of the X or Y axis using hidinput_calc_abs_res.
+ */
+static int wacom_calc_hid_res(int logical_extents, int physical_extents,
+			       unsigned unit, int exponent)
+{
+	struct hid_field field = {
+		.logical_maximum = logical_extents,
+		.physical_maximum = physical_extents,
+		.unit = unit,
+		.unit_exponent = exponent,
+	};
+
+	return hidinput_calc_abs_res(&field, ABS_X);
+}
+
+static void wacom_hid_usage_quirk(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_features *features = &wacom->wacom_wac.features;
+	unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	/*
+	 * The Dell Canvas 27 needs to be switched to its vendor-defined
+	 * report to provide the best resolution.
+	 */
+	if (hdev->vendor == USB_VENDOR_ID_WACOM &&
+	    hdev->product == 0x4200 &&
+	    field->application == HID_UP_MSVENDOR) {
+		wacom->wacom_wac.mode_report = field->report->id;
+		wacom->wacom_wac.mode_value = 2;
+	}
+
+	/*
+	 * ISDv4 devices which predate HID's adoption of the
+	 * HID_DG_BARELSWITCH2 usage use 0x000D0000 in its
+	 * position instead. We can accurately detect if a
+	 * usage with that value should be HID_DG_BARRELSWITCH2
+	 * based on the surrounding usages, which have remained
+	 * constant across generations.
+	 */
+	if (features->type == HID_GENERIC &&
+	    usage->hid == 0x000D0000 &&
+	    field->application == HID_DG_PEN &&
+	    field->physical == HID_DG_STYLUS) {
+		int i = usage->usage_index;
+
+		if (i-4 >= 0 && i+1 < field->maxusage &&
+		    field->usage[i-4].hid == HID_DG_TIPSWITCH &&
+		    field->usage[i-3].hid == HID_DG_BARRELSWITCH &&
+		    field->usage[i-2].hid == HID_DG_ERASER &&
+		    field->usage[i-1].hid == HID_DG_INVERT &&
+		    field->usage[i+1].hid == HID_DG_INRANGE) {
+			usage->hid = HID_DG_BARRELSWITCH2;
+		}
+	}
+
+	/* 2nd-generation Intuos Pro Large has incorrect Y maximum */
+	if (hdev->vendor == USB_VENDOR_ID_WACOM &&
+	    hdev->product == 0x0358 &&
+	    WACOM_PEN_FIELD(field) &&
+	    equivalent_usage == HID_GD_Y) {
+		field->logical_maximum = 43200;
+	}
+}
+
+static void wacom_feature_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_features *features = &wacom->wacom_wac.features;
+	struct hid_data *hid_data = &wacom->wacom_wac.hid_data;
+	unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
+	u8 *data;
+	int ret;
+	u32 n;
+
+	wacom_hid_usage_quirk(hdev, field, usage);
+
+	switch (equivalent_usage) {
+	case HID_DG_CONTACTMAX:
+		/* leave touch_max as is if predefined */
+		if (!features->touch_max) {
+			/* read manually */
+			data = kzalloc(2, GFP_KERNEL);
+			if (!data)
+				break;
+			data[0] = field->report->id;
+			ret = wacom_get_report(hdev, HID_FEATURE_REPORT,
+						data, 2, WAC_CMD_RETRIES);
+			if (ret == 2) {
+				features->touch_max = data[1];
+			} else {
+				features->touch_max = 16;
+				hid_warn(hdev, "wacom_feature_mapping: "
+					 "could not get HID_DG_CONTACTMAX, "
+					 "defaulting to %d\n",
+					  features->touch_max);
+			}
+			kfree(data);
+		}
+		break;
+	case HID_DG_INPUTMODE:
+		/* Ignore if value index is out of bounds. */
+		if (usage->usage_index >= field->report_count) {
+			dev_err(&hdev->dev, "HID_DG_INPUTMODE out of range\n");
+			break;
+		}
+
+		hid_data->inputmode = field->report->id;
+		hid_data->inputmode_index = usage->usage_index;
+		break;
+
+	case HID_UP_DIGITIZER:
+		if (field->report->id == 0x0B &&
+		    (field->application == WACOM_HID_G9_PEN ||
+		     field->application == WACOM_HID_G11_PEN)) {
+			wacom->wacom_wac.mode_report = field->report->id;
+			wacom->wacom_wac.mode_value = 0;
+		}
+		break;
+
+	case WACOM_HID_WD_DATAMODE:
+		wacom->wacom_wac.mode_report = field->report->id;
+		wacom->wacom_wac.mode_value = 2;
+		break;
+
+	case WACOM_HID_UP_G9:
+	case WACOM_HID_UP_G11:
+		if (field->report->id == 0x03 &&
+		    (field->application == WACOM_HID_G9_TOUCHSCREEN ||
+		     field->application == WACOM_HID_G11_TOUCHSCREEN)) {
+			wacom->wacom_wac.mode_report = field->report->id;
+			wacom->wacom_wac.mode_value = 0;
+		}
+		break;
+	case WACOM_HID_WD_OFFSETLEFT:
+	case WACOM_HID_WD_OFFSETTOP:
+	case WACOM_HID_WD_OFFSETRIGHT:
+	case WACOM_HID_WD_OFFSETBOTTOM:
+		/* read manually */
+		n = hid_report_len(field->report);
+		data = hid_alloc_report_buf(field->report, GFP_KERNEL);
+		if (!data)
+			break;
+		data[0] = field->report->id;
+		ret = wacom_get_report(hdev, HID_FEATURE_REPORT,
+					data, n, WAC_CMD_RETRIES);
+		if (ret == n) {
+			ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT,
+						   data, n, 0);
+		} else {
+			hid_warn(hdev, "%s: could not retrieve sensor offsets\n",
+				 __func__);
+		}
+		kfree(data);
+		break;
+	}
+}
+
+/*
+ * Interface Descriptor of wacom devices can be incomplete and
+ * inconsistent so wacom_features table is used to store stylus
+ * device's packet lengths, various maximum values, and tablet
+ * resolution based on product ID's.
+ *
+ * For devices that contain 2 interfaces, wacom_features table is
+ * inaccurate for the touch interface.  Since the Interface Descriptor
+ * for touch interfaces has pretty complete data, this function exists
+ * to query tablet for this missing information instead of hard coding in
+ * an additional table.
+ *
+ * A typical Interface Descriptor for a stylus will contain a
+ * boot mouse application collection that is not of interest and this
+ * function will ignore it.
+ *
+ * It also contains a digitizer application collection that also is not
+ * of interest since any information it contains would be duplicate
+ * of what is in wacom_features. Usually it defines a report of an array
+ * of bytes that could be used as max length of the stylus packet returned.
+ * If it happens to define a Digitizer-Stylus Physical Collection then
+ * the X and Y logical values contain valid data but it is ignored.
+ *
+ * A typical Interface Descriptor for a touch interface will contain a
+ * Digitizer-Finger Physical Collection which will define both logical
+ * X/Y maximum as well as the physical size of tablet. Since touch
+ * interfaces haven't supported pressure or distance, this is enough
+ * information to override invalid values in the wacom_features table.
+ *
+ * Intuos5 touch interface and 3rd gen Bamboo Touch do not contain useful
+ * data. We deal with them after returning from this function.
+ */
+static void wacom_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_features *features = &wacom->wacom_wac.features;
+	bool finger = WACOM_FINGER_FIELD(field);
+	bool pen = WACOM_PEN_FIELD(field);
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	/*
+	* Requiring Stylus Usage will ignore boot mouse
+	* X/Y values and some cases of invalid Digitizer X/Y
+	* values commonly reported.
+	*/
+	if (pen)
+		features->device_type |= WACOM_DEVICETYPE_PEN;
+	else if (finger)
+		features->device_type |= WACOM_DEVICETYPE_TOUCH;
+	else
+		return;
+
+	wacom_hid_usage_quirk(hdev, field, usage);
+
+	switch (equivalent_usage) {
+	case HID_GD_X:
+		features->x_max = field->logical_maximum;
+		if (finger) {
+			features->x_phy = field->physical_maximum;
+			if ((features->type != BAMBOO_PT) &&
+			    (features->type != BAMBOO_TOUCH)) {
+				features->unit = field->unit;
+				features->unitExpo = field->unit_exponent;
+			}
+		}
+		break;
+	case HID_GD_Y:
+		features->y_max = field->logical_maximum;
+		if (finger) {
+			features->y_phy = field->physical_maximum;
+			if ((features->type != BAMBOO_PT) &&
+			    (features->type != BAMBOO_TOUCH)) {
+				features->unit = field->unit;
+				features->unitExpo = field->unit_exponent;
+			}
+		}
+		break;
+	case HID_DG_TIPPRESSURE:
+		if (pen)
+			features->pressure_max = field->logical_maximum;
+		break;
+	}
+
+	if (features->type == HID_GENERIC)
+		wacom_wac_usage_mapping(hdev, field, usage);
+}
+
+static void wacom_post_parse_hid(struct hid_device *hdev,
+				 struct wacom_features *features)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+	if (features->type == HID_GENERIC) {
+		/* Any last-minute generic device setup */
+		if (wacom_wac->has_mode_change) {
+			if (wacom_wac->is_direct_mode)
+				features->device_type |= WACOM_DEVICETYPE_DIRECT;
+			else
+				features->device_type &= ~WACOM_DEVICETYPE_DIRECT;
+		}
+
+		if (features->touch_max > 1) {
+			if (features->device_type & WACOM_DEVICETYPE_DIRECT)
+				input_mt_init_slots(wacom_wac->touch_input,
+						    wacom_wac->features.touch_max,
+						    INPUT_MT_DIRECT);
+			else
+				input_mt_init_slots(wacom_wac->touch_input,
+						    wacom_wac->features.touch_max,
+						    INPUT_MT_POINTER);
+		}
+	}
+}
+
+static void wacom_parse_hid(struct hid_device *hdev,
+			   struct wacom_features *features)
+{
+	struct hid_report_enum *rep_enum;
+	struct hid_report *hreport;
+	int i, j;
+
+	/* check features first */
+	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
+	list_for_each_entry(hreport, &rep_enum->report_list, list) {
+		for (i = 0; i < hreport->maxfield; i++) {
+			/* Ignore if report count is out of bounds. */
+			if (hreport->field[i]->report_count < 1)
+				continue;
+
+			for (j = 0; j < hreport->field[i]->maxusage; j++) {
+				wacom_feature_mapping(hdev, hreport->field[i],
+						hreport->field[i]->usage + j);
+			}
+		}
+	}
+
+	/* now check the input usages */
+	rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
+	list_for_each_entry(hreport, &rep_enum->report_list, list) {
+
+		if (!hreport->maxfield)
+			continue;
+
+		for (i = 0; i < hreport->maxfield; i++)
+			for (j = 0; j < hreport->field[i]->maxusage; j++)
+				wacom_usage_mapping(hdev, hreport->field[i],
+						hreport->field[i]->usage + j);
+	}
+
+	wacom_post_parse_hid(hdev, features);
+}
+
+static int wacom_hid_set_device_mode(struct hid_device *hdev)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct hid_data *hid_data = &wacom->wacom_wac.hid_data;
+	struct hid_report *r;
+	struct hid_report_enum *re;
+
+	if (hid_data->inputmode < 0)
+		return 0;
+
+	re = &(hdev->report_enum[HID_FEATURE_REPORT]);
+	r = re->report_id_hash[hid_data->inputmode];
+	if (r) {
+		r->field[0]->value[hid_data->inputmode_index] = 2;
+		hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
+	}
+	return 0;
+}
+
+static int wacom_set_device_mode(struct hid_device *hdev,
+				 struct wacom_wac *wacom_wac)
+{
+	u8 *rep_data;
+	struct hid_report *r;
+	struct hid_report_enum *re;
+	u32 length;
+	int error = -ENOMEM, limit = 0;
+
+	if (wacom_wac->mode_report < 0)
+		return 0;
+
+	re = &(hdev->report_enum[HID_FEATURE_REPORT]);
+	r = re->report_id_hash[wacom_wac->mode_report];
+	if (!r)
+		return -EINVAL;
+
+	rep_data = hid_alloc_report_buf(r, GFP_KERNEL);
+	if (!rep_data)
+		return -ENOMEM;
+
+	length = hid_report_len(r);
+
+	do {
+		rep_data[0] = wacom_wac->mode_report;
+		rep_data[1] = wacom_wac->mode_value;
+
+		error = wacom_set_report(hdev, HID_FEATURE_REPORT, rep_data,
+					 length, 1);
+		if (error >= 0)
+			error = wacom_get_report(hdev, HID_FEATURE_REPORT,
+			                         rep_data, length, 1);
+	} while (error >= 0 &&
+		 rep_data[1] != wacom_wac->mode_report &&
+		 limit++ < WAC_MSG_RETRIES);
+
+	kfree(rep_data);
+
+	return error < 0 ? error : 0;
+}
+
+static int wacom_bt_query_tablet_data(struct hid_device *hdev, u8 speed,
+		struct wacom_features *features)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	int ret;
+	u8 rep_data[2];
+
+	switch (features->type) {
+	case GRAPHIRE_BT:
+		rep_data[0] = 0x03;
+		rep_data[1] = 0x00;
+		ret = wacom_set_report(hdev, HID_FEATURE_REPORT, rep_data, 2,
+					3);
+
+		if (ret >= 0) {
+			rep_data[0] = speed == 0 ? 0x05 : 0x06;
+			rep_data[1] = 0x00;
+
+			ret = wacom_set_report(hdev, HID_FEATURE_REPORT,
+						rep_data, 2, 3);
+
+			if (ret >= 0) {
+				wacom->wacom_wac.bt_high_speed = speed;
+				return 0;
+			}
+		}
+
+		/*
+		 * Note that if the raw queries fail, it's not a hard failure
+		 * and it is safe to continue
+		 */
+		hid_warn(hdev, "failed to poke device, command %d, err %d\n",
+			 rep_data[0], ret);
+		break;
+	case INTUOS4WL:
+		if (speed == 1)
+			wacom->wacom_wac.bt_features &= ~0x20;
+		else
+			wacom->wacom_wac.bt_features |= 0x20;
+
+		rep_data[0] = 0x03;
+		rep_data[1] = wacom->wacom_wac.bt_features;
+
+		ret = wacom_set_report(hdev, HID_FEATURE_REPORT, rep_data, 2,
+					1);
+		if (ret >= 0)
+			wacom->wacom_wac.bt_high_speed = speed;
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Switch the tablet into its most-capable mode. Wacom tablets are
+ * typically configured to power-up in a mode which sends mouse-like
+ * reports to the OS. To get absolute position, pressure data, etc.
+ * from the tablet, it is necessary to switch the tablet out of this
+ * mode and into one which sends the full range of tablet data.
+ */
+static int _wacom_query_tablet_data(struct wacom *wacom)
+{
+	struct hid_device *hdev = wacom->hdev;
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+
+	if (hdev->bus == BUS_BLUETOOTH)
+		return wacom_bt_query_tablet_data(hdev, 1, features);
+
+	if (features->type != HID_GENERIC) {
+		if (features->device_type & WACOM_DEVICETYPE_TOUCH) {
+			if (features->type > TABLETPC) {
+				/* MT Tablet PC touch */
+				wacom_wac->mode_report = 3;
+				wacom_wac->mode_value = 4;
+			} else if (features->type == WACOM_24HDT) {
+				wacom_wac->mode_report = 18;
+				wacom_wac->mode_value = 2;
+			} else if (features->type == WACOM_27QHDT) {
+				wacom_wac->mode_report = 131;
+				wacom_wac->mode_value = 2;
+			} else if (features->type == BAMBOO_PAD) {
+				wacom_wac->mode_report = 2;
+				wacom_wac->mode_value = 2;
+			}
+		} else if (features->device_type & WACOM_DEVICETYPE_PEN) {
+			if (features->type <= BAMBOO_PT) {
+				wacom_wac->mode_report = 2;
+				wacom_wac->mode_value = 2;
+			}
+		}
+	}
+
+	wacom_set_device_mode(hdev, wacom_wac);
+
+	if (features->type == HID_GENERIC)
+		return wacom_hid_set_device_mode(hdev);
+
+	return 0;
+}
+
+static void wacom_retrieve_hid_descriptor(struct hid_device *hdev,
+					 struct wacom_features *features)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct usb_interface *intf = wacom->intf;
+
+	/* default features */
+	features->x_fuzz = 4;
+	features->y_fuzz = 4;
+	features->pressure_fuzz = 0;
+	features->distance_fuzz = 1;
+	features->tilt_fuzz = 1;
+
+	/*
+	 * The wireless device HID is basic and layout conflicts with
+	 * other tablets (monitor and touch interface can look like pen).
+	 * Skip the query for this type and modify defaults based on
+	 * interface number.
+	 */
+	if (features->type == WIRELESS) {
+		if (intf->cur_altsetting->desc.bInterfaceNumber == 0)
+			features->device_type = WACOM_DEVICETYPE_WL_MONITOR;
+		else
+			features->device_type = WACOM_DEVICETYPE_NONE;
+		return;
+	}
+
+	wacom_parse_hid(hdev, features);
+}
+
+struct wacom_hdev_data {
+	struct list_head list;
+	struct kref kref;
+	struct hid_device *dev;
+	struct wacom_shared shared;
+};
+
+static LIST_HEAD(wacom_udev_list);
+static DEFINE_MUTEX(wacom_udev_list_lock);
+
+static bool wacom_are_sibling(struct hid_device *hdev,
+		struct hid_device *sibling)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_features *features = &wacom->wacom_wac.features;
+	struct wacom *sibling_wacom = hid_get_drvdata(sibling);
+	struct wacom_features *sibling_features = &sibling_wacom->wacom_wac.features;
+	__u32 oVid = features->oVid ? features->oVid : hdev->vendor;
+	__u32 oPid = features->oPid ? features->oPid : hdev->product;
+
+	/* The defined oVid/oPid must match that of the sibling */
+	if (features->oVid != HID_ANY_ID && sibling->vendor != oVid)
+		return false;
+	if (features->oPid != HID_ANY_ID && sibling->product != oPid)
+		return false;
+
+	/*
+	 * Devices with the same VID/PID must share the same physical
+	 * device path, while those with different VID/PID must share
+	 * the same physical parent device path.
+	 */
+	if (hdev->vendor == sibling->vendor && hdev->product == sibling->product) {
+		if (!hid_compare_device_paths(hdev, sibling, '/'))
+			return false;
+	} else {
+		if (!hid_compare_device_paths(hdev, sibling, '.'))
+			return false;
+	}
+
+	/* Skip the remaining heuristics unless you are a HID_GENERIC device */
+	if (features->type != HID_GENERIC)
+		return true;
+
+	/*
+	 * Direct-input devices may not be siblings of indirect-input
+	 * devices.
+	 */
+	if ((features->device_type & WACOM_DEVICETYPE_DIRECT) &&
+	    !(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
+		return false;
+
+	/*
+	 * Indirect-input devices may not be siblings of direct-input
+	 * devices.
+	 */
+	if (!(features->device_type & WACOM_DEVICETYPE_DIRECT) &&
+	    (sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
+		return false;
+
+	/* Pen devices may only be siblings of touch devices */
+	if ((features->device_type & WACOM_DEVICETYPE_PEN) &&
+	    !(sibling_features->device_type & WACOM_DEVICETYPE_TOUCH))
+		return false;
+
+	/* Touch devices may only be siblings of pen devices */
+	if ((features->device_type & WACOM_DEVICETYPE_TOUCH) &&
+	    !(sibling_features->device_type & WACOM_DEVICETYPE_PEN))
+		return false;
+
+	/*
+	 * No reason could be found for these two devices to NOT be
+	 * siblings, so there's a good chance they ARE siblings
+	 */
+	return true;
+}
+
+static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
+{
+	struct wacom_hdev_data *data;
+
+	/* Try to find an already-probed interface from the same device */
+	list_for_each_entry(data, &wacom_udev_list, list) {
+		if (hid_compare_device_paths(hdev, data->dev, '/')) {
+			kref_get(&data->kref);
+			return data;
+		}
+	}
+
+	/* Fallback to finding devices that appear to be "siblings" */
+	list_for_each_entry(data, &wacom_udev_list, list) {
+		if (wacom_are_sibling(hdev, data->dev)) {
+			kref_get(&data->kref);
+			return data;
+		}
+	}
+
+	return NULL;
+}
+
+static void wacom_release_shared_data(struct kref *kref)
+{
+	struct wacom_hdev_data *data =
+		container_of(kref, struct wacom_hdev_data, kref);
+
+	mutex_lock(&wacom_udev_list_lock);
+	list_del(&data->list);
+	mutex_unlock(&wacom_udev_list_lock);
+
+	kfree(data);
+}
+
+static void wacom_remove_shared_data(void *res)
+{
+	struct wacom *wacom = res;
+	struct wacom_hdev_data *data;
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+	if (wacom_wac->shared) {
+		data = container_of(wacom_wac->shared, struct wacom_hdev_data,
+				    shared);
+
+		if (wacom_wac->shared->touch == wacom->hdev)
+			wacom_wac->shared->touch = NULL;
+		else if (wacom_wac->shared->pen == wacom->hdev)
+			wacom_wac->shared->pen = NULL;
+
+		kref_put(&data->kref, wacom_release_shared_data);
+		wacom_wac->shared = NULL;
+	}
+}
+
+static int wacom_add_shared_data(struct hid_device *hdev)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_hdev_data *data;
+	int retval = 0;
+
+	mutex_lock(&wacom_udev_list_lock);
+
+	data = wacom_get_hdev_data(hdev);
+	if (!data) {
+		data = kzalloc(sizeof(struct wacom_hdev_data), GFP_KERNEL);
+		if (!data) {
+			retval = -ENOMEM;
+			goto out;
+		}
+
+		kref_init(&data->kref);
+		data->dev = hdev;
+		list_add_tail(&data->list, &wacom_udev_list);
+	}
+
+	wacom_wac->shared = &data->shared;
+
+	retval = devm_add_action(&hdev->dev, wacom_remove_shared_data, wacom);
+	if (retval) {
+		mutex_unlock(&wacom_udev_list_lock);
+		wacom_remove_shared_data(wacom);
+		return retval;
+	}
+
+	if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH)
+		wacom_wac->shared->touch = hdev;
+	else if (wacom_wac->features.device_type & WACOM_DEVICETYPE_PEN)
+		wacom_wac->shared->pen = hdev;
+
+out:
+	mutex_unlock(&wacom_udev_list_lock);
+	return retval;
+}
+
+static int wacom_led_control(struct wacom *wacom)
+{
+	unsigned char *buf;
+	int retval;
+	unsigned char report_id = WAC_CMD_LED_CONTROL;
+	int buf_size = 9;
+
+	if (!wacom->led.groups)
+		return -ENOTSUPP;
+
+	if (wacom->wacom_wac.features.type == REMOTE)
+		return -ENOTSUPP;
+
+	if (wacom->wacom_wac.pid) { /* wireless connected */
+		report_id = WAC_CMD_WL_LED_CONTROL;
+		buf_size = 13;
+	}
+	else if (wacom->wacom_wac.features.type == INTUOSP2_BT) {
+		report_id = WAC_CMD_WL_INTUOSP2;
+		buf_size = 51;
+	}
+	buf = kzalloc(buf_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (wacom->wacom_wac.features.type == HID_GENERIC) {
+		buf[0] = WAC_CMD_LED_CONTROL_GENERIC;
+		buf[1] = wacom->led.llv;
+		buf[2] = wacom->led.groups[0].select & 0x03;
+
+	} else if ((wacom->wacom_wac.features.type >= INTUOS5S &&
+	    wacom->wacom_wac.features.type <= INTUOSPL)) {
+		/*
+		 * Touch Ring and crop mark LED luminance may take on
+		 * one of four values:
+		 *    0 = Low; 1 = Medium; 2 = High; 3 = Off
+		 */
+		int ring_led = wacom->led.groups[0].select & 0x03;
+		int ring_lum = (((wacom->led.llv & 0x60) >> 5) - 1) & 0x03;
+		int crop_lum = 0;
+		unsigned char led_bits = (crop_lum << 4) | (ring_lum << 2) | (ring_led);
+
+		buf[0] = report_id;
+		if (wacom->wacom_wac.pid) {
+			wacom_get_report(wacom->hdev, HID_FEATURE_REPORT,
+					 buf, buf_size, WAC_CMD_RETRIES);
+			buf[0] = report_id;
+			buf[4] = led_bits;
+		} else
+			buf[1] = led_bits;
+	}
+	else if (wacom->wacom_wac.features.type == INTUOSP2_BT) {
+		buf[0] = report_id;
+		buf[4] = 100; // Power Connection LED (ORANGE)
+		buf[5] = 100; // BT Connection LED (BLUE)
+		buf[6] = 100; // Paper Mode (RED?)
+		buf[7] = 100; // Paper Mode (GREEN?)
+		buf[8] = 100; // Paper Mode (BLUE?)
+		buf[9] = wacom->led.llv;
+		buf[10] = wacom->led.groups[0].select & 0x03;
+	}
+	else {
+		int led = wacom->led.groups[0].select | 0x4;
+
+		if (wacom->wacom_wac.features.type == WACOM_21UX2 ||
+		    wacom->wacom_wac.features.type == WACOM_24HD)
+			led |= (wacom->led.groups[1].select << 4) | 0x40;
+
+		buf[0] = report_id;
+		buf[1] = led;
+		buf[2] = wacom->led.llv;
+		buf[3] = wacom->led.hlv;
+		buf[4] = wacom->led.img_lum;
+	}
+
+	retval = wacom_set_report(wacom->hdev, HID_FEATURE_REPORT, buf, buf_size,
+				  WAC_CMD_RETRIES);
+	kfree(buf);
+
+	return retval;
+}
+
+static int wacom_led_putimage(struct wacom *wacom, int button_id, u8 xfer_id,
+		const unsigned len, const void *img)
+{
+	unsigned char *buf;
+	int i, retval;
+	const unsigned chunk_len = len / 4; /* 4 chunks are needed to be sent */
+
+	buf = kzalloc(chunk_len + 3 , GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	/* Send 'start' command */
+	buf[0] = WAC_CMD_ICON_START;
+	buf[1] = 1;
+	retval = wacom_set_report(wacom->hdev, HID_FEATURE_REPORT, buf, 2,
+				  WAC_CMD_RETRIES);
+	if (retval < 0)
+		goto out;
+
+	buf[0] = xfer_id;
+	buf[1] = button_id & 0x07;
+	for (i = 0; i < 4; i++) {
+		buf[2] = i;
+		memcpy(buf + 3, img + i * chunk_len, chunk_len);
+
+		retval = wacom_set_report(wacom->hdev, HID_FEATURE_REPORT,
+					  buf, chunk_len + 3, WAC_CMD_RETRIES);
+		if (retval < 0)
+			break;
+	}
+
+	/* Send 'stop' */
+	buf[0] = WAC_CMD_ICON_START;
+	buf[1] = 0;
+	wacom_set_report(wacom->hdev, HID_FEATURE_REPORT, buf, 2,
+			 WAC_CMD_RETRIES);
+
+out:
+	kfree(buf);
+	return retval;
+}
+
+static ssize_t wacom_led_select_store(struct device *dev, int set_id,
+				      const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	unsigned int id;
+	int err;
+
+	err = kstrtouint(buf, 10, &id);
+	if (err)
+		return err;
+
+	mutex_lock(&wacom->lock);
+
+	wacom->led.groups[set_id].select = id & 0x3;
+	err = wacom_led_control(wacom);
+
+	mutex_unlock(&wacom->lock);
+
+	return err < 0 ? err : count;
+}
+
+#define DEVICE_LED_SELECT_ATTR(SET_ID)					\
+static ssize_t wacom_led##SET_ID##_select_store(struct device *dev,	\
+	struct device_attribute *attr, const char *buf, size_t count)	\
+{									\
+	return wacom_led_select_store(dev, SET_ID, buf, count);		\
+}									\
+static ssize_t wacom_led##SET_ID##_select_show(struct device *dev,	\
+	struct device_attribute *attr, char *buf)			\
+{									\
+	struct hid_device *hdev = to_hid_device(dev);\
+	struct wacom *wacom = hid_get_drvdata(hdev);			\
+	return scnprintf(buf, PAGE_SIZE, "%d\n",			\
+			 wacom->led.groups[SET_ID].select);		\
+}									\
+static DEVICE_ATTR(status_led##SET_ID##_select, DEV_ATTR_RW_PERM,	\
+		    wacom_led##SET_ID##_select_show,			\
+		    wacom_led##SET_ID##_select_store)
+
+DEVICE_LED_SELECT_ATTR(0);
+DEVICE_LED_SELECT_ATTR(1);
+
+static ssize_t wacom_luminance_store(struct wacom *wacom, u8 *dest,
+				     const char *buf, size_t count)
+{
+	unsigned int value;
+	int err;
+
+	err = kstrtouint(buf, 10, &value);
+	if (err)
+		return err;
+
+	mutex_lock(&wacom->lock);
+
+	*dest = value & 0x7f;
+	err = wacom_led_control(wacom);
+
+	mutex_unlock(&wacom->lock);
+
+	return err < 0 ? err : count;
+}
+
+#define DEVICE_LUMINANCE_ATTR(name, field)				\
+static ssize_t wacom_##name##_luminance_store(struct device *dev,	\
+	struct device_attribute *attr, const char *buf, size_t count)	\
+{									\
+	struct hid_device *hdev = to_hid_device(dev);\
+	struct wacom *wacom = hid_get_drvdata(hdev);			\
+									\
+	return wacom_luminance_store(wacom, &wacom->led.field,		\
+				     buf, count);			\
+}									\
+static ssize_t wacom_##name##_luminance_show(struct device *dev,	\
+	struct device_attribute *attr, char *buf)			\
+{									\
+	struct wacom *wacom = dev_get_drvdata(dev);			\
+	return scnprintf(buf, PAGE_SIZE, "%d\n", wacom->led.field);	\
+}									\
+static DEVICE_ATTR(name##_luminance, DEV_ATTR_RW_PERM,			\
+		   wacom_##name##_luminance_show,			\
+		   wacom_##name##_luminance_store)
+
+DEVICE_LUMINANCE_ATTR(status0, llv);
+DEVICE_LUMINANCE_ATTR(status1, hlv);
+DEVICE_LUMINANCE_ATTR(buttons, img_lum);
+
+static ssize_t wacom_button_image_store(struct device *dev, int button_id,
+					const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	int err;
+	unsigned len;
+	u8 xfer_id;
+
+	if (hdev->bus == BUS_BLUETOOTH) {
+		len = 256;
+		xfer_id = WAC_CMD_ICON_BT_XFER;
+	} else {
+		len = 1024;
+		xfer_id = WAC_CMD_ICON_XFER;
+	}
+
+	if (count != len)
+		return -EINVAL;
+
+	mutex_lock(&wacom->lock);
+
+	err = wacom_led_putimage(wacom, button_id, xfer_id, len, buf);
+
+	mutex_unlock(&wacom->lock);
+
+	return err < 0 ? err : count;
+}
+
+#define DEVICE_BTNIMG_ATTR(BUTTON_ID)					\
+static ssize_t wacom_btnimg##BUTTON_ID##_store(struct device *dev,	\
+	struct device_attribute *attr, const char *buf, size_t count)	\
+{									\
+	return wacom_button_image_store(dev, BUTTON_ID, buf, count);	\
+}									\
+static DEVICE_ATTR(button##BUTTON_ID##_rawimg, DEV_ATTR_WO_PERM,	\
+		   NULL, wacom_btnimg##BUTTON_ID##_store)
+
+DEVICE_BTNIMG_ATTR(0);
+DEVICE_BTNIMG_ATTR(1);
+DEVICE_BTNIMG_ATTR(2);
+DEVICE_BTNIMG_ATTR(3);
+DEVICE_BTNIMG_ATTR(4);
+DEVICE_BTNIMG_ATTR(5);
+DEVICE_BTNIMG_ATTR(6);
+DEVICE_BTNIMG_ATTR(7);
+
+static struct attribute *cintiq_led_attrs[] = {
+	&dev_attr_status_led0_select.attr,
+	&dev_attr_status_led1_select.attr,
+	NULL
+};
+
+static struct attribute_group cintiq_led_attr_group = {
+	.name = "wacom_led",
+	.attrs = cintiq_led_attrs,
+};
+
+static struct attribute *intuos4_led_attrs[] = {
+	&dev_attr_status0_luminance.attr,
+	&dev_attr_status1_luminance.attr,
+	&dev_attr_status_led0_select.attr,
+	&dev_attr_buttons_luminance.attr,
+	&dev_attr_button0_rawimg.attr,
+	&dev_attr_button1_rawimg.attr,
+	&dev_attr_button2_rawimg.attr,
+	&dev_attr_button3_rawimg.attr,
+	&dev_attr_button4_rawimg.attr,
+	&dev_attr_button5_rawimg.attr,
+	&dev_attr_button6_rawimg.attr,
+	&dev_attr_button7_rawimg.attr,
+	NULL
+};
+
+static struct attribute_group intuos4_led_attr_group = {
+	.name = "wacom_led",
+	.attrs = intuos4_led_attrs,
+};
+
+static struct attribute *intuos5_led_attrs[] = {
+	&dev_attr_status0_luminance.attr,
+	&dev_attr_status_led0_select.attr,
+	NULL
+};
+
+static struct attribute_group intuos5_led_attr_group = {
+	.name = "wacom_led",
+	.attrs = intuos5_led_attrs,
+};
+
+static struct attribute *generic_led_attrs[] = {
+	&dev_attr_status0_luminance.attr,
+	&dev_attr_status_led0_select.attr,
+	NULL
+};
+
+static struct attribute_group generic_led_attr_group = {
+	.name = "wacom_led",
+	.attrs = generic_led_attrs,
+};
+
+struct wacom_sysfs_group_devres {
+	struct attribute_group *group;
+	struct kobject *root;
+};
+
+static void wacom_devm_sysfs_group_release(struct device *dev, void *res)
+{
+	struct wacom_sysfs_group_devres *devres = res;
+	struct kobject *kobj = devres->root;
+
+	dev_dbg(dev, "%s: dropping reference to %s\n",
+		__func__, devres->group->name);
+	sysfs_remove_group(kobj, devres->group);
+}
+
+static int __wacom_devm_sysfs_create_group(struct wacom *wacom,
+					   struct kobject *root,
+					   struct attribute_group *group)
+{
+	struct wacom_sysfs_group_devres *devres;
+	int error;
+
+	devres = devres_alloc(wacom_devm_sysfs_group_release,
+			      sizeof(struct wacom_sysfs_group_devres),
+			      GFP_KERNEL);
+	if (!devres)
+		return -ENOMEM;
+
+	devres->group = group;
+	devres->root = root;
+
+	error = sysfs_create_group(devres->root, group);
+	if (error) {
+		devres_free(devres);
+		return error;
+	}
+
+	devres_add(&wacom->hdev->dev, devres);
+
+	return 0;
+}
+
+static int wacom_devm_sysfs_create_group(struct wacom *wacom,
+					 struct attribute_group *group)
+{
+	return __wacom_devm_sysfs_create_group(wacom, &wacom->hdev->dev.kobj,
+					       group);
+}
+
+enum led_brightness wacom_leds_brightness_get(struct wacom_led *led)
+{
+	struct wacom *wacom = led->wacom;
+
+	if (wacom->led.max_hlv)
+		return led->hlv * LED_FULL / wacom->led.max_hlv;
+
+	if (wacom->led.max_llv)
+		return led->llv * LED_FULL / wacom->led.max_llv;
+
+	/* device doesn't support brightness tuning */
+	return LED_FULL;
+}
+
+static enum led_brightness __wacom_led_brightness_get(struct led_classdev *cdev)
+{
+	struct wacom_led *led = container_of(cdev, struct wacom_led, cdev);
+	struct wacom *wacom = led->wacom;
+
+	if (wacom->led.groups[led->group].select != led->id)
+		return LED_OFF;
+
+	return wacom_leds_brightness_get(led);
+}
+
+static int wacom_led_brightness_set(struct led_classdev *cdev,
+				    enum led_brightness brightness)
+{
+	struct wacom_led *led = container_of(cdev, struct wacom_led, cdev);
+	struct wacom *wacom = led->wacom;
+	int error;
+
+	mutex_lock(&wacom->lock);
+
+	if (!wacom->led.groups || (brightness == LED_OFF &&
+	    wacom->led.groups[led->group].select != led->id)) {
+		error = 0;
+		goto out;
+	}
+
+	led->llv = wacom->led.llv = wacom->led.max_llv * brightness / LED_FULL;
+	led->hlv = wacom->led.hlv = wacom->led.max_hlv * brightness / LED_FULL;
+
+	wacom->led.groups[led->group].select = led->id;
+
+	error = wacom_led_control(wacom);
+
+out:
+	mutex_unlock(&wacom->lock);
+
+	return error;
+}
+
+static void wacom_led_readonly_brightness_set(struct led_classdev *cdev,
+					       enum led_brightness brightness)
+{
+}
+
+static int wacom_led_register_one(struct device *dev, struct wacom *wacom,
+				  struct wacom_led *led, unsigned int group,
+				  unsigned int id, bool read_only)
+{
+	int error;
+	char *name;
+
+	name = devm_kasprintf(dev, GFP_KERNEL,
+			      "%s::wacom-%d.%d",
+			      dev_name(dev),
+			      group,
+			      id);
+	if (!name)
+		return -ENOMEM;
+
+	if (!read_only) {
+		led->trigger.name = name;
+		error = devm_led_trigger_register(dev, &led->trigger);
+		if (error) {
+			hid_err(wacom->hdev,
+				"failed to register LED trigger %s: %d\n",
+				led->cdev.name, error);
+			return error;
+		}
+	}
+
+	led->group = group;
+	led->id = id;
+	led->wacom = wacom;
+	led->llv = wacom->led.llv;
+	led->hlv = wacom->led.hlv;
+	led->cdev.name = name;
+	led->cdev.max_brightness = LED_FULL;
+	led->cdev.flags = LED_HW_PLUGGABLE;
+	led->cdev.brightness_get = __wacom_led_brightness_get;
+	if (!read_only) {
+		led->cdev.brightness_set_blocking = wacom_led_brightness_set;
+		led->cdev.default_trigger = led->cdev.name;
+	} else {
+		led->cdev.brightness_set = wacom_led_readonly_brightness_set;
+	}
+
+	error = devm_led_classdev_register(dev, &led->cdev);
+	if (error) {
+		hid_err(wacom->hdev,
+			"failed to register LED %s: %d\n",
+			led->cdev.name, error);
+		led->cdev.name = NULL;
+		return error;
+	}
+
+	return 0;
+}
+
+static void wacom_led_groups_release_one(void *data)
+{
+	struct wacom_group_leds *group = data;
+
+	devres_release_group(group->dev, group);
+}
+
+static int wacom_led_groups_alloc_and_register_one(struct device *dev,
+						   struct wacom *wacom,
+						   int group_id, int count,
+						   bool read_only)
+{
+	struct wacom_led *leds;
+	int i, error;
+
+	if (group_id >= wacom->led.count || count <= 0)
+		return -EINVAL;
+
+	if (!devres_open_group(dev, &wacom->led.groups[group_id], GFP_KERNEL))
+		return -ENOMEM;
+
+	leds = devm_kcalloc(dev, count, sizeof(struct wacom_led), GFP_KERNEL);
+	if (!leds) {
+		error = -ENOMEM;
+		goto err;
+	}
+
+	wacom->led.groups[group_id].leds = leds;
+	wacom->led.groups[group_id].count = count;
+
+	for (i = 0; i < count; i++) {
+		error = wacom_led_register_one(dev, wacom, &leds[i],
+					       group_id, i, read_only);
+		if (error)
+			goto err;
+	}
+
+	wacom->led.groups[group_id].dev = dev;
+
+	devres_close_group(dev, &wacom->led.groups[group_id]);
+
+	/*
+	 * There is a bug (?) in devm_led_classdev_register() in which its
+	 * increments the refcount of the parent. If the parent is an input
+	 * device, that means the ref count never reaches 0 when
+	 * devm_input_device_release() gets called.
+	 * This means that the LEDs are still there after disconnect.
+	 * Manually force the release of the group so that the leds are released
+	 * once we are done using them.
+	 */
+	error = devm_add_action_or_reset(&wacom->hdev->dev,
+					 wacom_led_groups_release_one,
+					 &wacom->led.groups[group_id]);
+	if (error)
+		return error;
+
+	return 0;
+
+err:
+	devres_release_group(dev, &wacom->led.groups[group_id]);
+	return error;
+}
+
+struct wacom_led *wacom_led_find(struct wacom *wacom, unsigned int group_id,
+				 unsigned int id)
+{
+	struct wacom_group_leds *group;
+
+	if (group_id >= wacom->led.count)
+		return NULL;
+
+	group = &wacom->led.groups[group_id];
+
+	if (!group->leds)
+		return NULL;
+
+	id %= group->count;
+
+	return &group->leds[id];
+}
+
+/**
+ * wacom_led_next: gives the next available led with a wacom trigger.
+ *
+ * returns the next available struct wacom_led which has its default trigger
+ * or the current one if none is available.
+ */
+struct wacom_led *wacom_led_next(struct wacom *wacom, struct wacom_led *cur)
+{
+	struct wacom_led *next_led;
+	int group, next;
+
+	if (!wacom || !cur)
+		return NULL;
+
+	group = cur->group;
+	next = cur->id;
+
+	do {
+		next_led = wacom_led_find(wacom, group, ++next);
+		if (!next_led || next_led == cur)
+			return next_led;
+	} while (next_led->cdev.trigger != &next_led->trigger);
+
+	return next_led;
+}
+
+static void wacom_led_groups_release(void *data)
+{
+	struct wacom *wacom = data;
+
+	wacom->led.groups = NULL;
+	wacom->led.count = 0;
+}
+
+static int wacom_led_groups_allocate(struct wacom *wacom, int count)
+{
+	struct device *dev = &wacom->hdev->dev;
+	struct wacom_group_leds *groups;
+	int error;
+
+	groups = devm_kcalloc(dev, count, sizeof(struct wacom_group_leds),
+			      GFP_KERNEL);
+	if (!groups)
+		return -ENOMEM;
+
+	error = devm_add_action_or_reset(dev, wacom_led_groups_release, wacom);
+	if (error)
+		return error;
+
+	wacom->led.groups = groups;
+	wacom->led.count = count;
+
+	return 0;
+}
+
+static int wacom_leds_alloc_and_register(struct wacom *wacom, int group_count,
+					 int led_per_group, bool read_only)
+{
+	struct device *dev;
+	int i, error;
+
+	if (!wacom->wacom_wac.pad_input)
+		return -EINVAL;
+
+	dev = &wacom->wacom_wac.pad_input->dev;
+
+	error = wacom_led_groups_allocate(wacom, group_count);
+	if (error)
+		return error;
+
+	for (i = 0; i < group_count; i++) {
+		error = wacom_led_groups_alloc_and_register_one(dev, wacom, i,
+								led_per_group,
+								read_only);
+		if (error)
+			return error;
+	}
+
+	return 0;
+}
+
+int wacom_initialize_leds(struct wacom *wacom)
+{
+	int error;
+
+	if (!(wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD))
+		return 0;
+
+	/* Initialize default values */
+	switch (wacom->wacom_wac.features.type) {
+	case HID_GENERIC:
+		if (!wacom->generic_has_leds)
+			return 0;
+		wacom->led.llv = 100;
+		wacom->led.max_llv = 100;
+
+		error = wacom_leds_alloc_and_register(wacom, 1, 4, false);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+
+		error = wacom_devm_sysfs_create_group(wacom,
+						      &generic_led_attr_group);
+		break;
+
+	case INTUOS4S:
+	case INTUOS4:
+	case INTUOS4WL:
+	case INTUOS4L:
+		wacom->led.llv = 10;
+		wacom->led.hlv = 20;
+		wacom->led.max_llv = 127;
+		wacom->led.max_hlv = 127;
+		wacom->led.img_lum = 10;
+
+		error = wacom_leds_alloc_and_register(wacom, 1, 4, false);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+
+		error = wacom_devm_sysfs_create_group(wacom,
+						      &intuos4_led_attr_group);
+		break;
+
+	case WACOM_24HD:
+	case WACOM_21UX2:
+		wacom->led.llv = 0;
+		wacom->led.hlv = 0;
+		wacom->led.img_lum = 0;
+
+		error = wacom_leds_alloc_and_register(wacom, 2, 4, false);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+
+		error = wacom_devm_sysfs_create_group(wacom,
+						      &cintiq_led_attr_group);
+		break;
+
+	case INTUOS5S:
+	case INTUOS5:
+	case INTUOS5L:
+	case INTUOSPS:
+	case INTUOSPM:
+	case INTUOSPL:
+		wacom->led.llv = 32;
+		wacom->led.max_llv = 96;
+
+		error = wacom_leds_alloc_and_register(wacom, 1, 4, false);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+
+		error = wacom_devm_sysfs_create_group(wacom,
+						      &intuos5_led_attr_group);
+		break;
+
+	case INTUOSP2_BT:
+		wacom->led.llv = 50;
+		wacom->led.max_llv = 100;
+		error = wacom_leds_alloc_and_register(wacom, 1, 4, false);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+		return 0;
+
+	case REMOTE:
+		wacom->led.llv = 255;
+		wacom->led.max_llv = 255;
+		error = wacom_led_groups_allocate(wacom, 5);
+		if (error) {
+			hid_err(wacom->hdev,
+				"cannot create leds err: %d\n", error);
+			return error;
+		}
+		return 0;
+
+	default:
+		return 0;
+	}
+
+	if (error) {
+		hid_err(wacom->hdev,
+			"cannot create sysfs group err: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static void wacom_init_work(struct work_struct *work)
+{
+	struct wacom *wacom = container_of(work, struct wacom, init_work.work);
+
+	_wacom_query_tablet_data(wacom);
+	wacom_led_control(wacom);
+}
+
+static void wacom_query_tablet_data(struct wacom *wacom)
+{
+	schedule_delayed_work(&wacom->init_work, msecs_to_jiffies(1000));
+}
+
+static enum power_supply_property wacom_battery_props[] = {
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_SCOPE,
+	POWER_SUPPLY_PROP_CAPACITY
+};
+
+static int wacom_battery_get_property(struct power_supply *psy,
+				      enum power_supply_property psp,
+				      union power_supply_propval *val)
+{
+	struct wacom_battery *battery = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch (psp) {
+		case POWER_SUPPLY_PROP_MODEL_NAME:
+			val->strval = battery->wacom->wacom_wac.name;
+			break;
+		case POWER_SUPPLY_PROP_PRESENT:
+			val->intval = battery->bat_connected;
+			break;
+		case POWER_SUPPLY_PROP_SCOPE:
+			val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+			break;
+		case POWER_SUPPLY_PROP_CAPACITY:
+			val->intval = battery->battery_capacity;
+			break;
+		case POWER_SUPPLY_PROP_STATUS:
+			if (battery->bat_status != WACOM_POWER_SUPPLY_STATUS_AUTO)
+				val->intval = battery->bat_status;
+			else if (battery->bat_charging)
+				val->intval = POWER_SUPPLY_STATUS_CHARGING;
+			else if (battery->battery_capacity == 100 &&
+				    battery->ps_connected)
+				val->intval = POWER_SUPPLY_STATUS_FULL;
+			else if (battery->ps_connected)
+				val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+			else
+				val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+	}
+
+	return ret;
+}
+
+static int __wacom_initialize_battery(struct wacom *wacom,
+				      struct wacom_battery *battery)
+{
+	static atomic_t battery_no = ATOMIC_INIT(0);
+	struct device *dev = &wacom->hdev->dev;
+	struct power_supply_config psy_cfg = { .drv_data = battery, };
+	struct power_supply *ps_bat;
+	struct power_supply_desc *bat_desc = &battery->bat_desc;
+	unsigned long n;
+	int error;
+
+	if (!devres_open_group(dev, bat_desc, GFP_KERNEL))
+		return -ENOMEM;
+
+	battery->wacom = wacom;
+
+	n = atomic_inc_return(&battery_no) - 1;
+
+	bat_desc->properties = wacom_battery_props;
+	bat_desc->num_properties = ARRAY_SIZE(wacom_battery_props);
+	bat_desc->get_property = wacom_battery_get_property;
+	sprintf(battery->bat_name, "wacom_battery_%ld", n);
+	bat_desc->name = battery->bat_name;
+	bat_desc->type = POWER_SUPPLY_TYPE_USB;
+	bat_desc->use_for_apm = 0;
+
+	ps_bat = devm_power_supply_register(dev, bat_desc, &psy_cfg);
+	if (IS_ERR(ps_bat)) {
+		error = PTR_ERR(ps_bat);
+		goto err;
+	}
+
+	power_supply_powers(ps_bat, &wacom->hdev->dev);
+
+	battery->battery = ps_bat;
+
+	devres_close_group(dev, bat_desc);
+	return 0;
+
+err:
+	devres_release_group(dev, bat_desc);
+	return error;
+}
+
+static int wacom_initialize_battery(struct wacom *wacom)
+{
+	if (wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY)
+		return __wacom_initialize_battery(wacom, &wacom->battery);
+
+	return 0;
+}
+
+static void wacom_destroy_battery(struct wacom *wacom)
+{
+	if (wacom->battery.battery) {
+		devres_release_group(&wacom->hdev->dev,
+				     &wacom->battery.bat_desc);
+		wacom->battery.battery = NULL;
+	}
+}
+
+static ssize_t wacom_show_speed(struct device *dev,
+				struct device_attribute
+				*attr, char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%i\n", wacom->wacom_wac.bt_high_speed);
+}
+
+static ssize_t wacom_store_speed(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	u8 new_speed;
+
+	if (kstrtou8(buf, 0, &new_speed))
+		return -EINVAL;
+
+	if (new_speed != 0 && new_speed != 1)
+		return -EINVAL;
+
+	wacom_bt_query_tablet_data(hdev, new_speed, &wacom->wacom_wac.features);
+
+	return count;
+}
+
+static DEVICE_ATTR(speed, DEV_ATTR_RW_PERM,
+		wacom_show_speed, wacom_store_speed);
+
+
+static ssize_t wacom_show_remote_mode(struct kobject *kobj,
+				      struct kobj_attribute *kattr,
+				      char *buf, int index)
+{
+	struct device *dev = kobj_to_dev(kobj->parent);
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	u8 mode;
+
+	mode = wacom->led.groups[index].select;
+	return sprintf(buf, "%d\n", mode < 3 ? mode : -1);
+}
+
+#define DEVICE_EKR_ATTR_GROUP(SET_ID)					\
+static ssize_t wacom_show_remote##SET_ID##_mode(struct kobject *kobj,	\
+			       struct kobj_attribute *kattr, char *buf)	\
+{									\
+	return wacom_show_remote_mode(kobj, kattr, buf, SET_ID);	\
+}									\
+static struct kobj_attribute remote##SET_ID##_mode_attr = {		\
+	.attr = {.name = "remote_mode",					\
+		.mode = DEV_ATTR_RO_PERM},				\
+	.show = wacom_show_remote##SET_ID##_mode,			\
+};									\
+static struct attribute *remote##SET_ID##_serial_attrs[] = {		\
+	&remote##SET_ID##_mode_attr.attr,				\
+	NULL								\
+};									\
+static struct attribute_group remote##SET_ID##_serial_group = {		\
+	.name = NULL,							\
+	.attrs = remote##SET_ID##_serial_attrs,				\
+}
+
+DEVICE_EKR_ATTR_GROUP(0);
+DEVICE_EKR_ATTR_GROUP(1);
+DEVICE_EKR_ATTR_GROUP(2);
+DEVICE_EKR_ATTR_GROUP(3);
+DEVICE_EKR_ATTR_GROUP(4);
+
+static int wacom_remote_create_attr_group(struct wacom *wacom, __u32 serial,
+					  int index)
+{
+	int error = 0;
+	struct wacom_remote *remote = wacom->remote;
+
+	remote->remotes[index].group.name = devm_kasprintf(&wacom->hdev->dev,
+							  GFP_KERNEL,
+							  "%d", serial);
+	if (!remote->remotes[index].group.name)
+		return -ENOMEM;
+
+	error = __wacom_devm_sysfs_create_group(wacom, remote->remote_dir,
+						&remote->remotes[index].group);
+	if (error) {
+		remote->remotes[index].group.name = NULL;
+		hid_err(wacom->hdev,
+			"cannot create sysfs group err: %d\n", error);
+		return error;
+	}
+
+	return 0;
+}
+
+static int wacom_cmd_unpair_remote(struct wacom *wacom, unsigned char selector)
+{
+	const size_t buf_size = 2;
+	unsigned char *buf;
+	int retval;
+
+	buf = kzalloc(buf_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = WAC_CMD_DELETE_PAIRING;
+	buf[1] = selector;
+
+	retval = wacom_set_report(wacom->hdev, HID_OUTPUT_REPORT, buf,
+				  buf_size, WAC_CMD_RETRIES);
+	kfree(buf);
+
+	return retval;
+}
+
+static ssize_t wacom_store_unpair_remote(struct kobject *kobj,
+					 struct kobj_attribute *attr,
+					 const char *buf, size_t count)
+{
+	unsigned char selector = 0;
+	struct device *dev = kobj_to_dev(kobj->parent);
+	struct hid_device *hdev = to_hid_device(dev);
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	int err;
+
+	if (!strncmp(buf, "*\n", 2)) {
+		selector = WAC_CMD_UNPAIR_ALL;
+	} else {
+		hid_info(wacom->hdev, "remote: unrecognized unpair code: %s\n",
+			 buf);
+		return -1;
+	}
+
+	mutex_lock(&wacom->lock);
+
+	err = wacom_cmd_unpair_remote(wacom, selector);
+	mutex_unlock(&wacom->lock);
+
+	return err < 0 ? err : count;
+}
+
+static struct kobj_attribute unpair_remote_attr = {
+	.attr = {.name = "unpair_remote", .mode = 0200},
+	.store = wacom_store_unpair_remote,
+};
+
+static const struct attribute *remote_unpair_attrs[] = {
+	&unpair_remote_attr.attr,
+	NULL
+};
+
+static void wacom_remotes_destroy(void *data)
+{
+	struct wacom *wacom = data;
+	struct wacom_remote *remote = wacom->remote;
+
+	if (!remote)
+		return;
+
+	kobject_put(remote->remote_dir);
+	kfifo_free(&remote->remote_fifo);
+	wacom->remote = NULL;
+}
+
+static int wacom_initialize_remotes(struct wacom *wacom)
+{
+	int error = 0;
+	struct wacom_remote *remote;
+	int i;
+
+	if (wacom->wacom_wac.features.type != REMOTE)
+		return 0;
+
+	remote = devm_kzalloc(&wacom->hdev->dev, sizeof(*wacom->remote),
+			      GFP_KERNEL);
+	if (!remote)
+		return -ENOMEM;
+
+	wacom->remote = remote;
+
+	spin_lock_init(&remote->remote_lock);
+
+	error = kfifo_alloc(&remote->remote_fifo,
+			5 * sizeof(struct wacom_remote_data),
+			GFP_KERNEL);
+	if (error) {
+		hid_err(wacom->hdev, "failed allocating remote_fifo\n");
+		return -ENOMEM;
+	}
+
+	remote->remotes[0].group = remote0_serial_group;
+	remote->remotes[1].group = remote1_serial_group;
+	remote->remotes[2].group = remote2_serial_group;
+	remote->remotes[3].group = remote3_serial_group;
+	remote->remotes[4].group = remote4_serial_group;
+
+	remote->remote_dir = kobject_create_and_add("wacom_remote",
+						    &wacom->hdev->dev.kobj);
+	if (!remote->remote_dir)
+		return -ENOMEM;
+
+	error = sysfs_create_files(remote->remote_dir, remote_unpair_attrs);
+
+	if (error) {
+		hid_err(wacom->hdev,
+			"cannot create sysfs group err: %d\n", error);
+		return error;
+	}
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN;
+		remote->remotes[i].serial = 0;
+	}
+
+	error = devm_add_action_or_reset(&wacom->hdev->dev,
+					 wacom_remotes_destroy, wacom);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static struct input_dev *wacom_allocate_input(struct wacom *wacom)
+{
+	struct input_dev *input_dev;
+	struct hid_device *hdev = wacom->hdev;
+	struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+
+	input_dev = devm_input_allocate_device(&hdev->dev);
+	if (!input_dev)
+		return NULL;
+
+	input_dev->name = wacom_wac->features.name;
+	input_dev->phys = hdev->phys;
+	input_dev->dev.parent = &hdev->dev;
+	input_dev->open = wacom_open;
+	input_dev->close = wacom_close;
+	input_dev->uniq = hdev->uniq;
+	input_dev->id.bustype = hdev->bus;
+	input_dev->id.vendor  = hdev->vendor;
+	input_dev->id.product = wacom_wac->pid ? wacom_wac->pid : hdev->product;
+	input_dev->id.version = hdev->version;
+	input_set_drvdata(input_dev, wacom);
+
+	return input_dev;
+}
+
+static int wacom_allocate_inputs(struct wacom *wacom)
+{
+	struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+
+	wacom_wac->pen_input = wacom_allocate_input(wacom);
+	wacom_wac->touch_input = wacom_allocate_input(wacom);
+	wacom_wac->pad_input = wacom_allocate_input(wacom);
+	if (!wacom_wac->pen_input ||
+	    !wacom_wac->touch_input ||
+	    !wacom_wac->pad_input)
+		return -ENOMEM;
+
+	wacom_wac->pen_input->name = wacom_wac->pen_name;
+	wacom_wac->touch_input->name = wacom_wac->touch_name;
+	wacom_wac->pad_input->name = wacom_wac->pad_name;
+
+	return 0;
+}
+
+static int wacom_register_inputs(struct wacom *wacom)
+{
+	struct input_dev *pen_input_dev, *touch_input_dev, *pad_input_dev;
+	struct wacom_wac *wacom_wac = &(wacom->wacom_wac);
+	int error = 0;
+
+	pen_input_dev = wacom_wac->pen_input;
+	touch_input_dev = wacom_wac->touch_input;
+	pad_input_dev = wacom_wac->pad_input;
+
+	if (!pen_input_dev || !touch_input_dev || !pad_input_dev)
+		return -EINVAL;
+
+	error = wacom_setup_pen_input_capabilities(pen_input_dev, wacom_wac);
+	if (error) {
+		/* no pen in use on this interface */
+		input_free_device(pen_input_dev);
+		wacom_wac->pen_input = NULL;
+		pen_input_dev = NULL;
+	} else {
+		error = input_register_device(pen_input_dev);
+		if (error)
+			goto fail;
+	}
+
+	error = wacom_setup_touch_input_capabilities(touch_input_dev, wacom_wac);
+	if (error) {
+		/* no touch in use on this interface */
+		input_free_device(touch_input_dev);
+		wacom_wac->touch_input = NULL;
+		touch_input_dev = NULL;
+	} else {
+		error = input_register_device(touch_input_dev);
+		if (error)
+			goto fail;
+	}
+
+	error = wacom_setup_pad_input_capabilities(pad_input_dev, wacom_wac);
+	if (error) {
+		/* no pad in use on this interface */
+		input_free_device(pad_input_dev);
+		wacom_wac->pad_input = NULL;
+		pad_input_dev = NULL;
+	} else {
+		error = input_register_device(pad_input_dev);
+		if (error)
+			goto fail;
+	}
+
+	return 0;
+
+fail:
+	wacom_wac->pad_input = NULL;
+	wacom_wac->touch_input = NULL;
+	wacom_wac->pen_input = NULL;
+	return error;
+}
+
+/*
+ * Not all devices report physical dimensions from HID.
+ * Compute the default from hardcoded logical dimension
+ * and resolution before driver overwrites them.
+ */
+static void wacom_set_default_phy(struct wacom_features *features)
+{
+	if (features->x_resolution) {
+		features->x_phy = (features->x_max * 100) /
+					features->x_resolution;
+		features->y_phy = (features->y_max * 100) /
+					features->y_resolution;
+	}
+}
+
+static void wacom_calculate_res(struct wacom_features *features)
+{
+	/* set unit to "100th of a mm" for devices not reported by HID */
+	if (!features->unit) {
+		features->unit = 0x11;
+		features->unitExpo = -3;
+	}
+
+	features->x_resolution = wacom_calc_hid_res(features->x_max,
+						    features->x_phy,
+						    features->unit,
+						    features->unitExpo);
+	features->y_resolution = wacom_calc_hid_res(features->y_max,
+						    features->y_phy,
+						    features->unit,
+						    features->unitExpo);
+}
+
+void wacom_battery_work(struct work_struct *work)
+{
+	struct wacom *wacom = container_of(work, struct wacom, battery_work);
+
+	if ((wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+	     !wacom->battery.battery) {
+		wacom_initialize_battery(wacom);
+	}
+	else if (!(wacom->wacom_wac.features.quirks & WACOM_QUIRK_BATTERY) &&
+		 wacom->battery.battery) {
+		wacom_destroy_battery(wacom);
+	}
+}
+
+static size_t wacom_compute_pktlen(struct hid_device *hdev)
+{
+	struct hid_report_enum *report_enum;
+	struct hid_report *report;
+	size_t size = 0;
+
+	report_enum = hdev->report_enum + HID_INPUT_REPORT;
+
+	list_for_each_entry(report, &report_enum->report_list, list) {
+		size_t report_size = hid_report_len(report);
+		if (report_size > size)
+			size = report_size;
+	}
+
+	return size;
+}
+
+static void wacom_update_name(struct wacom *wacom, const char *suffix)
+{
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	char name[WACOM_NAME_MAX];
+
+	/* Generic devices name unspecified */
+	if ((features->type == HID_GENERIC) && !strcmp("Wacom HID", features->name)) {
+		char *product_name = wacom->hdev->name;
+
+		if (hid_is_using_ll_driver(wacom->hdev, &usb_hid_driver)) {
+			struct usb_interface *intf = to_usb_interface(wacom->hdev->dev.parent);
+			struct usb_device *dev = interface_to_usbdev(intf);
+			product_name = dev->product;
+		}
+
+		if (wacom->hdev->bus == BUS_I2C) {
+			snprintf(name, sizeof(name), "%s %X",
+				 features->name, wacom->hdev->product);
+		} else if (strstr(product_name, "Wacom") ||
+			   strstr(product_name, "wacom") ||
+			   strstr(product_name, "WACOM")) {
+			strlcpy(name, product_name, sizeof(name));
+		} else {
+			snprintf(name, sizeof(name), "Wacom %s", product_name);
+		}
+
+		/* strip out excess whitespaces */
+		while (1) {
+			char *gap = strstr(name, "  ");
+			if (gap == NULL)
+				break;
+			/* shift everything including the terminator */
+			memmove(gap, gap+1, strlen(gap));
+		}
+
+		/* get rid of trailing whitespace */
+		if (name[strlen(name)-1] == ' ')
+			name[strlen(name)-1] = '\0';
+	} else {
+		strlcpy(name, features->name, sizeof(name));
+	}
+
+	snprintf(wacom_wac->name, sizeof(wacom_wac->name), "%s%s",
+		 name, suffix);
+
+	/* Append the device type to the name */
+	snprintf(wacom_wac->pen_name, sizeof(wacom_wac->pen_name),
+		"%s%s Pen", name, suffix);
+	snprintf(wacom_wac->touch_name, sizeof(wacom_wac->touch_name),
+		"%s%s Finger", name, suffix);
+	snprintf(wacom_wac->pad_name, sizeof(wacom_wac->pad_name),
+		"%s%s Pad", name, suffix);
+}
+
+static void wacom_release_resources(struct wacom *wacom)
+{
+	struct hid_device *hdev = wacom->hdev;
+
+	if (!wacom->resources)
+		return;
+
+	devres_release_group(&hdev->dev, wacom);
+
+	wacom->resources = false;
+
+	wacom->wacom_wac.pen_input = NULL;
+	wacom->wacom_wac.touch_input = NULL;
+	wacom->wacom_wac.pad_input = NULL;
+}
+
+static void wacom_set_shared_values(struct wacom_wac *wacom_wac)
+{
+	if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH) {
+		wacom_wac->shared->type = wacom_wac->features.type;
+		wacom_wac->shared->touch_input = wacom_wac->touch_input;
+	}
+
+	if (wacom_wac->has_mute_touch_switch) {
+		wacom_wac->shared->has_mute_touch_switch = true;
+		wacom_wac->shared->is_touch_on = true;
+	}
+
+	if (wacom_wac->shared->has_mute_touch_switch &&
+	    wacom_wac->shared->touch_input) {
+		set_bit(EV_SW, wacom_wac->shared->touch_input->evbit);
+		input_set_capability(wacom_wac->shared->touch_input, EV_SW,
+				     SW_MUTE_DEVICE);
+	}
+}
+
+static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
+{
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	struct hid_device *hdev = wacom->hdev;
+	int error;
+	unsigned int connect_mask = HID_CONNECT_HIDRAW;
+
+	features->pktlen = wacom_compute_pktlen(hdev);
+	if (features->pktlen > WACOM_PKGLEN_MAX)
+		return -EINVAL;
+
+	if (!devres_open_group(&hdev->dev, wacom, GFP_KERNEL))
+		return -ENOMEM;
+
+	wacom->resources = true;
+
+	error = wacom_allocate_inputs(wacom);
+	if (error)
+		goto fail;
+
+	/*
+	 * Bamboo Pad has a generic hid handling for the Pen, and we switch it
+	 * into debug mode for the touch part.
+	 * We ignore the other interfaces.
+	 */
+	if (features->type == BAMBOO_PAD) {
+		if (features->pktlen == WACOM_PKGLEN_PENABLED) {
+			features->type = HID_GENERIC;
+		} else if ((features->pktlen != WACOM_PKGLEN_BPAD_TOUCH) &&
+			   (features->pktlen != WACOM_PKGLEN_BPAD_TOUCH_USB)) {
+			error = -ENODEV;
+			goto fail;
+		}
+	}
+
+	/* set the default size in case we do not get them from hid */
+	wacom_set_default_phy(features);
+
+	/* Retrieve the physical and logical size for touch devices */
+	wacom_retrieve_hid_descriptor(hdev, features);
+	wacom_setup_device_quirks(wacom);
+
+	if (features->device_type == WACOM_DEVICETYPE_NONE &&
+	    features->type != WIRELESS) {
+		error = features->type == HID_GENERIC ? -ENODEV : 0;
+
+		dev_warn(&hdev->dev, "Unknown device_type for '%s'. %s.",
+			 hdev->name,
+			 error ? "Ignoring" : "Assuming pen");
+
+		if (error)
+			goto fail;
+
+		features->device_type |= WACOM_DEVICETYPE_PEN;
+	}
+
+	wacom_calculate_res(features);
+
+	wacom_update_name(wacom, wireless ? " (WL)" : "");
+
+	/* pen only Bamboo neither support touch nor pad */
+	if ((features->type == BAMBOO_PEN) &&
+	    ((features->device_type & WACOM_DEVICETYPE_TOUCH) ||
+	    (features->device_type & WACOM_DEVICETYPE_PAD))) {
+		error = -ENODEV;
+		goto fail;
+	}
+
+	error = wacom_add_shared_data(hdev);
+	if (error)
+		goto fail;
+
+	if (!(features->device_type & WACOM_DEVICETYPE_WL_MONITOR) &&
+	     (features->quirks & WACOM_QUIRK_BATTERY)) {
+		error = wacom_initialize_battery(wacom);
+		if (error)
+			goto fail;
+	}
+
+	error = wacom_register_inputs(wacom);
+	if (error)
+		goto fail;
+
+	if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) {
+		error = wacom_initialize_leds(wacom);
+		if (error)
+			goto fail;
+
+		error = wacom_initialize_remotes(wacom);
+		if (error)
+			goto fail;
+	}
+
+	if (features->type == HID_GENERIC)
+		connect_mask |= HID_CONNECT_DRIVER;
+
+	/* Regular HID work starts now */
+	error = hid_hw_start(hdev, connect_mask);
+	if (error) {
+		hid_err(hdev, "hw start failed\n");
+		goto fail;
+	}
+
+	if (!wireless) {
+		/* Note that if query fails it is not a hard failure */
+		wacom_query_tablet_data(wacom);
+	}
+
+	/* touch only Bamboo doesn't support pen */
+	if ((features->type == BAMBOO_TOUCH) &&
+	    (features->device_type & WACOM_DEVICETYPE_PEN)) {
+		cancel_delayed_work_sync(&wacom->init_work);
+		_wacom_query_tablet_data(wacom);
+		error = -ENODEV;
+		goto fail_quirks;
+	}
+
+	if (features->device_type & WACOM_DEVICETYPE_WL_MONITOR)
+		error = hid_hw_open(hdev);
+
+	wacom_set_shared_values(wacom_wac);
+	devres_close_group(&hdev->dev, wacom);
+
+	return 0;
+
+fail_quirks:
+	hid_hw_stop(hdev);
+fail:
+	wacom_release_resources(wacom);
+	return error;
+}
+
+static void wacom_wireless_work(struct work_struct *work)
+{
+	struct wacom *wacom = container_of(work, struct wacom, wireless_work);
+	struct usb_device *usbdev = wacom->usbdev;
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct hid_device *hdev1, *hdev2;
+	struct wacom *wacom1, *wacom2;
+	struct wacom_wac *wacom_wac1, *wacom_wac2;
+	int error;
+
+	/*
+	 * Regardless if this is a disconnect or a new tablet,
+	 * remove any existing input and battery devices.
+	 */
+
+	wacom_destroy_battery(wacom);
+
+	/* Stylus interface */
+	hdev1 = usb_get_intfdata(usbdev->config->interface[1]);
+	wacom1 = hid_get_drvdata(hdev1);
+	wacom_wac1 = &(wacom1->wacom_wac);
+	wacom_release_resources(wacom1);
+
+	/* Touch interface */
+	hdev2 = usb_get_intfdata(usbdev->config->interface[2]);
+	wacom2 = hid_get_drvdata(hdev2);
+	wacom_wac2 = &(wacom2->wacom_wac);
+	wacom_release_resources(wacom2);
+
+	if (wacom_wac->pid == 0) {
+		hid_info(wacom->hdev, "wireless tablet disconnected\n");
+	} else {
+		const struct hid_device_id *id = wacom_ids;
+
+		hid_info(wacom->hdev, "wireless tablet connected with PID %x\n",
+			 wacom_wac->pid);
+
+		while (id->bus) {
+			if (id->vendor == USB_VENDOR_ID_WACOM &&
+			    id->product == wacom_wac->pid)
+				break;
+			id++;
+		}
+
+		if (!id->bus) {
+			hid_info(wacom->hdev, "ignoring unknown PID.\n");
+			return;
+		}
+
+		/* Stylus interface */
+		wacom_wac1->features =
+			*((struct wacom_features *)id->driver_data);
+
+		wacom_wac1->pid = wacom_wac->pid;
+		hid_hw_stop(hdev1);
+		error = wacom_parse_and_register(wacom1, true);
+		if (error)
+			goto fail;
+
+		/* Touch interface */
+		if (wacom_wac1->features.touch_max ||
+		    (wacom_wac1->features.type >= INTUOSHT &&
+		    wacom_wac1->features.type <= BAMBOO_PT)) {
+			wacom_wac2->features =
+				*((struct wacom_features *)id->driver_data);
+			wacom_wac2->pid = wacom_wac->pid;
+			hid_hw_stop(hdev2);
+			error = wacom_parse_and_register(wacom2, true);
+			if (error)
+				goto fail;
+		}
+
+		strlcpy(wacom_wac->name, wacom_wac1->name,
+			sizeof(wacom_wac->name));
+		error = wacom_initialize_battery(wacom);
+		if (error)
+			goto fail;
+	}
+
+	return;
+
+fail:
+	wacom_release_resources(wacom1);
+	wacom_release_resources(wacom2);
+	return;
+}
+
+static void wacom_remote_destroy_one(struct wacom *wacom, unsigned int index)
+{
+	struct wacom_remote *remote = wacom->remote;
+	u32 serial = remote->remotes[index].serial;
+	int i;
+	unsigned long flags;
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		if (remote->remotes[i].serial == serial) {
+
+			spin_lock_irqsave(&remote->remote_lock, flags);
+			remote->remotes[i].registered = false;
+			spin_unlock_irqrestore(&remote->remote_lock, flags);
+
+			if (remote->remotes[i].battery.battery)
+				devres_release_group(&wacom->hdev->dev,
+						     &remote->remotes[i].battery.bat_desc);
+
+			if (remote->remotes[i].group.name)
+				devres_release_group(&wacom->hdev->dev,
+						     &remote->remotes[i]);
+
+			remote->remotes[i].serial = 0;
+			remote->remotes[i].group.name = NULL;
+			remote->remotes[i].battery.battery = NULL;
+			wacom->led.groups[i].select = WACOM_STATUS_UNKNOWN;
+		}
+	}
+}
+
+static int wacom_remote_create_one(struct wacom *wacom, u32 serial,
+				   unsigned int index)
+{
+	struct wacom_remote *remote = wacom->remote;
+	struct device *dev = &wacom->hdev->dev;
+	int error, k;
+
+	/* A remote can pair more than once with an EKR,
+	 * check to make sure this serial isn't already paired.
+	 */
+	for (k = 0; k < WACOM_MAX_REMOTES; k++) {
+		if (remote->remotes[k].serial == serial)
+			break;
+	}
+
+	if (k < WACOM_MAX_REMOTES) {
+		remote->remotes[index].serial = serial;
+		return 0;
+	}
+
+	if (!devres_open_group(dev, &remote->remotes[index], GFP_KERNEL))
+		return -ENOMEM;
+
+	error = wacom_remote_create_attr_group(wacom, serial, index);
+	if (error)
+		goto fail;
+
+	remote->remotes[index].input = wacom_allocate_input(wacom);
+	if (!remote->remotes[index].input) {
+		error = -ENOMEM;
+		goto fail;
+	}
+	remote->remotes[index].input->uniq = remote->remotes[index].group.name;
+	remote->remotes[index].input->name = wacom->wacom_wac.pad_name;
+
+	if (!remote->remotes[index].input->name) {
+		error = -EINVAL;
+		goto fail;
+	}
+
+	error = wacom_setup_pad_input_capabilities(remote->remotes[index].input,
+						   &wacom->wacom_wac);
+	if (error)
+		goto fail;
+
+	remote->remotes[index].serial = serial;
+
+	error = input_register_device(remote->remotes[index].input);
+	if (error)
+		goto fail;
+
+	error = wacom_led_groups_alloc_and_register_one(
+					&remote->remotes[index].input->dev,
+					wacom, index, 3, true);
+	if (error)
+		goto fail;
+
+	remote->remotes[index].registered = true;
+
+	devres_close_group(dev, &remote->remotes[index]);
+	return 0;
+
+fail:
+	devres_release_group(dev, &remote->remotes[index]);
+	remote->remotes[index].serial = 0;
+	return error;
+}
+
+static int wacom_remote_attach_battery(struct wacom *wacom, int index)
+{
+	struct wacom_remote *remote = wacom->remote;
+	int error;
+
+	if (!remote->remotes[index].registered)
+		return 0;
+
+	if (remote->remotes[index].battery.battery)
+		return 0;
+
+	if (wacom->led.groups[index].select == WACOM_STATUS_UNKNOWN)
+		return 0;
+
+	error = __wacom_initialize_battery(wacom,
+					&wacom->remote->remotes[index].battery);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static void wacom_remote_work(struct work_struct *work)
+{
+	struct wacom *wacom = container_of(work, struct wacom, remote_work);
+	struct wacom_remote *remote = wacom->remote;
+	struct wacom_remote_data data;
+	unsigned long flags;
+	unsigned int count;
+	u32 serial;
+	int i;
+
+	spin_lock_irqsave(&remote->remote_lock, flags);
+
+	count = kfifo_out(&remote->remote_fifo, &data, sizeof(data));
+
+	if (count != sizeof(data)) {
+		hid_err(wacom->hdev,
+			"workitem triggered without status available\n");
+		spin_unlock_irqrestore(&remote->remote_lock, flags);
+		return;
+	}
+
+	if (!kfifo_is_empty(&remote->remote_fifo))
+		wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_REMOTE);
+
+	spin_unlock_irqrestore(&remote->remote_lock, flags);
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		serial = data.remote[i].serial;
+		if (data.remote[i].connected) {
+
+			if (remote->remotes[i].serial == serial) {
+				wacom_remote_attach_battery(wacom, i);
+				continue;
+			}
+
+			if (remote->remotes[i].serial)
+				wacom_remote_destroy_one(wacom, i);
+
+			wacom_remote_create_one(wacom, serial, i);
+
+		} else if (remote->remotes[i].serial) {
+			wacom_remote_destroy_one(wacom, i);
+		}
+	}
+}
+
+static void wacom_mode_change_work(struct work_struct *work)
+{
+	struct wacom *wacom = container_of(work, struct wacom, mode_change_work);
+	struct wacom_shared *shared = wacom->wacom_wac.shared;
+	struct wacom *wacom1 = NULL;
+	struct wacom *wacom2 = NULL;
+	bool is_direct = wacom->wacom_wac.is_direct_mode;
+	int error = 0;
+
+	if (shared->pen) {
+		wacom1 = hid_get_drvdata(shared->pen);
+		wacom_release_resources(wacom1);
+		hid_hw_stop(wacom1->hdev);
+		wacom1->wacom_wac.has_mode_change = true;
+		wacom1->wacom_wac.is_direct_mode = is_direct;
+	}
+
+	if (shared->touch) {
+		wacom2 = hid_get_drvdata(shared->touch);
+		wacom_release_resources(wacom2);
+		hid_hw_stop(wacom2->hdev);
+		wacom2->wacom_wac.has_mode_change = true;
+		wacom2->wacom_wac.is_direct_mode = is_direct;
+	}
+
+	if (wacom1) {
+		error = wacom_parse_and_register(wacom1, false);
+		if (error)
+			return;
+	}
+
+	if (wacom2) {
+		error = wacom_parse_and_register(wacom2, false);
+		if (error)
+			return;
+	}
+
+	return;
+}
+
+static int wacom_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct wacom *wacom;
+	struct wacom_wac *wacom_wac;
+	struct wacom_features *features;
+	int error;
+
+	if (!id->driver_data)
+		return -EINVAL;
+
+	hdev->quirks |= HID_QUIRK_NO_INIT_REPORTS;
+
+	/* hid-core sets this quirk for the boot interface */
+	hdev->quirks &= ~HID_QUIRK_NOGET;
+
+	wacom = devm_kzalloc(&hdev->dev, sizeof(struct wacom), GFP_KERNEL);
+	if (!wacom)
+		return -ENOMEM;
+
+	hid_set_drvdata(hdev, wacom);
+	wacom->hdev = hdev;
+
+	wacom_wac = &wacom->wacom_wac;
+	wacom_wac->features = *((struct wacom_features *)id->driver_data);
+	features = &wacom_wac->features;
+
+	if (features->check_for_hid_type && features->hid_type != hdev->type) {
+		error = -ENODEV;
+		goto fail;
+	}
+
+	error = kfifo_alloc(&wacom_wac->pen_fifo, WACOM_PKGLEN_MAX, GFP_KERNEL);
+	if (error)
+		goto fail;
+
+	wacom_wac->hid_data.inputmode = -1;
+	wacom_wac->mode_report = -1;
+
+	wacom->usbdev = dev;
+	wacom->intf = intf;
+	mutex_init(&wacom->lock);
+	INIT_DELAYED_WORK(&wacom->init_work, wacom_init_work);
+	INIT_WORK(&wacom->wireless_work, wacom_wireless_work);
+	INIT_WORK(&wacom->battery_work, wacom_battery_work);
+	INIT_WORK(&wacom->remote_work, wacom_remote_work);
+	INIT_WORK(&wacom->mode_change_work, wacom_mode_change_work);
+
+	/* ask for the report descriptor to be loaded by HID */
+	error = hid_parse(hdev);
+	if (error) {
+		hid_err(hdev, "parse failed\n");
+		goto fail;
+	}
+
+	error = wacom_parse_and_register(wacom, false);
+	if (error)
+		goto fail;
+
+	if (hdev->bus == BUS_BLUETOOTH) {
+		error = device_create_file(&hdev->dev, &dev_attr_speed);
+		if (error)
+			hid_warn(hdev,
+				 "can't create sysfs speed attribute err: %d\n",
+				 error);
+	}
+
+	return 0;
+
+fail:
+	hid_set_drvdata(hdev, NULL);
+	return error;
+}
+
+static void wacom_remove(struct hid_device *hdev)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+
+	if (features->device_type & WACOM_DEVICETYPE_WL_MONITOR)
+		hid_hw_close(hdev);
+
+	hid_hw_stop(hdev);
+
+	cancel_delayed_work_sync(&wacom->init_work);
+	cancel_work_sync(&wacom->wireless_work);
+	cancel_work_sync(&wacom->battery_work);
+	cancel_work_sync(&wacom->remote_work);
+	cancel_work_sync(&wacom->mode_change_work);
+	if (hdev->bus == BUS_BLUETOOTH)
+		device_remove_file(&hdev->dev, &dev_attr_speed);
+
+	/* make sure we don't trigger the LEDs */
+	wacom_led_groups_release(wacom);
+
+	if (wacom->wacom_wac.features.type != REMOTE)
+		wacom_release_resources(wacom);
+
+	kfifo_free(&wacom_wac->pen_fifo);
+
+	hid_set_drvdata(hdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int wacom_resume(struct hid_device *hdev)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+
+	mutex_lock(&wacom->lock);
+
+	/* switch to wacom mode first */
+	_wacom_query_tablet_data(wacom);
+	wacom_led_control(wacom);
+
+	mutex_unlock(&wacom->lock);
+
+	return 0;
+}
+
+static int wacom_reset_resume(struct hid_device *hdev)
+{
+	return wacom_resume(hdev);
+}
+#endif /* CONFIG_PM */
+
+static struct hid_driver wacom_driver = {
+	.name =		"wacom",
+	.id_table =	wacom_ids,
+	.probe =	wacom_probe,
+	.remove =	wacom_remove,
+	.report =	wacom_wac_report,
+#ifdef CONFIG_PM
+	.resume =	wacom_resume,
+	.reset_resume =	wacom_reset_resume,
+#endif
+	.raw_event =	wacom_raw_event,
+};
+module_hid_driver(wacom_driver);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
new file mode 100644
index 0000000..5dd3a82
--- /dev/null
+++ b/drivers/hid/wacom_wac.c
@@ -0,0 +1,4779 @@
+/*
+ * drivers/input/tablet/wacom_wac.c
+ *
+ *  USB Wacom tablet support - Wacom specific code
+ *
+ */
+
+/*
+ * 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 "wacom_wac.h"
+#include "wacom.h"
+#include <linux/input/mt.h>
+
+/* resolution for penabled devices */
+#define WACOM_PL_RES		20
+#define WACOM_PENPRTN_RES	40
+#define WACOM_VOLITO_RES	50
+#define WACOM_GRAPHIRE_RES	80
+#define WACOM_INTUOS_RES	100
+#define WACOM_INTUOS3_RES	200
+
+/* Newer Cintiq and DTU have an offset between tablet and screen areas */
+#define WACOM_DTU_OFFSET	200
+#define WACOM_CINTIQ_OFFSET	400
+
+/*
+ * Scale factor relating reported contact size to logical contact area.
+ * 2^14/pi is a good approximation on Intuos5 and 3rd-gen Bamboo
+ */
+#define WACOM_CONTACT_AREA_SCALE 2607
+
+static bool touch_arbitration = 1;
+module_param(touch_arbitration, bool, 0644);
+MODULE_PARM_DESC(touch_arbitration, " on (Y) off (N)");
+
+static void wacom_report_numbered_buttons(struct input_dev *input_dev,
+				int button_count, int mask);
+
+static int wacom_numbered_button_to_key(int n);
+
+static void wacom_update_led(struct wacom *wacom, int button_count, int mask,
+			     int group);
+/*
+ * Percent of battery capacity for Graphire.
+ * 8th value means AC online and show 100% capacity.
+ */
+static unsigned short batcap_gr[8] = { 1, 15, 25, 35, 50, 70, 100, 100 };
+
+/*
+ * Percent of battery capacity for Intuos4 WL, AC has a separate bit.
+ */
+static unsigned short batcap_i4[8] = { 1, 15, 30, 45, 60, 70, 85, 100 };
+
+static void __wacom_notify_battery(struct wacom_battery *battery,
+				   int bat_status, int bat_capacity,
+				   bool bat_charging, bool bat_connected,
+				   bool ps_connected)
+{
+	bool changed = battery->bat_status       != bat_status    ||
+		       battery->battery_capacity != bat_capacity  ||
+		       battery->bat_charging     != bat_charging  ||
+		       battery->bat_connected    != bat_connected ||
+		       battery->ps_connected     != ps_connected;
+
+	if (changed) {
+		battery->bat_status = bat_status;
+		battery->battery_capacity = bat_capacity;
+		battery->bat_charging = bat_charging;
+		battery->bat_connected = bat_connected;
+		battery->ps_connected = ps_connected;
+
+		if (battery->battery)
+			power_supply_changed(battery->battery);
+	}
+}
+
+static void wacom_notify_battery(struct wacom_wac *wacom_wac,
+	int bat_status, int bat_capacity, bool bat_charging,
+	bool bat_connected, bool ps_connected)
+{
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+
+	__wacom_notify_battery(&wacom->battery, bat_status, bat_capacity,
+			       bat_charging, bat_connected, ps_connected);
+}
+
+static int wacom_penpartner_irq(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+
+	switch (data[0]) {
+	case 1:
+		if (data[5] & 0x80) {
+			wacom->tool[0] = (data[5] & 0x20) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+			wacom->id[0] = (data[5] & 0x20) ? ERASER_DEVICE_ID : STYLUS_DEVICE_ID;
+			input_report_key(input, wacom->tool[0], 1);
+			input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
+			input_report_abs(input, ABS_X, get_unaligned_le16(&data[1]));
+			input_report_abs(input, ABS_Y, get_unaligned_le16(&data[3]));
+			input_report_abs(input, ABS_PRESSURE, (signed char)data[6] + 127);
+			input_report_key(input, BTN_TOUCH, ((signed char)data[6] > -127));
+			input_report_key(input, BTN_STYLUS, (data[5] & 0x40));
+		} else {
+			input_report_key(input, wacom->tool[0], 0);
+			input_report_abs(input, ABS_MISC, 0); /* report tool id */
+			input_report_abs(input, ABS_PRESSURE, -1);
+			input_report_key(input, BTN_TOUCH, 0);
+		}
+		break;
+
+	case 2:
+		input_report_key(input, BTN_TOOL_PEN, 1);
+		input_report_abs(input, ABS_MISC, STYLUS_DEVICE_ID); /* report tool id */
+		input_report_abs(input, ABS_X, get_unaligned_le16(&data[1]));
+		input_report_abs(input, ABS_Y, get_unaligned_le16(&data[3]));
+		input_report_abs(input, ABS_PRESSURE, (signed char)data[6] + 127);
+		input_report_key(input, BTN_TOUCH, ((signed char)data[6] > -80) && !(data[5] & 0x20));
+		input_report_key(input, BTN_STYLUS, (data[5] & 0x40));
+		break;
+
+	default:
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+		return 0;
+        }
+
+	return 1;
+}
+
+static int wacom_pl_irq(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	int prox, pressure;
+
+	if (data[0] != WACOM_REPORT_PENABLED) {
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+		return 0;
+	}
+
+	prox = data[1] & 0x40;
+
+	if (!wacom->id[0]) {
+		if ((data[0] & 0x10) || (data[4] & 0x20)) {
+			wacom->tool[0] = BTN_TOOL_RUBBER;
+			wacom->id[0] = ERASER_DEVICE_ID;
+		}
+		else {
+			wacom->tool[0] = BTN_TOOL_PEN;
+			wacom->id[0] = STYLUS_DEVICE_ID;
+		}
+	}
+
+	/* If the eraser is in prox, STYLUS2 is always set. If we
+	 * mis-detected the type and notice that STYLUS2 isn't set
+	 * then force the eraser out of prox and let the pen in.
+	 */
+	if (wacom->tool[0] == BTN_TOOL_RUBBER && !(data[4] & 0x20)) {
+		input_report_key(input, BTN_TOOL_RUBBER, 0);
+		input_report_abs(input, ABS_MISC, 0);
+		input_sync(input);
+		wacom->tool[0] = BTN_TOOL_PEN;
+		wacom->id[0] = STYLUS_DEVICE_ID;
+	}
+
+	if (prox) {
+		pressure = (signed char)((data[7] << 1) | ((data[4] >> 2) & 1));
+		if (features->pressure_max > 255)
+			pressure = (pressure << 1) | ((data[4] >> 6) & 1);
+		pressure += (features->pressure_max + 1) / 2;
+
+		input_report_abs(input, ABS_X, data[3] | (data[2] << 7) | ((data[1] & 0x03) << 14));
+		input_report_abs(input, ABS_Y, data[6] | (data[5] << 7) | ((data[4] & 0x03) << 14));
+		input_report_abs(input, ABS_PRESSURE, pressure);
+
+		input_report_key(input, BTN_TOUCH, data[4] & 0x08);
+		input_report_key(input, BTN_STYLUS, data[4] & 0x10);
+		/* Only allow the stylus2 button to be reported for the pen tool. */
+		input_report_key(input, BTN_STYLUS2, (wacom->tool[0] == BTN_TOOL_PEN) && (data[4] & 0x20));
+	}
+
+	if (!prox)
+		wacom->id[0] = 0;
+	input_report_key(input, wacom->tool[0], prox);
+	input_report_abs(input, ABS_MISC, wacom->id[0]);
+	return 1;
+}
+
+static int wacom_ptu_irq(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+
+	if (data[0] != WACOM_REPORT_PENABLED) {
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+		return 0;
+	}
+
+	if (data[1] & 0x04) {
+		input_report_key(input, BTN_TOOL_RUBBER, data[1] & 0x20);
+		input_report_key(input, BTN_TOUCH, data[1] & 0x08);
+		wacom->id[0] = ERASER_DEVICE_ID;
+	} else {
+		input_report_key(input, BTN_TOOL_PEN, data[1] & 0x20);
+		input_report_key(input, BTN_TOUCH, data[1] & 0x01);
+		wacom->id[0] = STYLUS_DEVICE_ID;
+	}
+	input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
+	input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
+	input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
+	input_report_abs(input, ABS_PRESSURE, le16_to_cpup((__le16 *)&data[6]));
+	input_report_key(input, BTN_STYLUS, data[1] & 0x02);
+	input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
+	return 1;
+}
+
+static int wacom_dtu_irq(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	int prox = data[1] & 0x20;
+
+	dev_dbg(input->dev.parent,
+		"%s: received report #%d", __func__, data[0]);
+
+	if (prox) {
+		/* Going into proximity select tool */
+		wacom->tool[0] = (data[1] & 0x0c) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+		if (wacom->tool[0] == BTN_TOOL_PEN)
+			wacom->id[0] = STYLUS_DEVICE_ID;
+		else
+			wacom->id[0] = ERASER_DEVICE_ID;
+	}
+	input_report_key(input, BTN_STYLUS, data[1] & 0x02);
+	input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
+	input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
+	input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
+	input_report_abs(input, ABS_PRESSURE, ((data[7] & 0x01) << 8) | data[6]);
+	input_report_key(input, BTN_TOUCH, data[1] & 0x05);
+	if (!prox) /* out-prox */
+		wacom->id[0] = 0;
+	input_report_key(input, wacom->tool[0], prox);
+	input_report_abs(input, ABS_MISC, wacom->id[0]);
+	return 1;
+}
+
+static int wacom_dtus_irq(struct wacom_wac *wacom)
+{
+	char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	unsigned short prox, pressure = 0;
+
+	if (data[0] != WACOM_REPORT_DTUS && data[0] != WACOM_REPORT_DTUSPAD) {
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d", __func__, data[0]);
+		return 0;
+	} else if (data[0] == WACOM_REPORT_DTUSPAD) {
+		input = wacom->pad_input;
+		input_report_key(input, BTN_0, (data[1] & 0x01));
+		input_report_key(input, BTN_1, (data[1] & 0x02));
+		input_report_key(input, BTN_2, (data[1] & 0x04));
+		input_report_key(input, BTN_3, (data[1] & 0x08));
+		input_report_abs(input, ABS_MISC,
+				 data[1] & 0x0f ? PAD_DEVICE_ID : 0);
+		return 1;
+	} else {
+		prox = data[1] & 0x80;
+		if (prox) {
+			switch ((data[1] >> 3) & 3) {
+			case 1: /* Rubber */
+				wacom->tool[0] = BTN_TOOL_RUBBER;
+				wacom->id[0] = ERASER_DEVICE_ID;
+				break;
+
+			case 2: /* Pen */
+				wacom->tool[0] = BTN_TOOL_PEN;
+				wacom->id[0] = STYLUS_DEVICE_ID;
+				break;
+			}
+		}
+
+		input_report_key(input, BTN_STYLUS, data[1] & 0x20);
+		input_report_key(input, BTN_STYLUS2, data[1] & 0x40);
+		input_report_abs(input, ABS_X, get_unaligned_be16(&data[3]));
+		input_report_abs(input, ABS_Y, get_unaligned_be16(&data[5]));
+		pressure = ((data[1] & 0x03) << 8) | (data[2] & 0xff);
+		input_report_abs(input, ABS_PRESSURE, pressure);
+		input_report_key(input, BTN_TOUCH, pressure > 10);
+
+		if (!prox) /* out-prox */
+			wacom->id[0] = 0;
+		input_report_key(input, wacom->tool[0], prox);
+		input_report_abs(input, ABS_MISC, wacom->id[0]);
+		return 1;
+	}
+}
+
+static int wacom_graphire_irq(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	struct input_dev *pad_input = wacom->pad_input;
+	int battery_capacity, ps_connected;
+	int prox;
+	int rw = 0;
+	int retval = 0;
+
+	if (features->type == GRAPHIRE_BT) {
+		if (data[0] != WACOM_REPORT_PENABLED_BT) {
+			dev_dbg(input->dev.parent,
+				"%s: received unknown report #%d\n", __func__,
+				data[0]);
+			goto exit;
+		}
+	} else if (data[0] != WACOM_REPORT_PENABLED) {
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+		goto exit;
+	}
+
+	prox = data[1] & 0x80;
+	if (prox || wacom->id[0]) {
+		if (prox) {
+			switch ((data[1] >> 5) & 3) {
+
+			case 0:	/* Pen */
+				wacom->tool[0] = BTN_TOOL_PEN;
+				wacom->id[0] = STYLUS_DEVICE_ID;
+				break;
+
+			case 1: /* Rubber */
+				wacom->tool[0] = BTN_TOOL_RUBBER;
+				wacom->id[0] = ERASER_DEVICE_ID;
+				break;
+
+			case 2: /* Mouse with wheel */
+				input_report_key(input, BTN_MIDDLE, data[1] & 0x04);
+				/* fall through */
+
+			case 3: /* Mouse without wheel */
+				wacom->tool[0] = BTN_TOOL_MOUSE;
+				wacom->id[0] = CURSOR_DEVICE_ID;
+				break;
+			}
+		}
+		input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
+		input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
+		if (wacom->tool[0] != BTN_TOOL_MOUSE) {
+			if (features->type == GRAPHIRE_BT)
+				input_report_abs(input, ABS_PRESSURE, data[6] |
+					(((__u16) (data[1] & 0x08)) << 5));
+			else
+				input_report_abs(input, ABS_PRESSURE, data[6] |
+					((data[7] & 0x03) << 8));
+			input_report_key(input, BTN_TOUCH, data[1] & 0x01);
+			input_report_key(input, BTN_STYLUS, data[1] & 0x02);
+			input_report_key(input, BTN_STYLUS2, data[1] & 0x04);
+		} else {
+			input_report_key(input, BTN_LEFT, data[1] & 0x01);
+			input_report_key(input, BTN_RIGHT, data[1] & 0x02);
+			if (features->type == WACOM_G4 ||
+					features->type == WACOM_MO) {
+				input_report_abs(input, ABS_DISTANCE, data[6] & 0x3f);
+				rw = (data[7] & 0x04) - (data[7] & 0x03);
+			} else if (features->type == GRAPHIRE_BT) {
+				/* Compute distance between mouse and tablet */
+				rw = 44 - (data[6] >> 2);
+				rw = clamp_val(rw, 0, 31);
+				input_report_abs(input, ABS_DISTANCE, rw);
+				if (((data[1] >> 5) & 3) == 2) {
+					/* Mouse with wheel */
+					input_report_key(input, BTN_MIDDLE,
+							data[1] & 0x04);
+					rw = (data[6] & 0x01) ? -1 :
+						(data[6] & 0x02) ? 1 : 0;
+				} else {
+					rw = 0;
+				}
+			} else {
+				input_report_abs(input, ABS_DISTANCE, data[7] & 0x3f);
+				rw = -(signed char)data[6];
+			}
+			input_report_rel(input, REL_WHEEL, rw);
+		}
+
+		if (!prox)
+			wacom->id[0] = 0;
+		input_report_abs(input, ABS_MISC, wacom->id[0]); /* report tool id */
+		input_report_key(input, wacom->tool[0], prox);
+		input_sync(input); /* sync last event */
+	}
+
+	/* send pad data */
+	switch (features->type) {
+	case WACOM_G4:
+		prox = data[7] & 0xf8;
+		if (prox || wacom->id[1]) {
+			wacom->id[1] = PAD_DEVICE_ID;
+			input_report_key(pad_input, BTN_BACK, (data[7] & 0x40));
+			input_report_key(pad_input, BTN_FORWARD, (data[7] & 0x80));
+			rw = ((data[7] & 0x18) >> 3) - ((data[7] & 0x20) >> 3);
+			input_report_rel(pad_input, REL_WHEEL, rw);
+			if (!prox)
+				wacom->id[1] = 0;
+			input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
+			retval = 1;
+		}
+		break;
+
+	case WACOM_MO:
+		prox = (data[7] & 0xf8) || data[8];
+		if (prox || wacom->id[1]) {
+			wacom->id[1] = PAD_DEVICE_ID;
+			input_report_key(pad_input, BTN_BACK, (data[7] & 0x08));
+			input_report_key(pad_input, BTN_LEFT, (data[7] & 0x20));
+			input_report_key(pad_input, BTN_FORWARD, (data[7] & 0x10));
+			input_report_key(pad_input, BTN_RIGHT, (data[7] & 0x40));
+			input_report_abs(pad_input, ABS_WHEEL, (data[8] & 0x7f));
+			if (!prox)
+				wacom->id[1] = 0;
+			input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
+			retval = 1;
+		}
+		break;
+	case GRAPHIRE_BT:
+		prox = data[7] & 0x03;
+		if (prox || wacom->id[1]) {
+			wacom->id[1] = PAD_DEVICE_ID;
+			input_report_key(pad_input, BTN_0, (data[7] & 0x02));
+			input_report_key(pad_input, BTN_1, (data[7] & 0x01));
+			if (!prox)
+				wacom->id[1] = 0;
+			input_report_abs(pad_input, ABS_MISC, wacom->id[1]);
+			retval = 1;
+		}
+		break;
+	}
+
+	/* Store current battery capacity and power supply state */
+	if (features->type == GRAPHIRE_BT) {
+		rw = (data[7] >> 2 & 0x07);
+		battery_capacity = batcap_gr[rw];
+		ps_connected = rw == 7;
+		wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
+				     battery_capacity, ps_connected, 1,
+				     ps_connected);
+	}
+exit:
+	return retval;
+}
+
+static void wacom_intuos_schedule_prox_event(struct wacom_wac *wacom_wac)
+{
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+	struct wacom_features *features = &wacom_wac->features;
+	struct hid_report *r;
+	struct hid_report_enum *re;
+
+	re = &(wacom->hdev->report_enum[HID_FEATURE_REPORT]);
+	if (features->type == INTUOSHT2)
+		r = re->report_id_hash[WACOM_REPORT_INTUOSHT2_ID];
+	else
+		r = re->report_id_hash[WACOM_REPORT_INTUOS_ID1];
+	if (r) {
+		hid_hw_request(wacom->hdev, r, HID_REQ_GET_REPORT);
+	}
+}
+
+static int wacom_intuos_pad(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pad_input;
+	int i;
+	int buttons = 0, nbuttons = features->numbered_buttons;
+	int keys = 0, nkeys = 0;
+	int ring1 = 0, ring2 = 0;
+	int strip1 = 0, strip2 = 0;
+	bool prox = false;
+
+	/* pad packets. Works as a second tool and is always in prox */
+	if (!(data[0] == WACOM_REPORT_INTUOSPAD || data[0] == WACOM_REPORT_INTUOS5PAD ||
+	      data[0] == WACOM_REPORT_CINTIQPAD))
+		return 0;
+
+	if (features->type >= INTUOS4S && features->type <= INTUOS4L) {
+		buttons = (data[3] << 1) | (data[2] & 0x01);
+		ring1 = data[1];
+	} else if (features->type == DTK) {
+		buttons = data[6];
+	} else if (features->type == WACOM_13HD) {
+		buttons = (data[4] << 1) | (data[3] & 0x01);
+	} else if (features->type == WACOM_24HD) {
+		buttons = (data[8] << 8) | data[6];
+		ring1 = data[1];
+		ring2 = data[2];
+
+		/*
+		 * Three "buttons" are available on the 24HD which are
+		 * physically implemented as a touchstrip. Each button
+		 * is approximately 3 bits wide with a 2 bit spacing.
+		 * The raw touchstrip bits are stored at:
+		 *    ((data[3] & 0x1f) << 8) | data[4])
+		 */
+		nkeys = 3;
+		keys = ((data[3] & 0x1C) ? 1<<2 : 0) |
+		       ((data[4] & 0xE0) ? 1<<1 : 0) |
+		       ((data[4] & 0x07) ? 1<<0 : 0);
+	} else if (features->type == WACOM_27QHD) {
+		nkeys = 3;
+		keys = data[2] & 0x07;
+
+		input_report_abs(input, ABS_X, be16_to_cpup((__be16 *)&data[4]));
+		input_report_abs(input, ABS_Y, be16_to_cpup((__be16 *)&data[6]));
+		input_report_abs(input, ABS_Z, be16_to_cpup((__be16 *)&data[8]));
+	} else if (features->type == CINTIQ_HYBRID) {
+		/*
+		 * Do not send hardware buttons under Android. They
+		 * are already sent to the system through GPIO (and
+		 * have different meaning).
+		 *
+		 * d-pad right  -> data[4] & 0x10
+		 * d-pad up     -> data[4] & 0x20
+		 * d-pad left   -> data[4] & 0x40
+		 * d-pad down   -> data[4] & 0x80
+		 * d-pad center -> data[3] & 0x01
+		 */
+		buttons = (data[4] << 1) | (data[3] & 0x01);
+	} else if (features->type == CINTIQ_COMPANION_2) {
+		/* d-pad right  -> data[4] & 0x10
+		 * d-pad up     -> data[4] & 0x20
+		 * d-pad left   -> data[4] & 0x40
+		 * d-pad down   -> data[4] & 0x80
+		 * d-pad center -> data[3] & 0x01
+		 */
+		buttons = ((data[2] >> 4) << 7) |
+		          ((data[1] & 0x04) << 6) |
+		          ((data[2] & 0x0F) << 2) |
+		          (data[1] & 0x03);
+	} else if (features->type >= INTUOS5S && features->type <= INTUOSPL) {
+		/*
+		 * ExpressKeys on Intuos5/Intuos Pro have a capacitive sensor in
+		 * addition to the mechanical switch. Switch data is
+		 * stored in data[4], capacitive data in data[5].
+		 *
+		 * Touch ring mode switch (data[3]) has no capacitive sensor
+		 */
+		buttons = (data[4] << 1) | (data[3] & 0x01);
+		ring1 = data[2];
+	} else {
+		if (features->type == WACOM_21UX2 || features->type == WACOM_22HD) {
+			buttons = (data[8] << 10) | ((data[7] & 0x01) << 9) |
+			          (data[6] << 1) | (data[5] & 0x01);
+
+			if (features->type == WACOM_22HD) {
+				nkeys = 3;
+				keys = data[9] & 0x07;
+			}
+		} else {
+			buttons = ((data[6] & 0x10) << 5)  |
+			          ((data[5] & 0x10) << 4)  |
+			          ((data[6] & 0x0F) << 4)  |
+			          (data[5] & 0x0F);
+		}
+		strip1 = ((data[1] & 0x1f) << 8) | data[2];
+		strip2 = ((data[3] & 0x1f) << 8) | data[4];
+	}
+
+	prox = (buttons & ~(~0 << nbuttons)) | (keys & ~(~0 << nkeys)) |
+	       (ring1 & 0x80) | (ring2 & 0x80) | strip1 | strip2;
+
+	wacom_report_numbered_buttons(input, nbuttons, buttons);
+
+	for (i = 0; i < nkeys; i++)
+		input_report_key(input, KEY_PROG1 + i, keys & (1 << i));
+
+	input_report_abs(input, ABS_RX, strip1);
+	input_report_abs(input, ABS_RY, strip2);
+
+	input_report_abs(input, ABS_WHEEL,    (ring1 & 0x80) ? (ring1 & 0x7f) : 0);
+	input_report_abs(input, ABS_THROTTLE, (ring2 & 0x80) ? (ring2 & 0x7f) : 0);
+
+	input_report_key(input, wacom->tool[1], prox ? 1 : 0);
+	input_report_abs(input, ABS_MISC, prox ? PAD_DEVICE_ID : 0);
+
+	input_event(input, EV_MSC, MSC_SERIAL, 0xffffffff);
+
+	return 1;
+}
+
+static int wacom_intuos_id_mangle(int tool_id)
+{
+	return (tool_id & ~0xFFF) << 4 | (tool_id & 0xFFF);
+}
+
+static int wacom_intuos_get_tool_type(int tool_id)
+{
+	int tool_type;
+
+	switch (tool_id) {
+	case 0x812: /* Inking pen */
+	case 0x801: /* Intuos3 Inking pen */
+	case 0x12802: /* Intuos4/5 Inking Pen */
+	case 0x012:
+		tool_type = BTN_TOOL_PENCIL;
+		break;
+
+	case 0x822: /* Pen */
+	case 0x842:
+	case 0x852:
+	case 0x823: /* Intuos3 Grip Pen */
+	case 0x813: /* Intuos3 Classic Pen */
+	case 0x885: /* Intuos3 Marker Pen */
+	case 0x802: /* Intuos4/5 13HD/24HD General Pen */
+	case 0x804: /* Intuos4/5 13HD/24HD Marker Pen */
+	case 0x8e2: /* IntuosHT2 pen */
+	case 0x022:
+	case 0x10804: /* Intuos4/5 13HD/24HD Art Pen */
+	case 0x14802: /* Intuos4/5 13HD/24HD Classic Pen */
+	case 0x16802: /* Cintiq 13HD Pro Pen */
+	case 0x18802: /* DTH2242 Pen */
+	case 0x10802: /* Intuos4/5 13HD/24HD General Pen */
+		tool_type = BTN_TOOL_PEN;
+		break;
+
+	case 0x832: /* Stroke pen */
+	case 0x032:
+		tool_type = BTN_TOOL_BRUSH;
+		break;
+
+	case 0x007: /* Mouse 4D and 2D */
+	case 0x09c:
+	case 0x094:
+	case 0x017: /* Intuos3 2D Mouse */
+	case 0x806: /* Intuos4 Mouse */
+		tool_type = BTN_TOOL_MOUSE;
+		break;
+
+	case 0x096: /* Lens cursor */
+	case 0x097: /* Intuos3 Lens cursor */
+	case 0x006: /* Intuos4 Lens cursor */
+		tool_type = BTN_TOOL_LENS;
+		break;
+
+	case 0x82a: /* Eraser */
+	case 0x84a:
+	case 0x85a:
+	case 0x91a:
+	case 0xd1a:
+	case 0x0fa:
+	case 0x82b: /* Intuos3 Grip Pen Eraser */
+	case 0x81b: /* Intuos3 Classic Pen Eraser */
+	case 0x91b: /* Intuos3 Airbrush Eraser */
+	case 0x80c: /* Intuos4/5 13HD/24HD Marker Pen Eraser */
+	case 0x80a: /* Intuos4/5 13HD/24HD General Pen Eraser */
+	case 0x90a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
+	case 0x1480a: /* Intuos4/5 13HD/24HD Classic Pen Eraser */
+	case 0x1090a: /* Intuos4/5 13HD/24HD Airbrush Eraser */
+	case 0x1080c: /* Intuos4/5 13HD/24HD Art Pen Eraser */
+	case 0x1680a: /* Cintiq 13HD Pro Pen Eraser */
+	case 0x1880a: /* DTH2242 Eraser */
+	case 0x1080a: /* Intuos4/5 13HD/24HD General Pen Eraser */
+		tool_type = BTN_TOOL_RUBBER;
+		break;
+
+	case 0xd12:
+	case 0x912:
+	case 0x112:
+	case 0x913: /* Intuos3 Airbrush */
+	case 0x902: /* Intuos4/5 13HD/24HD Airbrush */
+	case 0x10902: /* Intuos4/5 13HD/24HD Airbrush */
+		tool_type = BTN_TOOL_AIRBRUSH;
+		break;
+
+	default: /* Unknown tool */
+		tool_type = BTN_TOOL_PEN;
+		break;
+	}
+	return tool_type;
+}
+
+static void wacom_exit_report(struct wacom_wac *wacom)
+{
+	struct input_dev *input = wacom->pen_input;
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;
+
+	/*
+	 * Reset all states otherwise we lose the initial states
+	 * when in-prox next time
+	 */
+	input_report_abs(input, ABS_X, 0);
+	input_report_abs(input, ABS_Y, 0);
+	input_report_abs(input, ABS_DISTANCE, 0);
+	input_report_abs(input, ABS_TILT_X, 0);
+	input_report_abs(input, ABS_TILT_Y, 0);
+	if (wacom->tool[idx] >= BTN_TOOL_MOUSE) {
+		input_report_key(input, BTN_LEFT, 0);
+		input_report_key(input, BTN_MIDDLE, 0);
+		input_report_key(input, BTN_RIGHT, 0);
+		input_report_key(input, BTN_SIDE, 0);
+		input_report_key(input, BTN_EXTRA, 0);
+		input_report_abs(input, ABS_THROTTLE, 0);
+		input_report_abs(input, ABS_RZ, 0);
+	} else {
+		input_report_abs(input, ABS_PRESSURE, 0);
+		input_report_key(input, BTN_STYLUS, 0);
+		input_report_key(input, BTN_STYLUS2, 0);
+		input_report_key(input, BTN_TOUCH, 0);
+		input_report_abs(input, ABS_WHEEL, 0);
+		if (features->type >= INTUOS3S)
+			input_report_abs(input, ABS_Z, 0);
+	}
+	input_report_key(input, wacom->tool[idx], 0);
+	input_report_abs(input, ABS_MISC, 0); /* reset tool id */
+	input_event(input, EV_MSC, MSC_SERIAL, wacom->serial[idx]);
+	wacom->id[idx] = 0;
+}
+
+static int wacom_intuos_inout(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;
+
+	if (!(((data[1] & 0xfc) == 0xc0) ||  /* in prox */
+	    ((data[1] & 0xfe) == 0x20) ||    /* in range */
+	    ((data[1] & 0xfe) == 0x80)))     /* out prox */
+		return 0;
+
+	/* Enter report */
+	if ((data[1] & 0xfc) == 0xc0) {
+		/* serial number of the tool */
+		wacom->serial[idx] = ((data[3] & 0x0f) << 28) +
+			(data[4] << 20) + (data[5] << 12) +
+			(data[6] << 4) + (data[7] >> 4);
+
+		wacom->id[idx] = (data[2] << 4) | (data[3] >> 4) |
+		     ((data[7] & 0x0f) << 16) | ((data[8] & 0xf0) << 8);
+
+		wacom->tool[idx] = wacom_intuos_get_tool_type(wacom->id[idx]);
+
+		wacom->shared->stylus_in_proximity = true;
+		return 1;
+	}
+
+	/* in Range */
+	if ((data[1] & 0xfe) == 0x20) {
+		if (features->type != INTUOSHT2)
+			wacom->shared->stylus_in_proximity = true;
+
+		/* in Range while exiting */
+		if (wacom->reporting_data) {
+			input_report_key(input, BTN_TOUCH, 0);
+			input_report_abs(input, ABS_PRESSURE, 0);
+			input_report_abs(input, ABS_DISTANCE, wacom->features.distance_max);
+			return 2;
+		}
+		return 1;
+	}
+
+	/* Exit report */
+	if ((data[1] & 0xfe) == 0x80) {
+		wacom->shared->stylus_in_proximity = false;
+		wacom->reporting_data = false;
+
+		/* don't report exit if we don't know the ID */
+		if (!wacom->id[idx])
+			return 1;
+
+		wacom_exit_report(wacom);
+		return 2;
+	}
+
+	return 0;
+}
+
+static inline bool report_touch_events(struct wacom_wac *wacom)
+{
+	return (touch_arbitration ? !wacom->shared->stylus_in_proximity : 1);
+}
+
+static inline bool delay_pen_events(struct wacom_wac *wacom)
+{
+	return (wacom->shared->touch_down && touch_arbitration);
+}
+
+static int wacom_intuos_general(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	int idx = (features->type == INTUOS) ? (data[1] & 0x01) : 0;
+	unsigned char type = (data[1] >> 1) & 0x0F;
+	unsigned int x, y, distance, t;
+
+	if (data[0] != WACOM_REPORT_PENABLED && data[0] != WACOM_REPORT_CINTIQ &&
+		data[0] != WACOM_REPORT_INTUOS_PEN)
+		return 0;
+
+	if (delay_pen_events(wacom))
+		return 1;
+
+	/* don't report events if we don't know the tool ID */
+	if (!wacom->id[idx]) {
+		/* but reschedule a read of the current tool */
+		wacom_intuos_schedule_prox_event(wacom);
+		return 1;
+	}
+
+	/*
+	 * don't report events for invalid data
+	 */
+	/* older I4 styli don't work with new Cintiqs */
+	if ((!((wacom->id[idx] >> 16) & 0x01) &&
+			(features->type == WACOM_21UX2)) ||
+	    /* Only large Intuos support Lense Cursor */
+	    (wacom->tool[idx] == BTN_TOOL_LENS &&
+		(features->type == INTUOS3 ||
+		 features->type == INTUOS3S ||
+		 features->type == INTUOS4 ||
+		 features->type == INTUOS4S ||
+		 features->type == INTUOS5 ||
+		 features->type == INTUOS5S ||
+		 features->type == INTUOSPM ||
+		 features->type == INTUOSPS)) ||
+	   /* Cintiq doesn't send data when RDY bit isn't set */
+	   (features->type == CINTIQ && !(data[1] & 0x40)))
+		return 1;
+
+	x = (be16_to_cpup((__be16 *)&data[2]) << 1) | ((data[9] >> 1) & 1);
+	y = (be16_to_cpup((__be16 *)&data[4]) << 1) | (data[9] & 1);
+	distance = data[9] >> 2;
+	if (features->type < INTUOS3S) {
+		x >>= 1;
+		y >>= 1;
+		distance >>= 1;
+	}
+	input_report_abs(input, ABS_X, x);
+	input_report_abs(input, ABS_Y, y);
+	input_report_abs(input, ABS_DISTANCE, distance);
+
+	switch (type) {
+	case 0x00:
+	case 0x01:
+	case 0x02:
+	case 0x03:
+		/* general pen packet */
+		t = (data[6] << 3) | ((data[7] & 0xC0) >> 5) | (data[1] & 1);
+		if (features->pressure_max < 2047)
+			t >>= 1;
+		input_report_abs(input, ABS_PRESSURE, t);
+		if (features->type != INTUOSHT2) {
+		    input_report_abs(input, ABS_TILT_X,
+				 (((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
+		    input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
+		}
+		input_report_key(input, BTN_STYLUS, data[1] & 2);
+		input_report_key(input, BTN_STYLUS2, data[1] & 4);
+		input_report_key(input, BTN_TOUCH, t > 10);
+		break;
+
+	case 0x0a:
+		/* airbrush second packet */
+		input_report_abs(input, ABS_WHEEL,
+				(data[6] << 2) | ((data[7] >> 6) & 3));
+		input_report_abs(input, ABS_TILT_X,
+				 (((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
+		input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
+		break;
+
+	case 0x05:
+		/* Rotation packet */
+		if (features->type >= INTUOS3S) {
+			/* I3 marker pen rotation */
+			t = (data[6] << 3) | ((data[7] >> 5) & 7);
+			t = (data[7] & 0x20) ? ((t > 900) ? ((t-1) / 2 - 1350) :
+				((t-1) / 2 + 450)) : (450 - t / 2) ;
+			input_report_abs(input, ABS_Z, t);
+		} else {
+			/* 4D mouse 2nd packet */
+			t = (data[6] << 3) | ((data[7] >> 5) & 7);
+			input_report_abs(input, ABS_RZ, (data[7] & 0x20) ?
+				((t - 1) / 2) : -t / 2);
+		}
+		break;
+
+	case 0x04:
+		/* 4D mouse 1st packet */
+		input_report_key(input, BTN_LEFT,   data[8] & 0x01);
+		input_report_key(input, BTN_MIDDLE, data[8] & 0x02);
+		input_report_key(input, BTN_RIGHT,  data[8] & 0x04);
+
+		input_report_key(input, BTN_SIDE,   data[8] & 0x20);
+		input_report_key(input, BTN_EXTRA,  data[8] & 0x10);
+		t = (data[6] << 2) | ((data[7] >> 6) & 3);
+		input_report_abs(input, ABS_THROTTLE, (data[8] & 0x08) ? -t : t);
+		break;
+
+	case 0x06:
+		/* I4 mouse */
+		input_report_key(input, BTN_LEFT,   data[6] & 0x01);
+		input_report_key(input, BTN_MIDDLE, data[6] & 0x02);
+		input_report_key(input, BTN_RIGHT,  data[6] & 0x04);
+		input_report_rel(input, REL_WHEEL, ((data[7] & 0x80) >> 7)
+				 - ((data[7] & 0x40) >> 6));
+		input_report_key(input, BTN_SIDE,   data[6] & 0x08);
+		input_report_key(input, BTN_EXTRA,  data[6] & 0x10);
+
+		input_report_abs(input, ABS_TILT_X,
+			(((data[7] << 1) & 0x7e) | (data[8] >> 7)) - 64);
+		input_report_abs(input, ABS_TILT_Y, (data[8] & 0x7f) - 64);
+		break;
+
+	case 0x08:
+		if (wacom->tool[idx] == BTN_TOOL_MOUSE) {
+			/* 2D mouse packet */
+			input_report_key(input, BTN_LEFT,   data[8] & 0x04);
+			input_report_key(input, BTN_MIDDLE, data[8] & 0x08);
+			input_report_key(input, BTN_RIGHT,  data[8] & 0x10);
+			input_report_rel(input, REL_WHEEL, (data[8] & 0x01)
+					 - ((data[8] & 0x02) >> 1));
+
+			/* I3 2D mouse side buttons */
+			if (features->type >= INTUOS3S && features->type <= INTUOS3L) {
+				input_report_key(input, BTN_SIDE,   data[8] & 0x40);
+				input_report_key(input, BTN_EXTRA,  data[8] & 0x20);
+			}
+		}
+		else if (wacom->tool[idx] == BTN_TOOL_LENS) {
+			/* Lens cursor packets */
+			input_report_key(input, BTN_LEFT,   data[8] & 0x01);
+			input_report_key(input, BTN_MIDDLE, data[8] & 0x02);
+			input_report_key(input, BTN_RIGHT,  data[8] & 0x04);
+			input_report_key(input, BTN_SIDE,   data[8] & 0x10);
+			input_report_key(input, BTN_EXTRA,  data[8] & 0x08);
+		}
+		break;
+
+	case 0x07:
+	case 0x09:
+	case 0x0b:
+	case 0x0c:
+	case 0x0d:
+	case 0x0e:
+	case 0x0f:
+		/* unhandled */
+		break;
+	}
+
+	input_report_abs(input, ABS_MISC,
+			 wacom_intuos_id_mangle(wacom->id[idx])); /* report tool id */
+	input_report_key(input, wacom->tool[idx], 1);
+	input_event(input, EV_MSC, MSC_SERIAL, wacom->serial[idx]);
+	wacom->reporting_data = true;
+	return 2;
+}
+
+static int wacom_intuos_irq(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	int result;
+
+	if (data[0] != WACOM_REPORT_PENABLED &&
+	    data[0] != WACOM_REPORT_INTUOS_ID1 &&
+	    data[0] != WACOM_REPORT_INTUOS_ID2 &&
+	    data[0] != WACOM_REPORT_INTUOSPAD &&
+	    data[0] != WACOM_REPORT_INTUOS_PEN &&
+	    data[0] != WACOM_REPORT_CINTIQ &&
+	    data[0] != WACOM_REPORT_CINTIQPAD &&
+	    data[0] != WACOM_REPORT_INTUOS5PAD) {
+		dev_dbg(input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+                return 0;
+	}
+
+	/* process pad events */
+	result = wacom_intuos_pad(wacom);
+	if (result)
+		return result;
+
+	/* process in/out prox events */
+	result = wacom_intuos_inout(wacom);
+	if (result)
+		return result - 1;
+
+	/* process general packets */
+	result = wacom_intuos_general(wacom);
+	if (result)
+		return result - 1;
+
+	return 0;
+}
+
+static int wacom_remote_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+	unsigned char *data = wacom_wac->data;
+	struct input_dev *input;
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+	struct wacom_remote *remote = wacom->remote;
+	int bat_charging, bat_percent, touch_ring_mode;
+	__u32 serial;
+	int i, index = -1;
+	unsigned long flags;
+
+	if (data[0] != WACOM_REPORT_REMOTE) {
+		hid_dbg(wacom->hdev, "%s: received unknown report #%d",
+			__func__, data[0]);
+		return 0;
+	}
+
+	serial = data[3] + (data[4] << 8) + (data[5] << 16);
+	wacom_wac->id[0] = PAD_DEVICE_ID;
+
+	spin_lock_irqsave(&remote->remote_lock, flags);
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		if (remote->remotes[i].serial == serial) {
+			index = i;
+			break;
+		}
+	}
+
+	if (index < 0 || !remote->remotes[index].registered)
+		goto out;
+
+	input = remote->remotes[index].input;
+
+	input_report_key(input, BTN_0, (data[9] & 0x01));
+	input_report_key(input, BTN_1, (data[9] & 0x02));
+	input_report_key(input, BTN_2, (data[9] & 0x04));
+	input_report_key(input, BTN_3, (data[9] & 0x08));
+	input_report_key(input, BTN_4, (data[9] & 0x10));
+	input_report_key(input, BTN_5, (data[9] & 0x20));
+	input_report_key(input, BTN_6, (data[9] & 0x40));
+	input_report_key(input, BTN_7, (data[9] & 0x80));
+
+	input_report_key(input, BTN_8, (data[10] & 0x01));
+	input_report_key(input, BTN_9, (data[10] & 0x02));
+	input_report_key(input, BTN_A, (data[10] & 0x04));
+	input_report_key(input, BTN_B, (data[10] & 0x08));
+	input_report_key(input, BTN_C, (data[10] & 0x10));
+	input_report_key(input, BTN_X, (data[10] & 0x20));
+	input_report_key(input, BTN_Y, (data[10] & 0x40));
+	input_report_key(input, BTN_Z, (data[10] & 0x80));
+
+	input_report_key(input, BTN_BASE, (data[11] & 0x01));
+	input_report_key(input, BTN_BASE2, (data[11] & 0x02));
+
+	if (data[12] & 0x80)
+		input_report_abs(input, ABS_WHEEL, (data[12] & 0x7f));
+	else
+		input_report_abs(input, ABS_WHEEL, 0);
+
+	bat_percent = data[7] & 0x7f;
+	bat_charging = !!(data[7] & 0x80);
+
+	if (data[9] | data[10] | (data[11] & 0x03) | data[12])
+		input_report_abs(input, ABS_MISC, PAD_DEVICE_ID);
+	else
+		input_report_abs(input, ABS_MISC, 0);
+
+	input_event(input, EV_MSC, MSC_SERIAL, serial);
+
+	input_sync(input);
+
+	/*Which mode select (LED light) is currently on?*/
+	touch_ring_mode = (data[11] & 0xC0) >> 6;
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		if (remote->remotes[i].serial == serial)
+			wacom->led.groups[i].select = touch_ring_mode;
+	}
+
+	__wacom_notify_battery(&remote->remotes[index].battery,
+				WACOM_POWER_SUPPLY_STATUS_AUTO, bat_percent,
+				bat_charging, 1, bat_charging);
+
+out:
+	spin_unlock_irqrestore(&remote->remote_lock, flags);
+	return 0;
+}
+
+static void wacom_remote_status_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+	unsigned char *data = wacom_wac->data;
+	struct wacom_remote *remote = wacom->remote;
+	struct wacom_remote_data remote_data;
+	unsigned long flags;
+	int i, ret;
+
+	if (data[0] != WACOM_REPORT_DEVICE_LIST)
+		return;
+
+	memset(&remote_data, 0, sizeof(struct wacom_remote_data));
+
+	for (i = 0; i < WACOM_MAX_REMOTES; i++) {
+		int j = i * 6;
+		int serial = (data[j+6] << 16) + (data[j+5] << 8) + data[j+4];
+		bool connected = data[j+2];
+
+		remote_data.remote[i].serial = serial;
+		remote_data.remote[i].connected = connected;
+	}
+
+	spin_lock_irqsave(&remote->remote_lock, flags);
+
+	ret = kfifo_in(&remote->remote_fifo, &remote_data, sizeof(remote_data));
+	if (ret != sizeof(remote_data)) {
+		spin_unlock_irqrestore(&remote->remote_lock, flags);
+		hid_err(wacom->hdev, "Can't queue Remote status event.\n");
+		return;
+	}
+
+	spin_unlock_irqrestore(&remote->remote_lock, flags);
+
+	wacom_schedule_work(wacom_wac, WACOM_WORKER_REMOTE);
+}
+
+static int int_dist(int x1, int y1, int x2, int y2)
+{
+	int x = x2 - x1;
+	int y = y2 - y1;
+
+	return int_sqrt(x*x + y*y);
+}
+
+static void wacom_intuos_bt_process_data(struct wacom_wac *wacom,
+		unsigned char *data)
+{
+	memcpy(wacom->data, data, 10);
+	wacom_intuos_irq(wacom);
+
+	input_sync(wacom->pen_input);
+	if (wacom->pad_input)
+		input_sync(wacom->pad_input);
+}
+
+static int wacom_intuos_bt_irq(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char data[WACOM_PKGLEN_MAX];
+	int i = 1;
+	unsigned power_raw, battery_capacity, bat_charging, ps_connected;
+
+	memcpy(data, wacom->data, len);
+
+	switch (data[0]) {
+	case 0x04:
+		wacom_intuos_bt_process_data(wacom, data + i);
+		i += 10;
+		/* fall through */
+	case 0x03:
+		wacom_intuos_bt_process_data(wacom, data + i);
+		i += 10;
+		wacom_intuos_bt_process_data(wacom, data + i);
+		i += 10;
+		power_raw = data[i];
+		bat_charging = (power_raw & 0x08) ? 1 : 0;
+		ps_connected = (power_raw & 0x10) ? 1 : 0;
+		battery_capacity = batcap_i4[power_raw & 0x07];
+		wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
+				     battery_capacity, bat_charging,
+				     battery_capacity || bat_charging,
+				     ps_connected);
+		break;
+	default:
+		dev_dbg(wacom->pen_input->dev.parent,
+				"Unknown report: %d,%d size:%zu\n",
+				data[0], data[1], len);
+		return 0;
+	}
+	return 0;
+}
+
+static int wacom_wac_finger_count_touches(struct wacom_wac *wacom)
+{
+	struct input_dev *input = wacom->touch_input;
+	unsigned touch_max = wacom->features.touch_max;
+	int count = 0;
+	int i;
+
+	if (!touch_max)
+		return 0;
+
+	if (touch_max == 1)
+		return test_bit(BTN_TOUCH, input->key) &&
+			report_touch_events(wacom);
+
+	for (i = 0; i < input->mt->num_slots; i++) {
+		struct input_mt_slot *ps = &input->mt->slots[i];
+		int id = input_mt_get_value(ps, ABS_MT_TRACKING_ID);
+		if (id >= 0)
+			count++;
+	}
+
+	return count;
+}
+
+static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
+{
+	int pen_frame_len, pen_frames;
+
+	struct input_dev *pen_input = wacom->pen_input;
+	unsigned char *data = wacom->data;
+	int i;
+
+	if (wacom->features.type == INTUOSP2_BT) {
+		wacom->serial[0] = get_unaligned_le64(&data[99]);
+		wacom->id[0]     = get_unaligned_le16(&data[107]);
+		pen_frame_len = 14;
+		pen_frames = 7;
+	} else {
+		wacom->serial[0] = get_unaligned_le64(&data[33]);
+		wacom->id[0]     = get_unaligned_le16(&data[41]);
+		pen_frame_len = 8;
+		pen_frames = 4;
+	}
+
+	if (wacom->serial[0] >> 52 == 1) {
+		/* Add back in missing bits of ID for non-USI pens */
+		wacom->id[0] |= (wacom->serial[0] >> 32) & 0xFFFFF;
+	}
+	wacom->tool[0]   = wacom_intuos_get_tool_type(wacom_intuos_id_mangle(wacom->id[0]));
+
+	for (i = 0; i < pen_frames; i++) {
+		unsigned char *frame = &data[i*pen_frame_len + 1];
+		bool valid = frame[0] & 0x80;
+		bool prox = frame[0] & 0x40;
+		bool range = frame[0] & 0x20;
+
+		if (!valid)
+			continue;
+
+		if (!prox) {
+			wacom->shared->stylus_in_proximity = false;
+			wacom_exit_report(wacom);
+			input_sync(pen_input);
+			return;
+		}
+		if (range) {
+			input_report_abs(pen_input, ABS_X, get_unaligned_le16(&frame[1]));
+			input_report_abs(pen_input, ABS_Y, get_unaligned_le16(&frame[3]));
+
+			if (wacom->features.type == INTUOSP2_BT) {
+				/* Fix rotation alignment: userspace expects zero at left */
+				int16_t rotation =
+					(int16_t)get_unaligned_le16(&frame[9]);
+				rotation += 1800/4;
+
+				if (rotation > 899)
+					rotation -= 1800;
+
+				input_report_abs(pen_input, ABS_TILT_X,
+						 (char)frame[7]);
+				input_report_abs(pen_input, ABS_TILT_Y,
+						 (char)frame[8]);
+				input_report_abs(pen_input, ABS_Z, rotation);
+				input_report_abs(pen_input, ABS_WHEEL,
+						 get_unaligned_le16(&frame[11]));
+			}
+		}
+		input_report_abs(pen_input, ABS_PRESSURE, get_unaligned_le16(&frame[5]));
+		if (wacom->features.type == INTUOSP2_BT) {
+			input_report_abs(pen_input, ABS_DISTANCE,
+					 range ? frame[13] : wacom->features.distance_max);
+		} else {
+			input_report_abs(pen_input, ABS_DISTANCE,
+					 range ? frame[7] : wacom->features.distance_max);
+		}
+
+		input_report_key(pen_input, BTN_TOUCH, frame[0] & 0x01);
+		input_report_key(pen_input, BTN_STYLUS, frame[0] & 0x02);
+		input_report_key(pen_input, BTN_STYLUS2, frame[0] & 0x04);
+
+		input_report_key(pen_input, wacom->tool[0], prox);
+		input_event(pen_input, EV_MSC, MSC_SERIAL, wacom->serial[0]);
+		input_report_abs(pen_input, ABS_MISC,
+				 wacom_intuos_id_mangle(wacom->id[0])); /* report tool id */
+
+		wacom->shared->stylus_in_proximity = prox;
+
+		input_sync(pen_input);
+	}
+}
+
+static void wacom_intuos_pro2_bt_touch(struct wacom_wac *wacom)
+{
+	const int finger_touch_len = 8;
+	const int finger_frames = 4;
+	const int finger_frame_len = 43;
+
+	struct input_dev *touch_input = wacom->touch_input;
+	unsigned char *data = wacom->data;
+	int num_contacts_left = 5;
+	int i, j;
+
+	for (i = 0; i < finger_frames; i++) {
+		unsigned char *frame = &data[i*finger_frame_len + 109];
+		int current_num_contacts = frame[0] & 0x7F;
+		int contacts_to_send;
+
+		if (!(frame[0] & 0x80))
+			continue;
+
+		/*
+		 * First packet resets the counter since only the first
+		 * packet in series will have non-zero current_num_contacts.
+		 */
+		if (current_num_contacts)
+			wacom->num_contacts_left = current_num_contacts;
+
+		contacts_to_send = min(num_contacts_left, wacom->num_contacts_left);
+
+		for (j = 0; j < contacts_to_send; j++) {
+			unsigned char *touch = &frame[j*finger_touch_len + 1];
+			int slot = input_mt_get_slot_by_key(touch_input, touch[0]);
+			int x = get_unaligned_le16(&touch[2]);
+			int y = get_unaligned_le16(&touch[4]);
+			int w = touch[6] * input_abs_get_res(touch_input, ABS_MT_POSITION_X);
+			int h = touch[7] * input_abs_get_res(touch_input, ABS_MT_POSITION_Y);
+
+			if (slot < 0)
+				continue;
+
+			input_mt_slot(touch_input, slot);
+			input_mt_report_slot_state(touch_input, MT_TOOL_FINGER, touch[1] & 0x01);
+			input_report_abs(touch_input, ABS_MT_POSITION_X, x);
+			input_report_abs(touch_input, ABS_MT_POSITION_Y, y);
+			input_report_abs(touch_input, ABS_MT_TOUCH_MAJOR, max(w, h));
+			input_report_abs(touch_input, ABS_MT_TOUCH_MINOR, min(w, h));
+			input_report_abs(touch_input, ABS_MT_ORIENTATION, w > h);
+		}
+
+		input_mt_sync_frame(touch_input);
+
+		wacom->num_contacts_left -= contacts_to_send;
+		if (wacom->num_contacts_left <= 0) {
+			wacom->num_contacts_left = 0;
+			wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+		}
+	}
+
+	input_report_switch(touch_input, SW_MUTE_DEVICE, !(data[281] >> 7));
+	input_sync(touch_input);
+}
+
+static void wacom_intuos_pro2_bt_pad(struct wacom_wac *wacom)
+{
+	struct input_dev *pad_input = wacom->pad_input;
+	unsigned char *data = wacom->data;
+
+	int buttons = (data[282] << 1) | ((data[281] >> 6) & 0x01);
+	int ring = data[285] & 0x7F;
+	bool ringstatus = data[285] & 0x80;
+	bool prox = buttons || ringstatus;
+
+	/* Fix touchring data: userspace expects 0 at left and increasing clockwise */
+	ring = 71 - ring;
+	ring += 3*72/16;
+	if (ring > 71)
+		ring -= 72;
+
+	wacom_report_numbered_buttons(pad_input, 9, buttons);
+
+	input_report_abs(pad_input, ABS_WHEEL, ringstatus ? ring : 0);
+
+	input_report_key(pad_input, wacom->tool[1], prox ? 1 : 0);
+	input_report_abs(pad_input, ABS_MISC, prox ? PAD_DEVICE_ID : 0);
+	input_event(pad_input, EV_MSC, MSC_SERIAL, 0xffffffff);
+
+	input_sync(pad_input);
+}
+
+static void wacom_intuos_pro2_bt_battery(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+
+	bool chg = data[284] & 0x80;
+	int battery_status = data[284] & 0x7F;
+
+	wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
+			     battery_status, chg, 1, chg);
+}
+
+static void wacom_intuos_gen3_bt_pad(struct wacom_wac *wacom)
+{
+	struct input_dev *pad_input = wacom->pad_input;
+	unsigned char *data = wacom->data;
+
+	int buttons = data[44];
+
+	wacom_report_numbered_buttons(pad_input, 4, buttons);
+
+	input_report_key(pad_input, wacom->tool[1], buttons ? 1 : 0);
+	input_report_abs(pad_input, ABS_MISC, buttons ? PAD_DEVICE_ID : 0);
+	input_event(pad_input, EV_MSC, MSC_SERIAL, 0xffffffff);
+
+	input_sync(pad_input);
+}
+
+static void wacom_intuos_gen3_bt_battery(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+
+	bool chg = data[45] & 0x80;
+	int battery_status = data[45] & 0x7F;
+
+	wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
+			     battery_status, chg, 1, chg);
+}
+
+static int wacom_intuos_pro2_bt_irq(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char *data = wacom->data;
+
+	if (data[0] != 0x80 && data[0] != 0x81) {
+		dev_dbg(wacom->pen_input->dev.parent,
+			"%s: received unknown report #%d\n", __func__, data[0]);
+		return 0;
+	}
+
+	wacom_intuos_pro2_bt_pen(wacom);
+	if (wacom->features.type == INTUOSP2_BT) {
+		wacom_intuos_pro2_bt_touch(wacom);
+		wacom_intuos_pro2_bt_pad(wacom);
+		wacom_intuos_pro2_bt_battery(wacom);
+	} else {
+		wacom_intuos_gen3_bt_pad(wacom);
+		wacom_intuos_gen3_bt_battery(wacom);
+	}
+	return 0;
+}
+
+static int wacom_24hdt_irq(struct wacom_wac *wacom)
+{
+	struct input_dev *input = wacom->touch_input;
+	unsigned char *data = wacom->data;
+	int i;
+	int current_num_contacts = data[61];
+	int contacts_to_send = 0;
+	int num_contacts_left = 4; /* maximum contacts per packet */
+	int byte_per_packet = WACOM_BYTES_PER_24HDT_PACKET;
+	int y_offset = 2;
+
+	if (wacom->features.type == WACOM_27QHDT) {
+		current_num_contacts = data[63];
+		num_contacts_left = 10;
+		byte_per_packet = WACOM_BYTES_PER_QHDTHID_PACKET;
+		y_offset = 0;
+	}
+
+	/*
+	 * First packet resets the counter since only the first
+	 * packet in series will have non-zero current_num_contacts.
+	 */
+	if (current_num_contacts)
+		wacom->num_contacts_left = current_num_contacts;
+
+	contacts_to_send = min(num_contacts_left, wacom->num_contacts_left);
+
+	for (i = 0; i < contacts_to_send; i++) {
+		int offset = (byte_per_packet * i) + 1;
+		bool touch = (data[offset] & 0x1) && report_touch_events(wacom);
+		int slot = input_mt_get_slot_by_key(input, data[offset + 1]);
+
+		if (slot < 0)
+			continue;
+		input_mt_slot(input, slot);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+
+		if (touch) {
+			int t_x = get_unaligned_le16(&data[offset + 2]);
+			int t_y = get_unaligned_le16(&data[offset + 4 + y_offset]);
+
+			input_report_abs(input, ABS_MT_POSITION_X, t_x);
+			input_report_abs(input, ABS_MT_POSITION_Y, t_y);
+
+			if (wacom->features.type != WACOM_27QHDT) {
+				int c_x = get_unaligned_le16(&data[offset + 4]);
+				int c_y = get_unaligned_le16(&data[offset + 8]);
+				int w = get_unaligned_le16(&data[offset + 10]);
+				int h = get_unaligned_le16(&data[offset + 12]);
+
+				input_report_abs(input, ABS_MT_TOUCH_MAJOR, min(w,h));
+				input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+						 min(w, h) + int_dist(t_x, t_y, c_x, c_y));
+				input_report_abs(input, ABS_MT_WIDTH_MINOR, min(w, h));
+				input_report_abs(input, ABS_MT_ORIENTATION, w > h);
+			}
+		}
+	}
+	input_mt_sync_frame(input);
+
+	wacom->num_contacts_left -= contacts_to_send;
+	if (wacom->num_contacts_left <= 0) {
+		wacom->num_contacts_left = 0;
+		wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+	}
+	return 1;
+}
+
+static int wacom_mt_touch(struct wacom_wac *wacom)
+{
+	struct input_dev *input = wacom->touch_input;
+	unsigned char *data = wacom->data;
+	int i;
+	int current_num_contacts = data[2];
+	int contacts_to_send = 0;
+	int x_offset = 0;
+
+	/* MTTPC does not support Height and Width */
+	if (wacom->features.type == MTTPC || wacom->features.type == MTTPC_B)
+		x_offset = -4;
+
+	/*
+	 * First packet resets the counter since only the first
+	 * packet in series will have non-zero current_num_contacts.
+	 */
+	if (current_num_contacts)
+		wacom->num_contacts_left = current_num_contacts;
+
+	/* There are at most 5 contacts per packet */
+	contacts_to_send = min(5, wacom->num_contacts_left);
+
+	for (i = 0; i < contacts_to_send; i++) {
+		int offset = (WACOM_BYTES_PER_MT_PACKET + x_offset) * i + 3;
+		bool touch = (data[offset] & 0x1) && report_touch_events(wacom);
+		int id = get_unaligned_le16(&data[offset + 1]);
+		int slot = input_mt_get_slot_by_key(input, id);
+
+		if (slot < 0)
+			continue;
+
+		input_mt_slot(input, slot);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+		if (touch) {
+			int x = get_unaligned_le16(&data[offset + x_offset + 7]);
+			int y = get_unaligned_le16(&data[offset + x_offset + 9]);
+			input_report_abs(input, ABS_MT_POSITION_X, x);
+			input_report_abs(input, ABS_MT_POSITION_Y, y);
+		}
+	}
+	input_mt_sync_frame(input);
+
+	wacom->num_contacts_left -= contacts_to_send;
+	if (wacom->num_contacts_left <= 0) {
+		wacom->num_contacts_left = 0;
+		wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+	}
+	return 1;
+}
+
+static int wacom_tpc_mt_touch(struct wacom_wac *wacom)
+{
+	struct input_dev *input = wacom->touch_input;
+	unsigned char *data = wacom->data;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		int p = data[1] & (1 << i);
+		bool touch = p && report_touch_events(wacom);
+
+		input_mt_slot(input, i);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+		if (touch) {
+			int x = le16_to_cpup((__le16 *)&data[i * 2 + 2]) & 0x7fff;
+			int y = le16_to_cpup((__le16 *)&data[i * 2 + 6]) & 0x7fff;
+
+			input_report_abs(input, ABS_MT_POSITION_X, x);
+			input_report_abs(input, ABS_MT_POSITION_Y, y);
+		}
+	}
+	input_mt_sync_frame(input);
+
+	/* keep touch state for pen event */
+	wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+
+	return 1;
+}
+
+static int wacom_tpc_single_touch(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->touch_input;
+	bool prox = report_touch_events(wacom);
+	int x = 0, y = 0;
+
+	if (wacom->features.touch_max > 1 || len > WACOM_PKGLEN_TPC2FG)
+		return 0;
+
+	if (len == WACOM_PKGLEN_TPC1FG) {
+		prox = prox && (data[0] & 0x01);
+		x = get_unaligned_le16(&data[1]);
+		y = get_unaligned_le16(&data[3]);
+	} else if (len == WACOM_PKGLEN_TPC1FG_B) {
+		prox = prox && (data[2] & 0x01);
+		x = get_unaligned_le16(&data[3]);
+		y = get_unaligned_le16(&data[5]);
+	} else {
+		prox = prox && (data[1] & 0x01);
+		x = le16_to_cpup((__le16 *)&data[2]);
+		y = le16_to_cpup((__le16 *)&data[4]);
+	}
+
+	if (prox) {
+		input_report_abs(input, ABS_X, x);
+		input_report_abs(input, ABS_Y, y);
+	}
+	input_report_key(input, BTN_TOUCH, prox);
+
+	/* keep touch state for pen events */
+	wacom->shared->touch_down = prox;
+
+	return 1;
+}
+
+static int wacom_tpc_pen(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	struct input_dev *input = wacom->pen_input;
+	bool prox = data[1] & 0x20;
+
+	if (!wacom->shared->stylus_in_proximity) /* first in prox */
+		/* Going into proximity select tool */
+		wacom->tool[0] = (data[1] & 0x0c) ? BTN_TOOL_RUBBER : BTN_TOOL_PEN;
+
+	/* keep pen state for touch events */
+	wacom->shared->stylus_in_proximity = prox;
+
+	/* send pen events only when touch is up or forced out
+	 * or touch arbitration is off
+	 */
+	if (!delay_pen_events(wacom)) {
+		input_report_key(input, BTN_STYLUS, data[1] & 0x02);
+		input_report_key(input, BTN_STYLUS2, data[1] & 0x10);
+		input_report_abs(input, ABS_X, le16_to_cpup((__le16 *)&data[2]));
+		input_report_abs(input, ABS_Y, le16_to_cpup((__le16 *)&data[4]));
+		input_report_abs(input, ABS_PRESSURE, ((data[7] & 0x07) << 8) | data[6]);
+		input_report_key(input, BTN_TOUCH, data[1] & 0x05);
+		input_report_key(input, wacom->tool[0], prox);
+		return 1;
+	}
+
+	return 0;
+}
+
+static int wacom_tpc_irq(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char *data = wacom->data;
+
+	if (wacom->pen_input) {
+		dev_dbg(wacom->pen_input->dev.parent,
+			"%s: received report #%d\n", __func__, data[0]);
+
+		if (len == WACOM_PKGLEN_PENABLED ||
+		    data[0] == WACOM_REPORT_PENABLED)
+			return wacom_tpc_pen(wacom);
+	}
+	else if (wacom->touch_input) {
+		dev_dbg(wacom->touch_input->dev.parent,
+			"%s: received report #%d\n", __func__, data[0]);
+
+		switch (len) {
+		case WACOM_PKGLEN_TPC1FG:
+			return wacom_tpc_single_touch(wacom, len);
+
+		case WACOM_PKGLEN_TPC2FG:
+			return wacom_tpc_mt_touch(wacom);
+
+		default:
+			switch (data[0]) {
+			case WACOM_REPORT_TPC1FG:
+			case WACOM_REPORT_TPCHID:
+			case WACOM_REPORT_TPCST:
+			case WACOM_REPORT_TPC1FGE:
+				return wacom_tpc_single_touch(wacom, len);
+
+			case WACOM_REPORT_TPCMT:
+			case WACOM_REPORT_TPCMT2:
+				return wacom_mt_touch(wacom);
+
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int wacom_offset_rotation(struct input_dev *input, struct hid_usage *usage,
+				 int value, int num, int denom)
+{
+	struct input_absinfo *abs = &input->absinfo[usage->code];
+	int range = (abs->maximum - abs->minimum + 1);
+
+	value += num*range/denom;
+	if (value > abs->maximum)
+		value -= range;
+	else if (value < abs->minimum)
+		value += range;
+	return value;
+}
+
+int wacom_equivalent_usage(int usage)
+{
+	if ((usage & HID_USAGE_PAGE) == WACOM_HID_UP_WACOMDIGITIZER) {
+		int subpage = (usage & 0xFF00) << 8;
+		int subusage = (usage & 0xFF);
+
+		if (subpage == WACOM_HID_SP_PAD ||
+		    subpage == WACOM_HID_SP_BUTTON ||
+		    subpage == WACOM_HID_SP_DIGITIZER ||
+		    subpage == WACOM_HID_SP_DIGITIZERINFO ||
+		    usage == WACOM_HID_WD_SENSE ||
+		    usage == WACOM_HID_WD_SERIALHI ||
+		    usage == WACOM_HID_WD_TOOLTYPE ||
+		    usage == WACOM_HID_WD_DISTANCE ||
+		    usage == WACOM_HID_WD_TOUCHSTRIP ||
+		    usage == WACOM_HID_WD_TOUCHSTRIP2 ||
+		    usage == WACOM_HID_WD_TOUCHRING ||
+		    usage == WACOM_HID_WD_TOUCHRINGSTATUS ||
+		    usage == WACOM_HID_WD_REPORT_VALID) {
+			return usage;
+		}
+
+		if (subpage == HID_UP_UNDEFINED)
+			subpage = HID_UP_DIGITIZER;
+
+		return subpage | subusage;
+	}
+
+	if ((usage & HID_USAGE_PAGE) == WACOM_HID_UP_WACOMTOUCH) {
+		int subpage = (usage & 0xFF00) << 8;
+		int subusage = (usage & 0xFF);
+
+		if (subpage == HID_UP_UNDEFINED)
+			subpage = WACOM_HID_SP_DIGITIZER;
+
+		return subpage | subusage;
+	}
+
+	return usage;
+}
+
+static void wacom_map_usage(struct input_dev *input, struct hid_usage *usage,
+		struct hid_field *field, __u8 type, __u16 code, int fuzz)
+{
+	struct wacom *wacom = input_get_drvdata(input);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	int fmin = field->logical_minimum;
+	int fmax = field->logical_maximum;
+	unsigned int equivalent_usage = wacom_equivalent_usage(usage->hid);
+	int resolution_code = code;
+
+	if (equivalent_usage == HID_DG_TWIST) {
+		resolution_code = ABS_RZ;
+	}
+
+	if (equivalent_usage == HID_GD_X) {
+		fmin += features->offset_left;
+		fmax -= features->offset_right;
+	}
+	if (equivalent_usage == HID_GD_Y) {
+		fmin += features->offset_top;
+		fmax -= features->offset_bottom;
+	}
+
+	usage->type = type;
+	usage->code = code;
+
+	set_bit(type, input->evbit);
+
+	switch (type) {
+	case EV_ABS:
+		input_set_abs_params(input, code, fmin, fmax, fuzz, 0);
+		input_abs_set_res(input, code,
+				  hidinput_calc_abs_res(field, resolution_code));
+		break;
+	case EV_KEY:
+		input_set_capability(input, EV_KEY, code);
+		break;
+	case EV_MSC:
+		input_set_capability(input, EV_MSC, code);
+		break;
+	case EV_SW:
+		input_set_capability(input, EV_SW, code);
+		break;
+	}
+}
+
+static void wacom_wac_battery_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case HID_DG_BATTERYSTRENGTH:
+	case WACOM_HID_WD_BATTERY_LEVEL:
+	case WACOM_HID_WD_BATTERY_CHARGING:
+		features->quirks |= WACOM_QUIRK_BATTERY;
+		break;
+	}
+}
+
+static void wacom_wac_battery_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case HID_DG_BATTERYSTRENGTH:
+		if (value == 0) {
+			wacom_wac->hid_data.bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+		}
+		else {
+			value = value * 100 / (field->logical_maximum - field->logical_minimum);
+			wacom_wac->hid_data.battery_capacity = value;
+			wacom_wac->hid_data.bat_connected = 1;
+			wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
+		}
+		break;
+	case WACOM_HID_WD_BATTERY_LEVEL:
+		value = value * 100 / (field->logical_maximum - field->logical_minimum);
+		wacom_wac->hid_data.battery_capacity = value;
+		wacom_wac->hid_data.bat_connected = 1;
+		wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
+		break;
+	case WACOM_HID_WD_BATTERY_CHARGING:
+		wacom_wac->hid_data.bat_charging = value;
+		wacom_wac->hid_data.ps_connected = value;
+		wacom_wac->hid_data.bat_connected = 1;
+		wacom_wac->hid_data.bat_status = WACOM_POWER_SUPPLY_STATUS_AUTO;
+		break;
+	}
+}
+
+static void wacom_wac_battery_pre_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	return;
+}
+
+static void wacom_wac_battery_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+
+	if (features->quirks & WACOM_QUIRK_BATTERY) {
+		int status = wacom_wac->hid_data.bat_status;
+		int capacity = wacom_wac->hid_data.battery_capacity;
+		bool charging = wacom_wac->hid_data.bat_charging;
+		bool connected = wacom_wac->hid_data.bat_connected;
+		bool powered = wacom_wac->hid_data.ps_connected;
+
+		wacom_notify_battery(wacom_wac, status, capacity, charging,
+				     connected, powered);
+	}
+}
+
+static void wacom_wac_pad_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	struct input_dev *input = wacom_wac->pad_input;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case WACOM_HID_WD_ACCELEROMETER_X:
+		__set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_ACCELEROMETER_Y:
+		__set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_ACCELEROMETER_Z:
+		__set_bit(INPUT_PROP_ACCELEROMETER, input->propbit);
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_BUTTONCENTER:
+		wacom->generic_has_leds = true;
+		/* fall through */
+	case WACOM_HID_WD_BUTTONHOME:
+	case WACOM_HID_WD_BUTTONUP:
+	case WACOM_HID_WD_BUTTONDOWN:
+	case WACOM_HID_WD_BUTTONLEFT:
+	case WACOM_HID_WD_BUTTONRIGHT:
+		wacom_map_usage(input, usage, field, EV_KEY,
+				wacom_numbered_button_to_key(features->numbered_buttons),
+				0);
+		features->numbered_buttons++;
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_TOUCHONOFF:
+	case WACOM_HID_WD_MUTE_DEVICE:
+		/*
+		 * This usage, which is used to mute touch events, comes
+		 * from the pad packet, but is reported on the touch
+		 * interface. Because the touch interface may not have
+		 * been created yet, we cannot call wacom_map_usage(). In
+		 * order to process this usage when we receive it, we set
+		 * the usage type and code directly.
+		 */
+		wacom_wac->has_mute_touch_switch = true;
+		usage->type = EV_SW;
+		usage->code = SW_MUTE_DEVICE;
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_TOUCHSTRIP:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_RX, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_TOUCHSTRIP2:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_RY, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_TOUCHRING:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_TOUCHRINGSTATUS:
+		/*
+		 * Only set up type/code association. Completely mapping
+		 * this usage may overwrite the axis resolution and range.
+		 */
+		usage->type = EV_ABS;
+		usage->code = ABS_WHEEL;
+		set_bit(EV_ABS, input->evbit);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_BUTTONCONFIG:
+		wacom_map_usage(input, usage, field, EV_KEY, KEY_BUTTONCONFIG, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_ONSCREEN_KEYBOARD:
+		wacom_map_usage(input, usage, field, EV_KEY, KEY_ONSCREEN_KEYBOARD, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_CONTROLPANEL:
+		wacom_map_usage(input, usage, field, EV_KEY, KEY_CONTROLPANEL, 0);
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	case WACOM_HID_WD_MODE_CHANGE:
+		/* do not overwrite previous data */
+		if (!wacom_wac->has_mode_change) {
+			wacom_wac->has_mode_change = true;
+			wacom_wac->is_direct_mode = true;
+		}
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	}
+
+	switch (equivalent_usage & 0xfffffff0) {
+	case WACOM_HID_WD_EXPRESSKEY00:
+		wacom_map_usage(input, usage, field, EV_KEY,
+				wacom_numbered_button_to_key(features->numbered_buttons),
+				0);
+		features->numbered_buttons++;
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+		break;
+	}
+}
+
+static void wacom_wac_pad_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct input_dev *input = wacom_wac->pad_input;
+	struct wacom_features *features = &wacom_wac->features;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+	int i;
+	bool do_report = false;
+
+	/*
+	 * Avoid reporting this event and setting inrange_state if this usage
+	 * hasn't been mapped.
+	 */
+	if (!usage->type && equivalent_usage != WACOM_HID_WD_MODE_CHANGE)
+		return;
+
+	if (wacom_equivalent_usage(field->physical) == HID_DG_TABLETFUNCTIONKEY) {
+		if (usage->hid != WACOM_HID_WD_TOUCHRING)
+			wacom_wac->hid_data.inrange_state |= value;
+	}
+
+	switch (equivalent_usage) {
+	case WACOM_HID_WD_TOUCHRING:
+		/*
+		 * Userspace expects touchrings to increase in value with
+		 * clockwise gestures and have their zero point at the
+		 * tablet's left. HID events "should" be clockwise-
+		 * increasing and zero at top, though the MobileStudio
+		 * Pro and 2nd-gen Intuos Pro don't do this...
+		 */
+		if (hdev->vendor == 0x56a &&
+		    (hdev->product == 0x34d || hdev->product == 0x34e ||  /* MobileStudio Pro */
+		     hdev->product == 0x357 || hdev->product == 0x358)) { /* Intuos Pro 2 */
+			value = (field->logical_maximum - value);
+
+			if (hdev->product == 0x357 || hdev->product == 0x358)
+				value = wacom_offset_rotation(input, usage, value, 3, 16);
+			else if (hdev->product == 0x34d || hdev->product == 0x34e)
+				value = wacom_offset_rotation(input, usage, value, 1, 2);
+		}
+		else {
+			value = wacom_offset_rotation(input, usage, value, 1, 4);
+		}
+		do_report = true;
+		break;
+	case WACOM_HID_WD_TOUCHRINGSTATUS:
+		if (!value)
+			input_event(input, usage->type, usage->code, 0);
+		break;
+
+	case WACOM_HID_WD_MUTE_DEVICE:
+	case WACOM_HID_WD_TOUCHONOFF:
+		if (wacom_wac->shared->touch_input) {
+			bool *is_touch_on = &wacom_wac->shared->is_touch_on;
+
+			if (equivalent_usage == WACOM_HID_WD_MUTE_DEVICE && value)
+				*is_touch_on = !(*is_touch_on);
+			else if (equivalent_usage == WACOM_HID_WD_TOUCHONOFF)
+				*is_touch_on = value;
+
+			input_report_switch(wacom_wac->shared->touch_input,
+					    SW_MUTE_DEVICE, !(*is_touch_on));
+			input_sync(wacom_wac->shared->touch_input);
+		}
+		break;
+
+	case WACOM_HID_WD_MODE_CHANGE:
+		if (wacom_wac->is_direct_mode != value) {
+			wacom_wac->is_direct_mode = value;
+			wacom_schedule_work(&wacom->wacom_wac, WACOM_WORKER_MODE_CHANGE);
+		}
+		break;
+
+	case WACOM_HID_WD_BUTTONCENTER:
+		for (i = 0; i < wacom->led.count; i++)
+			wacom_update_led(wacom, features->numbered_buttons,
+					 value, i);
+		 /* fall through*/
+	default:
+		do_report = true;
+		break;
+	}
+
+	if (do_report) {
+		input_event(input, usage->type, usage->code, value);
+		if (value)
+			wacom_wac->hid_data.pad_input_event_flag = true;
+	}
+}
+
+static void wacom_wac_pad_pre_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+	wacom_wac->hid_data.inrange_state = 0;
+}
+
+static void wacom_wac_pad_report(struct hid_device *hdev,
+		struct hid_report *report, struct hid_field *field)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct input_dev *input = wacom_wac->pad_input;
+	bool active = wacom_wac->hid_data.inrange_state != 0;
+
+	/* report prox for expresskey events */
+	if ((wacom_equivalent_usage(field->physical) == HID_DG_TABLETFUNCTIONKEY) &&
+	    wacom_wac->hid_data.pad_input_event_flag) {
+		input_event(input, EV_ABS, ABS_MISC, active ? PAD_DEVICE_ID : 0);
+		input_sync(input);
+		if (!active)
+			wacom_wac->hid_data.pad_input_event_flag = false;
+	}
+
+}
+
+static void wacom_wac_pen_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	struct input_dev *input = wacom_wac->pen_input;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case HID_GD_X:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 4);
+		break;
+	case HID_GD_Y:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 4);
+		break;
+	case WACOM_HID_WD_DISTANCE:
+	case HID_GD_Z:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_DISTANCE, 0);
+		break;
+	case HID_DG_TIPPRESSURE:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_PRESSURE, 0);
+		break;
+	case HID_DG_INRANGE:
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_TOOL_PEN, 0);
+		break;
+	case HID_DG_INVERT:
+		wacom_map_usage(input, usage, field, EV_KEY,
+				BTN_TOOL_RUBBER, 0);
+		break;
+	case HID_DG_TILT_X:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_TILT_X, 0);
+		break;
+	case HID_DG_TILT_Y:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_TILT_Y, 0);
+		break;
+	case HID_DG_TWIST:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_Z, 0);
+		break;
+	case HID_DG_ERASER:
+	case HID_DG_TIPSWITCH:
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
+		break;
+	case HID_DG_BARRELSWITCH:
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS, 0);
+		break;
+	case HID_DG_BARRELSWITCH2:
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_STYLUS2, 0);
+		break;
+	case HID_DG_TOOLSERIALNUMBER:
+		features->quirks |= WACOM_QUIRK_TOOLSERIAL;
+		wacom_map_usage(input, usage, field, EV_MSC, MSC_SERIAL, 0);
+
+		/* Adjust AES usages to match modern convention */
+		if (usage->hid == WACOM_HID_WT_SERIALNUMBER && field->report_size == 16) {
+			if (field->index + 2 < field->report->maxfield) {
+				struct hid_field *a = field->report->field[field->index + 1];
+				struct hid_field *b = field->report->field[field->index + 2];
+
+				if (a->maxusage > 0 && a->usage[0].hid == HID_DG_TOOLSERIALNUMBER && a->report_size == 32 &&
+				    b->maxusage > 0 && b->usage[0].hid == 0xFF000000 && b->report_size == 8) {
+					features->quirks |= WACOM_QUIRK_AESPEN;
+					usage->hid = WACOM_HID_WD_TOOLTYPE;
+					field->logical_minimum = S16_MIN;
+					field->logical_maximum = S16_MAX;
+					a->logical_minimum = S32_MIN;
+					a->logical_maximum = S32_MAX;
+					b->usage[0].hid = WACOM_HID_WD_SERIALHI;
+					b->logical_minimum = 0;
+					b->logical_maximum = U8_MAX;
+				}
+			}
+		}
+		break;
+	case WACOM_HID_WD_SENSE:
+		features->quirks |= WACOM_QUIRK_SENSE;
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_TOOL_PEN, 0);
+		break;
+	case WACOM_HID_WD_SERIALHI:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_MISC, 0);
+
+		if (!(features->quirks & WACOM_QUIRK_AESPEN)) {
+			set_bit(EV_KEY, input->evbit);
+			input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
+			input_set_capability(input, EV_KEY, BTN_TOOL_RUBBER);
+			input_set_capability(input, EV_KEY, BTN_TOOL_BRUSH);
+			input_set_capability(input, EV_KEY, BTN_TOOL_PENCIL);
+			input_set_capability(input, EV_KEY, BTN_TOOL_AIRBRUSH);
+			if (!(features->device_type & WACOM_DEVICETYPE_DIRECT)) {
+				input_set_capability(input, EV_KEY, BTN_TOOL_MOUSE);
+				input_set_capability(input, EV_KEY, BTN_TOOL_LENS);
+			}
+		}
+		break;
+	case WACOM_HID_WD_FINGERWHEEL:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_WHEEL, 0);
+		break;
+	}
+}
+
+static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+	struct input_dev *input = wacom_wac->pen_input;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	if (wacom_wac->is_invalid_bt_frame)
+		return;
+
+	switch (equivalent_usage) {
+	case HID_GD_Z:
+		/*
+		 * HID_GD_Z "should increase as the control's position is
+		 * moved from high to low", while ABS_DISTANCE instead
+		 * increases in value as the tool moves from low to high.
+		 */
+		value = field->logical_maximum - value;
+		break;
+	case HID_DG_INRANGE:
+		wacom_wac->hid_data.inrange_state = value;
+		if (!(features->quirks & WACOM_QUIRK_SENSE))
+			wacom_wac->hid_data.sense_state = value;
+		return;
+	case HID_DG_INVERT:
+		wacom_wac->hid_data.invert_state = value;
+		return;
+	case HID_DG_ERASER:
+	case HID_DG_TIPSWITCH:
+		wacom_wac->hid_data.tipswitch |= value;
+		return;
+	case HID_DG_BARRELSWITCH:
+		wacom_wac->hid_data.barrelswitch = value;
+		return;
+	case HID_DG_BARRELSWITCH2:
+		wacom_wac->hid_data.barrelswitch2 = value;
+		return;
+	case HID_DG_TOOLSERIALNUMBER:
+		if (value) {
+			wacom_wac->serial[0] = (wacom_wac->serial[0] & ~0xFFFFFFFFULL);
+			wacom_wac->serial[0] |= (__u32)value;
+		}
+		return;
+	case HID_DG_TWIST:
+		/*
+		 * Userspace expects pen twist to have its zero point when
+		 * the buttons/finger is on the tablet's left. HID values
+		 * are zero when buttons are toward the top.
+		 */
+		value = wacom_offset_rotation(input, usage, value, 1, 4);
+		break;
+	case WACOM_HID_WD_SENSE:
+		wacom_wac->hid_data.sense_state = value;
+		return;
+	case WACOM_HID_WD_SERIALHI:
+		if (value) {
+			wacom_wac->serial[0] = (wacom_wac->serial[0] & 0xFFFFFFFF);
+			wacom_wac->serial[0] |= ((__u64)value) << 32;
+			/*
+			 * Non-USI EMR devices may contain additional tool type
+			 * information here. See WACOM_HID_WD_TOOLTYPE case for
+			 * more details.
+			 */
+			if (value >> 20 == 1) {
+				wacom_wac->id[0] |= value & 0xFFFFF;
+			}
+		}
+		return;
+	case WACOM_HID_WD_TOOLTYPE:
+		/*
+		 * Some devices (MobileStudio Pro, and possibly later
+		 * devices as well) do not return the complete tool
+		 * type in their WACOM_HID_WD_TOOLTYPE usage. Use a
+		 * bitwise OR so the complete value can be built
+		 * up over time :(
+		 */
+		wacom_wac->id[0] |= value;
+		return;
+	case WACOM_HID_WD_OFFSETLEFT:
+		if (features->offset_left && value != features->offset_left)
+			hid_warn(hdev, "%s: overriding existing left offset "
+				 "%d -> %d\n", __func__, value,
+				 features->offset_left);
+		features->offset_left = value;
+		return;
+	case WACOM_HID_WD_OFFSETRIGHT:
+		if (features->offset_right && value != features->offset_right)
+			hid_warn(hdev, "%s: overriding existing right offset "
+				 "%d -> %d\n", __func__, value,
+				 features->offset_right);
+		features->offset_right = value;
+		return;
+	case WACOM_HID_WD_OFFSETTOP:
+		if (features->offset_top && value != features->offset_top)
+			hid_warn(hdev, "%s: overriding existing top offset "
+				 "%d -> %d\n", __func__, value,
+				 features->offset_top);
+		features->offset_top = value;
+		return;
+	case WACOM_HID_WD_OFFSETBOTTOM:
+		if (features->offset_bottom && value != features->offset_bottom)
+			hid_warn(hdev, "%s: overriding existing bottom offset "
+				 "%d -> %d\n", __func__, value,
+				 features->offset_bottom);
+		features->offset_bottom = value;
+		return;
+	case WACOM_HID_WD_REPORT_VALID:
+		wacom_wac->is_invalid_bt_frame = !value;
+		return;
+	}
+
+	/* send pen events only when touch is up or forced out
+	 * or touch arbitration is off
+	 */
+	if (!usage->type || delay_pen_events(wacom_wac))
+		return;
+
+	/* send pen events only when the pen is in range */
+	if (wacom_wac->hid_data.inrange_state)
+		input_event(input, usage->type, usage->code, value);
+	else if (wacom_wac->shared->stylus_in_proximity && !wacom_wac->hid_data.sense_state)
+		input_event(input, usage->type, usage->code, 0);
+}
+
+static void wacom_wac_pen_pre_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+
+	wacom_wac->is_invalid_bt_frame = false;
+	return;
+}
+
+static void wacom_wac_pen_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct input_dev *input = wacom_wac->pen_input;
+	bool range = wacom_wac->hid_data.inrange_state;
+	bool sense = wacom_wac->hid_data.sense_state;
+
+	if (wacom_wac->is_invalid_bt_frame)
+		return;
+
+	if (!wacom_wac->tool[0] && range) { /* first in range */
+		/* Going into range select tool */
+		if (wacom_wac->hid_data.invert_state)
+			wacom_wac->tool[0] = BTN_TOOL_RUBBER;
+		else if (wacom_wac->id[0])
+			wacom_wac->tool[0] = wacom_intuos_get_tool_type(wacom_wac->id[0]);
+		else
+			wacom_wac->tool[0] = BTN_TOOL_PEN;
+	}
+
+	/* keep pen state for touch events */
+	wacom_wac->shared->stylus_in_proximity = sense;
+
+	if (!delay_pen_events(wacom_wac) && wacom_wac->tool[0]) {
+		int id = wacom_wac->id[0];
+		int sw_state = wacom_wac->hid_data.barrelswitch |
+			       (wacom_wac->hid_data.barrelswitch2 << 1);
+
+		input_report_key(input, BTN_STYLUS, sw_state == 1);
+		input_report_key(input, BTN_STYLUS2, sw_state == 2);
+		input_report_key(input, BTN_STYLUS3, sw_state == 3);
+
+		/*
+		 * Non-USI EMR tools should have their IDs mangled to
+		 * match the legacy behavior of wacom_intuos_general
+		 */
+		if (wacom_wac->serial[0] >> 52 == 1)
+			id = wacom_intuos_id_mangle(id);
+
+		/*
+		 * To ensure compatibility with xf86-input-wacom, we should
+		 * report the BTN_TOOL_* event prior to the ABS_MISC or
+		 * MSC_SERIAL events.
+		 */
+		input_report_key(input, BTN_TOUCH,
+				wacom_wac->hid_data.tipswitch);
+		input_report_key(input, wacom_wac->tool[0], sense);
+		if (wacom_wac->serial[0]) {
+			input_event(input, EV_MSC, MSC_SERIAL, wacom_wac->serial[0]);
+			input_report_abs(input, ABS_MISC, sense ? id : 0);
+		}
+
+		wacom_wac->hid_data.tipswitch = false;
+
+		input_sync(input);
+	}
+
+	if (!sense) {
+		wacom_wac->tool[0] = 0;
+		wacom_wac->id[0] = 0;
+		wacom_wac->serial[0] = 0;
+	}
+}
+
+static void wacom_wac_finger_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct input_dev *input = wacom_wac->touch_input;
+	unsigned touch_max = wacom_wac->features.touch_max;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case HID_GD_X:
+		if (touch_max == 1)
+			wacom_map_usage(input, usage, field, EV_ABS, ABS_X, 4);
+		else
+			wacom_map_usage(input, usage, field, EV_ABS,
+					ABS_MT_POSITION_X, 4);
+		break;
+	case HID_GD_Y:
+		if (touch_max == 1)
+			wacom_map_usage(input, usage, field, EV_ABS, ABS_Y, 4);
+		else
+			wacom_map_usage(input, usage, field, EV_ABS,
+					ABS_MT_POSITION_Y, 4);
+		break;
+	case HID_DG_WIDTH:
+	case HID_DG_HEIGHT:
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MAJOR, 0);
+		wacom_map_usage(input, usage, field, EV_ABS, ABS_MT_TOUCH_MINOR, 0);
+		input_set_abs_params(input, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+		break;
+	case HID_DG_TIPSWITCH:
+		wacom_map_usage(input, usage, field, EV_KEY, BTN_TOUCH, 0);
+		break;
+	case HID_DG_CONTACTCOUNT:
+		wacom_wac->hid_data.cc_report = field->report->id;
+		wacom_wac->hid_data.cc_index = field->index;
+		wacom_wac->hid_data.cc_value_index = usage->usage_index;
+		break;
+	case HID_DG_CONTACTID:
+		if ((field->logical_maximum - field->logical_minimum) < touch_max) {
+			/*
+			 * The HID descriptor for G11 sensors leaves logical
+			 * maximum set to '1' despite it being a multitouch
+			 * device. Override to a sensible number.
+			 */
+			field->logical_maximum = 255;
+		}
+		break;
+	}
+}
+
+static void wacom_wac_finger_slot(struct wacom_wac *wacom_wac,
+		struct input_dev *input)
+{
+	struct hid_data *hid_data = &wacom_wac->hid_data;
+	bool mt = wacom_wac->features.touch_max > 1;
+	bool prox = hid_data->tipswitch &&
+		    report_touch_events(wacom_wac);
+
+	if (wacom_wac->shared->has_mute_touch_switch &&
+	    !wacom_wac->shared->is_touch_on) {
+		if (!wacom_wac->shared->touch_down)
+			return;
+		prox = 0;
+	}
+
+	wacom_wac->hid_data.num_received++;
+	if (wacom_wac->hid_data.num_received > wacom_wac->hid_data.num_expected)
+		return;
+
+	if (mt) {
+		int slot;
+
+		slot = input_mt_get_slot_by_key(input, hid_data->id);
+		input_mt_slot(input, slot);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, prox);
+	}
+	else {
+		input_report_key(input, BTN_TOUCH, prox);
+	}
+
+	if (prox) {
+		input_report_abs(input, mt ? ABS_MT_POSITION_X : ABS_X,
+				 hid_data->x);
+		input_report_abs(input, mt ? ABS_MT_POSITION_Y : ABS_Y,
+				 hid_data->y);
+
+		if (test_bit(ABS_MT_TOUCH_MAJOR, input->absbit)) {
+			input_report_abs(input, ABS_MT_TOUCH_MAJOR, max(hid_data->width, hid_data->height));
+			input_report_abs(input, ABS_MT_TOUCH_MINOR, min(hid_data->width, hid_data->height));
+			if (hid_data->width != hid_data->height)
+				input_report_abs(input, ABS_MT_ORIENTATION, hid_data->width <= hid_data->height ? 0 : 1);
+		}
+	}
+}
+
+static void wacom_wac_finger_event(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	unsigned equivalent_usage = wacom_equivalent_usage(usage->hid);
+
+	switch (equivalent_usage) {
+	case HID_GD_X:
+		wacom_wac->hid_data.x = value;
+		break;
+	case HID_GD_Y:
+		wacom_wac->hid_data.y = value;
+		break;
+	case HID_DG_WIDTH:
+		wacom_wac->hid_data.width = value;
+		break;
+	case HID_DG_HEIGHT:
+		wacom_wac->hid_data.height = value;
+		break;
+	case HID_DG_CONTACTID:
+		wacom_wac->hid_data.id = value;
+		break;
+	case HID_DG_TIPSWITCH:
+		wacom_wac->hid_data.tipswitch = value;
+		break;
+	}
+
+
+	if (usage->usage_index + 1 == field->report_count) {
+		if (equivalent_usage == wacom_wac->hid_data.last_slot_field)
+			wacom_wac_finger_slot(wacom_wac, wacom_wac->touch_input);
+	}
+}
+
+static void wacom_wac_finger_pre_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct hid_data* hid_data = &wacom_wac->hid_data;
+	int i;
+
+	for (i = 0; i < report->maxfield; i++) {
+		struct hid_field *field = report->field[i];
+		int j;
+
+		for (j = 0; j < field->maxusage; j++) {
+			struct hid_usage *usage = &field->usage[j];
+			unsigned int equivalent_usage =
+				wacom_equivalent_usage(usage->hid);
+
+			switch (equivalent_usage) {
+			case HID_GD_X:
+			case HID_GD_Y:
+			case HID_DG_WIDTH:
+			case HID_DG_HEIGHT:
+			case HID_DG_CONTACTID:
+			case HID_DG_INRANGE:
+			case HID_DG_INVERT:
+			case HID_DG_TIPSWITCH:
+				hid_data->last_slot_field = equivalent_usage;
+				break;
+			case HID_DG_CONTACTCOUNT:
+				hid_data->cc_report = report->id;
+				hid_data->cc_index = i;
+				hid_data->cc_value_index = j;
+				break;
+			}
+		}
+	}
+
+	if (hid_data->cc_report != 0 &&
+	    hid_data->cc_index >= 0) {
+		struct hid_field *field = report->field[hid_data->cc_index];
+		int value = field->value[hid_data->cc_value_index];
+		if (value)
+			hid_data->num_expected = value;
+	}
+	else {
+		hid_data->num_expected = wacom_wac->features.touch_max;
+	}
+}
+
+static void wacom_wac_finger_report(struct hid_device *hdev,
+		struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct input_dev *input = wacom_wac->touch_input;
+	unsigned touch_max = wacom_wac->features.touch_max;
+
+	/* If more packets of data are expected, give us a chance to
+	 * process them rather than immediately syncing a partial
+	 * update.
+	 */
+	if (wacom_wac->hid_data.num_received < wacom_wac->hid_data.num_expected)
+		return;
+
+	if (touch_max > 1)
+		input_mt_sync_frame(input);
+
+	input_sync(input);
+	wacom_wac->hid_data.num_received = 0;
+
+	/* keep touch state for pen event */
+	wacom_wac->shared->touch_down = wacom_wac_finger_count_touches(wacom_wac);
+}
+
+void wacom_wac_usage_mapping(struct hid_device *hdev,
+		struct hid_field *field, struct hid_usage *usage)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom_wac->features;
+
+	if (WACOM_DIRECT_DEVICE(field))
+		features->device_type |= WACOM_DEVICETYPE_DIRECT;
+
+	/* usage tests must precede field tests */
+	if (WACOM_BATTERY_USAGE(usage))
+		wacom_wac_battery_usage_mapping(hdev, field, usage);
+	else if (WACOM_PAD_FIELD(field))
+		wacom_wac_pad_usage_mapping(hdev, field, usage);
+	else if (WACOM_PEN_FIELD(field))
+		wacom_wac_pen_usage_mapping(hdev, field, usage);
+	else if (WACOM_FINGER_FIELD(field))
+		wacom_wac_finger_usage_mapping(hdev, field, usage);
+}
+
+void wacom_wac_event(struct hid_device *hdev, struct hid_field *field,
+		struct hid_usage *usage, __s32 value)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+
+	if (wacom->wacom_wac.features.type != HID_GENERIC)
+		return;
+
+	if (value > field->logical_maximum || value < field->logical_minimum)
+		return;
+
+	/* usage tests must precede field tests */
+	if (WACOM_BATTERY_USAGE(usage))
+		wacom_wac_battery_event(hdev, field, usage, value);
+	else if (WACOM_PAD_FIELD(field) && wacom->wacom_wac.pad_input)
+		wacom_wac_pad_event(hdev, field, usage, value);
+	else if (WACOM_PEN_FIELD(field) && wacom->wacom_wac.pen_input)
+		wacom_wac_pen_event(hdev, field, usage, value);
+	else if (WACOM_FINGER_FIELD(field) && wacom->wacom_wac.touch_input)
+		wacom_wac_finger_event(hdev, field, usage, value);
+}
+
+static void wacom_report_events(struct hid_device *hdev,
+				struct hid_report *report, int collection_index,
+				int field_index)
+{
+	int r;
+
+	for (r = field_index; r < report->maxfield; r++) {
+		struct hid_field *field;
+		unsigned count, n;
+
+		field = report->field[r];
+		count = field->report_count;
+
+		if (!(HID_MAIN_ITEM_VARIABLE & field->flags))
+			continue;
+
+		for (n = 0 ; n < count; n++) {
+			if (field->usage[n].collection_index == collection_index)
+				wacom_wac_event(hdev, field, &field->usage[n],
+						field->value[n]);
+			else
+				return;
+		}
+	}
+}
+
+static int wacom_wac_collection(struct hid_device *hdev, struct hid_report *report,
+			 int collection_index, struct hid_field *field,
+			 int field_index)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+
+	wacom_report_events(hdev, report, collection_index, field_index);
+
+	/*
+	 * Non-input reports may be sent prior to the device being
+	 * completely initialized. Since only their events need
+	 * to be processed, exit after 'wacom_report_events' has
+	 * been called to prevent potential crashes in the report-
+	 * processing functions.
+	 */
+	if (report->type != HID_INPUT_REPORT)
+		return -1;
+
+	if (WACOM_PAD_FIELD(field) && wacom->wacom_wac.pad_input)
+		wacom_wac_pad_report(hdev, report, field);
+	else if (WACOM_PEN_FIELD(field) && wacom->wacom_wac.pen_input)
+		wacom_wac_pen_report(hdev, report);
+	else if (WACOM_FINGER_FIELD(field) && wacom->wacom_wac.touch_input)
+		wacom_wac_finger_report(hdev, report);
+
+	return 0;
+}
+
+void wacom_wac_report(struct hid_device *hdev, struct hid_report *report)
+{
+	struct wacom *wacom = hid_get_drvdata(hdev);
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct hid_field *field;
+	bool pad_in_hid_field = false, pen_in_hid_field = false,
+		finger_in_hid_field = false;
+	int r;
+	int prev_collection = -1;
+
+	if (wacom_wac->features.type != HID_GENERIC)
+		return;
+
+	for (r = 0; r < report->maxfield; r++) {
+		field = report->field[r];
+
+		if (WACOM_PAD_FIELD(field))
+			pad_in_hid_field = true;
+		if (WACOM_PEN_FIELD(field))
+			pen_in_hid_field = true;
+		if (WACOM_FINGER_FIELD(field))
+			finger_in_hid_field = true;
+	}
+
+	wacom_wac_battery_pre_report(hdev, report);
+
+	if (pad_in_hid_field && wacom->wacom_wac.pad_input)
+		wacom_wac_pad_pre_report(hdev, report);
+	if (pen_in_hid_field && wacom->wacom_wac.pen_input)
+		wacom_wac_pen_pre_report(hdev, report);
+	if (finger_in_hid_field && wacom->wacom_wac.touch_input)
+		wacom_wac_finger_pre_report(hdev, report);
+
+	for (r = 0; r < report->maxfield; r++) {
+		field = report->field[r];
+
+		if (field->usage[0].collection_index != prev_collection) {
+			if (wacom_wac_collection(hdev, report,
+				field->usage[0].collection_index, field, r) < 0)
+				return;
+			prev_collection = field->usage[0].collection_index;
+		}
+	}
+
+	wacom_wac_battery_report(hdev, report);
+}
+
+static int wacom_bpt_touch(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	struct input_dev *input = wacom->touch_input;
+	struct input_dev *pad_input = wacom->pad_input;
+	unsigned char *data = wacom->data;
+	int i;
+
+	if (data[0] != 0x02)
+	    return 0;
+
+	for (i = 0; i < 2; i++) {
+		int offset = (data[1] & 0x80) ? (8 * i) : (9 * i);
+		bool touch = report_touch_events(wacom)
+			   && (data[offset + 3] & 0x80);
+
+		input_mt_slot(input, i);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+		if (touch) {
+			int x = get_unaligned_be16(&data[offset + 3]) & 0x7ff;
+			int y = get_unaligned_be16(&data[offset + 5]) & 0x7ff;
+			if (features->quirks & WACOM_QUIRK_BBTOUCH_LOWRES) {
+				x <<= 5;
+				y <<= 5;
+			}
+			input_report_abs(input, ABS_MT_POSITION_X, x);
+			input_report_abs(input, ABS_MT_POSITION_Y, y);
+		}
+	}
+
+	input_mt_sync_frame(input);
+
+	input_report_key(pad_input, BTN_LEFT, (data[1] & 0x08) != 0);
+	input_report_key(pad_input, BTN_FORWARD, (data[1] & 0x04) != 0);
+	input_report_key(pad_input, BTN_BACK, (data[1] & 0x02) != 0);
+	input_report_key(pad_input, BTN_RIGHT, (data[1] & 0x01) != 0);
+	wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+
+	return 1;
+}
+
+static void wacom_bpt3_touch_msg(struct wacom_wac *wacom, unsigned char *data)
+{
+	struct wacom_features *features = &wacom->features;
+	struct input_dev *input = wacom->touch_input;
+	bool touch = data[1] & 0x80;
+	int slot = input_mt_get_slot_by_key(input, data[0]);
+
+	if (slot < 0)
+		return;
+
+	touch = touch && report_touch_events(wacom);
+
+	input_mt_slot(input, slot);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER, touch);
+
+	if (touch) {
+		int x = (data[2] << 4) | (data[4] >> 4);
+		int y = (data[3] << 4) | (data[4] & 0x0f);
+		int width, height;
+
+		if (features->type >= INTUOSPS && features->type <= INTUOSHT2) {
+			width  = data[5] * 100;
+			height = data[6] * 100;
+		} else {
+			/*
+			 * "a" is a scaled-down area which we assume is
+			 * roughly circular and which can be described as:
+			 * a=(pi*r^2)/C.
+			 */
+			int a = data[5];
+			int x_res = input_abs_get_res(input, ABS_MT_POSITION_X);
+			int y_res = input_abs_get_res(input, ABS_MT_POSITION_Y);
+			width = 2 * int_sqrt(a * WACOM_CONTACT_AREA_SCALE);
+			height = width * y_res / x_res;
+		}
+
+		input_report_abs(input, ABS_MT_POSITION_X, x);
+		input_report_abs(input, ABS_MT_POSITION_Y, y);
+		input_report_abs(input, ABS_MT_TOUCH_MAJOR, width);
+		input_report_abs(input, ABS_MT_TOUCH_MINOR, height);
+	}
+}
+
+static void wacom_bpt3_button_msg(struct wacom_wac *wacom, unsigned char *data)
+{
+	struct input_dev *input = wacom->pad_input;
+	struct wacom_features *features = &wacom->features;
+
+	if (features->type == INTUOSHT || features->type == INTUOSHT2) {
+		input_report_key(input, BTN_LEFT, (data[1] & 0x02) != 0);
+		input_report_key(input, BTN_BACK, (data[1] & 0x08) != 0);
+	} else {
+		input_report_key(input, BTN_BACK, (data[1] & 0x02) != 0);
+		input_report_key(input, BTN_LEFT, (data[1] & 0x08) != 0);
+	}
+	input_report_key(input, BTN_FORWARD, (data[1] & 0x04) != 0);
+	input_report_key(input, BTN_RIGHT, (data[1] & 0x01) != 0);
+}
+
+static int wacom_bpt3_touch(struct wacom_wac *wacom)
+{
+	unsigned char *data = wacom->data;
+	int count = data[1] & 0x07;
+	int  touch_changed = 0, i;
+
+	if (data[0] != 0x02)
+	    return 0;
+
+	/* data has up to 7 fixed sized 8-byte messages starting at data[2] */
+	for (i = 0; i < count; i++) {
+		int offset = (8 * i) + 2;
+		int msg_id = data[offset];
+
+		if (msg_id >= 2 && msg_id <= 17) {
+			wacom_bpt3_touch_msg(wacom, data + offset);
+			touch_changed++;
+		} else if (msg_id == 128)
+			wacom_bpt3_button_msg(wacom, data + offset);
+
+	}
+
+	/* only update touch if we actually have a touchpad and touch data changed */
+	if (wacom->touch_input && touch_changed) {
+		input_mt_sync_frame(wacom->touch_input);
+		wacom->shared->touch_down = wacom_wac_finger_count_touches(wacom);
+	}
+
+	return 1;
+}
+
+static int wacom_bpt_pen(struct wacom_wac *wacom)
+{
+	struct wacom_features *features = &wacom->features;
+	struct input_dev *input = wacom->pen_input;
+	unsigned char *data = wacom->data;
+	int x = 0, y = 0, p = 0, d = 0;
+	bool pen = false, btn1 = false, btn2 = false;
+	bool range, prox, rdy;
+
+	if (data[0] != WACOM_REPORT_PENABLED)
+	    return 0;
+
+	range = (data[1] & 0x80) == 0x80;
+	prox = (data[1] & 0x40) == 0x40;
+	rdy = (data[1] & 0x20) == 0x20;
+
+	wacom->shared->stylus_in_proximity = range;
+	if (delay_pen_events(wacom))
+		return 0;
+
+	if (rdy) {
+		p = le16_to_cpup((__le16 *)&data[6]);
+		pen = data[1] & 0x01;
+		btn1 = data[1] & 0x02;
+		btn2 = data[1] & 0x04;
+	}
+	if (prox) {
+		x = le16_to_cpup((__le16 *)&data[2]);
+		y = le16_to_cpup((__le16 *)&data[4]);
+
+		if (data[1] & 0x08) {
+			wacom->tool[0] = BTN_TOOL_RUBBER;
+			wacom->id[0] = ERASER_DEVICE_ID;
+		} else {
+			wacom->tool[0] = BTN_TOOL_PEN;
+			wacom->id[0] = STYLUS_DEVICE_ID;
+		}
+		wacom->reporting_data = true;
+	}
+	if (range) {
+		/*
+		 * Convert distance from out prox to distance from tablet.
+		 * distance will be greater than distance_max once
+		 * touching and applying pressure; do not report negative
+		 * distance.
+		 */
+		if (data[8] <= features->distance_max)
+			d = features->distance_max - data[8];
+	} else {
+		wacom->id[0] = 0;
+	}
+
+	if (wacom->reporting_data) {
+		input_report_key(input, BTN_TOUCH, pen);
+		input_report_key(input, BTN_STYLUS, btn1);
+		input_report_key(input, BTN_STYLUS2, btn2);
+
+		if (prox || !range) {
+			input_report_abs(input, ABS_X, x);
+			input_report_abs(input, ABS_Y, y);
+		}
+		input_report_abs(input, ABS_PRESSURE, p);
+		input_report_abs(input, ABS_DISTANCE, d);
+
+		input_report_key(input, wacom->tool[0], range); /* PEN or RUBBER */
+		input_report_abs(input, ABS_MISC, wacom->id[0]); /* TOOL ID */
+	}
+
+	if (!range) {
+		wacom->reporting_data = false;
+	}
+
+	return 1;
+}
+
+static int wacom_bpt_irq(struct wacom_wac *wacom, size_t len)
+{
+	struct wacom_features *features = &wacom->features;
+
+	if ((features->type == INTUOSHT2) &&
+	    (features->device_type & WACOM_DEVICETYPE_PEN))
+		return wacom_intuos_irq(wacom);
+	else if (len == WACOM_PKGLEN_BBTOUCH)
+		return wacom_bpt_touch(wacom);
+	else if (len == WACOM_PKGLEN_BBTOUCH3)
+		return wacom_bpt3_touch(wacom);
+	else if (len == WACOM_PKGLEN_BBFUN || len == WACOM_PKGLEN_BBPEN)
+		return wacom_bpt_pen(wacom);
+
+	return 0;
+}
+
+static void wacom_bamboo_pad_pen_event(struct wacom_wac *wacom,
+		unsigned char *data)
+{
+	unsigned char prefix;
+
+	/*
+	 * We need to reroute the event from the debug interface to the
+	 * pen interface.
+	 * We need to add the report ID to the actual pen report, so we
+	 * temporary overwrite the first byte to prevent having to kzalloc/kfree
+	 * and memcpy the report.
+	 */
+	prefix = data[0];
+	data[0] = WACOM_REPORT_BPAD_PEN;
+
+	/*
+	 * actually reroute the event.
+	 * No need to check if wacom->shared->pen is valid, hid_input_report()
+	 * will check for us.
+	 */
+	hid_input_report(wacom->shared->pen, HID_INPUT_REPORT, data,
+			 WACOM_PKGLEN_PENABLED, 1);
+
+	data[0] = prefix;
+}
+
+static int wacom_bamboo_pad_touch_event(struct wacom_wac *wacom,
+		unsigned char *data)
+{
+	struct input_dev *input = wacom->touch_input;
+	unsigned char *finger_data, prefix;
+	unsigned id;
+	int x, y;
+	bool valid;
+
+	prefix = data[0];
+
+	for (id = 0; id < wacom->features.touch_max; id++) {
+		valid = !!(prefix & BIT(id)) &&
+			report_touch_events(wacom);
+
+		input_mt_slot(input, id);
+		input_mt_report_slot_state(input, MT_TOOL_FINGER, valid);
+
+		if (!valid)
+			continue;
+
+		finger_data = data + 1 + id * 3;
+		x = finger_data[0] | ((finger_data[1] & 0x0f) << 8);
+		y = (finger_data[2] << 4) | (finger_data[1] >> 4);
+
+		input_report_abs(input, ABS_MT_POSITION_X, x);
+		input_report_abs(input, ABS_MT_POSITION_Y, y);
+	}
+
+	input_mt_sync_frame(input);
+
+	input_report_key(input, BTN_LEFT, prefix & 0x40);
+	input_report_key(input, BTN_RIGHT, prefix & 0x80);
+
+	/* keep touch state for pen event */
+	wacom->shared->touch_down = !!prefix && report_touch_events(wacom);
+
+	return 1;
+}
+
+static int wacom_bamboo_pad_irq(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char *data = wacom->data;
+
+	if (!((len == WACOM_PKGLEN_BPAD_TOUCH) ||
+	      (len == WACOM_PKGLEN_BPAD_TOUCH_USB)) ||
+	    (data[0] != WACOM_REPORT_BPAD_TOUCH))
+		return 0;
+
+	if (data[1] & 0x01)
+		wacom_bamboo_pad_pen_event(wacom, &data[1]);
+
+	if (data[1] & 0x02)
+		return wacom_bamboo_pad_touch_event(wacom, &data[9]);
+
+	return 0;
+}
+
+static int wacom_wireless_irq(struct wacom_wac *wacom, size_t len)
+{
+	unsigned char *data = wacom->data;
+	int connected;
+
+	if (len != WACOM_PKGLEN_WIRELESS || data[0] != WACOM_REPORT_WL)
+		return 0;
+
+	connected = data[1] & 0x01;
+	if (connected) {
+		int pid, battery, charging;
+
+		if ((wacom->shared->type == INTUOSHT ||
+		    wacom->shared->type == INTUOSHT2) &&
+		    wacom->shared->touch_input &&
+		    wacom->shared->touch_max) {
+			input_report_switch(wacom->shared->touch_input,
+					SW_MUTE_DEVICE, data[5] & 0x40);
+			input_sync(wacom->shared->touch_input);
+		}
+
+		pid = get_unaligned_be16(&data[6]);
+		battery = (data[5] & 0x3f) * 100 / 31;
+		charging = !!(data[5] & 0x80);
+		if (wacom->pid != pid) {
+			wacom->pid = pid;
+			wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS);
+		}
+
+		wacom_notify_battery(wacom, WACOM_POWER_SUPPLY_STATUS_AUTO,
+				     battery, charging, 1, 0);
+
+	} else if (wacom->pid != 0) {
+		/* disconnected while previously connected */
+		wacom->pid = 0;
+		wacom_schedule_work(wacom, WACOM_WORKER_WIRELESS);
+		wacom_notify_battery(wacom, POWER_SUPPLY_STATUS_UNKNOWN, 0, 0, 0, 0);
+	}
+
+	return 0;
+}
+
+static int wacom_status_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+	struct wacom *wacom = container_of(wacom_wac, struct wacom, wacom_wac);
+	struct wacom_features *features = &wacom_wac->features;
+	unsigned char *data = wacom_wac->data;
+
+	if (data[0] != WACOM_REPORT_USB)
+		return 0;
+
+	if ((features->type == INTUOSHT ||
+	    features->type == INTUOSHT2) &&
+	    wacom_wac->shared->touch_input &&
+	    features->touch_max) {
+		input_report_switch(wacom_wac->shared->touch_input,
+				    SW_MUTE_DEVICE, data[8] & 0x40);
+		input_sync(wacom_wac->shared->touch_input);
+	}
+
+	if (data[9] & 0x02) { /* wireless module is attached */
+		int battery = (data[8] & 0x3f) * 100 / 31;
+		bool charging = !!(data[8] & 0x80);
+
+		wacom_notify_battery(wacom_wac, WACOM_POWER_SUPPLY_STATUS_AUTO,
+				     battery, charging, battery || charging, 1);
+
+		if (!wacom->battery.battery &&
+		    !(features->quirks & WACOM_QUIRK_BATTERY)) {
+			features->quirks |= WACOM_QUIRK_BATTERY;
+			wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);
+		}
+	}
+	else if ((features->quirks & WACOM_QUIRK_BATTERY) &&
+		 wacom->battery.battery) {
+		features->quirks &= ~WACOM_QUIRK_BATTERY;
+		wacom_schedule_work(wacom_wac, WACOM_WORKER_BATTERY);
+		wacom_notify_battery(wacom_wac, POWER_SUPPLY_STATUS_UNKNOWN, 0, 0, 0, 0);
+	}
+	return 0;
+}
+
+void wacom_wac_irq(struct wacom_wac *wacom_wac, size_t len)
+{
+	bool sync;
+
+	switch (wacom_wac->features.type) {
+	case PENPARTNER:
+		sync = wacom_penpartner_irq(wacom_wac);
+		break;
+
+	case PL:
+		sync = wacom_pl_irq(wacom_wac);
+		break;
+
+	case WACOM_G4:
+	case GRAPHIRE:
+	case GRAPHIRE_BT:
+	case WACOM_MO:
+		sync = wacom_graphire_irq(wacom_wac);
+		break;
+
+	case PTU:
+		sync = wacom_ptu_irq(wacom_wac);
+		break;
+
+	case DTU:
+		sync = wacom_dtu_irq(wacom_wac);
+		break;
+
+	case DTUS:
+	case DTUSX:
+		sync = wacom_dtus_irq(wacom_wac);
+		break;
+
+	case INTUOS:
+	case INTUOS3S:
+	case INTUOS3:
+	case INTUOS3L:
+	case INTUOS4S:
+	case INTUOS4:
+	case INTUOS4L:
+	case CINTIQ:
+	case WACOM_BEE:
+	case WACOM_13HD:
+	case WACOM_21UX2:
+	case WACOM_22HD:
+	case WACOM_24HD:
+	case WACOM_27QHD:
+	case DTK:
+	case CINTIQ_HYBRID:
+	case CINTIQ_COMPANION_2:
+		sync = wacom_intuos_irq(wacom_wac);
+		break;
+
+	case INTUOS4WL:
+		sync = wacom_intuos_bt_irq(wacom_wac, len);
+		break;
+
+	case WACOM_24HDT:
+	case WACOM_27QHDT:
+		sync = wacom_24hdt_irq(wacom_wac);
+		break;
+
+	case INTUOS5S:
+	case INTUOS5:
+	case INTUOS5L:
+	case INTUOSPS:
+	case INTUOSPM:
+	case INTUOSPL:
+		if (len == WACOM_PKGLEN_BBTOUCH3)
+			sync = wacom_bpt3_touch(wacom_wac);
+		else if (wacom_wac->data[0] == WACOM_REPORT_USB)
+			sync = wacom_status_irq(wacom_wac, len);
+		else
+			sync = wacom_intuos_irq(wacom_wac);
+		break;
+
+	case INTUOSP2_BT:
+	case INTUOSHT3_BT:
+		sync = wacom_intuos_pro2_bt_irq(wacom_wac, len);
+		break;
+
+	case TABLETPC:
+	case TABLETPCE:
+	case TABLETPC2FG:
+	case MTSCREEN:
+	case MTTPC:
+	case MTTPC_B:
+		sync = wacom_tpc_irq(wacom_wac, len);
+		break;
+
+	case BAMBOO_PT:
+	case BAMBOO_PEN:
+	case BAMBOO_TOUCH:
+	case INTUOSHT:
+	case INTUOSHT2:
+		if (wacom_wac->data[0] == WACOM_REPORT_USB)
+			sync = wacom_status_irq(wacom_wac, len);
+		else
+			sync = wacom_bpt_irq(wacom_wac, len);
+		break;
+
+	case BAMBOO_PAD:
+		sync = wacom_bamboo_pad_irq(wacom_wac, len);
+		break;
+
+	case WIRELESS:
+		sync = wacom_wireless_irq(wacom_wac, len);
+		break;
+
+	case REMOTE:
+		sync = false;
+		if (wacom_wac->data[0] == WACOM_REPORT_DEVICE_LIST)
+			wacom_remote_status_irq(wacom_wac, len);
+		else
+			sync = wacom_remote_irq(wacom_wac, len);
+		break;
+
+	default:
+		sync = false;
+		break;
+	}
+
+	if (sync) {
+		if (wacom_wac->pen_input)
+			input_sync(wacom_wac->pen_input);
+		if (wacom_wac->touch_input)
+			input_sync(wacom_wac->touch_input);
+		if (wacom_wac->pad_input)
+			input_sync(wacom_wac->pad_input);
+	}
+}
+
+static void wacom_setup_basic_pro_pen(struct wacom_wac *wacom_wac)
+{
+	struct input_dev *input_dev = wacom_wac->pen_input;
+
+	input_set_capability(input_dev, EV_MSC, MSC_SERIAL);
+
+	__set_bit(BTN_TOOL_PEN, input_dev->keybit);
+	__set_bit(BTN_STYLUS, input_dev->keybit);
+	__set_bit(BTN_STYLUS2, input_dev->keybit);
+
+	input_set_abs_params(input_dev, ABS_DISTANCE,
+			     0, wacom_wac->features.distance_max, wacom_wac->features.distance_fuzz, 0);
+}
+
+static void wacom_setup_cintiq(struct wacom_wac *wacom_wac)
+{
+	struct input_dev *input_dev = wacom_wac->pen_input;
+	struct wacom_features *features = &wacom_wac->features;
+
+	wacom_setup_basic_pro_pen(wacom_wac);
+
+	__set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+	__set_bit(BTN_TOOL_BRUSH, input_dev->keybit);
+	__set_bit(BTN_TOOL_PENCIL, input_dev->keybit);
+	__set_bit(BTN_TOOL_AIRBRUSH, input_dev->keybit);
+
+	input_set_abs_params(input_dev, ABS_WHEEL, 0, 1023, 0, 0);
+	input_set_abs_params(input_dev, ABS_TILT_X, -64, 63, features->tilt_fuzz, 0);
+	input_abs_set_res(input_dev, ABS_TILT_X, 57);
+	input_set_abs_params(input_dev, ABS_TILT_Y, -64, 63, features->tilt_fuzz, 0);
+	input_abs_set_res(input_dev, ABS_TILT_Y, 57);
+}
+
+static void wacom_setup_intuos(struct wacom_wac *wacom_wac)
+{
+	struct input_dev *input_dev = wacom_wac->pen_input;
+
+	input_set_capability(input_dev, EV_REL, REL_WHEEL);
+
+	wacom_setup_cintiq(wacom_wac);
+
+	__set_bit(BTN_LEFT, input_dev->keybit);
+	__set_bit(BTN_RIGHT, input_dev->keybit);
+	__set_bit(BTN_MIDDLE, input_dev->keybit);
+	__set_bit(BTN_SIDE, input_dev->keybit);
+	__set_bit(BTN_EXTRA, input_dev->keybit);
+	__set_bit(BTN_TOOL_MOUSE, input_dev->keybit);
+	__set_bit(BTN_TOOL_LENS, input_dev->keybit);
+
+	input_set_abs_params(input_dev, ABS_RZ, -900, 899, 0, 0);
+	input_abs_set_res(input_dev, ABS_RZ, 287);
+	input_set_abs_params(input_dev, ABS_THROTTLE, -1023, 1023, 0, 0);
+}
+
+void wacom_setup_device_quirks(struct wacom *wacom)
+{
+	struct wacom_wac *wacom_wac = &wacom->wacom_wac;
+	struct wacom_features *features = &wacom->wacom_wac.features;
+
+	/* The pen and pad share the same interface on most devices */
+	if (features->type == GRAPHIRE_BT || features->type == WACOM_G4 ||
+	    features->type == DTUS ||
+	    (features->type >= INTUOS3S && features->type <= WACOM_MO)) {
+		if (features->device_type & WACOM_DEVICETYPE_PEN)
+			features->device_type |= WACOM_DEVICETYPE_PAD;
+	}
+
+	/* touch device found but size is not defined. use default */
+	if (features->device_type & WACOM_DEVICETYPE_TOUCH && !features->x_max) {
+		features->x_max = 1023;
+		features->y_max = 1023;
+	}
+
+	/*
+	 * Intuos5/Pro and Bamboo 3rd gen have no useful data about its
+	 * touch interface in its HID descriptor. If this is the touch
+	 * interface (PacketSize of WACOM_PKGLEN_BBTOUCH3), override the
+	 * tablet values.
+	 */
+	if ((features->type >= INTUOS5S && features->type <= INTUOSPL) ||
+		(features->type >= INTUOSHT && features->type <= BAMBOO_PT)) {
+		if (features->pktlen == WACOM_PKGLEN_BBTOUCH3) {
+			if (features->touch_max)
+				features->device_type |= WACOM_DEVICETYPE_TOUCH;
+			if (features->type >= INTUOSHT && features->type <= BAMBOO_PT)
+				features->device_type |= WACOM_DEVICETYPE_PAD;
+
+			if (features->type == INTUOSHT2) {
+				features->x_max = features->x_max / 10;
+				features->y_max = features->y_max / 10;
+			}
+			else {
+				features->x_max = 4096;
+				features->y_max = 4096;
+			}
+		}
+		else if (features->pktlen == WACOM_PKGLEN_BBTOUCH) {
+			features->device_type |= WACOM_DEVICETYPE_PAD;
+		}
+	}
+
+	/*
+	 * Hack for the Bamboo One:
+	 * the device presents a PAD/Touch interface as most Bamboos and even
+	 * sends ghosts PAD data on it. However, later, we must disable this
+	 * ghost interface, and we can not detect it unless we set it here
+	 * to WACOM_DEVICETYPE_PAD or WACOM_DEVICETYPE_TOUCH.
+	 */
+	if (features->type == BAMBOO_PEN &&
+	    features->pktlen == WACOM_PKGLEN_BBTOUCH3)
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+
+	/*
+	 * Raw Wacom-mode pen and touch events both come from interface
+	 * 0, whose HID descriptor has an application usage of 0xFF0D
+	 * (i.e., WACOM_HID_WD_DIGITIZER). We route pen packets back
+	 * out through the HID_GENERIC device created for interface 1,
+	 * so rewrite this one to be of type WACOM_DEVICETYPE_TOUCH.
+	 */
+	if (features->type == BAMBOO_PAD)
+		features->device_type = WACOM_DEVICETYPE_TOUCH;
+
+	if (features->type == REMOTE)
+		features->device_type = WACOM_DEVICETYPE_PAD;
+
+	if (features->type == INTUOSP2_BT) {
+		features->device_type |= WACOM_DEVICETYPE_PEN |
+					 WACOM_DEVICETYPE_PAD |
+					 WACOM_DEVICETYPE_TOUCH;
+		features->quirks |= WACOM_QUIRK_BATTERY;
+	}
+
+	if (features->type == INTUOSHT3_BT) {
+		features->device_type |= WACOM_DEVICETYPE_PEN |
+					 WACOM_DEVICETYPE_PAD;
+		features->quirks |= WACOM_QUIRK_BATTERY;
+	}
+
+	switch (features->type) {
+	case PL:
+	case DTU:
+	case DTUS:
+	case DTUSX:
+	case WACOM_21UX2:
+	case WACOM_22HD:
+	case DTK:
+	case WACOM_24HD:
+	case WACOM_27QHD:
+	case CINTIQ_HYBRID:
+	case CINTIQ_COMPANION_2:
+	case CINTIQ:
+	case WACOM_BEE:
+	case WACOM_13HD:
+	case WACOM_24HDT:
+	case WACOM_27QHDT:
+	case TABLETPC:
+	case TABLETPCE:
+	case TABLETPC2FG:
+	case MTSCREEN:
+	case MTTPC:
+	case MTTPC_B:
+		features->device_type |= WACOM_DEVICETYPE_DIRECT;
+		break;
+	}
+
+	if (wacom->hdev->bus == BUS_BLUETOOTH)
+		features->quirks |= WACOM_QUIRK_BATTERY;
+
+	/* quirk for bamboo touch with 2 low res touches */
+	if ((features->type == BAMBOO_PT || features->type == BAMBOO_TOUCH) &&
+	    features->pktlen == WACOM_PKGLEN_BBTOUCH) {
+		features->x_max <<= 5;
+		features->y_max <<= 5;
+		features->x_fuzz <<= 5;
+		features->y_fuzz <<= 5;
+		features->quirks |= WACOM_QUIRK_BBTOUCH_LOWRES;
+	}
+
+	if (features->type == WIRELESS) {
+		if (features->device_type == WACOM_DEVICETYPE_WL_MONITOR) {
+			features->quirks |= WACOM_QUIRK_BATTERY;
+		}
+	}
+
+	if (features->type == REMOTE)
+		features->device_type |= WACOM_DEVICETYPE_WL_MONITOR;
+
+	/* HID descriptor for DTK-2451 / DTH-2452 claims to report lots
+	 * of things it shouldn't. Lets fix up the damage...
+	 */
+	if (wacom->hdev->product == 0x382 || wacom->hdev->product == 0x37d) {
+		features->quirks &= ~WACOM_QUIRK_TOOLSERIAL;
+		__clear_bit(BTN_TOOL_BRUSH, wacom_wac->pen_input->keybit);
+		__clear_bit(BTN_TOOL_PENCIL, wacom_wac->pen_input->keybit);
+		__clear_bit(BTN_TOOL_AIRBRUSH, wacom_wac->pen_input->keybit);
+		__clear_bit(ABS_Z, wacom_wac->pen_input->absbit);
+		__clear_bit(ABS_DISTANCE, wacom_wac->pen_input->absbit);
+		__clear_bit(ABS_TILT_X, wacom_wac->pen_input->absbit);
+		__clear_bit(ABS_TILT_Y, wacom_wac->pen_input->absbit);
+		__clear_bit(ABS_WHEEL, wacom_wac->pen_input->absbit);
+		__clear_bit(ABS_MISC, wacom_wac->pen_input->absbit);
+		__clear_bit(MSC_SERIAL, wacom_wac->pen_input->mscbit);
+		__clear_bit(EV_MSC, wacom_wac->pen_input->evbit);
+	}
+}
+
+int wacom_setup_pen_input_capabilities(struct input_dev *input_dev,
+				   struct wacom_wac *wacom_wac)
+{
+	struct wacom_features *features = &wacom_wac->features;
+
+	input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+	if (!(features->device_type & WACOM_DEVICETYPE_PEN))
+		return -ENODEV;
+
+	if (features->device_type & WACOM_DEVICETYPE_DIRECT)
+		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+	else
+		__set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+	if (features->type == HID_GENERIC) {
+		/* setup has already been done; apply otherwise-undetectible quirks */
+		input_set_capability(input_dev, EV_KEY, BTN_STYLUS3);
+		return 0;
+	}
+
+	__set_bit(BTN_TOUCH, input_dev->keybit);
+	__set_bit(ABS_MISC, input_dev->absbit);
+
+	input_set_abs_params(input_dev, ABS_X, 0 + features->offset_left,
+			     features->x_max - features->offset_right,
+			     features->x_fuzz, 0);
+	input_set_abs_params(input_dev, ABS_Y, 0 + features->offset_top,
+			     features->y_max - features->offset_bottom,
+			     features->y_fuzz, 0);
+	input_set_abs_params(input_dev, ABS_PRESSURE, 0,
+		features->pressure_max, features->pressure_fuzz, 0);
+
+	/* penabled devices have fixed resolution for each model */
+	input_abs_set_res(input_dev, ABS_X, features->x_resolution);
+	input_abs_set_res(input_dev, ABS_Y, features->y_resolution);
+
+	switch (features->type) {
+	case GRAPHIRE_BT:
+		__clear_bit(ABS_MISC, input_dev->absbit);
+
+	case WACOM_MO:
+	case WACOM_G4:
+		input_set_abs_params(input_dev, ABS_DISTANCE, 0,
+					      features->distance_max,
+					      features->distance_fuzz, 0);
+		/* fall through */
+
+	case GRAPHIRE:
+		input_set_capability(input_dev, EV_REL, REL_WHEEL);
+
+		__set_bit(BTN_LEFT, input_dev->keybit);
+		__set_bit(BTN_RIGHT, input_dev->keybit);
+		__set_bit(BTN_MIDDLE, input_dev->keybit);
+
+		__set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+		__set_bit(BTN_TOOL_PEN, input_dev->keybit);
+		__set_bit(BTN_TOOL_MOUSE, input_dev->keybit);
+		__set_bit(BTN_STYLUS, input_dev->keybit);
+		__set_bit(BTN_STYLUS2, input_dev->keybit);
+		break;
+
+	case WACOM_27QHD:
+	case WACOM_24HD:
+	case DTK:
+	case WACOM_22HD:
+	case WACOM_21UX2:
+	case WACOM_BEE:
+	case CINTIQ:
+	case WACOM_13HD:
+	case CINTIQ_HYBRID:
+	case CINTIQ_COMPANION_2:
+		input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
+		input_abs_set_res(input_dev, ABS_Z, 287);
+		wacom_setup_cintiq(wacom_wac);
+		break;
+
+	case INTUOS3:
+	case INTUOS3L:
+	case INTUOS3S:
+	case INTUOS4:
+	case INTUOS4WL:
+	case INTUOS4L:
+	case INTUOS4S:
+		input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
+		input_abs_set_res(input_dev, ABS_Z, 287);
+		/* fall through */
+
+	case INTUOS:
+		wacom_setup_intuos(wacom_wac);
+		break;
+
+	case INTUOS5:
+	case INTUOS5L:
+	case INTUOSPM:
+	case INTUOSPL:
+	case INTUOS5S:
+	case INTUOSPS:
+	case INTUOSP2_BT:
+		input_set_abs_params(input_dev, ABS_DISTANCE, 0,
+				      features->distance_max,
+				      features->distance_fuzz, 0);
+
+		input_set_abs_params(input_dev, ABS_Z, -900, 899, 0, 0);
+		input_abs_set_res(input_dev, ABS_Z, 287);
+
+		wacom_setup_intuos(wacom_wac);
+		break;
+
+	case WACOM_24HDT:
+	case WACOM_27QHDT:
+	case MTSCREEN:
+	case MTTPC:
+	case MTTPC_B:
+	case TABLETPC2FG:
+	case TABLETPC:
+	case TABLETPCE:
+		__clear_bit(ABS_MISC, input_dev->absbit);
+		/* fall through */
+
+	case DTUS:
+	case DTUSX:
+	case PL:
+	case DTU:
+		__set_bit(BTN_TOOL_PEN, input_dev->keybit);
+		__set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+		__set_bit(BTN_STYLUS, input_dev->keybit);
+		__set_bit(BTN_STYLUS2, input_dev->keybit);
+		break;
+
+	case PTU:
+		__set_bit(BTN_STYLUS2, input_dev->keybit);
+		/* fall through */
+
+	case PENPARTNER:
+		__set_bit(BTN_TOOL_PEN, input_dev->keybit);
+		__set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+		__set_bit(BTN_STYLUS, input_dev->keybit);
+		break;
+
+	case INTUOSHT:
+	case BAMBOO_PT:
+	case BAMBOO_PEN:
+	case INTUOSHT2:
+	case INTUOSHT3_BT:
+		if (features->type == INTUOSHT2 ||
+		    features->type == INTUOSHT3_BT) {
+			wacom_setup_basic_pro_pen(wacom_wac);
+		} else {
+			__clear_bit(ABS_MISC, input_dev->absbit);
+			__set_bit(BTN_TOOL_PEN, input_dev->keybit);
+			__set_bit(BTN_TOOL_RUBBER, input_dev->keybit);
+			__set_bit(BTN_STYLUS, input_dev->keybit);
+			__set_bit(BTN_STYLUS2, input_dev->keybit);
+			input_set_abs_params(input_dev, ABS_DISTANCE, 0,
+				      features->distance_max,
+				      features->distance_fuzz, 0);
+		}
+		break;
+	case BAMBOO_PAD:
+		__clear_bit(ABS_MISC, input_dev->absbit);
+		break;
+	}
+	return 0;
+}
+
+int wacom_setup_touch_input_capabilities(struct input_dev *input_dev,
+					 struct wacom_wac *wacom_wac)
+{
+	struct wacom_features *features = &wacom_wac->features;
+
+	input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+	if (!(features->device_type & WACOM_DEVICETYPE_TOUCH))
+		return -ENODEV;
+
+	if (features->device_type & WACOM_DEVICETYPE_DIRECT)
+		__set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
+	else
+		__set_bit(INPUT_PROP_POINTER, input_dev->propbit);
+
+	if (features->type == HID_GENERIC)
+		/* setup has already been done */
+		return 0;
+
+	__set_bit(BTN_TOUCH, input_dev->keybit);
+
+	if (features->touch_max == 1) {
+		input_set_abs_params(input_dev, ABS_X, 0,
+			features->x_max, features->x_fuzz, 0);
+		input_set_abs_params(input_dev, ABS_Y, 0,
+			features->y_max, features->y_fuzz, 0);
+		input_abs_set_res(input_dev, ABS_X,
+				  features->x_resolution);
+		input_abs_set_res(input_dev, ABS_Y,
+				  features->y_resolution);
+	}
+	else if (features->touch_max > 1) {
+		input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0,
+			features->x_max, features->x_fuzz, 0);
+		input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0,
+			features->y_max, features->y_fuzz, 0);
+		input_abs_set_res(input_dev, ABS_MT_POSITION_X,
+				  features->x_resolution);
+		input_abs_set_res(input_dev, ABS_MT_POSITION_Y,
+				  features->y_resolution);
+	}
+
+	switch (features->type) {
+	case INTUOSP2_BT:
+		input_dev->evbit[0] |= BIT_MASK(EV_SW);
+		__set_bit(SW_MUTE_DEVICE, input_dev->swbit);
+
+		if (wacom_wac->shared->touch->product == 0x361) {
+			input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+					     0, 12440, 4, 0);
+			input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+					     0, 8640, 4, 0);
+		}
+		else if (wacom_wac->shared->touch->product == 0x360) {
+			input_set_abs_params(input_dev, ABS_MT_POSITION_X,
+					     0, 8960, 4, 0);
+			input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
+					     0, 5920, 4, 0);
+		}
+		input_abs_set_res(input_dev, ABS_MT_POSITION_X, 40);
+		input_abs_set_res(input_dev, ABS_MT_POSITION_X, 40);
+
+		/* fall through */
+
+	case INTUOS5:
+	case INTUOS5L:
+	case INTUOSPM:
+	case INTUOSPL:
+	case INTUOS5S:
+	case INTUOSPS:
+		input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, features->x_max, 0, 0);
+		input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, features->y_max, 0, 0);
+		input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_POINTER);
+		break;
+
+	case WACOM_24HDT:
+		input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, features->x_max, 0, 0);
+		input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, features->x_max, 0, 0);
+		input_set_abs_params(input_dev, ABS_MT_WIDTH_MINOR, 0, features->y_max, 0, 0);
+		input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
+		/* fall through */
+
+	case WACOM_27QHDT:
+	case MTSCREEN:
+	case MTTPC:
+	case MTTPC_B:
+	case TABLETPC2FG:
+		input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_DIRECT);
+		/*fall through */
+
+	case TABLETPC:
+	case TABLETPCE:
+		break;
+
+	case INTUOSHT:
+	case INTUOSHT2:
+		input_dev->evbit[0] |= BIT_MASK(EV_SW);
+		__set_bit(SW_MUTE_DEVICE, input_dev->swbit);
+		/* fall through */
+
+	case BAMBOO_PT:
+	case BAMBOO_TOUCH:
+		if (features->pktlen == WACOM_PKGLEN_BBTOUCH3) {
+			input_set_abs_params(input_dev,
+				     ABS_MT_TOUCH_MAJOR,
+				     0, features->x_max, 0, 0);
+			input_set_abs_params(input_dev,
+				     ABS_MT_TOUCH_MINOR,
+				     0, features->y_max, 0, 0);
+		}
+		input_mt_init_slots(input_dev, features->touch_max, INPUT_MT_POINTER);
+		break;
+
+	case BAMBOO_PAD:
+		input_mt_init_slots(input_dev, features->touch_max,
+				    INPUT_MT_POINTER);
+		__set_bit(BTN_LEFT, input_dev->keybit);
+		__set_bit(BTN_RIGHT, input_dev->keybit);
+		break;
+	}
+	return 0;
+}
+
+static int wacom_numbered_button_to_key(int n)
+{
+	if (n < 10)
+		return BTN_0 + n;
+	else if (n < 16)
+		return BTN_A + (n-10);
+	else if (n < 18)
+		return BTN_BASE + (n-16);
+	else
+		return 0;
+}
+
+static void wacom_setup_numbered_buttons(struct input_dev *input_dev,
+				int button_count)
+{
+	int i;
+
+	for (i = 0; i < button_count; i++) {
+		int key = wacom_numbered_button_to_key(i);
+
+		if (key)
+			__set_bit(key, input_dev->keybit);
+	}
+}
+
+static void wacom_24hd_update_leds(struct wacom *wacom, int mask, int group)
+{
+	struct wacom_led *led;
+	int i;
+	bool updated = false;
+
+	/*
+	 * 24HD has LED group 1 to the left and LED group 0 to the right.
+	 * So group 0 matches the second half of the buttons and thus the mask
+	 * needs to be shifted.
+	 */
+	if (group == 0)
+		mask >>= 8;
+
+	for (i = 0; i < 3; i++) {
+		led = wacom_led_find(wacom, group, i);
+		if (!led) {
+			hid_err(wacom->hdev, "can't find LED %d in group %d\n",
+				i, group);
+			continue;
+		}
+		if (!updated && mask & BIT(i)) {
+			led->held = true;
+			led_trigger_event(&led->trigger, LED_FULL);
+		} else {
+			led->held = false;
+		}
+	}
+}
+
+static bool wacom_is_led_toggled(struct wacom *wacom, int button_count,
+				 int mask, int group)
+{
+	int button_per_group;
+
+	/*
+	 * 21UX2 has LED group 1 to the left and LED group 0
+	 * to the right. We need to reverse the group to match this
+	 * historical behavior.
+	 */
+	if (wacom->wacom_wac.features.type == WACOM_21UX2)
+		group = 1 - group;
+
+	button_per_group = button_count/wacom->led.count;
+
+	return mask & (1 << (group * button_per_group));
+}
+
+static void wacom_update_led(struct wacom *wacom, int button_count, int mask,
+			     int group)
+{
+	struct wacom_led *led, *next_led;
+	int cur;
+	bool pressed;
+
+	if (wacom->wacom_wac.features.type == WACOM_24HD)
+		return wacom_24hd_update_leds(wacom, mask, group);
+
+	pressed = wacom_is_led_toggled(wacom, button_count, mask, group);
+	cur = wacom->led.groups[group].select;
+
+	led = wacom_led_find(wacom, group, cur);
+	if (!led) {
+		hid_err(wacom->hdev, "can't find current LED %d in group %d\n",
+			cur, group);
+		return;
+	}
+
+	if (!pressed) {
+		led->held = false;
+		return;
+	}
+
+	if (led->held && pressed)
+		return;
+
+	next_led = wacom_led_next(wacom, led);
+	if (!next_led) {
+		hid_err(wacom->hdev, "can't find next LED in group %d\n",
+			group);
+		return;
+	}
+	if (next_led == led)
+		return;
+
+	next_led->held = true;
+	led_trigger_event(&next_led->trigger,
+			  wacom_leds_brightness_get(next_led));
+}
+
+static void wacom_report_numbered_buttons(struct input_dev *input_dev,
+				int button_count, int mask)
+{
+	struct wacom *wacom = input_get_drvdata(input_dev);
+	int i;
+
+	for (i = 0; i < wacom->led.count; i++)
+		wacom_update_led(wacom,  button_count, mask, i);
+
+	for (i = 0; i < button_count; i++) {
+		int key = wacom_numbered_button_to_key(i);
+
+		if (key)
+			input_report_key(input_dev, key, mask & (1 << i));
+	}
+}
+
+int wacom_setup_pad_input_capabilities(struct input_dev *input_dev,
+				   struct wacom_wac *wacom_wac)
+{
+	struct wacom_features *features = &wacom_wac->features;
+
+	if ((features->type == HID_GENERIC) && features->numbered_buttons > 0)
+		features->device_type |= WACOM_DEVICETYPE_PAD;
+
+	if (!(features->device_type & WACOM_DEVICETYPE_PAD))
+		return -ENODEV;
+
+	if (features->type == REMOTE && input_dev == wacom_wac->pad_input)
+		return -ENODEV;
+
+	input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+
+	/* kept for making legacy xf86-input-wacom working with the wheels */
+	__set_bit(ABS_MISC, input_dev->absbit);
+
+	/* kept for making legacy xf86-input-wacom accepting the pad */
+	if (!(input_dev->absinfo && (input_dev->absinfo[ABS_X].minimum ||
+	      input_dev->absinfo[ABS_X].maximum)))
+		input_set_abs_params(input_dev, ABS_X, 0, 1, 0, 0);
+	if (!(input_dev->absinfo && (input_dev->absinfo[ABS_Y].minimum ||
+	      input_dev->absinfo[ABS_Y].maximum)))
+		input_set_abs_params(input_dev, ABS_Y, 0, 1, 0, 0);
+
+	/* kept for making udev and libwacom accepting the pad */
+	__set_bit(BTN_STYLUS, input_dev->keybit);
+
+	wacom_setup_numbered_buttons(input_dev, features->numbered_buttons);
+
+	switch (features->type) {
+
+	case CINTIQ_HYBRID:
+	case CINTIQ_COMPANION_2:
+	case DTK:
+	case DTUS:
+	case GRAPHIRE_BT:
+		break;
+
+	case WACOM_MO:
+		__set_bit(BTN_BACK, input_dev->keybit);
+		__set_bit(BTN_LEFT, input_dev->keybit);
+		__set_bit(BTN_FORWARD, input_dev->keybit);
+		__set_bit(BTN_RIGHT, input_dev->keybit);
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		break;
+
+	case WACOM_G4:
+		__set_bit(BTN_BACK, input_dev->keybit);
+		__set_bit(BTN_FORWARD, input_dev->keybit);
+		input_set_capability(input_dev, EV_REL, REL_WHEEL);
+		break;
+
+	case WACOM_24HD:
+		__set_bit(KEY_PROG1, input_dev->keybit);
+		__set_bit(KEY_PROG2, input_dev->keybit);
+		__set_bit(KEY_PROG3, input_dev->keybit);
+
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		input_set_abs_params(input_dev, ABS_THROTTLE, 0, 71, 0, 0);
+		break;
+
+	case WACOM_27QHD:
+		__set_bit(KEY_PROG1, input_dev->keybit);
+		__set_bit(KEY_PROG2, input_dev->keybit);
+		__set_bit(KEY_PROG3, input_dev->keybit);
+		input_set_abs_params(input_dev, ABS_X, -2048, 2048, 0, 0);
+		input_abs_set_res(input_dev, ABS_X, 1024); /* points/g */
+		input_set_abs_params(input_dev, ABS_Y, -2048, 2048, 0, 0);
+		input_abs_set_res(input_dev, ABS_Y, 1024);
+		input_set_abs_params(input_dev, ABS_Z, -2048, 2048, 0, 0);
+		input_abs_set_res(input_dev, ABS_Z, 1024);
+		__set_bit(INPUT_PROP_ACCELEROMETER, input_dev->propbit);
+		break;
+
+	case WACOM_22HD:
+		__set_bit(KEY_PROG1, input_dev->keybit);
+		__set_bit(KEY_PROG2, input_dev->keybit);
+		__set_bit(KEY_PROG3, input_dev->keybit);
+		/* fall through */
+
+	case WACOM_21UX2:
+	case WACOM_BEE:
+	case CINTIQ:
+		input_set_abs_params(input_dev, ABS_RX, 0, 4096, 0, 0);
+		input_set_abs_params(input_dev, ABS_RY, 0, 4096, 0, 0);
+		break;
+
+	case WACOM_13HD:
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		break;
+
+	case INTUOS3:
+	case INTUOS3L:
+		input_set_abs_params(input_dev, ABS_RY, 0, 4096, 0, 0);
+		/* fall through */
+
+	case INTUOS3S:
+		input_set_abs_params(input_dev, ABS_RX, 0, 4096, 0, 0);
+		break;
+
+	case INTUOS5:
+	case INTUOS5L:
+	case INTUOSPM:
+	case INTUOSPL:
+	case INTUOS5S:
+	case INTUOSPS:
+	case INTUOSP2_BT:
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		break;
+
+	case INTUOS4WL:
+		/*
+		 * For Bluetooth devices, the udev rule does not work correctly
+		 * for pads unless we add a stylus capability, which forces
+		 * ID_INPUT_TABLET to be set.
+		 */
+		__set_bit(BTN_STYLUS, input_dev->keybit);
+		/* fall through */
+
+	case INTUOS4:
+	case INTUOS4L:
+	case INTUOS4S:
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		break;
+
+	case INTUOSHT:
+	case BAMBOO_PT:
+	case BAMBOO_TOUCH:
+	case INTUOSHT2:
+		__clear_bit(ABS_MISC, input_dev->absbit);
+
+		__set_bit(BTN_LEFT, input_dev->keybit);
+		__set_bit(BTN_FORWARD, input_dev->keybit);
+		__set_bit(BTN_BACK, input_dev->keybit);
+		__set_bit(BTN_RIGHT, input_dev->keybit);
+
+		break;
+
+	case REMOTE:
+		input_set_capability(input_dev, EV_MSC, MSC_SERIAL);
+		input_set_abs_params(input_dev, ABS_WHEEL, 0, 71, 0, 0);
+		break;
+
+	case INTUOSHT3_BT:
+	case HID_GENERIC:
+		break;
+
+	default:
+		/* no pad supported */
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static const struct wacom_features wacom_features_0x00 =
+	{ "Wacom Penpartner", 5040, 3780, 255, 0,
+	  PENPARTNER, WACOM_PENPRTN_RES, WACOM_PENPRTN_RES };
+static const struct wacom_features wacom_features_0x10 =
+	{ "Wacom Graphire", 10206, 7422, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x81 =
+	{ "Wacom Graphire BT", 16704, 12064, 511, 32,
+	  GRAPHIRE_BT, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES, 2 };
+static const struct wacom_features wacom_features_0x11 =
+	{ "Wacom Graphire2 4x5", 10206, 7422, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x12 =
+	{ "Wacom Graphire2 5x7", 13918, 10206, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x13 =
+	{ "Wacom Graphire3", 10208, 7424, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x14 =
+	{ "Wacom Graphire3 6x8", 16704, 12064, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x15 =
+	{ "Wacom Graphire4 4x5", 10208, 7424, 511, 63,
+	  WACOM_G4, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x16 =
+	{ "Wacom Graphire4 6x8", 16704, 12064, 511, 63,
+	  WACOM_G4, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x17 =
+	{ "Wacom BambooFun 4x5", 14760, 9225, 511, 63,
+	  WACOM_MO, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x18 =
+	{ "Wacom BambooFun 6x8", 21648, 13530, 511, 63,
+	  WACOM_MO, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x19 =
+	{ "Wacom Bamboo1 Medium", 16704, 12064, 511, 63,
+	  GRAPHIRE, WACOM_GRAPHIRE_RES, WACOM_GRAPHIRE_RES };
+static const struct wacom_features wacom_features_0x60 =
+	{ "Wacom Volito", 5104, 3712, 511, 63,
+	  GRAPHIRE, WACOM_VOLITO_RES, WACOM_VOLITO_RES };
+static const struct wacom_features wacom_features_0x61 =
+	{ "Wacom PenStation2", 3250, 2320, 255, 63,
+	  GRAPHIRE, WACOM_VOLITO_RES, WACOM_VOLITO_RES };
+static const struct wacom_features wacom_features_0x62 =
+	{ "Wacom Volito2 4x5", 5104, 3712, 511, 63,
+	  GRAPHIRE, WACOM_VOLITO_RES, WACOM_VOLITO_RES };
+static const struct wacom_features wacom_features_0x63 =
+	{ "Wacom Volito2 2x3", 3248, 2320, 511, 63,
+	  GRAPHIRE, WACOM_VOLITO_RES, WACOM_VOLITO_RES };
+static const struct wacom_features wacom_features_0x64 =
+	{ "Wacom PenPartner2", 3250, 2320, 511, 63,
+	  GRAPHIRE, WACOM_VOLITO_RES, WACOM_VOLITO_RES };
+static const struct wacom_features wacom_features_0x65 =
+	{ "Wacom Bamboo", 14760, 9225, 511, 63,
+	  WACOM_MO, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x69 =
+	{ "Wacom Bamboo1", 5104, 3712, 511, 63,
+	  GRAPHIRE, WACOM_PENPRTN_RES, WACOM_PENPRTN_RES };
+static const struct wacom_features wacom_features_0x6A =
+	{ "Wacom Bamboo1 4x6", 14760, 9225, 1023, 63,
+	  GRAPHIRE, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x6B =
+	{ "Wacom Bamboo1 5x8", 21648, 13530, 1023, 63,
+	  GRAPHIRE, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x20 =
+	{ "Wacom Intuos 4x5", 12700, 10600, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x21 =
+	{ "Wacom Intuos 6x8", 20320, 16240, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x22 =
+	{ "Wacom Intuos 9x12", 30480, 24060, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x23 =
+	{ "Wacom Intuos 12x12", 30480, 31680, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x24 =
+	{ "Wacom Intuos 12x18", 45720, 31680, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x30 =
+	{ "Wacom PL400", 5408, 4056, 255, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x31 =
+	{ "Wacom PL500", 6144, 4608, 255, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x32 =
+	{ "Wacom PL600", 6126, 4604, 255, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x33 =
+	{ "Wacom PL600SX", 6260, 5016, 255, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x34 =
+	{ "Wacom PL550", 6144, 4608, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x35 =
+	{ "Wacom PL800", 7220, 5780, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x37 =
+	{ "Wacom PL700", 6758, 5406, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x38 =
+	{ "Wacom PL510", 6282, 4762, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x39 =
+	{ "Wacom DTU710", 34080, 27660, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0xC4 =
+	{ "Wacom DTF521", 6282, 4762, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0xC0 =
+	{ "Wacom DTF720", 6858, 5506, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0xC2 =
+	{ "Wacom DTF720a", 6858, 5506, 511, 0,
+	  PL, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x03 =
+	{ "Wacom Cintiq Partner", 20480, 15360, 511, 0,
+	  PTU, WACOM_PL_RES, WACOM_PL_RES };
+static const struct wacom_features wacom_features_0x41 =
+	{ "Wacom Intuos2 4x5", 12700, 10600, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x42 =
+	{ "Wacom Intuos2 6x8", 20320, 16240, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x43 =
+	{ "Wacom Intuos2 9x12", 30480, 24060, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x44 =
+	{ "Wacom Intuos2 12x12", 30480, 31680, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x45 =
+	{ "Wacom Intuos2 12x18", 45720, 31680, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xB0 =
+	{ "Wacom Intuos3 4x5", 25400, 20320, 1023, 63,
+	  INTUOS3S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 4 };
+static const struct wacom_features wacom_features_0xB1 =
+	{ "Wacom Intuos3 6x8", 40640, 30480, 1023, 63,
+	  INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xB2 =
+	{ "Wacom Intuos3 9x12", 60960, 45720, 1023, 63,
+	  INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xB3 =
+	{ "Wacom Intuos3 12x12", 60960, 60960, 1023, 63,
+	  INTUOS3L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xB4 =
+	{ "Wacom Intuos3 12x19", 97536, 60960, 1023, 63,
+	  INTUOS3L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xB5 =
+	{ "Wacom Intuos3 6x11", 54204, 31750, 1023, 63,
+	  INTUOS3, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xB7 =
+	{ "Wacom Intuos3 4x6", 31496, 19685, 1023, 63,
+	  INTUOS3S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 4 };
+static const struct wacom_features wacom_features_0xB8 =
+	{ "Wacom Intuos4 4x6", 31496, 19685, 2047, 63,
+	  INTUOS4S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 7 };
+static const struct wacom_features wacom_features_0xB9 =
+	{ "Wacom Intuos4 6x9", 44704, 27940, 2047, 63,
+	  INTUOS4, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0xBA =
+	{ "Wacom Intuos4 8x13", 65024, 40640, 2047, 63,
+	  INTUOS4L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0xBB =
+	{ "Wacom Intuos4 12x19", 97536, 60960, 2047, 63,
+	  INTUOS4L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0xBC =
+	{ "Wacom Intuos4 WL", 40640, 25400, 2047, 63,
+	  INTUOS4, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0xBD =
+	{ "Wacom Intuos4 WL", 40640, 25400, 2047, 63,
+	  INTUOS4WL, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0x26 =
+	{ "Wacom Intuos5 touch S", 31496, 19685, 2047, 63,
+	  INTUOS5S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 7, .touch_max = 16 };
+static const struct wacom_features wacom_features_0x27 =
+	{ "Wacom Intuos5 touch M", 44704, 27940, 2047, 63,
+	  INTUOS5, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 16 };
+static const struct wacom_features wacom_features_0x28 =
+	{ "Wacom Intuos5 touch L", 65024, 40640, 2047, 63,
+	  INTUOS5L, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 16 };
+static const struct wacom_features wacom_features_0x29 =
+	{ "Wacom Intuos5 S", 31496, 19685, 2047, 63,
+	  INTUOS5S, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 7 };
+static const struct wacom_features wacom_features_0x2A =
+	{ "Wacom Intuos5 M", 44704, 27940, 2047, 63,
+	  INTUOS5, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9 };
+static const struct wacom_features wacom_features_0x314 =
+	{ "Wacom Intuos Pro S", 31496, 19685, 2047, 63,
+	  INTUOSPS, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 7, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x315 =
+	{ "Wacom Intuos Pro M", 44704, 27940, 2047, 63,
+	  INTUOSPM, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x317 =
+	{ "Wacom Intuos Pro L", 65024, 40640, 2047, 63,
+	  INTUOSPL, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0xF4 =
+	{ "Wacom Cintiq 24HD", 104480, 65600, 2047, 63,
+	  WACOM_24HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 16,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0xF8 =
+	{ "Wacom Cintiq 24HD touch", 104480, 65600, 2047, 63, /* Pen */
+	  WACOM_24HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 16,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0xf6 };
+static const struct wacom_features wacom_features_0xF6 =
+	{ "Wacom Cintiq 24HD touch", .type = WACOM_24HDT, /* Touch */
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0xf8, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x32A =
+	{ "Wacom Cintiq 27QHD", 120140, 67920, 2047, 63,
+	  WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 0,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x32B =
+	{ "Wacom Cintiq 27QHD touch", 120140, 67920, 2047, 63,
+	  WACOM_27QHD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 0,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x32C };
+static const struct wacom_features wacom_features_0x32C =
+	{ "Wacom Cintiq 27QHD touch", .type = WACOM_27QHDT,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x32B, .touch_max = 10 };
+static const struct wacom_features wacom_features_0x3F =
+	{ "Wacom Cintiq 21UX", 87200, 65600, 1023, 63,
+	  CINTIQ, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 8 };
+static const struct wacom_features wacom_features_0xC5 =
+	{ "Wacom Cintiq 20WSX", 86680, 54180, 1023, 63,
+	  WACOM_BEE, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 10 };
+static const struct wacom_features wacom_features_0xC6 =
+	{ "Wacom Cintiq 12WX", 53020, 33440, 1023, 63,
+	  WACOM_BEE, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 10 };
+static const struct wacom_features wacom_features_0x304 =
+	{ "Wacom Cintiq 13HD", 59552, 33848, 1023, 63,
+	  WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x333 =
+	{ "Wacom Cintiq 13HD touch", 59552, 33848, 2047, 63,
+	  WACOM_13HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x335 };
+static const struct wacom_features wacom_features_0x335 =
+	{ "Wacom Cintiq 13HD touch", .type = WACOM_24HDT, /* Touch */
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x333, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0xC7 =
+	{ "Wacom DTU1931", 37832, 30305, 511, 0,
+	  PL, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xCE =
+	{ "Wacom DTU2231", 47864, 27011, 511, 0,
+	  DTU, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBMOUSE };
+static const struct wacom_features wacom_features_0xF0 =
+	{ "Wacom DTU1631", 34623, 19553, 511, 0,
+	  DTU, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xFB =
+	{ "Wacom DTU1031", 22096, 13960, 511, 0,
+	  DTUS, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 4,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x32F =
+	{ "Wacom DTU1031X", 22672, 12928, 511, 0,
+	  DTUSX, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 0,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x336 =
+	{ "Wacom DTU1141", 23672, 13403, 1023, 0,
+	  DTUS, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 4,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x57 =
+	{ "Wacom DTK2241", 95840, 54260, 2047, 63,
+	  DTK, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 6,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x59 = /* Pen */
+	{ "Wacom DTH2242", 95840, 54260, 2047, 63,
+	  DTK, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 6,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x5D };
+static const struct wacom_features wacom_features_0x5D = /* Touch */
+	{ "Wacom DTH2242",       .type = WACOM_24HDT,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x59, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0xCC =
+	{ "Wacom Cintiq 21UX2", 87200, 65600, 2047, 63,
+	  WACOM_21UX2, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 18,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0xFA =
+	{ "Wacom Cintiq 22HD", 95840, 54260, 2047, 63,
+	  WACOM_22HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 18,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET };
+static const struct wacom_features wacom_features_0x5B =
+	{ "Wacom Cintiq 22HDT", 95840, 54260, 2047, 63,
+	  WACOM_22HD, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 18,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x5e };
+static const struct wacom_features wacom_features_0x5E =
+	{ "Wacom Cintiq 22HDT", .type = WACOM_24HDT,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x5b, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x90 =
+	{ "Wacom ISDv4 90", 26202, 16325, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; /* Pen-only */
+static const struct wacom_features wacom_features_0x93 =
+	{ "Wacom ISDv4 93", 26202, 16325, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 1 };
+static const struct wacom_features wacom_features_0x97 =
+	{ "Wacom ISDv4 97", 26202, 16325, 511, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; /* Pen-only */
+static const struct wacom_features wacom_features_0x9A =
+	{ "Wacom ISDv4 9A", 26202, 16325, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 1 };
+static const struct wacom_features wacom_features_0x9F =
+	{ "Wacom ISDv4 9F", 26202, 16325, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 1 };
+static const struct wacom_features wacom_features_0xE2 =
+	{ "Wacom ISDv4 E2", 26202, 16325, 255, 0,
+	  TABLETPC2FG, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xE3 =
+	{ "Wacom ISDv4 E3", 26202, 16325, 255, 0,
+	  TABLETPC2FG, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xE5 =
+	{ "Wacom ISDv4 E5", 26202, 16325, 255, 0,
+	  MTSCREEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xE6 =
+	{ "Wacom ISDv4 E6", 27760, 15694, 255, 0,
+	  TABLETPC2FG, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xEC =
+	{ "Wacom ISDv4 EC", 25710, 14500, 255, 0,
+	  TABLETPC,    WACOM_INTUOS_RES, WACOM_INTUOS_RES }; /* Pen-only */
+static const struct wacom_features wacom_features_0xED =
+	{ "Wacom ISDv4 ED", 26202, 16325, 255, 0,
+	  TABLETPCE, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 1 };
+static const struct wacom_features wacom_features_0xEF =
+	{ "Wacom ISDv4 EF", 26202, 16325, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; /* Pen-only */
+static const struct wacom_features wacom_features_0x100 =
+	{ "Wacom ISDv4 100", 26202, 16325, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x101 =
+	{ "Wacom ISDv4 101", 26202, 16325, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x10D =
+	{ "Wacom ISDv4 10D", 26202, 16325, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x10E =
+	{ "Wacom ISDv4 10E", 27760, 15694, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x10F =
+	{ "Wacom ISDv4 10F", 27760, 15694, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x116 =
+	{ "Wacom ISDv4 116", 26202, 16325, 255, 0,
+	  TABLETPCE, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 1 };
+static const struct wacom_features wacom_features_0x12C =
+	{ "Wacom ISDv4 12C", 27848, 15752, 2047, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES }; /* Pen-only */
+static const struct wacom_features wacom_features_0x4001 =
+	{ "Wacom ISDv4 4001", 26202, 16325, 255, 0,
+	  MTTPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x4004 =
+	{ "Wacom ISDv4 4004", 11060, 6220, 255, 0,
+	  MTTPC_B, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x5000 =
+	{ "Wacom ISDv4 5000", 27848, 15752, 1023, 0,
+	  MTTPC_B, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x5002 =
+	{ "Wacom ISDv4 5002", 29576, 16724, 1023, 0,
+	  MTTPC_B, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x47 =
+	{ "Wacom Intuos2 6x8", 20320, 16240, 1023, 31,
+	  INTUOS, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x84 =
+	{ "Wacom Wireless Receiver", .type = WIRELESS, .touch_max = 16 };
+static const struct wacom_features wacom_features_0xD0 =
+	{ "Wacom Bamboo 2FG", 14720, 9200, 1023, 31,
+	  BAMBOO_TOUCH, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD1 =
+	{ "Wacom Bamboo 2FG 4x5", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD2 =
+	{ "Wacom Bamboo Craft", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD3 =
+	{ "Wacom Bamboo 2FG 6x8", 21648, 13700, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD4 =
+	{ "Wacom Bamboo Pen", 14720, 9200, 1023, 31,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xD5 =
+	{ "Wacom Bamboo Pen 6x8", 21648, 13700, 1023, 31,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xD6 =
+	{ "Wacom BambooPT 2FG 4x5", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD7 =
+	{ "Wacom BambooPT 2FG Small", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xD8 =
+	{ "Wacom Bamboo Comic 2FG", 21648, 13700, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xDA =
+	{ "Wacom Bamboo 2FG 4x5 SE", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xDB =
+	{ "Wacom Bamboo 2FG 6x8 SE", 21648, 13700, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 2 };
+static const struct wacom_features wacom_features_0xDD =
+        { "Wacom Bamboo Connect", 14720, 9200, 1023, 31,
+          BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0xDE =
+        { "Wacom Bamboo 16FG 4x5", 14720, 9200, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16 };
+static const struct wacom_features wacom_features_0xDF =
+        { "Wacom Bamboo 16FG 6x8", 21648, 13700, 1023, 31,
+	  BAMBOO_PT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16 };
+static const struct wacom_features wacom_features_0x300 =
+	{ "Wacom Bamboo One S", 14720, 9225, 1023, 31,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x301 =
+	{ "Wacom Bamboo One M", 21648, 13530, 1023, 31,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x302 =
+	{ "Wacom Intuos PT S", 15200, 9500, 1023, 31,
+	  INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x303 =
+	{ "Wacom Intuos PT M", 21600, 13500, 1023, 31,
+	  INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x30E =
+	{ "Wacom Intuos S", 15200, 9500, 1023, 31,
+	  INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x6004 =
+	{ "ISD-V4", 12800, 8000, 255, 0,
+	  TABLETPC, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x307 =
+	{ "Wacom ISDv5 307", 59552, 33848, 2047, 63,
+	  CINTIQ_HYBRID, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x309 };
+static const struct wacom_features wacom_features_0x309 =
+	{ "Wacom ISDv5 309", .type = WACOM_24HDT, /* Touch */
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x0307, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x30A =
+	{ "Wacom ISDv5 30A", 59552, 33848, 2047, 63,
+	  CINTIQ_HYBRID, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x30C };
+static const struct wacom_features wacom_features_0x30C =
+	{ "Wacom ISDv5 30C", .type = WACOM_24HDT, /* Touch */
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x30A, .touch_max = 10,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x318 =
+	{ "Wacom USB Bamboo PAD", 4095, 4095, /* Touch */
+	  .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
+static const struct wacom_features wacom_features_0x319 =
+	{ "Wacom Wireless Bamboo PAD", 4095, 4095, /* Touch */
+	  .type = BAMBOO_PAD, 35, 48, .touch_max = 4 };
+static const struct wacom_features wacom_features_0x325 =
+	{ "Wacom ISDv5 325", 59552, 33848, 2047, 63,
+	  CINTIQ_COMPANION_2, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 11,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  WACOM_CINTIQ_OFFSET, WACOM_CINTIQ_OFFSET,
+	  .oVid = USB_VENDOR_ID_WACOM, .oPid = 0x326 };
+static const struct wacom_features wacom_features_0x326 = /* Touch */
+	{ "Wacom ISDv5 326", .type = HID_GENERIC, .oVid = USB_VENDOR_ID_WACOM,
+	  .oPid = 0x325 };
+static const struct wacom_features wacom_features_0x323 =
+	{ "Wacom Intuos P M", 21600, 13500, 1023, 31,
+	  INTUOSHT, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x331 =
+	{ "Wacom Express Key Remote", .type = REMOTE,
+	  .numbered_buttons = 18, .check_for_hid_type = true,
+	  .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x33B =
+	{ "Wacom Intuos S 2", 15200, 9500, 2047, 63,
+	  INTUOSHT2, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x33C =
+	{ "Wacom Intuos PT S 2", 15200, 9500, 2047, 63,
+	  INTUOSHT2, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x33D =
+	{ "Wacom Intuos P M 2", 21600, 13500, 2047, 63,
+	  INTUOSHT2, WACOM_INTUOS_RES, WACOM_INTUOS_RES,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x33E =
+	{ "Wacom Intuos PT M 2", 21600, 13500, 2047, 63,
+	  INTUOSHT2, WACOM_INTUOS_RES, WACOM_INTUOS_RES, .touch_max = 16,
+	  .check_for_hid_type = true, .hid_type = HID_TYPE_USBNONE };
+static const struct wacom_features wacom_features_0x343 =
+	{ "Wacom DTK1651", 34816, 19759, 1023, 0,
+	  DTUS, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 4,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET,
+	  WACOM_DTU_OFFSET, WACOM_DTU_OFFSET };
+static const struct wacom_features wacom_features_0x360 =
+	{ "Wacom Intuos Pro M", 44800, 29600, 8191, 63,
+	  INTUOSP2_BT, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 10 };
+static const struct wacom_features wacom_features_0x361 =
+	{ "Wacom Intuos Pro L", 62200, 43200, 8191, 63,
+	  INTUOSP2_BT, WACOM_INTUOS3_RES, WACOM_INTUOS3_RES, 9, .touch_max = 10 };
+static const struct wacom_features wacom_features_0x377 =
+	{ "Wacom Intuos BT S", 15200, 9500, 4095, 63,
+	  INTUOSHT3_BT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 4 };
+static const struct wacom_features wacom_features_0x379 =
+	{ "Wacom Intuos BT M", 21600, 13500, 4095, 63,
+	  INTUOSHT3_BT, WACOM_INTUOS_RES, WACOM_INTUOS_RES, 4 };
+static const struct wacom_features wacom_features_0x37A =
+	{ "Wacom One by Wacom S", 15200, 9500, 2047, 63,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+static const struct wacom_features wacom_features_0x37B =
+	{ "Wacom One by Wacom M", 21600, 13500, 2047, 63,
+	  BAMBOO_PEN, WACOM_INTUOS_RES, WACOM_INTUOS_RES };
+
+static const struct wacom_features wacom_features_HID_ANY_ID =
+	{ "Wacom HID", .type = HID_GENERIC, .oVid = HID_ANY_ID, .oPid = HID_ANY_ID };
+
+#define USB_DEVICE_WACOM(prod)						\
+	HID_DEVICE(BUS_USB, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+
+#define BT_DEVICE_WACOM(prod)						\
+	HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+
+#define I2C_DEVICE_WACOM(prod)						\
+	HID_DEVICE(BUS_I2C, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
+	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+
+#define USB_DEVICE_LENOVO(prod)					\
+	HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, prod),			\
+	.driver_data = (kernel_ulong_t)&wacom_features_##prod
+
+const struct hid_device_id wacom_ids[] = {
+	{ USB_DEVICE_WACOM(0x00) },
+	{ USB_DEVICE_WACOM(0x03) },
+	{ USB_DEVICE_WACOM(0x10) },
+	{ USB_DEVICE_WACOM(0x11) },
+	{ USB_DEVICE_WACOM(0x12) },
+	{ USB_DEVICE_WACOM(0x13) },
+	{ USB_DEVICE_WACOM(0x14) },
+	{ USB_DEVICE_WACOM(0x15) },
+	{ USB_DEVICE_WACOM(0x16) },
+	{ USB_DEVICE_WACOM(0x17) },
+	{ USB_DEVICE_WACOM(0x18) },
+	{ USB_DEVICE_WACOM(0x19) },
+	{ USB_DEVICE_WACOM(0x20) },
+	{ USB_DEVICE_WACOM(0x21) },
+	{ USB_DEVICE_WACOM(0x22) },
+	{ USB_DEVICE_WACOM(0x23) },
+	{ USB_DEVICE_WACOM(0x24) },
+	{ USB_DEVICE_WACOM(0x26) },
+	{ USB_DEVICE_WACOM(0x27) },
+	{ USB_DEVICE_WACOM(0x28) },
+	{ USB_DEVICE_WACOM(0x29) },
+	{ USB_DEVICE_WACOM(0x2A) },
+	{ USB_DEVICE_WACOM(0x30) },
+	{ USB_DEVICE_WACOM(0x31) },
+	{ USB_DEVICE_WACOM(0x32) },
+	{ USB_DEVICE_WACOM(0x33) },
+	{ USB_DEVICE_WACOM(0x34) },
+	{ USB_DEVICE_WACOM(0x35) },
+	{ USB_DEVICE_WACOM(0x37) },
+	{ USB_DEVICE_WACOM(0x38) },
+	{ USB_DEVICE_WACOM(0x39) },
+	{ USB_DEVICE_WACOM(0x3F) },
+	{ USB_DEVICE_WACOM(0x41) },
+	{ USB_DEVICE_WACOM(0x42) },
+	{ USB_DEVICE_WACOM(0x43) },
+	{ USB_DEVICE_WACOM(0x44) },
+	{ USB_DEVICE_WACOM(0x45) },
+	{ USB_DEVICE_WACOM(0x47) },
+	{ USB_DEVICE_WACOM(0x57) },
+	{ USB_DEVICE_WACOM(0x59) },
+	{ USB_DEVICE_WACOM(0x5B) },
+	{ USB_DEVICE_WACOM(0x5D) },
+	{ USB_DEVICE_WACOM(0x5E) },
+	{ USB_DEVICE_WACOM(0x60) },
+	{ USB_DEVICE_WACOM(0x61) },
+	{ USB_DEVICE_WACOM(0x62) },
+	{ USB_DEVICE_WACOM(0x63) },
+	{ USB_DEVICE_WACOM(0x64) },
+	{ USB_DEVICE_WACOM(0x65) },
+	{ USB_DEVICE_WACOM(0x69) },
+	{ USB_DEVICE_WACOM(0x6A) },
+	{ USB_DEVICE_WACOM(0x6B) },
+	{ BT_DEVICE_WACOM(0x81) },
+	{ USB_DEVICE_WACOM(0x84) },
+	{ USB_DEVICE_WACOM(0x90) },
+	{ USB_DEVICE_WACOM(0x93) },
+	{ USB_DEVICE_WACOM(0x97) },
+	{ USB_DEVICE_WACOM(0x9A) },
+	{ USB_DEVICE_WACOM(0x9F) },
+	{ USB_DEVICE_WACOM(0xB0) },
+	{ USB_DEVICE_WACOM(0xB1) },
+	{ USB_DEVICE_WACOM(0xB2) },
+	{ USB_DEVICE_WACOM(0xB3) },
+	{ USB_DEVICE_WACOM(0xB4) },
+	{ USB_DEVICE_WACOM(0xB5) },
+	{ USB_DEVICE_WACOM(0xB7) },
+	{ USB_DEVICE_WACOM(0xB8) },
+	{ USB_DEVICE_WACOM(0xB9) },
+	{ USB_DEVICE_WACOM(0xBA) },
+	{ USB_DEVICE_WACOM(0xBB) },
+	{ USB_DEVICE_WACOM(0xBC) },
+	{ BT_DEVICE_WACOM(0xBD) },
+	{ USB_DEVICE_WACOM(0xC0) },
+	{ USB_DEVICE_WACOM(0xC2) },
+	{ USB_DEVICE_WACOM(0xC4) },
+	{ USB_DEVICE_WACOM(0xC5) },
+	{ USB_DEVICE_WACOM(0xC6) },
+	{ USB_DEVICE_WACOM(0xC7) },
+	{ USB_DEVICE_WACOM(0xCC) },
+	{ USB_DEVICE_WACOM(0xCE) },
+	{ USB_DEVICE_WACOM(0xD0) },
+	{ USB_DEVICE_WACOM(0xD1) },
+	{ USB_DEVICE_WACOM(0xD2) },
+	{ USB_DEVICE_WACOM(0xD3) },
+	{ USB_DEVICE_WACOM(0xD4) },
+	{ USB_DEVICE_WACOM(0xD5) },
+	{ USB_DEVICE_WACOM(0xD6) },
+	{ USB_DEVICE_WACOM(0xD7) },
+	{ USB_DEVICE_WACOM(0xD8) },
+	{ USB_DEVICE_WACOM(0xDA) },
+	{ USB_DEVICE_WACOM(0xDB) },
+	{ USB_DEVICE_WACOM(0xDD) },
+	{ USB_DEVICE_WACOM(0xDE) },
+	{ USB_DEVICE_WACOM(0xDF) },
+	{ USB_DEVICE_WACOM(0xE2) },
+	{ USB_DEVICE_WACOM(0xE3) },
+	{ USB_DEVICE_WACOM(0xE5) },
+	{ USB_DEVICE_WACOM(0xE6) },
+	{ USB_DEVICE_WACOM(0xEC) },
+	{ USB_DEVICE_WACOM(0xED) },
+	{ USB_DEVICE_WACOM(0xEF) },
+	{ USB_DEVICE_WACOM(0xF0) },
+	{ USB_DEVICE_WACOM(0xF4) },
+	{ USB_DEVICE_WACOM(0xF6) },
+	{ USB_DEVICE_WACOM(0xF8) },
+	{ USB_DEVICE_WACOM(0xFA) },
+	{ USB_DEVICE_WACOM(0xFB) },
+	{ USB_DEVICE_WACOM(0x100) },
+	{ USB_DEVICE_WACOM(0x101) },
+	{ USB_DEVICE_WACOM(0x10D) },
+	{ USB_DEVICE_WACOM(0x10E) },
+	{ USB_DEVICE_WACOM(0x10F) },
+	{ USB_DEVICE_WACOM(0x116) },
+	{ USB_DEVICE_WACOM(0x12C) },
+	{ USB_DEVICE_WACOM(0x300) },
+	{ USB_DEVICE_WACOM(0x301) },
+	{ USB_DEVICE_WACOM(0x302) },
+	{ USB_DEVICE_WACOM(0x303) },
+	{ USB_DEVICE_WACOM(0x304) },
+	{ USB_DEVICE_WACOM(0x307) },
+	{ USB_DEVICE_WACOM(0x309) },
+	{ USB_DEVICE_WACOM(0x30A) },
+	{ USB_DEVICE_WACOM(0x30C) },
+	{ USB_DEVICE_WACOM(0x30E) },
+	{ USB_DEVICE_WACOM(0x314) },
+	{ USB_DEVICE_WACOM(0x315) },
+	{ USB_DEVICE_WACOM(0x317) },
+	{ USB_DEVICE_WACOM(0x318) },
+	{ USB_DEVICE_WACOM(0x319) },
+	{ USB_DEVICE_WACOM(0x323) },
+	{ USB_DEVICE_WACOM(0x325) },
+	{ USB_DEVICE_WACOM(0x326) },
+	{ USB_DEVICE_WACOM(0x32A) },
+	{ USB_DEVICE_WACOM(0x32B) },
+	{ USB_DEVICE_WACOM(0x32C) },
+	{ USB_DEVICE_WACOM(0x32F) },
+	{ USB_DEVICE_WACOM(0x331) },
+	{ USB_DEVICE_WACOM(0x333) },
+	{ USB_DEVICE_WACOM(0x335) },
+	{ USB_DEVICE_WACOM(0x336) },
+	{ USB_DEVICE_WACOM(0x33B) },
+	{ USB_DEVICE_WACOM(0x33C) },
+	{ USB_DEVICE_WACOM(0x33D) },
+	{ USB_DEVICE_WACOM(0x33E) },
+	{ USB_DEVICE_WACOM(0x343) },
+	{ BT_DEVICE_WACOM(0x360) },
+	{ BT_DEVICE_WACOM(0x361) },
+	{ BT_DEVICE_WACOM(0x377) },
+	{ BT_DEVICE_WACOM(0x379) },
+	{ USB_DEVICE_WACOM(0x37A) },
+	{ USB_DEVICE_WACOM(0x37B) },
+	{ USB_DEVICE_WACOM(0x4001) },
+	{ USB_DEVICE_WACOM(0x4004) },
+	{ USB_DEVICE_WACOM(0x5000) },
+	{ USB_DEVICE_WACOM(0x5002) },
+	{ USB_DEVICE_LENOVO(0x6004) },
+
+	{ USB_DEVICE_WACOM(HID_ANY_ID) },
+	{ I2C_DEVICE_WACOM(HID_ANY_ID) },
+	{ BT_DEVICE_WACOM(HID_ANY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(hid, wacom_ids);
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
new file mode 100644
index 0000000..295fd37
--- /dev/null
+++ b/drivers/hid/wacom_wac.h
@@ -0,0 +1,360 @@
+/*
+ * drivers/input/tablet/wacom_wac.h
+ *
+ * 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.
+ */
+#ifndef WACOM_WAC_H
+#define WACOM_WAC_H
+
+#include <linux/types.h>
+#include <linux/hid.h>
+#include <linux/kfifo.h>
+
+/* maximum packet length for USB/BT devices */
+#define WACOM_PKGLEN_MAX	361
+
+#define WACOM_NAME_MAX		64
+#define WACOM_MAX_REMOTES	5
+#define WACOM_STATUS_UNKNOWN	255
+
+/* packet length for individual models */
+#define WACOM_PKGLEN_BBFUN	 9
+#define WACOM_PKGLEN_TPC1FG	 5
+#define WACOM_PKGLEN_TPC1FG_B	10
+#define WACOM_PKGLEN_TPC2FG	14
+#define WACOM_PKGLEN_BBTOUCH	20
+#define WACOM_PKGLEN_BBTOUCH3	64
+#define WACOM_PKGLEN_BBPEN	10
+#define WACOM_PKGLEN_WIRELESS	32
+#define WACOM_PKGLEN_PENABLED	 8
+#define WACOM_PKGLEN_BPAD_TOUCH	32
+#define WACOM_PKGLEN_BPAD_TOUCH_USB	64
+
+/* wacom data size per MT contact */
+#define WACOM_BYTES_PER_MT_PACKET	11
+#define WACOM_BYTES_PER_24HDT_PACKET	14
+#define WACOM_BYTES_PER_QHDTHID_PACKET	 6
+
+/* device IDs */
+#define STYLUS_DEVICE_ID	0x02
+#define TOUCH_DEVICE_ID		0x03
+#define CURSOR_DEVICE_ID	0x06
+#define ERASER_DEVICE_ID	0x0A
+#define PAD_DEVICE_ID		0x0F
+
+/* wacom data packet report IDs */
+#define WACOM_REPORT_PENABLED		2
+#define WACOM_REPORT_PENABLED_BT	3
+#define WACOM_REPORT_INTUOS_ID1		5
+#define WACOM_REPORT_INTUOS_ID2		6
+#define WACOM_REPORT_INTUOSPAD		12
+#define WACOM_REPORT_INTUOS5PAD		3
+#define WACOM_REPORT_DTUSPAD		21
+#define WACOM_REPORT_TPC1FG		6
+#define WACOM_REPORT_TPC2FG		13
+#define WACOM_REPORT_TPCMT		13
+#define WACOM_REPORT_TPCMT2		3
+#define WACOM_REPORT_TPCHID		15
+#define WACOM_REPORT_CINTIQ		16
+#define WACOM_REPORT_CINTIQPAD		17
+#define WACOM_REPORT_TPCST		16
+#define WACOM_REPORT_DTUS		17
+#define WACOM_REPORT_TPC1FGE		18
+#define WACOM_REPORT_24HDT		1
+#define WACOM_REPORT_WL			128
+#define WACOM_REPORT_USB		192
+#define WACOM_REPORT_BPAD_PEN		3
+#define WACOM_REPORT_BPAD_TOUCH		16
+#define WACOM_REPORT_DEVICE_LIST	16
+#define WACOM_REPORT_INTUOS_PEN		16
+#define WACOM_REPORT_REMOTE		17
+#define WACOM_REPORT_INTUOSHT2_ID	8
+
+/* wacom command report ids */
+#define WAC_CMD_WL_LED_CONTROL          0x03
+#define WAC_CMD_LED_CONTROL             0x20
+#define WAC_CMD_ICON_START              0x21
+#define WAC_CMD_ICON_XFER               0x23
+#define WAC_CMD_ICON_BT_XFER            0x26
+#define WAC_CMD_DELETE_PAIRING          0x20
+#define WAC_CMD_LED_CONTROL_GENERIC     0x32
+#define WAC_CMD_UNPAIR_ALL              0xFF
+#define WAC_CMD_WL_INTUOSP2             0x82
+
+/* device quirks */
+#define WACOM_QUIRK_BBTOUCH_LOWRES	0x0001
+#define WACOM_QUIRK_SENSE		0x0002
+#define WACOM_QUIRK_AESPEN		0x0004
+#define WACOM_QUIRK_BATTERY		0x0008
+#define WACOM_QUIRK_TOOLSERIAL		0x0010
+
+/* device types */
+#define WACOM_DEVICETYPE_NONE           0x0000
+#define WACOM_DEVICETYPE_PEN            0x0001
+#define WACOM_DEVICETYPE_TOUCH          0x0002
+#define WACOM_DEVICETYPE_PAD            0x0004
+#define WACOM_DEVICETYPE_WL_MONITOR     0x0008
+#define WACOM_DEVICETYPE_DIRECT         0x0010
+
+#define WACOM_POWER_SUPPLY_STATUS_AUTO  -1
+
+#define WACOM_HID_UP_WACOMDIGITIZER     0xff0d0000
+#define WACOM_HID_SP_PAD                0x00040000
+#define WACOM_HID_SP_BUTTON             0x00090000
+#define WACOM_HID_SP_DIGITIZER          0x000d0000
+#define WACOM_HID_SP_DIGITIZERINFO      0x00100000
+#define WACOM_HID_WD_DIGITIZER          (WACOM_HID_UP_WACOMDIGITIZER | 0x01)
+#define WACOM_HID_WD_PEN                (WACOM_HID_UP_WACOMDIGITIZER | 0x02)
+#define WACOM_HID_WD_SENSE              (WACOM_HID_UP_WACOMDIGITIZER | 0x36)
+#define WACOM_HID_WD_DIGITIZERFNKEYS    (WACOM_HID_UP_WACOMDIGITIZER | 0x39)
+#define WACOM_HID_WD_SERIALNUMBER       (WACOM_HID_UP_WACOMDIGITIZER | 0x5b)
+#define WACOM_HID_WD_SERIALHI           (WACOM_HID_UP_WACOMDIGITIZER | 0x5c)
+#define WACOM_HID_WD_TOOLTYPE           (WACOM_HID_UP_WACOMDIGITIZER | 0x77)
+#define WACOM_HID_WD_DISTANCE           (WACOM_HID_UP_WACOMDIGITIZER | 0x0132)
+#define WACOM_HID_WD_TOUCHSTRIP         (WACOM_HID_UP_WACOMDIGITIZER | 0x0136)
+#define WACOM_HID_WD_TOUCHSTRIP2        (WACOM_HID_UP_WACOMDIGITIZER | 0x0137)
+#define WACOM_HID_WD_TOUCHRING          (WACOM_HID_UP_WACOMDIGITIZER | 0x0138)
+#define WACOM_HID_WD_TOUCHRINGSTATUS    (WACOM_HID_UP_WACOMDIGITIZER | 0x0139)
+#define WACOM_HID_WD_REPORT_VALID       (WACOM_HID_UP_WACOMDIGITIZER | 0x01d0)
+#define WACOM_HID_WD_ACCELEROMETER_X    (WACOM_HID_UP_WACOMDIGITIZER | 0x0401)
+#define WACOM_HID_WD_ACCELEROMETER_Y    (WACOM_HID_UP_WACOMDIGITIZER | 0x0402)
+#define WACOM_HID_WD_ACCELEROMETER_Z    (WACOM_HID_UP_WACOMDIGITIZER | 0x0403)
+#define WACOM_HID_WD_BATTERY_CHARGING   (WACOM_HID_UP_WACOMDIGITIZER | 0x0404)
+#define WACOM_HID_WD_TOUCHONOFF         (WACOM_HID_UP_WACOMDIGITIZER | 0x0454)
+#define WACOM_HID_WD_BATTERY_LEVEL      (WACOM_HID_UP_WACOMDIGITIZER | 0x043b)
+#define WACOM_HID_WD_EXPRESSKEY00       (WACOM_HID_UP_WACOMDIGITIZER | 0x0910)
+#define WACOM_HID_WD_EXPRESSKEYCAP00    (WACOM_HID_UP_WACOMDIGITIZER | 0x0950)
+#define WACOM_HID_WD_MODE_CHANGE        (WACOM_HID_UP_WACOMDIGITIZER | 0x0980)
+#define WACOM_HID_WD_MUTE_DEVICE        (WACOM_HID_UP_WACOMDIGITIZER | 0x0981)
+#define WACOM_HID_WD_CONTROLPANEL       (WACOM_HID_UP_WACOMDIGITIZER | 0x0982)
+#define WACOM_HID_WD_ONSCREEN_KEYBOARD  (WACOM_HID_UP_WACOMDIGITIZER | 0x0983)
+#define WACOM_HID_WD_BUTTONCONFIG       (WACOM_HID_UP_WACOMDIGITIZER | 0x0986)
+#define WACOM_HID_WD_BUTTONHOME         (WACOM_HID_UP_WACOMDIGITIZER | 0x0990)
+#define WACOM_HID_WD_BUTTONUP           (WACOM_HID_UP_WACOMDIGITIZER | 0x0991)
+#define WACOM_HID_WD_BUTTONDOWN         (WACOM_HID_UP_WACOMDIGITIZER | 0x0992)
+#define WACOM_HID_WD_BUTTONLEFT         (WACOM_HID_UP_WACOMDIGITIZER | 0x0993)
+#define WACOM_HID_WD_BUTTONRIGHT        (WACOM_HID_UP_WACOMDIGITIZER | 0x0994)
+#define WACOM_HID_WD_BUTTONCENTER       (WACOM_HID_UP_WACOMDIGITIZER | 0x0995)
+#define WACOM_HID_WD_FINGERWHEEL        (WACOM_HID_UP_WACOMDIGITIZER | 0x0d03)
+#define WACOM_HID_WD_OFFSETLEFT         (WACOM_HID_UP_WACOMDIGITIZER | 0x0d30)
+#define WACOM_HID_WD_OFFSETTOP          (WACOM_HID_UP_WACOMDIGITIZER | 0x0d31)
+#define WACOM_HID_WD_OFFSETRIGHT        (WACOM_HID_UP_WACOMDIGITIZER | 0x0d32)
+#define WACOM_HID_WD_OFFSETBOTTOM       (WACOM_HID_UP_WACOMDIGITIZER | 0x0d33)
+#define WACOM_HID_WD_DATAMODE           (WACOM_HID_UP_WACOMDIGITIZER | 0x1002)
+#define WACOM_HID_WD_DIGITIZERINFO      (WACOM_HID_UP_WACOMDIGITIZER | 0x1013)
+#define WACOM_HID_UP_G9                 0xff090000
+#define WACOM_HID_G9_PEN                (WACOM_HID_UP_G9 | 0x02)
+#define WACOM_HID_G9_TOUCHSCREEN        (WACOM_HID_UP_G9 | 0x11)
+#define WACOM_HID_UP_G11                0xff110000
+#define WACOM_HID_G11_PEN               (WACOM_HID_UP_G11 | 0x02)
+#define WACOM_HID_G11_TOUCHSCREEN       (WACOM_HID_UP_G11 | 0x11)
+#define WACOM_HID_UP_WACOMTOUCH         0xff000000
+#define WACOM_HID_WT_TOUCHSCREEN        (WACOM_HID_UP_WACOMTOUCH | 0x04)
+#define WACOM_HID_WT_TOUCHPAD           (WACOM_HID_UP_WACOMTOUCH | 0x05)
+#define WACOM_HID_WT_CONTACTMAX         (WACOM_HID_UP_WACOMTOUCH | 0x55)
+#define WACOM_HID_WT_SERIALNUMBER       (WACOM_HID_UP_WACOMTOUCH | 0x5b)
+#define WACOM_HID_WT_X                  (WACOM_HID_UP_WACOMTOUCH | 0x130)
+#define WACOM_HID_WT_Y                  (WACOM_HID_UP_WACOMTOUCH | 0x131)
+
+#define WACOM_BATTERY_USAGE(f)	(((f)->hid == HID_DG_BATTERYSTRENGTH) || \
+				 ((f)->hid == WACOM_HID_WD_BATTERY_CHARGING) || \
+				 ((f)->hid == WACOM_HID_WD_BATTERY_LEVEL))
+
+#define WACOM_PAD_FIELD(f)	(((f)->physical == HID_DG_TABLETFUNCTIONKEY) || \
+				 ((f)->physical == WACOM_HID_WD_DIGITIZERFNKEYS) || \
+				 ((f)->physical == WACOM_HID_WD_DIGITIZERINFO))
+
+#define WACOM_PEN_FIELD(f)	(((f)->logical == HID_DG_STYLUS) || \
+				 ((f)->physical == HID_DG_STYLUS) || \
+				 ((f)->physical == HID_DG_PEN) || \
+				 ((f)->application == HID_DG_PEN) || \
+				 ((f)->application == HID_DG_DIGITIZER) || \
+				 ((f)->application == WACOM_HID_WD_PEN) || \
+				 ((f)->application == WACOM_HID_WD_DIGITIZER) || \
+				 ((f)->application == WACOM_HID_G9_PEN) || \
+				 ((f)->application == WACOM_HID_G11_PEN))
+#define WACOM_FINGER_FIELD(f)	(((f)->logical == HID_DG_FINGER) || \
+				 ((f)->physical == HID_DG_FINGER) || \
+				 ((f)->application == HID_DG_TOUCHSCREEN) || \
+				 ((f)->application == WACOM_HID_G9_TOUCHSCREEN) || \
+				 ((f)->application == WACOM_HID_G11_TOUCHSCREEN) || \
+				 ((f)->application == WACOM_HID_WT_TOUCHPAD) || \
+				 ((f)->application == HID_DG_TOUCHPAD))
+
+#define WACOM_DIRECT_DEVICE(f)	(((f)->application == HID_DG_TOUCHSCREEN) || \
+				 ((f)->application == WACOM_HID_WT_TOUCHSCREEN) || \
+				 ((f)->application == HID_DG_PEN) || \
+				 ((f)->application == WACOM_HID_WD_PEN))
+
+enum {
+	PENPARTNER = 0,
+	GRAPHIRE,
+	GRAPHIRE_BT,
+	WACOM_G4,
+	PTU,
+	PL,
+	DTU,
+	DTUS,
+	DTUSX,
+	INTUOS,
+	INTUOS3S,
+	INTUOS3,
+	INTUOS3L,
+	INTUOS4S,
+	INTUOS4,
+	INTUOS4WL,
+	INTUOS4L,
+	INTUOS5S,
+	INTUOS5,
+	INTUOS5L,
+	INTUOSPS,
+	INTUOSPM,
+	INTUOSPL,
+	INTUOSP2_BT,
+	INTUOSHT3_BT,
+	WACOM_21UX2,
+	WACOM_22HD,
+	DTK,
+	WACOM_24HD,
+	WACOM_27QHD,
+	CINTIQ_HYBRID,
+	CINTIQ_COMPANION_2,
+	CINTIQ,
+	WACOM_BEE,
+	WACOM_13HD,
+	WACOM_MO,
+	BAMBOO_PEN,
+	INTUOSHT,
+	INTUOSHT2,
+	BAMBOO_TOUCH,
+	BAMBOO_PT,
+	WACOM_24HDT,
+	WACOM_27QHDT,
+	BAMBOO_PAD,
+	WIRELESS,
+	REMOTE,
+	TABLETPC,   /* add new TPC below */
+	TABLETPCE,
+	TABLETPC2FG,
+	MTSCREEN,
+	MTTPC,
+	MTTPC_B,
+	HID_GENERIC,
+	MAX_TYPE
+};
+
+struct wacom_features {
+	const char *name;
+	int x_max;
+	int y_max;
+	int pressure_max;
+	int distance_max;
+	int type;
+	int x_resolution;
+	int y_resolution;
+	int numbered_buttons;
+	int offset_left;
+	int offset_right;
+	int offset_top;
+	int offset_bottom;
+	int device_type;
+	int x_phy;
+	int y_phy;
+	unsigned unit;
+	int unitExpo;
+	int x_fuzz;
+	int y_fuzz;
+	int pressure_fuzz;
+	int distance_fuzz;
+	int tilt_fuzz;
+	unsigned quirks;
+	unsigned touch_max;
+	int oVid;
+	int oPid;
+	int pktlen;
+	bool check_for_hid_type;
+	int hid_type;
+};
+
+struct wacom_shared {
+	bool stylus_in_proximity;
+	bool touch_down;
+	/* for wireless device to access USB interfaces */
+	unsigned touch_max;
+	int type;
+	struct input_dev *touch_input;
+	struct hid_device *pen;
+	struct hid_device *touch;
+	bool has_mute_touch_switch;
+	bool is_touch_on;
+};
+
+struct hid_data {
+	__s16 inputmode;	/* InputMode HID feature, -1 if non-existent */
+	__s16 inputmode_index;	/* InputMode HID feature index in the report */
+	bool sense_state;
+	bool inrange_state;
+	bool invert_state;
+	bool tipswitch;
+	bool barrelswitch;
+	bool barrelswitch2;
+	int x;
+	int y;
+	int pressure;
+	int width;
+	int height;
+	int id;
+	int cc_report;
+	int cc_index;
+	int cc_value_index;
+	int last_slot_field;
+	int num_expected;
+	int num_received;
+	int bat_status;
+	int battery_capacity;
+	int bat_charging;
+	int bat_connected;
+	int ps_connected;
+	bool pad_input_event_flag;
+};
+
+struct wacom_remote_data {
+	struct {
+		u32 serial;
+		bool connected;
+	} remote[WACOM_MAX_REMOTES];
+};
+
+struct wacom_wac {
+	char name[WACOM_NAME_MAX];
+	char pen_name[WACOM_NAME_MAX];
+	char touch_name[WACOM_NAME_MAX];
+	char pad_name[WACOM_NAME_MAX];
+	unsigned char data[WACOM_PKGLEN_MAX];
+	int tool[2];
+	int id[2];
+	__u64 serial[2];
+	bool reporting_data;
+	struct wacom_features features;
+	struct wacom_shared *shared;
+	struct input_dev *pen_input;
+	struct input_dev *touch_input;
+	struct input_dev *pad_input;
+	struct kfifo_rec_ptr_2 pen_fifo;
+	int pid;
+	int num_contacts_left;
+	u8 bt_features;
+	u8 bt_high_speed;
+	int mode_report;
+	int mode_value;
+	struct hid_data hid_data;
+	bool has_mute_touch_switch;
+	bool has_mode_change;
+	bool is_direct_mode;
+	bool is_invalid_bt_frame;
+};
+
+#endif